Files

64 lines
1.9 KiB
Go

package store
import (
"context"
"crypto/rand"
"encoding/hex"
"fmt"
"gitea.mynodes.xyz/ilker/llmgw/internal/secrets"
"time"
)
// ClusterSecret returns the named secret, creating a random one the first time
// it is asked for.
//
// Insert-if-absent and then read, so two control planes asking at once end up
// with the same value: the loser's insert changes nothing and it reads the
// winner's.
func (s *DB) ClusterSecret(ctx context.Context, name string) (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
fresh := hex.EncodeToString(b)
stored := fresh
if s.sealer != nil {
var err error
if stored, err = s.sealer.Seal(ctx, []byte(fresh)); err != nil {
return "", err
}
}
if _, err := s.db.ExecContext(ctx,
`INSERT INTO cluster_secrets (name, value, created_at) VALUES (?, ?, ?)
ON CONFLICT (name) DO NOTHING`,
name, stored, time.Now().UnixMilli()); err != nil {
return "", err
}
var v string
if err := s.db.QueryRowContext(ctx,
`SELECT value FROM cluster_secrets WHERE name = ?`, name).Scan(&v); err != nil {
return "", err
}
if secrets.IsSealed(v) {
if s.sealer == nil {
return "", fmt.Errorf("the store's secret %q is sealed, and secrets.master_key is not set to open it", name)
}
plain, err := s.sealer.Open(ctx, v)
if err != nil {
return "", fmt.Errorf("the store's secret %q: %w", name, err)
}
return string(plain), nil
}
// Kept plain from before a master key: sealed now, the value unchanged.
if s.sealer != nil {
if sealed, err := s.sealer.Seal(ctx, []byte(v)); err == nil {
_, _ = s.db.ExecContext(ctx, `UPDATE cluster_secrets SET value = ? WHERE name = ? AND value = ?`, sealed, name, v)
}
}
return v, nil
}
// SetSealer seals the store's own secrets with s from now on; values kept
// plain are sealed as they are next read.
func (s *DB) SetSealer(x secrets.Sealer) { s.sealer = x }