Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
64 lines
1.9 KiB
Go
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 }
|