wireguard.go raw
1 //go:build !(js && wasm)
2
3 package database
4
5 import (
6 "encoding/json"
7 "errors"
8 "fmt"
9 "time"
10
11 "github.com/dgraph-io/badger/v4"
12 "next.orly.dev/pkg/lol/chk"
13 "next.orly.dev/pkg/lol/log"
14
15 "next.orly.dev/pkg/nostr/encoders/hex"
16 "next.orly.dev/pkg/wireguard"
17 )
18
19 // Key prefixes for WireGuard data
20 const (
21 wgServerKeyPrefix = "wg:server:key" // Server's WireGuard private key
22 wgSubnetSeedPrefix = "wg:subnet:seed" // Seed for deterministic subnet generation
23 wgPeerPrefix = "wg:peer:" // Peer data by Nostr pubkey hex
24 wgSequenceKey = "wg:seq" // Badger sequence key for subnet allocation
25 wgRevokedPrefix = "wg:revoked:" // Revoked keypairs by Nostr pubkey hex
26 wgAccessLogPrefix = "wg:accesslog:" // Access log for obsolete addresses
27 )
28
29 // WireGuardPeer stores WireGuard peer information in the database.
30 type WireGuardPeer struct {
31 NostrPubkey []byte `json:"nostr_pubkey"` // User's Nostr pubkey (32 bytes)
32 WGPrivateKey []byte `json:"wg_private_key"` // WireGuard private key (32 bytes)
33 WGPublicKey []byte `json:"wg_public_key"` // WireGuard public key (32 bytes)
34 Sequence uint32 `json:"sequence"` // Sequence number for subnet derivation
35 CreatedAt int64 `json:"created_at"` // Unix timestamp
36 }
37
38 // WireGuardRevokedKey stores a revoked/old WireGuard keypair for audit purposes.
39 type WireGuardRevokedKey struct {
40 NostrPubkey []byte `json:"nostr_pubkey"` // User's Nostr pubkey (32 bytes)
41 WGPublicKey []byte `json:"wg_public_key"` // Revoked WireGuard public key (32 bytes)
42 Sequence uint32 `json:"sequence"` // Sequence number (subnet)
43 CreatedAt int64 `json:"created_at"` // When the key was originally created
44 RevokedAt int64 `json:"revoked_at"` // When the key was revoked
45 AccessCount int `json:"access_count"` // Number of access attempts since revocation
46 LastAccessAt int64 `json:"last_access_at"` // Last access attempt timestamp (0 if never)
47 }
48
49 // WireGuardAccessLog records an access attempt to an obsolete address.
50 type WireGuardAccessLog struct {
51 NostrPubkey []byte `json:"nostr_pubkey"` // User's Nostr pubkey
52 WGPublicKey []byte `json:"wg_public_key"` // The obsolete public key used
53 Sequence uint32 `json:"sequence"` // Subnet sequence
54 Timestamp int64 `json:"timestamp"` // When the access occurred
55 RemoteAddr string `json:"remote_addr"` // Remote IP address
56 }
57
58 // ServerIP returns the derived server IP for this peer's subnet.
59 func (p *WireGuardPeer) ServerIP(pool *wireguard.SubnetPool) string {
60 subnet := pool.SubnetForSequence(p.Sequence)
61 return subnet.ServerIP.String()
62 }
63
64 // ClientIP returns the derived client IP for this peer's subnet.
65 func (p *WireGuardPeer) ClientIP(pool *wireguard.SubnetPool) string {
66 subnet := pool.SubnetForSequence(p.Sequence)
67 return subnet.ClientIP.String()
68 }
69
70 // GetWireGuardServerKey retrieves the WireGuard server private key.
71 func (d *D) GetWireGuardServerKey() (key []byte, err error) {
72 err = d.DB.View(func(txn *badger.Txn) error {
73 item, err := txn.Get([]byte(wgServerKeyPrefix))
74 if errors.Is(err, badger.ErrKeyNotFound) {
75 return err
76 }
77 if err != nil {
78 return err
79 }
80 return item.Value(func(val []byte) error {
81 key = make([]byte, len(val))
82 copy(key, val)
83 return nil
84 })
85 })
86 return
87 }
88
89 // SetWireGuardServerKey stores the WireGuard server private key.
90 func (d *D) SetWireGuardServerKey(key []byte) error {
91 if len(key) != 32 {
92 return fmt.Errorf("invalid key length: %d (expected 32)", len(key))
93 }
94 return d.DB.Update(func(txn *badger.Txn) error {
95 return txn.Set([]byte(wgServerKeyPrefix), key)
96 })
97 }
98
99 // GetOrCreateWireGuardServerKey retrieves or creates the WireGuard server key.
100 func (d *D) GetOrCreateWireGuardServerKey() (key []byte, err error) {
101 // Try to get existing key
102 if key, err = d.GetWireGuardServerKey(); err == nil && len(key) == 32 {
103 return key, nil
104 }
105 if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
106 return nil, err
107 }
108
109 // Generate new keypair
110 privateKey, publicKey, err := wireguard.GenerateKeyPair()
111 if err != nil {
112 return nil, fmt.Errorf("failed to generate WireGuard keypair: %w", err)
113 }
114
115 // Store the private key
116 if err = d.SetWireGuardServerKey(privateKey); chk.E(err) {
117 return nil, err
118 }
119
120 log.I.F("generated new WireGuard server key (pubkey=%s...)", hex.Enc(publicKey[:8]))
121 return privateKey, nil
122 }
123
124 // GetSubnetSeed retrieves the subnet pool seed.
125 func (d *D) GetSubnetSeed() (seed []byte, err error) {
126 err = d.DB.View(func(txn *badger.Txn) error {
127 item, err := txn.Get([]byte(wgSubnetSeedPrefix))
128 if errors.Is(err, badger.ErrKeyNotFound) {
129 return err
130 }
131 if err != nil {
132 return err
133 }
134 return item.Value(func(val []byte) error {
135 seed = make([]byte, len(val))
136 copy(seed, val)
137 return nil
138 })
139 })
140 return
141 }
142
143 // SetSubnetSeed stores the subnet pool seed.
144 func (d *D) SetSubnetSeed(seed []byte) error {
145 if len(seed) != 32 {
146 return fmt.Errorf("invalid seed length: %d (expected 32)", len(seed))
147 }
148 return d.DB.Update(func(txn *badger.Txn) error {
149 return txn.Set([]byte(wgSubnetSeedPrefix), seed)
150 })
151 }
152
153 // GetOrCreateSubnetPool creates or restores a subnet pool from the database.
154 func (d *D) GetOrCreateSubnetPool(baseNetwork string) (*wireguard.SubnetPool, error) {
155 // Try to get existing seed
156 seed, err := d.GetSubnetSeed()
157 if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
158 return nil, err
159 }
160
161 var pool *wireguard.SubnetPool
162
163 if len(seed) == 32 {
164 // Restore pool with existing seed
165 pool, err = wireguard.NewSubnetPoolWithSeed(baseNetwork, seed)
166 if err != nil {
167 return nil, err
168 }
169 log.D.F("restored subnet pool with existing seed")
170 } else {
171 // Create new pool with random seed
172 pool, err = wireguard.NewSubnetPool(baseNetwork)
173 if err != nil {
174 return nil, err
175 }
176
177 // Store the new seed
178 if err = d.SetSubnetSeed(pool.Seed()); err != nil {
179 return nil, fmt.Errorf("failed to store subnet seed: %w", err)
180 }
181 log.I.F("generated new subnet pool seed")
182 }
183
184 // Restore existing allocations from database
185 peers, err := d.GetAllWireGuardPeers()
186 if err != nil {
187 return nil, fmt.Errorf("failed to load existing peers: %w", err)
188 }
189
190 for _, peer := range peers {
191 pool.RestoreAllocation(hex.Enc(peer.NostrPubkey), peer.Sequence)
192 }
193
194 if len(peers) > 0 {
195 log.D.F("restored %d subnet allocations", len(peers))
196 }
197
198 return pool, nil
199 }
200
201 // GetWireGuardPeer retrieves a WireGuard peer by Nostr pubkey.
202 func (d *D) GetWireGuardPeer(nostrPubkey []byte) (peer *WireGuardPeer, err error) {
203 key := append([]byte(wgPeerPrefix), []byte(hex.Enc(nostrPubkey))...)
204
205 err = d.DB.View(func(txn *badger.Txn) error {
206 item, err := txn.Get(key)
207 if errors.Is(err, badger.ErrKeyNotFound) {
208 return err
209 }
210 if err != nil {
211 return err
212 }
213 return item.Value(func(val []byte) error {
214 peer = &WireGuardPeer{}
215 return json.Unmarshal(val, peer)
216 })
217 })
218 return
219 }
220
221 // GetOrCreateWireGuardPeer retrieves or creates a WireGuard peer.
222 // The pool is used for subnet derivation from the sequence number.
223 func (d *D) GetOrCreateWireGuardPeer(nostrPubkey []byte, pool *wireguard.SubnetPool) (peer *WireGuardPeer, err error) {
224 // Try to get existing peer
225 if peer, err = d.GetWireGuardPeer(nostrPubkey); err == nil {
226 return peer, nil
227 }
228 if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
229 return nil, err
230 }
231
232 // Generate new WireGuard keypair
233 privateKey, publicKey, err := wireguard.GenerateKeyPair()
234 if err != nil {
235 return nil, fmt.Errorf("failed to generate WireGuard keypair: %w", err)
236 }
237
238 // Get next sequence number from Badger's sequence
239 seq64, err := d.GetNextWGSequence()
240 if err != nil {
241 return nil, fmt.Errorf("failed to allocate sequence: %w", err)
242 }
243 seq := uint32(seq64)
244
245 // Register allocation with pool for in-memory tracking
246 pubkeyHex := hex.Enc(nostrPubkey)
247 pool.RestoreAllocation(pubkeyHex, seq)
248
249 peer = &WireGuardPeer{
250 NostrPubkey: nostrPubkey,
251 WGPrivateKey: privateKey,
252 WGPublicKey: publicKey,
253 Sequence: seq,
254 CreatedAt: time.Now().Unix(),
255 }
256
257 // Store peer data
258 if err = d.setWireGuardPeer(peer); err != nil {
259 return nil, err
260 }
261
262 subnet := pool.SubnetForSequence(seq)
263 log.I.F("created WireGuard peer: nostr=%s... -> subnet %s/%s (seq=%d)",
264 hex.Enc(nostrPubkey[:8]), subnet.ServerIP, subnet.ClientIP, seq)
265
266 return peer, nil
267 }
268
269 // RegenerateWireGuardPeer generates a new keypair for an existing peer.
270 // The sequence number (and thus subnet) is preserved.
271 // The old keypair is archived for audit purposes.
272 func (d *D) RegenerateWireGuardPeer(nostrPubkey []byte, pool *wireguard.SubnetPool) (peer *WireGuardPeer, err error) {
273 // Get existing peer to preserve sequence
274 existing, err := d.GetWireGuardPeer(nostrPubkey)
275 if err != nil {
276 return nil, err
277 }
278
279 // Archive the old keypair for audit purposes
280 if err = d.ArchiveRevokedKey(existing); err != nil {
281 log.W.F("failed to archive revoked key: %v", err)
282 // Continue anyway - this is audit logging, not critical
283 }
284
285 // Generate new WireGuard keypair
286 privateKey, publicKey, err := wireguard.GenerateKeyPair()
287 if err != nil {
288 return nil, fmt.Errorf("failed to generate WireGuard keypair: %w", err)
289 }
290
291 peer = &WireGuardPeer{
292 NostrPubkey: nostrPubkey,
293 WGPrivateKey: privateKey,
294 WGPublicKey: publicKey,
295 Sequence: existing.Sequence, // Keep same sequence (same subnet)
296 CreatedAt: time.Now().Unix(),
297 }
298
299 // Store updated peer data
300 if err = d.setWireGuardPeer(peer); err != nil {
301 return nil, err
302 }
303
304 subnet := pool.SubnetForSequence(peer.Sequence)
305 log.I.F("regenerated WireGuard peer: nostr=%s... -> subnet %s/%s (old key archived)",
306 hex.Enc(nostrPubkey[:8]), subnet.ServerIP, subnet.ClientIP)
307
308 return peer, nil
309 }
310
311 // DeleteWireGuardPeer removes a WireGuard peer from the database.
312 // Note: The sequence number is not recycled to prevent subnet reuse.
313 func (d *D) DeleteWireGuardPeer(nostrPubkey []byte) error {
314 peerKey := append([]byte(wgPeerPrefix), []byte(hex.Enc(nostrPubkey))...)
315
316 return d.DB.Update(func(txn *badger.Txn) error {
317 if err := txn.Delete(peerKey); err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
318 return err
319 }
320 return nil
321 })
322 }
323
324 // GetAllWireGuardPeers returns all WireGuard peers.
325 func (d *D) GetAllWireGuardPeers() (peers []*WireGuardPeer, err error) {
326 prefix := []byte(wgPeerPrefix)
327
328 err = d.DB.View(func(txn *badger.Txn) error {
329 opts := badger.DefaultIteratorOptions
330 opts.Prefix = prefix
331 it := txn.NewIterator(opts)
332 defer it.Close()
333
334 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
335 item := it.Item()
336 err := item.Value(func(val []byte) error {
337 peer := &WireGuardPeer{}
338 if err := json.Unmarshal(val, peer); err != nil {
339 return err
340 }
341 peers = append(peers, peer)
342 return nil
343 })
344 if err != nil {
345 return err
346 }
347 }
348 return nil
349 })
350 return
351 }
352
353 // setWireGuardPeer stores a WireGuard peer in the database.
354 func (d *D) setWireGuardPeer(peer *WireGuardPeer) error {
355 data, err := json.Marshal(peer)
356 if err != nil {
357 return fmt.Errorf("failed to marshal peer: %w", err)
358 }
359
360 peerKey := append([]byte(wgPeerPrefix), []byte(hex.Enc(peer.NostrPubkey))...)
361
362 return d.DB.Update(func(txn *badger.Txn) error {
363 return txn.Set(peerKey, data)
364 })
365 }
366
367 // GetNextWGSequence retrieves and increments the sequence counter using Badger's Sequence.
368 func (d *D) GetNextWGSequence() (seq uint64, err error) {
369 // Get a sequence with bandwidth 1 (allocate 1 number at a time)
370 badgerSeq, err := d.DB.GetSequence([]byte(wgSequenceKey), 1)
371 if err != nil {
372 return 0, fmt.Errorf("failed to get sequence: %w", err)
373 }
374 defer badgerSeq.Release()
375
376 seq, err = badgerSeq.Next()
377 if err != nil {
378 return 0, fmt.Errorf("failed to get next sequence number: %w", err)
379 }
380 return seq, nil
381 }
382
383 // ArchiveRevokedKey stores a revoked keypair for audit purposes.
384 func (d *D) ArchiveRevokedKey(peer *WireGuardPeer) error {
385 revoked := &WireGuardRevokedKey{
386 NostrPubkey: peer.NostrPubkey,
387 WGPublicKey: peer.WGPublicKey,
388 Sequence: peer.Sequence,
389 CreatedAt: peer.CreatedAt,
390 RevokedAt: time.Now().Unix(),
391 AccessCount: 0,
392 LastAccessAt: 0,
393 }
394
395 data, err := json.Marshal(revoked)
396 if err != nil {
397 return fmt.Errorf("failed to marshal revoked key: %w", err)
398 }
399
400 // Key: wg:revoked:<pubkey-hex>:<revoked-timestamp>
401 keyStr := fmt.Sprintf("%s%s:%d", wgRevokedPrefix, hex.Enc(peer.NostrPubkey), revoked.RevokedAt)
402
403 return d.DB.Update(func(txn *badger.Txn) error {
404 return txn.Set([]byte(keyStr), data)
405 })
406 }
407
408 // GetRevokedKeys returns all revoked keys for a user.
409 func (d *D) GetRevokedKeys(nostrPubkey []byte) (keys []*WireGuardRevokedKey, err error) {
410 prefix := []byte(wgRevokedPrefix + hex.Enc(nostrPubkey) + ":")
411
412 err = d.DB.View(func(txn *badger.Txn) error {
413 opts := badger.DefaultIteratorOptions
414 opts.Prefix = prefix
415 it := txn.NewIterator(opts)
416 defer it.Close()
417
418 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
419 item := it.Item()
420 err := item.Value(func(val []byte) error {
421 key := &WireGuardRevokedKey{}
422 if err := json.Unmarshal(val, key); err != nil {
423 return err
424 }
425 keys = append(keys, key)
426 return nil
427 })
428 if err != nil {
429 return err
430 }
431 }
432 return nil
433 })
434 return
435 }
436
437 // GetAllRevokedKeys returns all revoked keys across all users (admin view).
438 func (d *D) GetAllRevokedKeys() (keys []*WireGuardRevokedKey, err error) {
439 prefix := []byte(wgRevokedPrefix)
440
441 err = d.DB.View(func(txn *badger.Txn) error {
442 opts := badger.DefaultIteratorOptions
443 opts.Prefix = prefix
444 it := txn.NewIterator(opts)
445 defer it.Close()
446
447 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
448 item := it.Item()
449 err := item.Value(func(val []byte) error {
450 key := &WireGuardRevokedKey{}
451 if err := json.Unmarshal(val, key); err != nil {
452 return err
453 }
454 keys = append(keys, key)
455 return nil
456 })
457 if err != nil {
458 return err
459 }
460 }
461 return nil
462 })
463 return
464 }
465
466 // LogObsoleteAccess records an access attempt to an obsolete WireGuard address.
467 func (d *D) LogObsoleteAccess(nostrPubkey, wgPubkey []byte, sequence uint32, remoteAddr string) error {
468 now := time.Now().Unix()
469
470 logEntry := &WireGuardAccessLog{
471 NostrPubkey: nostrPubkey,
472 WGPublicKey: wgPubkey,
473 Sequence: sequence,
474 Timestamp: now,
475 RemoteAddr: remoteAddr,
476 }
477
478 data, err := json.Marshal(logEntry)
479 if err != nil {
480 return fmt.Errorf("failed to marshal access log: %w", err)
481 }
482
483 // Key: wg:accesslog:<pubkey-hex>:<timestamp>
484 keyStr := fmt.Sprintf("%s%s:%d", wgAccessLogPrefix, hex.Enc(nostrPubkey), now)
485
486 return d.DB.Update(func(txn *badger.Txn) error {
487 return txn.Set([]byte(keyStr), data)
488 })
489 }
490
491 // GetAccessLogs returns access logs for a user.
492 func (d *D) GetAccessLogs(nostrPubkey []byte) (logs []*WireGuardAccessLog, err error) {
493 prefix := []byte(wgAccessLogPrefix + hex.Enc(nostrPubkey) + ":")
494
495 err = d.DB.View(func(txn *badger.Txn) error {
496 opts := badger.DefaultIteratorOptions
497 opts.Prefix = prefix
498 it := txn.NewIterator(opts)
499 defer it.Close()
500
501 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
502 item := it.Item()
503 err := item.Value(func(val []byte) error {
504 logEntry := &WireGuardAccessLog{}
505 if err := json.Unmarshal(val, logEntry); err != nil {
506 return err
507 }
508 logs = append(logs, logEntry)
509 return nil
510 })
511 if err != nil {
512 return err
513 }
514 }
515 return nil
516 })
517 return
518 }
519
520 // GetAllAccessLogs returns all access logs (admin view).
521 func (d *D) GetAllAccessLogs() (logs []*WireGuardAccessLog, err error) {
522 prefix := []byte(wgAccessLogPrefix)
523
524 err = d.DB.View(func(txn *badger.Txn) error {
525 opts := badger.DefaultIteratorOptions
526 opts.Prefix = prefix
527 it := txn.NewIterator(opts)
528 defer it.Close()
529
530 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
531 item := it.Item()
532 err := item.Value(func(val []byte) error {
533 logEntry := &WireGuardAccessLog{}
534 if err := json.Unmarshal(val, logEntry); err != nil {
535 return err
536 }
537 logs = append(logs, logEntry)
538 return nil
539 })
540 if err != nil {
541 return err
542 }
543 }
544 return nil
545 })
546 return
547 }
548
549 // IncrementRevokedKeyAccess updates the access count for a revoked key.
550 func (d *D) IncrementRevokedKeyAccess(nostrPubkey, wgPubkey []byte) error {
551 // Find and update the matching revoked key
552 prefix := []byte(wgRevokedPrefix + hex.Enc(nostrPubkey) + ":")
553 wgPubkeyHex := hex.Enc(wgPubkey)
554 now := time.Now().Unix()
555
556 return d.DB.Update(func(txn *badger.Txn) error {
557 opts := badger.DefaultIteratorOptions
558 opts.Prefix = prefix
559 it := txn.NewIterator(opts)
560 defer it.Close()
561
562 for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
563 item := it.Item()
564 key := item.KeyCopy(nil)
565
566 err := item.Value(func(val []byte) error {
567 revoked := &WireGuardRevokedKey{}
568 if err := json.Unmarshal(val, revoked); err != nil {
569 return err
570 }
571
572 // Check if this is the matching revoked key
573 if hex.Enc(revoked.WGPublicKey) == wgPubkeyHex {
574 revoked.AccessCount++
575 revoked.LastAccessAt = now
576
577 data, err := json.Marshal(revoked)
578 if err != nil {
579 return err
580 }
581 return txn.Set(key, data)
582 }
583 return nil
584 })
585 if err != nil {
586 return err
587 }
588 }
589 return nil
590 })
591 }
592