Skip to content

Security & Encryption

How the master stores credentials, which keys exist, how to rotate them, and what limits are enforced on remote command execution.


1. The Two Keys

The master uses two independent Fernet keys. They serve different purposes and must not be confused.

Field key Transport key
System parameter sadeem_saas_field_key sadeem_saas_secret_key
File alternative sadeem_saas_field_key_path sadeem_saas_secret_key_path
Protects Credentials stored on the master: server passwords, SSH private keys, API keys, tokens Payloads sent to the instances (database names, login tokens, timestamps) on SaaS Slave route calls
Shared with instances No — never leaves the master Yes — every slave instance holds a copy
Rotatable from the UI Yes (see below) No — rotating it means re-deploying the key to every instance

Why they are separate

The transport key is copied into every customer instance, so it is the more exposed of the two. Splitting the keys means a leak of the transport key does not expose the credentials stored on the master.

Key resolution order

For each key, the _path parameter wins when it is set: the file's first line is read and used. If no path is configured, the plain system parameter value is used.

sadeem_saas_field_key_path (file)  →  sadeem_saas_field_key (parameter)

Databases created before the key split

If sadeem_saas_field_key is not configured at all, stored credentials fall back to the transport key. When both keys exist, the master encrypts with the field key and decrypts with either, so values written under the old scheme keep working and are upgraded whenever they are rewritten.


2. Which Fields Are Encrypted

Each addon declares the encrypted fields of its own models in a class-level _ENCRYPTED_FIELDS list. There is no central inventory: the effective list for a model is the union of the declarations across its whole class hierarchy, so only installed addons contribute.

class SadeemRootServer(models.Model):
    _name = 'sadeem.root.server'
    _ENCRYPTED_FIELDS = ['password', 'code_server_password']

Declared in sadeem_saas_base:

Model Encrypted fields
sadeem.root.server password, code_server_password
sadeem.postgresql.server password
sadeem.saas.backup.server api_key
sadeem.ntfy.server password, admin_password, fcm_server_key, auth_token

Other addons (Cloudflare, Nginx Proxy Manager, Portainer, Git accounts, subscriptions) add their own declarations the same way.

Values are stored as Fernet tokens, recognisable by their gAAAAA prefix. Helpers live in sadeem_saas_base/utils/utils.py:

Helper Purpose
encrypt_field(env, value) Encrypt with the field key; returns the value unchanged if it is already a token
decrypt_field(env, value) Decrypt with the field key (falling back to the legacy transport key)
encryption_actions(env, data, encrypt=True) Encrypt/decrypt with the transport key — used for slave route payloads
model_encrypted_fields(model) Union of _ENCRYPTED_FIELDS across the model's MRO

Plaintext sweep

The daily cron Sadeem: Encrypt Plaintext Sensitive Fields walks every model that declares _ENCRYPTED_FIELDS and encrypts any value still stored in plaintext. It is idempotent — already-encrypted values are skipped — and deactivates itself once every record is clean.


3. Rotating the Field Encryption Key

Settings → SAAS Manager → SAAS Base Configuration → Rotate Field Encryption Key (visible to base.group_system only).

The wizard generates a new key, re-encrypts every stored credential with it, and stores the key where the configuration expects it.

Result field Meaning
New Key The generated key — shown once, right after the rotation
Stored In key file <path> or system parameter "sadeem_saas_field_key"
Re-encrypted Values Number of values successfully re-encrypted
Skipped Values Values that could not be decrypted with any known key; left unchanged, details in the server log
Storage Warning Present only when the key file could not be written (see below)

Back up first

Back up the database — and the current key file if you use sadeem_saas_field_key_path — before rotating. The new key is displayed only once; losing it means losing every stored credential.

Unwritable key file

If sadeem_saas_field_key_path points at a file that cannot be written, the database has already been re-encrypted, so the rotation must still finish consistently: the key falls back to the sadeem_saas_field_key parameter and the stale path setting is cleared. To return to file mode — write the displayed key into the file, set sadeem_saas_field_key_path again, then delete the sadeem_saas_field_key parameter.

Rotation touches only credentials stored on the master. The transport key shared with the instances is not affected, so no instance needs to be redeployed.

What "skipped" means

A skipped value is a token that no current key can decrypt — typically a leftover from a key rotated away long ago (for example stale chatter tracking values). It is left byte-for-byte unchanged so nothing is destroyed; check the server log for the exact table, column and record id.


4. Access Groups

Three groups are defined under the Sadeem SAAS category, each implying the previous one:

Group Typical rights
User Read-only access to the SaaS records they are allowed to see
Operator Day-to-day operation: read/write on subscriptions, migrations and infrastructure records
Administrator Full create/delete, plus the destructive actions (migrate, cancel, key rotation is base.group_system)

Record rules further restrict root servers, backup servers and Git accounts, and every shared record model carries a multi-company rule.

SSH is manager-only

Executing SSH commands on a root server requires the SaaS manager group — Only SaaS managers can execute SSH commands.


5. Remote Command Execution Limits

Two different execution paths exist, with different trust levels.

Root server SSH terminal (`sadeem_saas_base`)

Runs the typed command over SSH on the root server. Manager-only, every execution is written to the action log (Input Text / Output Text / Action Type) for audit.

Container command execution (`sadeem_saas_docker`)

Commands typed for a subscription's container are checked by is_safe_command() before they are sent:

  • Any command containing ;, & or | is rejected outright — no chaining, no pipes
  • The base command must be in the whitelist, and every argument must be in that command's allowed list
Command Allowed forms
ls ls, -l, -a
apt apt update, apt upgrade, apt install python3-<package>, apt remove …
pip / pip3 pip install <package>, pip uninstall <package>

Package names are validated separately, so pip install evil;rm -rf / never reaches the container.

Elevated privileges

Commands run inside the container with elevated privileges, and root servers may have sudo enabled. Always take a backup before running anything that changes the system.


6. Public Endpoints and Their Secrets

Route Authentication
/sadeem/webhook/<token> 64-character token in the URL, unique per webhook record
/sadeem/migration/restore/<token> Single-use restore callback token, rotated on every restore request; accepted only while the migration is in state Restoring
Slave routes (/sadeem/database/*, …) Payloads encrypted with the transport key; the instance decrypts them to verify the caller

See Webhooks, Server Migration and SaaS Slave for the full contracts.