Secret encryption
CTFreak stores sensitive data such as node credentials, API tokens, webhook secrets, and SMTP passwords in its backend database.
These fields are protected by a configurable encryption method. Two methods are currently available:
- PLAIN (default): secrets are stored as-is, without encryption.
- AES (AES-256-GCM): secrets are encrypted on the fly using key-based encryption. Requires a Business or Sovereign Edition license. Free Edition users who plan to upgrade later can also enable AES encryption in advance.
Encrypted fields
The following fields are affected by the encryption method:
- Credentials: password, private key, passphrase
- Databases: connection password
- Auth providers: OIDC client secret
- Projects: GitHub, GitLab, Jira, Linear, Telegram, YouTrack and Ntfy integration API tokens
- Webhooks: webhook secret (GitHub, GitLab, Gitea, Forgejo)
- Notifiers: webhook URL (Discord, Mattermost, Microsoft Teams, Slack), SMTP password (Email)
- License key
How it works
The encryption method and its parameters (key, etc.) are stored in CTFreak’s configuration file (config.json).
CTFreak transparently encrypts secrets on write and decrypts them on read. The encryption is invisible to end users and API consumers.
Changing the encryption method
To change the encryption method, stop your CTFreak instance first (to avoid database cache corruption), then run:
ctfreak encrypt <method>
For example:
# Encrypt all secrets with AES-256-GCM
ctfreak encrypt AES
# Revert all secrets to plaintext
ctfreak encrypt PLAIN
This command will:
- Check that no CTFreak instance is currently running
- Generate a new encryption key (if applicable)
- Re-encrypt all secrets in a single database transaction
- Save the new encryption configuration to the configuration file
Running ctfreak encrypt AES when AES encryption is already enabled will generate a new key and re-encrypt all secrets with it. This allows you to perform key rotation, a security best practice that limits the exposure window if an encryption key is ever compromised.
Protecting the configuration file
When using an encryption method other than PLAIN, the configuration file contains the encryption key. Make sure to:
- Restrict file permissions on the configuration file (e.g.
chmod 600 config.json) - Include the configuration file in your backup strategy
- Never share the configuration file with unauthorized parties
If you want to prevent a CTFreak administrator from reading the configuration file (and thus the encryption key) through task execution, consider disabling local command and ansible playbook task types at startup.
If the configuration file is lost or the key is corrupted, encrypted secrets cannot be recovered.
Backup considerations
When using an encryption method other than PLAIN, your backup strategy must include both:
- The backend database (containing the encrypted secrets)
- The configuration file (containing the encryption key)
One without the other will result in data loss.
However, store these two backups in separate locations. If the database backup and the configuration file are stored side by side, anyone who gains access to the backup has everything needed to decrypt the secrets, which defeats the purpose of encryption.
Note that while secret encryption works with both SQLite and PostgreSQL backends, it is particularly relevant when using PostgreSQL as the backend database. With SQLite, the database file and the configuration file reside on the same server, so an attacker with access to the server could potentially access both. With PostgreSQL, the database is hosted separately from the configuration file, providing a natural separation between the encrypted data and the encryption key.