Add WireGuard VPN with random /31 subnet isolation (v0.40.0)
Some checks failed
Go / build-and-release (push) Has been cancelled
Some checks failed
Go / build-and-release (push) Has been cancelled
- Add embedded WireGuard VPN server using wireguard-go + netstack - Implement deterministic /31 subnet allocation from seed + sequence - Use Badger's built-in Sequence for atomic counter allocation - Add NIP-46 bunker server for remote signing over VPN - Add revoked key tracking and access audit logging for users - Add Bunker tab to web UI with WireGuard/bunker QR codes - Support key regeneration with old keypair archiving New environment variables: - ORLY_WG_ENABLED: Enable WireGuard VPN server - ORLY_WG_PORT: UDP port for WireGuard (default 51820) - ORLY_WG_ENDPOINT: Public endpoint for WireGuard - ORLY_WG_NETWORK: Base network for subnet pool (default 10.0.0.0/8) - ORLY_BUNKER_ENABLED: Enable NIP-46 bunker - ORLY_BUNKER_PORT: WebSocket port for bunker (default 3335) Files added: - pkg/wireguard/: WireGuard server, keygen, subnet pool, errors - pkg/bunker/: NIP-46 bunker server and session handling - pkg/database/wireguard.go: Peer storage with audit logging - app/handle-wireguard.go: API endpoints for config/regenerate/audit - app/wireguard-helpers.go: Key derivation helpers - app/web/src/BunkerView.svelte: Bunker UI with QR codes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
591
pkg/database/wireguard.go
Normal file
591
pkg/database/wireguard.go
Normal file
@@ -0,0 +1,591 @@
|
||||
//go:build !(js && wasm)
|
||||
|
||||
package database
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/dgraph-io/badger/v4"
|
||||
"lol.mleku.dev/chk"
|
||||
"lol.mleku.dev/log"
|
||||
|
||||
"git.mleku.dev/mleku/nostr/encoders/hex"
|
||||
"next.orly.dev/pkg/wireguard"
|
||||
)
|
||||
|
||||
// Key prefixes for WireGuard data
|
||||
const (
|
||||
wgServerKeyPrefix = "wg:server:key" // Server's WireGuard private key
|
||||
wgSubnetSeedPrefix = "wg:subnet:seed" // Seed for deterministic subnet generation
|
||||
wgPeerPrefix = "wg:peer:" // Peer data by Nostr pubkey hex
|
||||
wgSequenceKey = "wg:seq" // Badger sequence key for subnet allocation
|
||||
wgRevokedPrefix = "wg:revoked:" // Revoked keypairs by Nostr pubkey hex
|
||||
wgAccessLogPrefix = "wg:accesslog:" // Access log for obsolete addresses
|
||||
)
|
||||
|
||||
// WireGuardPeer stores WireGuard peer information in the database.
|
||||
type WireGuardPeer struct {
|
||||
NostrPubkey []byte `json:"nostr_pubkey"` // User's Nostr pubkey (32 bytes)
|
||||
WGPrivateKey []byte `json:"wg_private_key"` // WireGuard private key (32 bytes)
|
||||
WGPublicKey []byte `json:"wg_public_key"` // WireGuard public key (32 bytes)
|
||||
Sequence uint32 `json:"sequence"` // Sequence number for subnet derivation
|
||||
CreatedAt int64 `json:"created_at"` // Unix timestamp
|
||||
}
|
||||
|
||||
// WireGuardRevokedKey stores a revoked/old WireGuard keypair for audit purposes.
|
||||
type WireGuardRevokedKey struct {
|
||||
NostrPubkey []byte `json:"nostr_pubkey"` // User's Nostr pubkey (32 bytes)
|
||||
WGPublicKey []byte `json:"wg_public_key"` // Revoked WireGuard public key (32 bytes)
|
||||
Sequence uint32 `json:"sequence"` // Sequence number (subnet)
|
||||
CreatedAt int64 `json:"created_at"` // When the key was originally created
|
||||
RevokedAt int64 `json:"revoked_at"` // When the key was revoked
|
||||
AccessCount int `json:"access_count"` // Number of access attempts since revocation
|
||||
LastAccessAt int64 `json:"last_access_at"` // Last access attempt timestamp (0 if never)
|
||||
}
|
||||
|
||||
// WireGuardAccessLog records an access attempt to an obsolete address.
|
||||
type WireGuardAccessLog struct {
|
||||
NostrPubkey []byte `json:"nostr_pubkey"` // User's Nostr pubkey
|
||||
WGPublicKey []byte `json:"wg_public_key"` // The obsolete public key used
|
||||
Sequence uint32 `json:"sequence"` // Subnet sequence
|
||||
Timestamp int64 `json:"timestamp"` // When the access occurred
|
||||
RemoteAddr string `json:"remote_addr"` // Remote IP address
|
||||
}
|
||||
|
||||
// ServerIP returns the derived server IP for this peer's subnet.
|
||||
func (p *WireGuardPeer) ServerIP(pool *wireguard.SubnetPool) string {
|
||||
subnet := pool.SubnetForSequence(p.Sequence)
|
||||
return subnet.ServerIP.String()
|
||||
}
|
||||
|
||||
// ClientIP returns the derived client IP for this peer's subnet.
|
||||
func (p *WireGuardPeer) ClientIP(pool *wireguard.SubnetPool) string {
|
||||
subnet := pool.SubnetForSequence(p.Sequence)
|
||||
return subnet.ClientIP.String()
|
||||
}
|
||||
|
||||
// GetWireGuardServerKey retrieves the WireGuard server private key.
|
||||
func (d *D) GetWireGuardServerKey() (key []byte, err error) {
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get([]byte(wgServerKeyPrefix))
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return item.Value(func(val []byte) error {
|
||||
key = make([]byte, len(val))
|
||||
copy(key, val)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// SetWireGuardServerKey stores the WireGuard server private key.
|
||||
func (d *D) SetWireGuardServerKey(key []byte) error {
|
||||
if len(key) != 32 {
|
||||
return fmt.Errorf("invalid key length: %d (expected 32)", len(key))
|
||||
}
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set([]byte(wgServerKeyPrefix), key)
|
||||
})
|
||||
}
|
||||
|
||||
// GetOrCreateWireGuardServerKey retrieves or creates the WireGuard server key.
|
||||
func (d *D) GetOrCreateWireGuardServerKey() (key []byte, err error) {
|
||||
// Try to get existing key
|
||||
if key, err = d.GetWireGuardServerKey(); err == nil && len(key) == 32 {
|
||||
return key, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate new keypair
|
||||
privateKey, publicKey, err := wireguard.GenerateKeyPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate WireGuard keypair: %w", err)
|
||||
}
|
||||
|
||||
// Store the private key
|
||||
if err = d.SetWireGuardServerKey(privateKey); chk.E(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log.I.F("generated new WireGuard server key (pubkey=%s...)", hex.Enc(publicKey[:8]))
|
||||
return privateKey, nil
|
||||
}
|
||||
|
||||
// GetSubnetSeed retrieves the subnet pool seed.
|
||||
func (d *D) GetSubnetSeed() (seed []byte, err error) {
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get([]byte(wgSubnetSeedPrefix))
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return item.Value(func(val []byte) error {
|
||||
seed = make([]byte, len(val))
|
||||
copy(seed, val)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// SetSubnetSeed stores the subnet pool seed.
|
||||
func (d *D) SetSubnetSeed(seed []byte) error {
|
||||
if len(seed) != 32 {
|
||||
return fmt.Errorf("invalid seed length: %d (expected 32)", len(seed))
|
||||
}
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set([]byte(wgSubnetSeedPrefix), seed)
|
||||
})
|
||||
}
|
||||
|
||||
// GetOrCreateSubnetPool creates or restores a subnet pool from the database.
|
||||
func (d *D) GetOrCreateSubnetPool(baseNetwork string) (*wireguard.SubnetPool, error) {
|
||||
// Try to get existing seed
|
||||
seed, err := d.GetSubnetSeed()
|
||||
if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var pool *wireguard.SubnetPool
|
||||
|
||||
if len(seed) == 32 {
|
||||
// Restore pool with existing seed
|
||||
pool, err = wireguard.NewSubnetPoolWithSeed(baseNetwork, seed)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
log.D.F("restored subnet pool with existing seed")
|
||||
} else {
|
||||
// Create new pool with random seed
|
||||
pool, err = wireguard.NewSubnetPool(baseNetwork)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Store the new seed
|
||||
if err = d.SetSubnetSeed(pool.Seed()); err != nil {
|
||||
return nil, fmt.Errorf("failed to store subnet seed: %w", err)
|
||||
}
|
||||
log.I.F("generated new subnet pool seed")
|
||||
}
|
||||
|
||||
// Restore existing allocations from database
|
||||
peers, err := d.GetAllWireGuardPeers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load existing peers: %w", err)
|
||||
}
|
||||
|
||||
for _, peer := range peers {
|
||||
pool.RestoreAllocation(hex.Enc(peer.NostrPubkey), peer.Sequence)
|
||||
}
|
||||
|
||||
if len(peers) > 0 {
|
||||
log.D.F("restored %d subnet allocations", len(peers))
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
// GetWireGuardPeer retrieves a WireGuard peer by Nostr pubkey.
|
||||
func (d *D) GetWireGuardPeer(nostrPubkey []byte) (peer *WireGuardPeer, err error) {
|
||||
key := append([]byte(wgPeerPrefix), []byte(hex.Enc(nostrPubkey))...)
|
||||
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
item, err := txn.Get(key)
|
||||
if errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return item.Value(func(val []byte) error {
|
||||
peer = &WireGuardPeer{}
|
||||
return json.Unmarshal(val, peer)
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// GetOrCreateWireGuardPeer retrieves or creates a WireGuard peer.
|
||||
// The pool is used for subnet derivation from the sequence number.
|
||||
func (d *D) GetOrCreateWireGuardPeer(nostrPubkey []byte, pool *wireguard.SubnetPool) (peer *WireGuardPeer, err error) {
|
||||
// Try to get existing peer
|
||||
if peer, err = d.GetWireGuardPeer(nostrPubkey); err == nil {
|
||||
return peer, nil
|
||||
}
|
||||
if err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Generate new WireGuard keypair
|
||||
privateKey, publicKey, err := wireguard.GenerateKeyPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate WireGuard keypair: %w", err)
|
||||
}
|
||||
|
||||
// Get next sequence number from Badger's sequence
|
||||
seq64, err := d.GetNextWGSequence()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to allocate sequence: %w", err)
|
||||
}
|
||||
seq := uint32(seq64)
|
||||
|
||||
// Register allocation with pool for in-memory tracking
|
||||
pubkeyHex := hex.Enc(nostrPubkey)
|
||||
pool.RestoreAllocation(pubkeyHex, seq)
|
||||
|
||||
peer = &WireGuardPeer{
|
||||
NostrPubkey: nostrPubkey,
|
||||
WGPrivateKey: privateKey,
|
||||
WGPublicKey: publicKey,
|
||||
Sequence: seq,
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Store peer data
|
||||
if err = d.setWireGuardPeer(peer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subnet := pool.SubnetForSequence(seq)
|
||||
log.I.F("created WireGuard peer: nostr=%s... -> subnet %s/%s (seq=%d)",
|
||||
hex.Enc(nostrPubkey[:8]), subnet.ServerIP, subnet.ClientIP, seq)
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
// RegenerateWireGuardPeer generates a new keypair for an existing peer.
|
||||
// The sequence number (and thus subnet) is preserved.
|
||||
// The old keypair is archived for audit purposes.
|
||||
func (d *D) RegenerateWireGuardPeer(nostrPubkey []byte, pool *wireguard.SubnetPool) (peer *WireGuardPeer, err error) {
|
||||
// Get existing peer to preserve sequence
|
||||
existing, err := d.GetWireGuardPeer(nostrPubkey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Archive the old keypair for audit purposes
|
||||
if err = d.ArchiveRevokedKey(existing); err != nil {
|
||||
log.W.F("failed to archive revoked key: %v", err)
|
||||
// Continue anyway - this is audit logging, not critical
|
||||
}
|
||||
|
||||
// Generate new WireGuard keypair
|
||||
privateKey, publicKey, err := wireguard.GenerateKeyPair()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to generate WireGuard keypair: %w", err)
|
||||
}
|
||||
|
||||
peer = &WireGuardPeer{
|
||||
NostrPubkey: nostrPubkey,
|
||||
WGPrivateKey: privateKey,
|
||||
WGPublicKey: publicKey,
|
||||
Sequence: existing.Sequence, // Keep same sequence (same subnet)
|
||||
CreatedAt: time.Now().Unix(),
|
||||
}
|
||||
|
||||
// Store updated peer data
|
||||
if err = d.setWireGuardPeer(peer); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
subnet := pool.SubnetForSequence(peer.Sequence)
|
||||
log.I.F("regenerated WireGuard peer: nostr=%s... -> subnet %s/%s (old key archived)",
|
||||
hex.Enc(nostrPubkey[:8]), subnet.ServerIP, subnet.ClientIP)
|
||||
|
||||
return peer, nil
|
||||
}
|
||||
|
||||
// DeleteWireGuardPeer removes a WireGuard peer from the database.
|
||||
// Note: The sequence number is not recycled to prevent subnet reuse.
|
||||
func (d *D) DeleteWireGuardPeer(nostrPubkey []byte) error {
|
||||
peerKey := append([]byte(wgPeerPrefix), []byte(hex.Enc(nostrPubkey))...)
|
||||
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
if err := txn.Delete(peerKey); err != nil && !errors.Is(err, badger.ErrKeyNotFound) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetAllWireGuardPeers returns all WireGuard peers.
|
||||
func (d *D) GetAllWireGuardPeers() (peers []*WireGuardPeer, err error) {
|
||||
prefix := []byte(wgPeerPrefix)
|
||||
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix = prefix
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
err := item.Value(func(val []byte) error {
|
||||
peer := &WireGuardPeer{}
|
||||
if err := json.Unmarshal(val, peer); err != nil {
|
||||
return err
|
||||
}
|
||||
peers = append(peers, peer)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// setWireGuardPeer stores a WireGuard peer in the database.
|
||||
func (d *D) setWireGuardPeer(peer *WireGuardPeer) error {
|
||||
data, err := json.Marshal(peer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal peer: %w", err)
|
||||
}
|
||||
|
||||
peerKey := append([]byte(wgPeerPrefix), []byte(hex.Enc(peer.NostrPubkey))...)
|
||||
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set(peerKey, data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetNextWGSequence retrieves and increments the sequence counter using Badger's Sequence.
|
||||
func (d *D) GetNextWGSequence() (seq uint64, err error) {
|
||||
// Get a sequence with bandwidth 1 (allocate 1 number at a time)
|
||||
badgerSeq, err := d.DB.GetSequence([]byte(wgSequenceKey), 1)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get sequence: %w", err)
|
||||
}
|
||||
defer badgerSeq.Release()
|
||||
|
||||
seq, err = badgerSeq.Next()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("failed to get next sequence number: %w", err)
|
||||
}
|
||||
return seq, nil
|
||||
}
|
||||
|
||||
// ArchiveRevokedKey stores a revoked keypair for audit purposes.
|
||||
func (d *D) ArchiveRevokedKey(peer *WireGuardPeer) error {
|
||||
revoked := &WireGuardRevokedKey{
|
||||
NostrPubkey: peer.NostrPubkey,
|
||||
WGPublicKey: peer.WGPublicKey,
|
||||
Sequence: peer.Sequence,
|
||||
CreatedAt: peer.CreatedAt,
|
||||
RevokedAt: time.Now().Unix(),
|
||||
AccessCount: 0,
|
||||
LastAccessAt: 0,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(revoked)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal revoked key: %w", err)
|
||||
}
|
||||
|
||||
// Key: wg:revoked:<pubkey-hex>:<revoked-timestamp>
|
||||
keyStr := fmt.Sprintf("%s%s:%d", wgRevokedPrefix, hex.Enc(peer.NostrPubkey), revoked.RevokedAt)
|
||||
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set([]byte(keyStr), data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetRevokedKeys returns all revoked keys for a user.
|
||||
func (d *D) GetRevokedKeys(nostrPubkey []byte) (keys []*WireGuardRevokedKey, err error) {
|
||||
prefix := []byte(wgRevokedPrefix + hex.Enc(nostrPubkey) + ":")
|
||||
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix = prefix
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
err := item.Value(func(val []byte) error {
|
||||
key := &WireGuardRevokedKey{}
|
||||
if err := json.Unmarshal(val, key); err != nil {
|
||||
return err
|
||||
}
|
||||
keys = append(keys, key)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllRevokedKeys returns all revoked keys across all users (admin view).
|
||||
func (d *D) GetAllRevokedKeys() (keys []*WireGuardRevokedKey, err error) {
|
||||
prefix := []byte(wgRevokedPrefix)
|
||||
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix = prefix
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
err := item.Value(func(val []byte) error {
|
||||
key := &WireGuardRevokedKey{}
|
||||
if err := json.Unmarshal(val, key); err != nil {
|
||||
return err
|
||||
}
|
||||
keys = append(keys, key)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// LogObsoleteAccess records an access attempt to an obsolete WireGuard address.
|
||||
func (d *D) LogObsoleteAccess(nostrPubkey, wgPubkey []byte, sequence uint32, remoteAddr string) error {
|
||||
now := time.Now().Unix()
|
||||
|
||||
logEntry := &WireGuardAccessLog{
|
||||
NostrPubkey: nostrPubkey,
|
||||
WGPublicKey: wgPubkey,
|
||||
Sequence: sequence,
|
||||
Timestamp: now,
|
||||
RemoteAddr: remoteAddr,
|
||||
}
|
||||
|
||||
data, err := json.Marshal(logEntry)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to marshal access log: %w", err)
|
||||
}
|
||||
|
||||
// Key: wg:accesslog:<pubkey-hex>:<timestamp>
|
||||
keyStr := fmt.Sprintf("%s%s:%d", wgAccessLogPrefix, hex.Enc(nostrPubkey), now)
|
||||
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
return txn.Set([]byte(keyStr), data)
|
||||
})
|
||||
}
|
||||
|
||||
// GetAccessLogs returns access logs for a user.
|
||||
func (d *D) GetAccessLogs(nostrPubkey []byte) (logs []*WireGuardAccessLog, err error) {
|
||||
prefix := []byte(wgAccessLogPrefix + hex.Enc(nostrPubkey) + ":")
|
||||
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix = prefix
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
err := item.Value(func(val []byte) error {
|
||||
logEntry := &WireGuardAccessLog{}
|
||||
if err := json.Unmarshal(val, logEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
logs = append(logs, logEntry)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// GetAllAccessLogs returns all access logs (admin view).
|
||||
func (d *D) GetAllAccessLogs() (logs []*WireGuardAccessLog, err error) {
|
||||
prefix := []byte(wgAccessLogPrefix)
|
||||
|
||||
err = d.DB.View(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix = prefix
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
err := item.Value(func(val []byte) error {
|
||||
logEntry := &WireGuardAccessLog{}
|
||||
if err := json.Unmarshal(val, logEntry); err != nil {
|
||||
return err
|
||||
}
|
||||
logs = append(logs, logEntry)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// IncrementRevokedKeyAccess updates the access count for a revoked key.
|
||||
func (d *D) IncrementRevokedKeyAccess(nostrPubkey, wgPubkey []byte) error {
|
||||
// Find and update the matching revoked key
|
||||
prefix := []byte(wgRevokedPrefix + hex.Enc(nostrPubkey) + ":")
|
||||
wgPubkeyHex := hex.Enc(wgPubkey)
|
||||
now := time.Now().Unix()
|
||||
|
||||
return d.DB.Update(func(txn *badger.Txn) error {
|
||||
opts := badger.DefaultIteratorOptions
|
||||
opts.Prefix = prefix
|
||||
it := txn.NewIterator(opts)
|
||||
defer it.Close()
|
||||
|
||||
for it.Seek(prefix); it.ValidForPrefix(prefix); it.Next() {
|
||||
item := it.Item()
|
||||
key := item.KeyCopy(nil)
|
||||
|
||||
err := item.Value(func(val []byte) error {
|
||||
revoked := &WireGuardRevokedKey{}
|
||||
if err := json.Unmarshal(val, revoked); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check if this is the matching revoked key
|
||||
if hex.Enc(revoked.WGPublicKey) == wgPubkeyHex {
|
||||
revoked.AccessCount++
|
||||
revoked.LastAccessAt = now
|
||||
|
||||
data, err := json.Marshal(revoked)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return txn.Set(key, data)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user