mirror of
https://github.com/coder/coder.git
synced 2025-07-12 00:14:10 +00:00
feat(coderd): connect dbcrypt package implementation (#9523)
See also: https://github.com/coder/coder/pull/9522 - Adds commands `server dbcrypt {rotate,decrypt,delete}` to re-encrypt, decrypt, or delete encrypted data, respectively. - Plumbs through dbcrypt in enterprise/coderd (including unit tests). - Adds documentation in admin/encryption.md. This enables dbcrypt by default, but the feature is soft-enforced on supplying external token encryption keys. Without specifying any keys, encryption/decryption is a no-op.
This commit is contained in:
173
docs/admin/encryption.md
Normal file
173
docs/admin/encryption.md
Normal file
@ -0,0 +1,173 @@
|
||||
# Database Encryption
|
||||
|
||||
By default, Coder stores external user tokens in plaintext in the database.
|
||||
Database Encryption allows Coder administrators to encrypt these tokens at-rest,
|
||||
preventing attackers with database access from using them to impersonate users.
|
||||
|
||||
## How it works
|
||||
|
||||
Coder allows administrators to specify
|
||||
[external token encryption keys](../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`
|
||||
- `git_auth_links.oauth_access_token`
|
||||
- `git_auth_links.oauth_refresh_token`
|
||||
|
||||
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
|
||||
|
||||
1. 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`](../cli/server_postgres-builtin-url.md)
|
||||
to get the connection URL.
|
||||
|
||||
1. Generate a 32-byte random key and base64-encode it. For example:
|
||||
|
||||
```shell
|
||||
dd if=/dev/urandom bs=32 count=1 | base64
|
||||
```
|
||||
|
||||
1. Store this key in a secure location (for example, a Kubernetes secret):
|
||||
|
||||
```shell
|
||||
kubectl create secret generic coder-external-token-encryption-keys --from-literal=keys=<key>
|
||||
```
|
||||
|
||||
1. 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
|
||||
```
|
||||
|
||||
1. 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:
|
||||
|
||||
1. Ensure you have a valid backup of your database. **Do not skip this step.**
|
||||
|
||||
1. Generate a new encryption key following the same procedure as above.
|
||||
|
||||
1. Add the above key to the list of
|
||||
[external token encryption keys](../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>,...
|
||||
```
|
||||
|
||||
1. 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).
|
||||
|
||||
1. To re-encrypt all encrypted database fields with the new key, run
|
||||
[`coder server dbcrypt rotate`](../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.
|
||||
|
||||
> Note: 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`](../cli/server_postgres-builtin-url.md)
|
||||
> to get the connection URL.
|
||||
|
||||
1. 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:
|
||||
|
||||
1. Ensure you have a valid backup of your database. **Do not skip this step.**
|
||||
|
||||
1. Stop all active coderd instances. This will prevent new encrypted data from
|
||||
being written.
|
||||
|
||||
1. Run [`coder server dbcrypt decrypt`](../cli/server_dbcrypt_decrypt.md). This
|
||||
command will decrypt all encrypted user tokens and revoke all active
|
||||
encryption keys.
|
||||
|
||||
1. Remove all
|
||||
[external token encryption keys](../cli/server.md#external-token-encryption-keys)
|
||||
from Coder's configuration.
|
||||
|
||||
1. Start coderd. You can now safely delete the encryption keys from your secret
|
||||
store.
|
||||
|
||||
## Deleting Encrypted Data
|
||||
|
||||
> NOTE: This is a destructive operation.
|
||||
|
||||
To delete all encrypted data from your database, perform the following actions:
|
||||
|
||||
1. Ensure you have a valid backup of your database. **Do not skip this step.**
|
||||
|
||||
1. Stop all active coderd instances. This will prevent new encrypted data from
|
||||
being written.
|
||||
|
||||
1. Run [`coder server dbcrypt delete`](../cli/server_dbcrypt_delete.md). This
|
||||
command will delete all encrypted user tokens and revoke all active
|
||||
encryption keys.
|
||||
|
||||
1. Remove all
|
||||
[external token encryption keys](../cli/server.md#external-token-encryption-keys)
|
||||
from Coder's configuration.
|
||||
|
||||
1. 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 behaviour,
|
||||
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
|
||||
behaviour, ensure that the encryption keys provided are correct and that you
|
||||
have not revoked any keys that are still in use.
|
1
docs/api/general.md
generated
1
docs/api/general.md
generated
@ -212,6 +212,7 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"experiments": ["string"],
|
||||
"external_token_encryption_keys": ["string"],
|
||||
"git_auth": {
|
||||
"value": [
|
||||
{
|
||||
|
3
docs/api/schemas.md
generated
3
docs/api/schemas.md
generated
@ -2036,6 +2036,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"experiments": ["string"],
|
||||
"external_token_encryption_keys": ["string"],
|
||||
"git_auth": {
|
||||
"value": [
|
||||
{
|
||||
@ -2400,6 +2401,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
},
|
||||
"enable_terraform_debug_mode": true,
|
||||
"experiments": ["string"],
|
||||
"external_token_encryption_keys": ["string"],
|
||||
"git_auth": {
|
||||
"value": [
|
||||
{
|
||||
@ -2613,6 +2615,7 @@ AuthorizationObject can represent a "set" of objects, such as: all workspaces in
|
||||
| `docs_url` | [clibase.URL](#clibaseurl) | false | | |
|
||||
| `enable_terraform_debug_mode` | boolean | false | | |
|
||||
| `experiments` | array of string | false | | |
|
||||
| `external_token_encryption_keys` | array of string | false | | |
|
||||
| `git_auth` | [clibase.Struct-array_codersdk_GitAuthConfig](#clibasestruct-array_codersdk_gitauthconfig) | false | | |
|
||||
| `http_address` | string | false | | Http address is a string because it may be set to zero to disable. |
|
||||
| `in_memory_database` | boolean | false | | |
|
||||
|
10
docs/cli/server.md
generated
10
docs/cli/server.md
generated
@ -15,6 +15,7 @@ coder server [flags]
|
||||
| Name | Purpose |
|
||||
| ------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| [<code>create-admin-user</code>](./server_create-admin-user.md) | Create a new admin user with the given username, email and password and adds it to every organization. |
|
||||
| [<code>dbcrypt</code>](./server_dbcrypt.md) | Manage database encryption. |
|
||||
| [<code>postgres-builtin-serve</code>](./server_postgres-builtin-serve.md) | Run the built-in PostgreSQL deployment. |
|
||||
| [<code>postgres-builtin-url</code>](./server_postgres-builtin-url.md) | Output the connection URL for the built-in PostgreSQL deployment. |
|
||||
|
||||
@ -273,6 +274,15 @@ Expose the swagger endpoint via /swagger.
|
||||
|
||||
Enable one or more experiments. These are not ready for production. Separate multiple experiments with commas, or enter '\*' to opt-in to all available experiments.
|
||||
|
||||
### --external-token-encryption-keys
|
||||
|
||||
| | |
|
||||
| ----------- | -------------------------------------------------- |
|
||||
| Type | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS</code> |
|
||||
|
||||
Encrypt OIDC and Git authentication tokens with AES-256-GCM in the database. The value must be a comma-separated list of base64-encoded keys. Each key, when base64-decoded, must be exactly 32 bytes in length. The first key will be used to encrypt new values. Subsequent keys will be used as a fallback when decrypting. During normal operation it is recommended to only set one key unless you are in the process of rotating keys with the `coder server dbcrypt rotate` command.
|
||||
|
||||
### --provisioner-force-cancel-interval
|
||||
|
||||
| | |
|
||||
|
19
docs/cli/server_dbcrypt.md
generated
Normal file
19
docs/cli/server_dbcrypt.md
generated
Normal file
@ -0,0 +1,19 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
|
||||
# server dbcrypt
|
||||
|
||||
Manage database encryption.
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder server dbcrypt
|
||||
```
|
||||
|
||||
## Subcommands
|
||||
|
||||
| Name | Purpose |
|
||||
| --------------------------------------------------- | ----------------------------------------------------------------------------- |
|
||||
| [<code>decrypt</code>](./server_dbcrypt_decrypt.md) | Decrypt a previously encrypted database. |
|
||||
| [<code>delete</code>](./server_dbcrypt_delete.md) | Delete all encrypted data from the database. THIS IS A DESTRUCTIVE OPERATION. |
|
||||
| [<code>rotate</code>](./server_dbcrypt_rotate.md) | Rotate database encryption keys. |
|
39
docs/cli/server_dbcrypt_decrypt.md
generated
Normal file
39
docs/cli/server_dbcrypt_decrypt.md
generated
Normal file
@ -0,0 +1,39 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
|
||||
# server dbcrypt decrypt
|
||||
|
||||
Decrypt a previously encrypted database.
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder server dbcrypt decrypt [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### --keys
|
||||
|
||||
| | |
|
||||
| ----------- | ---------------------------------------------------------- |
|
||||
| Type | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_EXTERNAL_TOKEN_ENCRYPTION_DECRYPT_KEYS</code> |
|
||||
|
||||
Keys required to decrypt existing data. Must be a comma-separated list of base64-encoded keys.
|
||||
|
||||
### --postgres-url
|
||||
|
||||
| | |
|
||||
| ----------- | ------------------------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_PG_CONNECTION_URL</code> |
|
||||
|
||||
The connection URL for the Postgres database.
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Bypass prompts.
|
34
docs/cli/server_dbcrypt_delete.md
generated
Normal file
34
docs/cli/server_dbcrypt_delete.md
generated
Normal file
@ -0,0 +1,34 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
|
||||
# server dbcrypt delete
|
||||
|
||||
Delete all encrypted data from the database. THIS IS A DESTRUCTIVE OPERATION.
|
||||
|
||||
Aliases:
|
||||
|
||||
- rm
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder server dbcrypt delete [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### --postgres-url
|
||||
|
||||
| | |
|
||||
| ----------- | ---------------------------------------------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_EXTERNAL_TOKEN_ENCRYPTION_POSTGRES_URL</code> |
|
||||
|
||||
The connection URL for the Postgres database.
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Bypass prompts.
|
48
docs/cli/server_dbcrypt_rotate.md
generated
Normal file
48
docs/cli/server_dbcrypt_rotate.md
generated
Normal file
@ -0,0 +1,48 @@
|
||||
<!-- DO NOT EDIT | GENERATED CONTENT -->
|
||||
|
||||
# server dbcrypt rotate
|
||||
|
||||
Rotate database encryption keys.
|
||||
|
||||
## Usage
|
||||
|
||||
```console
|
||||
coder server dbcrypt rotate [flags]
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
### --new-key
|
||||
|
||||
| | |
|
||||
| ----------- | ------------------------------------------------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_EXTERNAL_TOKEN_ENCRYPTION_ENCRYPT_NEW_KEY</code> |
|
||||
|
||||
The new external token encryption key. Must be base64-encoded.
|
||||
|
||||
### --old-keys
|
||||
|
||||
| | |
|
||||
| ----------- | -------------------------------------------------------------- |
|
||||
| Type | <code>string-array</code> |
|
||||
| Environment | <code>$CODER_EXTERNAL_TOKEN_ENCRYPTION_ENCRYPT_OLD_KEYS</code> |
|
||||
|
||||
The old external token encryption keys. Must be a comma-separated list of base64-encoded keys.
|
||||
|
||||
### --postgres-url
|
||||
|
||||
| | |
|
||||
| ----------- | ------------------------------------- |
|
||||
| Type | <code>string</code> |
|
||||
| Environment | <code>$CODER_PG_CONNECTION_URL</code> |
|
||||
|
||||
The connection URL for the Postgres database.
|
||||
|
||||
### -y, --yes
|
||||
|
||||
| | |
|
||||
| ---- | ----------------- |
|
||||
| Type | <code>bool</code> |
|
||||
|
||||
Bypass prompts.
|
3
docs/images/icons/lock.svg
Normal file
3
docs/images/icons/lock.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512">
|
||||
<path d="M400 224h-24v-72C376 68.2 307.8 0 224 0S72 68.2 72 152v72H48c-26.5 0-48 21.5-48 48v192c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V272c0-26.5-21.5-48-48-48zm-104 0H152v-72c0-39.7 32.3-72 72-72s72 32.3 72 72v72z"/>
|
||||
</svg>
|
After Width: | Height: | Size: 296 B |
@ -390,6 +390,13 @@
|
||||
"description": "Learn what usage telemetry Coder collects",
|
||||
"path": "./admin/telemetry.md",
|
||||
"icon_path": "./images/icons/science.svg"
|
||||
},
|
||||
{
|
||||
"title": "Database Encryption",
|
||||
"description": "Learn how to encrypt sensitive data at rest in Coder",
|
||||
"path": "./admin/database-encryption.md",
|
||||
"icon_path": "./images/icons/lock.svg",
|
||||
"state": "enterprise"
|
||||
}
|
||||
]
|
||||
},
|
||||
@ -699,6 +706,26 @@
|
||||
"description": "Create a new admin user with the given username, email and password and adds it to every organization.",
|
||||
"path": "cli/server_create-admin-user.md"
|
||||
},
|
||||
{
|
||||
"title": "server dbcrypt",
|
||||
"description": "Manage database encryption.",
|
||||
"path": "cli/server_dbcrypt.md"
|
||||
},
|
||||
{
|
||||
"title": "server dbcrypt decrypt",
|
||||
"description": "Decrypt a previously encrypted database.",
|
||||
"path": "cli/server_dbcrypt_decrypt.md"
|
||||
},
|
||||
{
|
||||
"title": "server dbcrypt delete",
|
||||
"description": "Delete all encrypted data from the database. THIS IS A DESTRUCTIVE OPERATION.",
|
||||
"path": "cli/server_dbcrypt_delete.md"
|
||||
},
|
||||
{
|
||||
"title": "server dbcrypt rotate",
|
||||
"description": "Rotate database encryption keys.",
|
||||
"path": "cli/server_dbcrypt_rotate.md"
|
||||
},
|
||||
{
|
||||
"title": "server postgres-builtin-serve",
|
||||
"description": "Run the built-in PostgreSQL deployment.",
|
||||
|
Reference in New Issue
Block a user