|
| 1 | +# Database Encryption |
| 2 | + |
| 3 | +By default, Coder stores external user tokens in plaintext in the database. |
| 4 | +Database Encryption allows Coder administrators to encrypt these tokens at-rest, |
| 5 | +preventing attackers with database access from using them to impersonate users. |
| 6 | + |
| 7 | +## How it works |
| 8 | + |
| 9 | +Coder allows administrators to specify up to two |
| 10 | +[external token encryption keys](../cli/server.md#external-token-encryption-keys). |
| 11 | +If configured, Coder will use these keys to encrypt external user tokens before |
| 12 | +storing them in the database. The encryption algorithm used is AES-256-GCM with |
| 13 | +a 32-byte key length. |
| 14 | + |
| 15 | +Coder will use the first key provided for both encryption and decryption. If a |
| 16 | +second key is provided, Coder will use it for decryption only. This allows |
| 17 | +administrators to rotate encryption keys without invalidating existing tokens. |
| 18 | + |
| 19 | +The following database fields are currently encrypted: |
| 20 | + |
| 21 | +- `user_links.oauth_access_token` |
| 22 | +- `user_links.oauth_refresh_token` |
| 23 | +- `git_auth_links.oauth_access_token` |
| 24 | +- `git_auth_links.oauth_refresh_token` |
| 25 | + |
| 26 | +Additional database fields may be encrypted in the future. |
| 27 | + |
| 28 | +> Implementation notes: each encrypted database column `$C` has a corresponding |
| 29 | +> `$C_key_id` column. This column is used to determine which encryption key was |
| 30 | +> used to encrypt the data. This allows Coder to rotate encryption keys without |
| 31 | +> invalidating existing tokens, and provides referential integrity for encrypted |
| 32 | +> data. |
| 33 | +> |
| 34 | +> The `$C_key_id` column stores the first 7 bytes of the SHA-256 hash of the |
| 35 | +> encryption key used to encrypt the data. |
| 36 | +> |
| 37 | +> Encryption keys in use are stored in `dbcrypt_keys`. This table stores a |
| 38 | +> record of all encryption keys that have been used to encrypt data. Active keys |
| 39 | +> have a null `revoked_key_id` column, and revoked keys have a non-null |
| 40 | +> `revoked_key_id` column. A key cannot be revoked until all rows referring to |
| 41 | +> it have been re-encrypted with a different key. |
| 42 | +
|
| 43 | +## Enabling encryption |
| 44 | + |
| 45 | +1. Ensure you have a valid backup of your database. **Do not skip this step.** |
| 46 | + If you are using the built-in PostgreSQL database, you can run |
| 47 | + [`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md) |
| 48 | + to get the connection URL. |
| 49 | + |
| 50 | +1. Generate a 32-byte random key and base64-encode it. For example: |
| 51 | + |
| 52 | +```shell |
| 53 | +dd if=/dev/urandom bs=32 count=1 | base64 |
| 54 | +``` |
| 55 | + |
| 56 | +1. Store this key in a secure location (for example, a Kubernetes secret): |
| 57 | + |
| 58 | +```shell |
| 59 | +kubectl create secret generic coder-external-token-encryption-keys --from-literal=keys=<key> |
| 60 | +``` |
| 61 | + |
| 62 | +1. In your Coder configuration set `CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS` to a |
| 63 | + comma-separated list of base64-encoded keys. For example, in your Helm |
| 64 | + `values.yaml`: |
| 65 | + |
| 66 | +```yaml |
| 67 | +coder: |
| 68 | + env: |
| 69 | + [...] |
| 70 | + - name: CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS |
| 71 | + valueFrom: |
| 72 | + secretKeyRef: |
| 73 | + name: coder-external-token-encryption-keys |
| 74 | + key: keys |
| 75 | +``` |
| 76 | +
|
| 77 | +## Rotating keys |
| 78 | +
|
| 79 | +We recommend only having one active encryption key at a time normally. However, |
| 80 | +if you need to rotate keys, you can perform the following procedure: |
| 81 | +
|
| 82 | +1. Ensure you have a valid backup of your database. **Do not skip this step.** |
| 83 | +
|
| 84 | +1. Generate a new encryption key following the same procedure as above. |
| 85 | +
|
| 86 | +1. Add the above key to the list of |
| 87 | + [external token encryption keys](../cli/server.md#external-token-encryption-keys). |
| 88 | + **The new key must appear first in the list**. For example, in the Kubernetes |
| 89 | + secret created above: |
| 90 | +
|
| 91 | +```yaml |
| 92 | +apiVersion: v1 |
| 93 | +kind: Secret |
| 94 | +type: Opaque |
| 95 | +metadata: |
| 96 | + name: coder-external-token-encryption-keys |
| 97 | + namespace: coder-namespace |
| 98 | +data: |
| 99 | + keys: <new-key>,<old-key1>,<old-key2>,... |
| 100 | +``` |
| 101 | +
|
| 102 | +1. After updating the configuration, restart the Coder server. The server will |
| 103 | + now encrypt all new data with the new key, but will be able to decrypt tokens |
| 104 | + encrypted with the old key(s). |
| 105 | +
|
| 106 | +1. To re-encrypt all encrypted database fields with the new key, run |
| 107 | + [`coder dbcrypt-rotate`](../cli/dbcrypt-rotate.md). This command will |
| 108 | + re-encrypt all tokens with the first key in the list of external token |
| 109 | + encryption keys. We recommend performing this action during a maintenance |
| 110 | + window. |
| 111 | + |
| 112 | + > Note: this command requires direct access to the database. If you are using |
| 113 | + > the built-in PostgreSQL database, you can run |
| 114 | + > [`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md) |
| 115 | + > to get the connection URL. |
| 116 | + |
| 117 | +1. Once the above command completes successfully, remove the old encryption key |
| 118 | + from Coder's configuration and restart Coder once more. You can now safely |
| 119 | + delete the old key from your secret store. |
| 120 | + |
| 121 | +## Disabling encryption |
| 122 | + |
| 123 | +Disabling encryption is currently not supported. |
| 124 | + |
| 125 | +## Troubleshooting |
| 126 | + |
| 127 | +- If Coder detects that the data stored in the database was not encrypted with |
| 128 | + any known keys, it will refuse to start. If you are seeing this behaviour, |
| 129 | + ensure that the encryption keys provided are correct. |
0 commit comments