Skip to content

Store AES encryption key in environment variable instead of database #36

@leodip

Description

@leodip

Problem

The AES-256 encryption key used to encrypt sensitive data (client secrets, SMTP passwords, etc.) is currently generated during database
seeding and stored in plaintext in the settings table (aes_encryption_key column).

This creates a security concern: if the database is compromised, an attacker obtains both the encrypted data AND the key to decrypt it,
rendering the encryption ineffective.

Current flow:
Database compromise → attacker gets encrypted client secrets + AES key → full decryption possible

Current Implementation

The key is generated in database_seeder.go and stored in the database:

// database_seeder.go:63
encryptionKey := securecookie.GenerateRandomKey(32)

// database_seeder.go:357
settings := &models.Settings{
AESEncryptionKey: encryptionKey, // stored in DB
}

Meanwhile, goiabada-setup already generates other secrets externally (session keys, OAuth client secret) and passes them via environment
variables — but the AES key is not handled this way.

Proposed Solution

Align AES encryption key handling with the existing pattern used for session keys:

  1. Add to goiabada-setup — Generate GOIABADA_AES_ENCRYPTION_KEY alongside other secrets
  2. Add to Config struct — Include AESEncryptionKey field in goiabada-setup/main.go
  3. Update output templates — Add the env var to docker-compose, Kubernetes manifests, and .env files
  4. Add to core config — Read GOIABADA_AES_ENCRYPTION_KEY in src/core/config/config.go
  5. Modify database seeder — Accept key from config instead of generating it
  6. Remove from database — Drop aes_encryption_key column from settings table

Changes Required

src/cmd/goiabada-setup/main.go:
// Add to credential generation (around line 601)
aesEncryptionKey := generateHexKey(32)
printSuccess("AES encryption key generated")

// Add to Config struct
type Config struct {
// ... existing fields ...
AESEncryptionKey string
}

Output templates (docker-compose, k8s, .env):
GOIABADA_AES_ENCRYPTION_KEY: ${config.AESEncryptionKey}

src/core/config/config.go:
AESEncryptionKey string env:"GOIABADA_AES_ENCRYPTION_KEY"

src/core/data/database_seeder.go:

  • Remove encryptionKey := securecookie.GenerateRandomKey(32)
  • Accept key from config/parameter instead

Migration for Existing Installations

Add startup validation with three scenarios:

DB Key Env Var Result
Exists Not set Fail — require migration
Exists Set, matches OK — continue startup
Exists Set, different Fail — key mismatch error
Empty/null Set OK — new installation pattern
Empty/null Not set Fail — key required

Scenario 1: DB key exists, env var not set

MIGRATION REQUIRED

Detected AES encryption key in database but GOIABADA_AES_ENCRYPTION_KEY
environment variable is not set.

Starting with this version, the AES encryption key must be provided via
environment variable for improved security.

To migrate, run:
goiabada-authserver --export-aes-key

This will output the key from your database. Add it to your environment:
GOIABADA_AES_ENCRYPTION_KEY=

Then restart the server.

Scenario 2: DB key exists, env var set but DIFFERENT

FATAL: AES ENCRYPTION KEY MISMATCH

The GOIABADA_AES_ENCRYPTION_KEY environment variable does not match the key
stored in the database.

Using the wrong key will cause decryption failures for existing encrypted
data (client secrets, SMTP passwords, etc.).

To resolve:
1. If migrating from DB key: run 'goiabada-authserver --export-aes-key'
and use the output as your GOIABADA_AES_ENCRYPTION_KEY value.

2. If intentionally rotating keys: this requires re-encrypting all
   existing secrets, which is not yet supported.

Server cannot start with mismatched keys.

Add CLI flag: --export-aes-key

  • Connects to database
  • Reads aes_encryption_key from settings
  • Outputs hex-encoded key to stdout
  • Exits

Security Benefit

Database compromise → attacker gets encrypted data only → decryption not possible without env var

Implementation Checklist

  • Add AESEncryptionKey to goiabada-setup Config struct and generation
  • Update generateDockerCompose() to include the env var
  • Update generateKubernetesManifests() to include the env var in Secret
  • Update generateEnvFile() to include the env var
  • Add GOIABADA_AES_ENCRYPTION_KEY to src/core/config/config.go
  • Modify DatabaseSeeder to accept key from config
  • Add --export-aes-key CLI flag to authserver
  • Add startup validation:
    • Fail if DB key exists but env var missing
    • Fail if DB key exists and env var differs (key mismatch)
    • Pass if keys match or new installation
  • Create database migration to remove aes_encryption_key column (future release)
  • Update documentation

References

  • Setup wizard: src/cmd/goiabada-setup/main.go
  • Database seeder: src/core/data/database_seeder.go:63
  • Settings model: src/core/models/settings.go:25
  • Encryption utility: src/core/encryption/encryption.go
  • Documentation: site/src/content/docs/getting-started/setup-wizard.md

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions