Skip to content

Commit 5c7bcee

Browse files
committed
add docs for encryption
1 parent 66048de commit 5c7bcee

File tree

3 files changed

+161
-0
lines changed

3 files changed

+161
-0
lines changed

docs/admin/encryption.md

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
# Database Encryption
2+
3+
By default, Coder stores external user tokens in plaintext in the database. This
4+
is undesirable in high-security environments, as an attacker with access to the
5+
database can use these tokens to impersonate users. Database Encryption allows
6+
Coder administrators to encrypt these tokens at-rest, preventing attackers from
7+
using them.
8+
9+
## How it works
10+
11+
Coder allows administrators to specify up to two
12+
[external token encryption keys](../cli/server.md#external-token-encryption-keys).
13+
If configured, Coder will use these keys to encrypt external user tokens before
14+
storing them in the database. The encryption algorithm used is AES-256-GCM with
15+
a 32-byte key length.
16+
17+
Coder will use the first key provided for both encryption and decryption. If a
18+
second key is provided, Coder will use it for decryption only. This allows
19+
administrators to rotate encryption keys without invalidating existing tokens.
20+
21+
The following database fields are currently encrypted:
22+
23+
- `user_links.oauth_access_token`
24+
- `user_links.oauth_refresh_token`
25+
- `git_auth_links.oauth_access_token`
26+
- `git_auth_links.oauth_refresh_token`
27+
28+
Additional database fields may be encrypted in the future.
29+
30+
> Implementation note: there is an additional encrypted database field
31+
> `dbcrypt_sentinel.value`. This field is used to verify that the encryption
32+
> keys are valid for the configured database. It is not used to encrypt any user
33+
> data.
34+
35+
Encrypted data is stored in the following format:
36+
37+
- `encrypted_data = dbcrypt-<b64data>`
38+
- `b64data = <cipher checksum>-<ciphertext>`
39+
40+
All encrypted data is prefixed with the string `dbcrypt-`. The cipher checksum
41+
is the first 7 bytes of the SHA256 hex digest of the encryption key used to
42+
encrypt the data.
43+
44+
## Enabling encryption
45+
46+
1. Ensure you have a valid backup of your database. **Do not skip this step.**
47+
If you are using the built-in PostgreSQL database, you can run
48+
[`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md)
49+
to get the connection URL.
50+
51+
1. Generate a 32-byte random key and base64-encode it. For example:
52+
53+
```shell
54+
dd if=/dev/urandom bs=32 count=1 | base64
55+
```
56+
57+
1. Store this key in a secure location (for example, a Kubernetes secret):
58+
59+
```shell
60+
kubectl create secret generate coder-external-token-encryption-keys --from-literal=keys=<key>
61+
```
62+
63+
1. In your Coder configuration set the `external_token_encryption_keys` field to
64+
a comma-separated list of base64-encoded keys. For example, in your Helm
65+
`values.yaml`:
66+
67+
```yaml
68+
coder:
69+
env:
70+
[...]
71+
- name: CODER_EXTERNAL_TOKEN_ENCRYPTION_KEYS
72+
valueFrom:
73+
secretKeyRef:
74+
name: coder-external-token-encryption-keys
75+
key: keys
76+
```
77+
78+
## Rotating keys
79+
80+
We recommend only having one active encryption key at a time normally. However,
81+
if you need to rotate keys, you can perform the following procedure:
82+
83+
1. Ensure you have a valid backup of your database. **Do not skip this step.**
84+
85+
1. Generate a new encryption key following the same procedure as above.
86+
87+
1. Add the above key to the list of
88+
[external token encryption keys](../cli/server.md#external-token-encryption-keys).
89+
**The new key must appear first in the list**. For example, in the Kubernetes
90+
secret created above:
91+
92+
```yaml
93+
apiVersion: v1
94+
kind: Secret
95+
type: Opaque
96+
metadata:
97+
name: coder-external-token-encryption-keys
98+
namespace: coder-namespace
99+
data:
100+
keys: <new-key>,<old-key>
101+
```
102+
103+
1. After updating the configuration, restart the Coder server. The server will
104+
now encrypt all new data with the new key, but will be able to decrypt tokens
105+
encrypted with the old key.
106+
107+
1. To re-encrypt all encrypted database fields with the new key, run
108+
[`coder dbcrypt-rotate`](../cli/dbcrypt-rotate.md). This command will
109+
re-encrypt all tokens with the first key in the list of external token
110+
encryption keys. We recommend performing this action during a maintenance
111+
window.
112+
113+
> Note: this command requires direct access to the database. If you are using
114+
> the built-in PostgreSQL database, you can run
115+
> [`coder server postgres-builtin-url`](../cli/server_postgres-builtin-url.md)
116+
> to get the connection URL.
117+
118+
1. Once the above command completes successfully, remove the old encryption key
119+
from Coder's configuration and restart Coder once more. You can now safely
120+
delete the old key from your secret store.
121+
122+
## Disabling encryption
123+
124+
Automatically disabling encryption is currently not supported. Encryption can be
125+
disabled by removing the encrypted data manually from the database:
126+
127+
```sql
128+
DELETE FROM user_links WHERE oauth_access_token LIKE 'dbcrypt-%';
129+
DELETE FROM user_links WHERE oauth_refresh_token LIKE 'dbcrypt-%';
130+
DELETE FROM git_auth_links WHERE oauth_access_token LIKE 'dbcrypt-%';
131+
DELETE FROM git_auth_links WHERE oauth_refresh_token LIKE 'dbcrypt-%';
132+
DELETE FROM dbcrypt_sentinel WHERE value LIKE 'dbcrypt-%';
133+
```
134+
135+
Users will then need to re-authenticate with external authentication providers.
136+
137+
## Troubleshooting
138+
139+
- If Coder detects that the data stored in the database under
140+
`dbcrypt_sentinel.value` was not encrypted with a known key, it will refuse to
141+
start. If you are seeing this behaviour, ensure that the encryption keys
142+
provided are correct.
143+
- If Coder is unable to decrypt a token, it will be treated as if the data were
144+
not present. This means that the user will be prompted to re-authenticate with
145+
the external provider. If you are seeing this behaviour consistently, ensure
146+
that the encryption keys are correct.

docs/images/icons/lock.svg

+3
Loading

docs/manifest.json

+12
Original file line numberDiff line numberDiff line change
@@ -385,6 +385,13 @@
385385
"description": "Learn what usage telemetry Coder collects",
386386
"path": "./admin/telemetry.md",
387387
"icon_path": "./images/icons/science.svg"
388+
},
389+
{
390+
"title": "Database Encryption",
391+
"description": "Learn how to encrypt sensitive data at rest in Coder",
392+
"path": "./admin/database-encryption.md",
393+
"icon_path": "./images/icons/lock.svg",
394+
"state": "enterprise"
388395
}
389396
]
390397
},
@@ -535,6 +542,11 @@
535542
"description": "Create a workspace",
536543
"path": "cli/create.md"
537544
},
545+
{
546+
"title": "dbcrypt-rotate",
547+
"description": "Rotate database encryption keys",
548+
"path": "cli/dbcrypt-rotate.md"
549+
},
538550
{
539551
"title": "delete",
540552
"description": "Delete a workspace",

0 commit comments

Comments
 (0)