Files
llmgw/internal/store/secrets.go
T
Ilker ManapandClaude Opus 5.5 ac25a9136a Sign-in is set up from the console, and a directory tried before it is kept
People → Sign-in shows single sign-on, Active Directory and SCIM, and
the directory's last sync. E sets up Entra ID from the tenant and client
IDs; D sets up Active Directory, tried from the control plane first:
each server bound, people who may sign in, members of each mapped group.
The secrets go into the store as store:auth/..., sealed by the master
key, and the CA into the settings, so no control plane needs a file.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
2026-10-19 12:55:27 +02:00

104 lines
3.2 KiB
Go

package store
import (
"context"
"crypto/rand"
"database/sql"
"encoding/hex"
"errors"
"fmt"
"time"
"gitea.mynodes.xyz/ilker/llmgw/internal/secrets"
)
// 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
}
return s.openSecret(ctx, name, v)
}
// SetClusterSecret keeps value under name, sealed when a master key is set,
// in place of what was there: a secret an operator gives, such as the
// password the directory is read with, the same for every control plane.
func (s *DB) SetClusterSecret(ctx context.Context, name, value string) error {
stored := value
if s.sealer != nil {
var err error
if stored, err = s.sealer.Seal(ctx, []byte(value)); err != nil {
return err
}
}
_, err := s.db.ExecContext(ctx,
`INSERT INTO cluster_secrets (name, value, created_at) VALUES (?, ?, ?)
ON CONFLICT (name) DO UPDATE SET value = excluded.value, created_at = excluded.created_at`,
name, stored, time.Now().UnixMilli())
return err
}
// StoredSecret is the named secret, ErrNotFound when none was kept; unlike
// ClusterSecret it never makes one.
func (s *DB) StoredSecret(ctx context.Context, name string) (string, error) {
var v string
err := s.db.QueryRowContext(ctx, `SELECT value FROM cluster_secrets WHERE name = ?`, name).Scan(&v)
if errors.Is(err, sql.ErrNoRows) {
return "", ErrNotFound
}
if err != nil {
return "", err
}
return s.openSecret(ctx, name, v)
}
// openSecret is a kept value in the clear.
func (s *DB) openSecret(ctx context.Context, name, v string) (string, error) {
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 }