fix(auth): repair persistence layer and stop storing plaintext keys
- Store SHA-256 digests (key_hash + key_preview) instead of raw keys, in both the in-memory store and Postgres; migrate legacy plaintext rows in place and drop the plaintext column. - Fix TEXT[] scans that failed at runtime (missing pq.Array) in GetKey/ListKeys/LoadAllKeys. - Load persisted keys at startup (InitializeFromDatabase was never called) and fall back to the database on cache miss, so issued keys survive restarts. - Make HybridKeyStore.CreateKey genuinely write-through: cache is only updated after a successful DB write. - Guard nil database backend (auth enabled without DB previously panicked on key creation) and degrade to in-memory operation. - Persist bootstrap admin keys when a database is configured. - Record real audit-log details as JSON and log audit failures instead of silently dropping them; add DB pool limits and ping timeout. - Sentinel errors matched with errors.Is; unit tests for hashing, write-through ordering, DB fallback, and nil-DB operation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
+25
-4
@@ -99,7 +99,7 @@ func main() {
|
||||
// Create database backend if configured
|
||||
var dbStore *auth.KeyStoreDB
|
||||
if cfg.Auth.Database.URL != "" {
|
||||
dbStore, err = auth.NewKeyStoreDB(cfg.Auth.Database.URL)
|
||||
dbStore, err = auth.NewKeyStoreDB(cfg.Auth.Database.URL, logger)
|
||||
if err != nil {
|
||||
logger.Fatalf("Failed to create database key store: %v", err)
|
||||
}
|
||||
@@ -112,6 +112,16 @@ func main() {
|
||||
hybridKeyStore = auth.NewHybridKeyStore(authStore, dbStore)
|
||||
logger.Debugf("Initialized hybrid key store for API key management")
|
||||
|
||||
// Load persisted keys into the cache so previously issued keys
|
||||
// keep authenticating across restarts.
|
||||
if hybridKeyStore.HasDatabase() {
|
||||
if loaded, err := hybridKeyStore.InitializeFromDatabase(ctx); err != nil {
|
||||
logger.Errorf("Failed to load API keys from database: %v", err)
|
||||
} else {
|
||||
logger.Infof("Loaded %d API key(s) from database into cache", loaded)
|
||||
}
|
||||
}
|
||||
|
||||
// Bootstrap admin key if configured
|
||||
if cfg.Auth.Bootstrap.Enabled {
|
||||
bootstrapCfg := &auth.BootstrapConfig{
|
||||
@@ -133,12 +143,23 @@ func main() {
|
||||
|
||||
// If we have an existing key, use it
|
||||
if existingKey != "" {
|
||||
if _, err := auth.RegisterAdminKeyInMemory(authStore, existingKey, logger); err != nil {
|
||||
apiKey, err := auth.RegisterAdminKeyInMemory(authStore, existingKey, logger)
|
||||
if err != nil {
|
||||
logger.Warnf("Failed to register existing admin key: %v", err)
|
||||
} else if err := hybridKeyStore.EnsurePersisted(ctx, apiKey, "bootstrap"); err != nil {
|
||||
logger.Warnf("Failed to persist existing admin key: %v", err)
|
||||
}
|
||||
} else {
|
||||
// Generate new key
|
||||
if apiKey, err := auth.BootstrapAdminKeyInMemory(authStore, bootstrapCfg, logger); err != nil {
|
||||
// Generate a new key — through the hybrid store when a
|
||||
// database is configured so the admin key survives restarts.
|
||||
var apiKey *auth.APIKey
|
||||
var err error
|
||||
if hybridKeyStore.HasDatabase() {
|
||||
apiKey, err = auth.BootstrapAdminKey(ctx, hybridKeyStore, bootstrapCfg, logger)
|
||||
} else {
|
||||
apiKey, err = auth.BootstrapAdminKeyInMemory(authStore, bootstrapCfg, logger)
|
||||
}
|
||||
if err != nil {
|
||||
logger.Warnf("Bootstrap admin key creation failed: %v", err)
|
||||
} else if apiKey != nil {
|
||||
// Store in Kubernetes secret if configured
|
||||
|
||||
Reference in New Issue
Block a user