Something went wrong. Try again.
This repository has no description
Something went wrong. Try again.
4.2 kB · 161 lines
Go
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162// Package acme obtains and renews the node's TLS certificates from an ACME// CA (Let's Encrypt by default) with certmagic, keeping everything in// statedb so a station's nodes share one set of certificates.package acme
import ( "context" "fmt" "io/fs" "sort" "strings" "sync"
"github.com/caddyserver/certmagic"
"stream.place/streamplace/pkg/statedb")
// Storage is certmagic.Storage on top of statedb. Values live in the// certmagic_items table; locks are statedb named locks, which on Postgres// are session-scoped advisory locks (released automatically if the holder// dies, so there is no stale-lock bookkeeping) and on sqlite are in-process// mutexes (a sqlite statedb is a single-node deployment).type Storage struct { state *statedb.StatefulDB
mu sync.Mutex locks map[string]func()}
var _ certmagic.Storage = (*Storage)(nil)
// NewStorage wraps state.func NewStorage(state *statedb.StatefulDB) *Storage { return &Storage{state: state, locks: map[string]func(){}}}
const lockPrefix = "certmagic/"
// Lock implements certmagic.Locker. It blocks until the lock is held or ctx// ends. A lock held by another node in the station (Postgres) or another// goroutine (sqlite) makes the call wait, as certmagic expects.func (s *Storage) Lock(ctx context.Context, name string) error { unlock, err := s.state.WaitForNamedLock(ctx, lockPrefix+name) if err != nil { return fmt.Errorf("acme lock %q: %w", name, err) } s.mu.Lock() s.locks[name] = unlock s.mu.Unlock() return nil}
func (s *Storage) Unlock(ctx context.Context, name string) error { s.mu.Lock() unlock, ok := s.locks[name] delete(s.locks, name) s.mu.Unlock() if !ok { return fmt.Errorf("acme unlock %q: not held", name) } unlock() return nil}
// Store implements certmagic.Storage.func (s *Storage) Store(ctx context.Context, key string, value []byte) error { return s.state.CertmagicPut(ctx, key, value)}
// Load implements certmagic.Storage.func (s *Storage) Load(ctx context.Context, key string) ([]byte, error) { item, err := s.state.CertmagicGet(ctx, key) if err != nil { return nil, err } if item == nil { return nil, fs.ErrNotExist } return item.Value, nil}
// Delete implements certmagic.Storage: key and everything under it.func (s *Storage) Delete(ctx context.Context, key string) error { _, err := s.state.CertmagicDelete(ctx, key) return err}
// Exists implements certmagic.Storage.func (s *Storage) Exists(ctx context.Context, key string) bool { rows, err := s.state.CertmagicList(ctx, key) return err == nil && len(rows) > 0}
// List implements certmagic.Storage with the same shape as certmagic's own// FileStorage: every key below prefix when recursive (implicit directories// included), else only the immediate children.func (s *Storage) List(ctx context.Context, prefix string, recursive bool) ([]string, error) { rows, err := s.state.CertmagicList(ctx, prefix) if err != nil { return nil, err } base := prefix if base != "" { base += "/" } seen := map[string]struct{}{} var keys []string add := func(k string) { if _, dup := seen[k]; dup { return } seen[k] = struct{}{} keys = append(keys, k) } for _, row := range rows { if row.Key == prefix { continue } rel := strings.TrimPrefix(row.Key, base) parts := strings.Split(rel, "/") if !recursive { add(base + parts[0]) continue } for i := range parts { add(base + strings.Join(parts[:i+1], "/")) } } sort.Strings(keys) return keys, nil}
// Stat implements certmagic.Storage.func (s *Storage) Stat(ctx context.Context, key string) (certmagic.KeyInfo, error) { rows, err := s.state.CertmagicList(ctx, key) if err != nil { return certmagic.KeyInfo{}, err } if len(rows) == 0 { return certmagic.KeyInfo{}, fs.ErrNotExist } info := certmagic.KeyInfo{Key: key} for _, row := range rows { if row.Key == key { info.IsTerminal = true info.Size = row.Size info.Modified = row.UpdatedAt return info, nil } if row.UpdatedAt.After(info.Modified) { info.Modified = row.UpdatedAt } } return info, nil}
// String makes storage identifiable in certmagic's logs.func (s *Storage) String() string { return "statedb"}