Files
coder/docs/admin/security/database-encryption.md
T
Nick Vigilante c84aa564ba docs: normalize code-fence languages for Shiki compatibility (#27161)
Normalizes non-standard code-fence language tags across `docs/**` so a
strict highlighter (Shiki, used by Fumadocs) won't fail the build on an
unrecognized language, and unifies redundant synonym tags onto one
canonical form per language. The current renderer (Speed-Highlight)
detects the language from the code content, not the fence label, so this
drift wasn't visible until now.

## Changes

- `hcl` -> `tf` (199 fences, including indented ones nested in
numbered/bulleted lists). Shiki ships `hcl` and `terraform` as two
distinct grammars (not aliases); every `hcl`-tagged fence in `docs/**`
is actually Terraform resource/data/provider syntax, so the more
specific `terraform` grammar is correct for all of them. `tf` is Shiki's
own alias for that grammar, and it's also what GitHub's own markdown
renderer resolves to the same HCL/Terraform highlighting.
- `pwsh`/`powershell` -> `ps1`. Both `ps` and `ps1` are registered
PowerShell aliases in Shiki, but on GitHub's renderer only `.ps1` is a
registered file extension (`.ps` isn't), so `ps1` renders identically to
`powershell` there today while bare `ps` would silently lose
highlighting.
- `env` -> `dotenv` (a dedicated Shiki grammar for `KEY=VALUE` files)
- `text`/`output`/`none`/`url` -> `txt`. Same built-in plain-text
fallback either way, just shorter.
- `Dockerfile` -> `dockerfile` (lowercase)
- `bash`/`shell` -> `sh` (732 fences). Shiki and GitHub both alias all
three to a single shell grammar; this was already the style guide's
stated preference, just not enforced across the existing corpus until
now.
- `markdown` -> `md` (4 fences). Alias of the same grammar in both Shiki
and GitHub.
- `jsonc` -> `json` (1 fence). The block has no comments or trailing
commas, so it doesn't need the comments-capable grammar.
- `ts` -> `tsx` (2 fences, `docs/about/contributing/frontend.md`).
Verified the actual content tokenizes identically under both grammars,
and a sibling block in the same file already needs `tsx` for real JSX,
so unifying to one tag is safe for this file. Documented a caveat: `tsx`
mis-tokenizes the legacy angle-bracket type-assertion syntax
(`<Type>value`), which is invalid in real `.tsx` files anyway, so use
`value as Type` instead.
- `yml` -> `yaml` (1 fence)
- Updated `docs/.style/style-guide/formatting.md` to document all
canonical tags

`promql` (2 fences) and `caddyfile` (2 fences) are left as-is. Shiki
doesn't bundle a grammar for either, so they need a custom grammar
registration when the site adopts Shiki, rather than degrading to `txt`.
Tracked as follow-up work under DOCS-118 and
[DOCS-544](https://linear.app/codercom/issue/DOCS-544/vendor-a-local-promql-grammar-for-shiki-syntax-highlighting)
(promql).

Does not touch `offlinedocs/`.

Linear:
[DOCS-476](https://linear.app/codercom/issue/DOCS-476/normalize-docs-code-fence-languages-de-risk-shikifumadocs)

<details>
<summary>How the fence tags were verified</summary>

Each tag was tested against a real `shiki@latest` highlighter instance
(`codeToHtml`/`codeToTokens`) and cross-checked against GitHub's
`@wooorm/starry-night` grammar sources (the renderer that actually
displays these `.md` files today, in repo browsing and PR diffs), since
that's what determines whether brevity is safe before Shiki adoption:

```text
FAIL  env        -- Language `env` is not included in this bundle.
FAIL  Dockerfile -- Language `Dockerfile` is not included in this bundle.
FAIL  promql     -- Language `promql` is not included in this bundle.
FAIL  caddyfile  -- Language `caddyfile` is not included in this bundle.
FAIL  pwsh       -- Language `pwsh` is not included in this bundle.
FAIL  output     -- Language `output` is not included in this bundle.
```

`hcl` doesn't error in Shiki, since it's a real grammar, but that's
exactly the trap: it was silently rendering every fence with the generic
HCL grammar instead of the Terraform-specific one. Every `hcl`-tagged
fence in `docs/**` was manually checked against `origin/main` and is
genuinely Terraform content.

For `ts`/`tsx`, tokenizing the actual doc content confirmed identical
output under both grammars; a synthetic test with the legacy
angle-bracket cast syntax confirmed `tsx` degrades on that specific
construct, which the style guide now calls out.

The first normalization pass only matched fence tags at column 0
(`^```tag$`), missing tags indented inside numbered/bulleted lists. A
follow-up pass caught the remaining occurrences at any indentation
level.

</details>


---

*This PR description and the underlying changes were prepared with Coder
Agents assistance.*
2026-07-15 14:07:09 -04:00

200 lines
7.2 KiB
Markdown

# Database Encryption
By default, Coder stores external user tokens and other sensitive values in
plaintext in the database. Database Encryption allows Coder administrators to
encrypt these values at-rest, preventing attackers with database access from
reading or misusing them.
## How it works
Coder allows administrators to specify
[external token encryption keys](../../reference/cli/server.md#--external-token-encryption-keys).
If configured, Coder will use these keys to encrypt external user tokens before
storing them in the database. The encryption algorithm used is AES-256-GCM with
a 32-byte key length.
Coder will use the first key provided for both encryption and decryption. If
additional keys are provided, Coder will use it for decryption only. This allows
administrators to rotate encryption keys without invalidating existing tokens.
The following database fields are currently encrypted:
- `user_links.oauth_access_token`
- `user_links.oauth_refresh_token`
- `external_auth_links.oauth_access_token`
- `external_auth_links.oauth_refresh_token`
- `crypto_keys.secret`
- `user_secrets.value`
- `gitsshkeys.private_key`
Additional database fields may be encrypted in the future.
### Implementation notes
Each encrypted database column `$C` has a corresponding
`$C_key_id` column. This column is used to determine which encryption key was
used to encrypt the data. This allows Coder to rotate encryption keys without
invalidating existing tokens, and provides referential integrity for encrypted
data.
The `$C_key_id` column stores the first 7 bytes of the SHA-256 hash of the
encryption key used to encrypt the data.
Encryption keys in use are stored in `dbcrypt_keys`. This table stores a
record of all encryption keys that have been used to encrypt data. Active keys
have a null `revoked_key_id` column, and revoked keys have a non-null
`revoked_key_id` column. You cannot revoke a key until you have rotated all
values using that key to a new key.
## Enabling encryption
> [!NOTE]
> Enabling encryption does not encrypt all existing data. To encrypt
> existing data, see [rotating keys](#rotating-keys) below.
- Ensure you have a valid backup of your database. **Do not skip this step.** If
you are using the built-in PostgreSQL database, you can run
[`coder server postgres-builtin-url`](../../reference/cli/server_postgres-builtin-url.md)
to get the connection URL.
- Generate a 32-byte random key and base64-encode it. For example:
```sh
dd if=/dev/urandom bs=32 count=1 | base64
```
- Store this key in a secure location (for example, a Kubernetes secret):
```sh
kubectl create secret generic coder-external-token-encryption-keys --from-literal=keys=<key>
```
- In your Coder configuration set `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS` to a
comma-separated list of base64-encoded keys. For example, in your Helm
`values.yaml`:
```yaml
coder:
env:
[...]
- name: CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS
valueFrom:
secretKeyRef:
name: coder-external-token-encryption-keys
key: keys
```
- Restart the Coder server. The server will now encrypt all new data with the
provided key.
## Rotating keys
We recommend only having one active encryption key at a time normally. However,
if you need to rotate keys, you can perform the following procedure:
- Ensure you have a valid backup of your database. **Do not skip this step.**
- Generate a new encryption key following the same procedure as above.
- Add the above key to the list of
[external token encryption keys](../../reference/cli/server.md#--external-token-encryption-keys).
**The new key must appear first in the list**. For example, in the Kubernetes
secret created above:
```yaml
apiVersion: v1
kind: Secret
type: Opaque
metadata:
name: coder-external-token-encryption-keys
namespace: coder-namespace
data:
keys: <new-key>,<old-key1>,<old-key2>,...
```
- After updating the configuration, restart the Coder server. The server will
now encrypt all new data with the new key, but will be able to decrypt tokens
encrypted with the old key(s).
- To re-encrypt all encrypted database fields with the new key, run
[`coder server dbcrypt rotate`](../../reference/cli/server_dbcrypt_rotate.md).
This command will re-encrypt all tokens with the specified new encryption key.
We recommend performing this action during a maintenance window.
This command requires direct access to the database.
If you are using the built-in PostgreSQL database, you can run
[`coder server postgres-builtin-url`](../../reference/cli/server_postgres-builtin-url.md)
to get the connection URL.
- Once the above command completes successfully, remove the old encryption key
from Coder's configuration and restart Coder once more. You can now safely
delete the old key from your secret store.
## Disabling encryption
To disable encryption, perform the following actions:
- Ensure you have a valid backup of your database. **Do not skip this step.**
- Stop all active coderd instances. This will prevent new encrypted data from
being written, which may cause the next step to fail.
- Run
[`coder server dbcrypt decrypt`](../../reference/cli/server_dbcrypt_decrypt.md).
This command will decrypt all encrypted user tokens and revoke all active
encryption keys.
> [!NOTE]
> for `decrypt` command, the equivalent environment variable for
> `--keys` is `CODER_EXTERNAL_TOKEN_ENCRYPTION_DECRYPT_KEYS` and not
> `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS`. This is explicitly named differently
> to help prevent accidentally decrypting data.
- Remove all
[external token encryption keys](../../reference/cli/server.md#--external-token-encryption-keys)
from Coder's configuration.
- Start coderd. You can now safely delete the encryption keys from your secret
store.
## Deleting Encrypted Data
> [!CAUTION]
> This is a destructive operation.
To delete all encrypted data from your database, perform the following actions:
- Ensure you have a valid backup of your database. **Do not skip this step.**
- Stop all active coderd instances. This will prevent new encrypted data from
being written.
- Run
[`coder server dbcrypt delete`](../../reference/cli/server_dbcrypt_delete.md).
This command will delete all encrypted user tokens and revoke all active
encryption keys.
- Remove all
[external token encryption keys](../../reference/cli/server.md#--external-token-encryption-keys)
from Coder's configuration.
- Start coderd. You can now safely delete the encryption keys from your secret
store.
## Troubleshooting
- If Coder detects that the data stored in the database was not encrypted with
any known keys, it will refuse to start. If you are seeing this behavior,
ensure that the encryption keys provided are correct.
- If Coder detects that the data stored in the database was encrypted with a key
that is no longer active, it will refuse to start. If you are seeing this
behavior, ensure that the encryption keys provided are correct and that you
have not revoked any keys that are still in use.
- Decryption may fail if newly encrypted data is written while decryption is in
progress. If this happens, ensure that all active coder instances are stopped,
and retry.
## Next steps
- [Security - best practices](../../tutorials/best-practices/security-best-practices.md)