Skip to content

Secrets & Encryption

This page covers how the database password (and other secrets) can be encrypted at rest for native/PM2 installs — the mechanism a security-conscious on-prem customer will want to know about before going to production.

Docker Compose

This mechanism applies to native/PM2 installs. In the Docker deployment, secrets are passed as container environment variables via .env and are not written to a file inside any container; standard Docker secrets-management practices (host-level .env permissions, or swapping in Docker secrets/a vault if your platform requires it) apply instead.

What's encrypted, and where

By default, a fresh native install keeps DB_PASSWORD (and DB_ADMIN_PASSWORD, if set) as plaintext in backend/.env, so the app works out of the box. Running one script converts it to ciphertext at rest:

cd backend && npm run build && cd ..   # only if backend/dist isn't already built
sudo ./scripts/setup-env-encryption.sh

What this does:

  1. Generates a 256-bit master key at /etc/itsm/env-master.key (or a path you pass as an argument), owned by root, chmod 400 — outside the application directory and outside .env itself. If a key already exists there, it's left untouched (regenerating it would make existing encrypted values permanently undecryptable).
  2. Encrypts DB_PASSWORD (and DB_ADMIN_PASSWORD) with AES-256-GCM, verifies the encrypt→decrypt round-trip in memory, and only then writes DB_PASSWORD_ENC=<ciphertext> and ENV_MASTER_KEY_FILE=<key path> into backend/.env, removing the plaintext line.
  3. Drops any leftover literal DATABASE_URL= line from .env if it's exactly reproducible from DB_USER/DB_PASSWORD/DB_NAME (so the password doesn't also sit unencrypted inside a connection string).
  4. Scrubs the same plaintext DB_PASSWORD/DB_ADMIN_PASSWORD out of the root .env too — install-onprem.sh copies root .env into backend/.env on every run, so a second plaintext copy there would otherwise persist even after step 2.
  5. Runs a final live check: loads the rewritten .env exactly as the compiled backend would at startup, and confirms it resolves to the same connection details as before. If anything doesn't match, it automatically restores the pre-script backend/.env from a timestamped backup — no partial state is left behind.

Safe to re-run: if DB_PASSWORD is already encrypted, the script does nothing.

How decryption works at runtime

loadEncryptedEnvSecrets() in backend/src/common/config/env-secrets.ts runs as the very first statement in the backend's entrypoints (main.ts, data-source.ts). It:

  1. Loads backend/.env.
  2. Finds every *_ENC variable (e.g. DB_PASSWORD_ENC), decrypts it using the key at ENV_MASTER_KEY_FILE, and sets the plain DB_PASSWORD in process.env for the rest of the app to use as normal.
  3. If DATABASE_URL still isn't set after that, builds it from DB_USER/DB_PASSWORD/DB_NAME/DB_HOST/DB_PORT.

The master key is derived from the raw file contents via SHA-256 — it's deliberately kept out of .env entirely (not reused from JWT_SECRET or APP_ENCRYPTION_KEY, which already live in the file this mechanism protects).

If ENV_MASTER_KEY_FILE is unset and no *_ENC variable is present, this is a complete no-op — a plaintext DB_PASSWORD install behaves exactly as before.

Cipher details

  • Algorithm: AES-256-GCM
  • Format: iv:authTag:ciphertext, all hex — the same format used elsewhere in the app's CryptoService, so a .env file reads consistently
  • Key derivation: SHA-256 of the master key file's contents

Manually encrypting/decrypting a value

Useful for support, verification, or rotating a value by hand. The CLI must be run against the compiled dist/ output, never via ts-node — production installs run npm ci --omit=dev, which strips ts-node/typescript entirely, so anything invoking npx ts-node for this will work in a dev checkout and silently fail on the real server.

cd backend
node dist/scripts/encrypt-env-value.js encrypt /etc/itsm/env-master.key 'MyNewPassword!'
node dist/scripts/encrypt-env-value.js decrypt /etc/itsm/env-master.key '<iv>:<authTag>:<ciphertext>'

Backing up the master key

The master key is not included in a normal .tar.gz release package or backup archive by default location — it lives outside the app directory on purpose. If you lose it, every *_ENC value in backend/.env becomes permanently unrecoverable and the backend will refuse to start.

scripts/backup-onprem.sh and scripts/restore-onprem.sh carry ENV_MASTER_KEY_FILE and backend/.env together as part of the standard backup bundle — see Backups & Disaster Recovery. If you back up manually, always back up the key file alongside backend/.env.

Next steps