mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat(enterprise/dbcrypt): encrypt ai_providers and ai_provider_keys at rest (#25326)
This commit is contained in:
@@ -163,6 +163,52 @@ func Rotate(ctx context.Context, log slog.Logger, sqlDB *sql.DB, ciphers []Ciphe
|
||||
log.Debug(ctx, "encrypted chat provider key", slog.F("provider", provider.Provider), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
}
|
||||
|
||||
aiProviders, err := cryptDB.GetAIProviders(ctx, database.GetAIProvidersParams{IncludeDeleted: true, IncludeDisabled: true})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get ai providers: %w", err)
|
||||
}
|
||||
log.Info(ctx, "encrypting ai provider settings", slog.F("provider_count", len(aiProviders)))
|
||||
for idx, ap := range aiProviders {
|
||||
if !ap.Settings.Valid || strings.TrimSpace(ap.Settings.String) == "" {
|
||||
continue
|
||||
}
|
||||
if ap.SettingsKeyID.Valid && ap.SettingsKeyID.String == ciphers[0].HexDigest() {
|
||||
log.Debug(ctx, "skipping ai provider", slog.F("ai_provider_id", ap.ID), slog.F("name", ap.Name), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
continue
|
||||
}
|
||||
if _, err := cryptDB.UpdateEncryptedAIProviderSettings(ctx, database.UpdateEncryptedAIProviderSettingsParams{
|
||||
ID: ap.ID,
|
||||
Settings: ap.Settings,
|
||||
SettingsKeyID: sql.NullString{}, // dbcrypt will update as required
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("update ai provider id=%s name=%s: %w", ap.ID, ap.Name, err)
|
||||
}
|
||||
log.Debug(ctx, "encrypted ai provider settings", slog.F("ai_provider_id", ap.ID), slog.F("name", ap.Name), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
}
|
||||
|
||||
aiProviderKeys, err := cryptDB.GetAIProviderKeys(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get ai provider keys: %w", err)
|
||||
}
|
||||
log.Info(ctx, "encrypting ai provider keys", slog.F("key_count", len(aiProviderKeys)))
|
||||
for idx, apk := range aiProviderKeys {
|
||||
if strings.TrimSpace(apk.APIKey) == "" {
|
||||
continue
|
||||
}
|
||||
if apk.ApiKeyKeyID.Valid && apk.ApiKeyKeyID.String == ciphers[0].HexDigest() {
|
||||
log.Debug(ctx, "skipping ai provider key", slog.F("ai_provider_key_id", apk.ID), slog.F("provider_id", apk.ProviderID), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
continue
|
||||
}
|
||||
if _, err := cryptDB.UpdateEncryptedAIProviderKey(ctx, database.UpdateEncryptedAIProviderKeyParams{
|
||||
ID: apk.ID,
|
||||
APIKey: apk.APIKey,
|
||||
ApiKeyKeyID: sql.NullString{}, // dbcrypt will update as required
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("update ai provider key id=%s provider_id=%s: %w", apk.ID, apk.ProviderID, err)
|
||||
}
|
||||
log.Debug(ctx, "encrypted ai provider key", slog.F("ai_provider_key_id", apk.ID), slog.F("provider_id", apk.ProviderID), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
}
|
||||
|
||||
// Revoke old keys
|
||||
for _, c := range ciphers[1:] {
|
||||
if err := db.RevokeDBCryptKey(ctx, c.HexDigest()); err != nil {
|
||||
@@ -326,6 +372,46 @@ func Decrypt(ctx context.Context, log slog.Logger, sqlDB *sql.DB, ciphers []Ciph
|
||||
log.Debug(ctx, "decrypted chat provider key", slog.F("provider", provider.Provider), slog.F("current", idx+1), slog.F("cipher", ciphers[0].HexDigest()))
|
||||
}
|
||||
|
||||
aiProviders, err := cryptDB.GetAIProviders(ctx, database.GetAIProvidersParams{IncludeDeleted: true, IncludeDisabled: true})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get ai providers: %w", err)
|
||||
}
|
||||
log.Info(ctx, "decrypting ai provider settings", slog.F("provider_count", len(aiProviders)))
|
||||
for idx, ap := range aiProviders {
|
||||
if !ap.SettingsKeyID.Valid {
|
||||
log.Debug(ctx, "skipping ai provider", slog.F("ai_provider_id", ap.ID), slog.F("name", ap.Name), slog.F("current", idx+1))
|
||||
continue
|
||||
}
|
||||
if _, err := cryptDB.UpdateEncryptedAIProviderSettings(ctx, database.UpdateEncryptedAIProviderSettingsParams{
|
||||
ID: ap.ID,
|
||||
Settings: ap.Settings,
|
||||
SettingsKeyID: sql.NullString{}, // explicitly clear the key id
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("decrypt ai provider id=%s name=%s: %w", ap.ID, ap.Name, err)
|
||||
}
|
||||
log.Debug(ctx, "decrypted ai provider", slog.F("ai_provider_id", ap.ID), slog.F("name", ap.Name), slog.F("current", idx+1))
|
||||
}
|
||||
|
||||
aiProviderKeys, err := cryptDB.GetAIProviderKeys(ctx)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get ai provider keys: %w", err)
|
||||
}
|
||||
log.Info(ctx, "decrypting ai provider keys", slog.F("key_count", len(aiProviderKeys)))
|
||||
for idx, apk := range aiProviderKeys {
|
||||
if !apk.ApiKeyKeyID.Valid {
|
||||
log.Debug(ctx, "skipping ai provider key", slog.F("ai_provider_key_id", apk.ID), slog.F("provider_id", apk.ProviderID), slog.F("current", idx+1))
|
||||
continue
|
||||
}
|
||||
if _, err := cryptDB.UpdateEncryptedAIProviderKey(ctx, database.UpdateEncryptedAIProviderKeyParams{
|
||||
ID: apk.ID,
|
||||
APIKey: apk.APIKey,
|
||||
ApiKeyKeyID: sql.NullString{}, // explicitly clear the key id
|
||||
}); err != nil {
|
||||
return xerrors.Errorf("decrypt ai provider key id=%s provider_id=%s: %w", apk.ID, apk.ProviderID, err)
|
||||
}
|
||||
log.Debug(ctx, "decrypted ai provider key", slog.F("ai_provider_key_id", apk.ID), slog.F("provider_id", apk.ProviderID), slog.F("current", idx+1))
|
||||
}
|
||||
|
||||
// Revoke _all_ keys
|
||||
for _, c := range ciphers {
|
||||
if err := db.RevokeDBCryptKey(ctx, c.HexDigest()); err != nil {
|
||||
@@ -354,6 +440,12 @@ UPDATE chat_providers
|
||||
SET api_key = '',
|
||||
api_key_key_id = NULL
|
||||
WHERE api_key_key_id IS NOT NULL;
|
||||
UPDATE ai_providers
|
||||
SET settings = NULL,
|
||||
settings_key_id = NULL
|
||||
WHERE settings_key_id IS NOT NULL;
|
||||
DELETE FROM ai_provider_keys
|
||||
WHERE api_key_key_id IS NOT NULL;
|
||||
COMMIT;
|
||||
`
|
||||
|
||||
|
||||
Reference in New Issue
Block a user