mirror of
https://github.com/coder/coder.git
synced 2026-09-22 05:05:20 +08:00
docs: restructure docs (#14421)
Closes #13434 Supersedes #14182 --------- Co-authored-by: Ethan <39577870+ethanndickson@users.noreply.github.com> Co-authored-by: Ethan Dickson <ethan@coder.com> Co-authored-by: Ben Potter <ben@coder.com> Co-authored-by: Stephen Kirby <58410745+stirby@users.noreply.github.com> Co-authored-by: Stephen Kirby <me@skirby.dev> Co-authored-by: EdwardAngert <17991901+EdwardAngert@users.noreply.github.com> Co-authored-by: Edward Angert <EdwardAngert@users.noreply.github.com>
This commit is contained in:
co-authored by
Ethan
Ethan Dickson
Ben Potter
Stephen Kirby
Stephen Kirby
EdwardAngert
Edward Angert
parent
288df75686
commit
419eba5fb6
@@ -0,0 +1,87 @@
|
||||
# API Tokens of deleted users not invalidated
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
Coder identified an issue in
|
||||
[https://github.com/coder/coder](https://github.com/coder/coder) where API
|
||||
tokens belonging to a deleted user were not invalidated. A deleted user in
|
||||
possession of a valid and non-expired API token is still able to use the above
|
||||
token with their full suite of capabilities.
|
||||
|
||||
## Impact: HIGH
|
||||
|
||||
If exploited, an attacker could perform any action that the deleted user was
|
||||
authorized to perform.
|
||||
|
||||
## Exploitability: HIGH
|
||||
|
||||
The CLI writes the API key to `~/.coderv2/session` by default, so any deleted
|
||||
user who previously logged in via the Coder CLI has the potential to exploit
|
||||
this. Note that there is a time window for exploitation; API tokens have a
|
||||
maximum lifetime after which they are no longer valid.
|
||||
|
||||
The issue only affects users who were active (not suspended) at the time they
|
||||
were deleted. Users who were first suspended and later deleted cannot exploit
|
||||
this issue.
|
||||
|
||||
## Affected Versions
|
||||
|
||||
All versions of Coder between v0.8.15 and v0.22.2 (inclusive) are affected.
|
||||
|
||||
All customers are advised to upgrade to
|
||||
[v0.23.0](https://github.com/coder/coder/releases/tag/v0.23.0) as soon as
|
||||
possible.
|
||||
|
||||
## Details
|
||||
|
||||
Coder incorrectly failed to invalidate API keys belonging to a user when they
|
||||
were deleted. When authenticating a user via their API key, Coder incorrectly
|
||||
failed to check whether the API key corresponds to a deleted user.
|
||||
|
||||
## Indications of Compromise
|
||||
|
||||
> 💡 Automated remediation steps in the upgrade purge all affected API keys.
|
||||
> Either perform the following query before upgrade or run it on a backup of
|
||||
> your database from before the upgrade.
|
||||
|
||||
Execute the following SQL query:
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
users.email,
|
||||
users.updated_at,
|
||||
api_keys.id,
|
||||
api_keys.last_used
|
||||
FROM
|
||||
users
|
||||
LEFT JOIN
|
||||
api_keys
|
||||
ON
|
||||
api_keys.user_id = users.id
|
||||
WHERE
|
||||
users.deleted
|
||||
AND
|
||||
api_keys.last_used > users.updated_at
|
||||
;
|
||||
```
|
||||
|
||||
If the output is similar to the below, then you are not affected:
|
||||
|
||||
```sql
|
||||
-----
|
||||
(0 rows)
|
||||
```
|
||||
|
||||
Otherwise, the following information will be reported:
|
||||
|
||||
- User email
|
||||
- Time the user was last modified (i.e. deleted)
|
||||
- User API key ID
|
||||
- Time the affected API key was last used
|
||||
|
||||
> 💡 If your license includes the
|
||||
> [Audit Logs](https://coder.com/docs/admin/audit-logs#filtering-logs) feature,
|
||||
> you can then query all actions performed by the above users by using the
|
||||
> filter `email:$USER_EMAIL`.
|
||||
@@ -0,0 +1,126 @@
|
||||
# Audit Logs
|
||||
|
||||
Audit Logs allows **Auditors** to monitor user operations in their deployment.
|
||||
|
||||
## Tracked Events
|
||||
|
||||
We track the following resources:
|
||||
|
||||
<!-- Code generated by 'make docs/admin/security/audit-logs.md'. DO NOT EDIT -->
|
||||
|
||||
| <b>Resource<b> | |
|
||||
| -------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| APIKey<br><i>login, logout, register, create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>ip_address</td><td>false</td></tr><tr><td>last_used</td><td>true</td></tr><tr><td>lifetime_seconds</td><td>false</td></tr><tr><td>login_type</td><td>false</td></tr><tr><td>scope</td><td>false</td></tr><tr><td>token_name</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
|
||||
| AuditOAuthConvertState<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>expires_at</td><td>true</td></tr><tr><td>from_login_type</td><td>true</td></tr><tr><td>to_login_type</td><td>true</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
|
||||
| Group<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>members</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>quota_allowance</td><td>true</td></tr><tr><td>source</td><td>false</td></tr></tbody></table> |
|
||||
| AuditableOrganizationMember<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>roles</td><td>true</td></tr><tr><td>updated_at</td><td>true</td></tr><tr><td>user_id</td><td>true</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| CustomRole<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>org_permissions</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>site_permissions</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_permissions</td><td>true</td></tr></tbody></table> |
|
||||
| GitSSHKey<br><i>create</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
|
||||
| HealthSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>dismissed_healthchecks</td><td>true</td></tr><tr><td>id</td><td>false</td></tr></tbody></table> |
|
||||
| License<br><i>create, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>exp</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>jwt</td><td>false</td></tr><tr><td>uploaded_at</td><td>true</td></tr><tr><td>uuid</td><td>true</td></tr></tbody></table> |
|
||||
| NotificationTemplate<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>actions</td><td>true</td></tr><tr><td>body_template</td><td>true</td></tr><tr><td>group</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>kind</td><td>true</td></tr><tr><td>method</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>title_template</td><td>true</td></tr></tbody></table> |
|
||||
| NotificationsSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>id</td><td>false</td></tr><tr><td>notifier_paused</td><td>true</td></tr></tbody></table> |
|
||||
| OAuth2ProviderApp<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>callback_url</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| OAuth2ProviderAppSecret<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>app_id</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>display_secret</td><td>false</td></tr><tr><td>hashed_secret</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>secret_prefix</td><td>false</td></tr></tbody></table> |
|
||||
| Organization<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>false</td></tr><tr><td>is_default</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>true</td></tr></tbody></table> |
|
||||
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>activity_bump</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_port_sharing_level</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_display_name</td><td>false</td></tr><tr><td>organization_icon</td><td>false</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>organization_name</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
|
||||
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>true</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>must_reset_password</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>theme_preference</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
|
||||
| Workspace<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>automatic_updates</td><td>true</td></tr><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deleting_at</td><td>true</td></tr><tr><td>dormant_at</td><td>true</td></tr><tr><td>favorite</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceBuild<br><i>start, stop</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_by_avatar_url</td><td>false</td></tr><tr><td>initiator_by_username</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>max_deadline</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
|
||||
| WorkspaceProxy<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>derp_enabled</td><td>true</td></tr><tr><td>derp_only</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>region_id</td><td>true</td></tr><tr><td>token_hashed_secret</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>url</td><td>true</td></tr><tr><td>version</td><td>true</td></tr><tr><td>wildcard_hostname</td><td>true</td></tr></tbody></table> |
|
||||
|
||||
<!-- End generated by 'make docs/admin/security/audit-logs.md'. -->
|
||||
|
||||
## Filtering logs
|
||||
|
||||
In the Coder UI you can filter your audit logs using the pre-defined filter or
|
||||
by using the Coder's filter query like the examples below:
|
||||
|
||||
- `resource_type:workspace action:delete` to find deleted workspaces
|
||||
- `resource_type:template action:create` to find created templates
|
||||
|
||||
The supported filters are:
|
||||
|
||||
- `resource_type` - The type of the resource. It can be a workspace, template,
|
||||
user, etc. You can
|
||||
[find here](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#ResourceType)
|
||||
all the resource types that are supported.
|
||||
- `resource_id` - The ID of the resource.
|
||||
- `resource_target` - The name of the resource. Can be used instead of
|
||||
`resource_id`.
|
||||
- `action`- The action applied to a resource. You can
|
||||
[find here](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#AuditAction)
|
||||
all the actions that are supported.
|
||||
- `username` - The username of the user who triggered the action. You can also
|
||||
use `me` as a convenient alias for the logged-in user.
|
||||
- `email` - The email of the user who triggered the action.
|
||||
- `date_from` - The inclusive start date with format `YYYY-MM-DD`.
|
||||
- `date_to` - The inclusive end date with format `YYYY-MM-DD`.
|
||||
- `build_reason` - To be used with `resource_type:workspace_build`, the
|
||||
[initiator](https://pkg.go.dev/github.com/coder/coder/v2/codersdk#BuildReason)
|
||||
behind the build start or stop.
|
||||
|
||||
## Capturing/Exporting Audit Logs
|
||||
|
||||
In addition to the user interface, there are multiple ways to consume or query
|
||||
audit trails.
|
||||
|
||||
## REST API
|
||||
|
||||
Audit logs can be accessed through our REST API. You can find detailed
|
||||
information about this in our
|
||||
[endpoint documentation](../../reference/api/audit.md#get-audit-logs).
|
||||
|
||||
## Service Logs
|
||||
|
||||
Audit trails are also dispatched as service logs and can be captured and
|
||||
categorized using any log management tool such as [Splunk](https://splunk.com).
|
||||
|
||||
Example of a [JSON formatted](../../reference/cli/server.md#--log-json) audit
|
||||
log entry:
|
||||
|
||||
```json
|
||||
{
|
||||
"ts": "2023-06-13T03:45:37.294730279Z",
|
||||
"level": "INFO",
|
||||
"msg": "audit_log",
|
||||
"caller": "/home/runner/work/coder/coder/enterprise/audit/backends/slog.go:36",
|
||||
"func": "github.com/coder/coder/enterprise/audit/backends.slogBackend.Export",
|
||||
"logger_names": ["coderd"],
|
||||
"fields": {
|
||||
"ID": "033a9ffa-b54d-4c10-8ec3-2aaf9e6d741a",
|
||||
"Time": "2023-06-13T03:45:37.288506Z",
|
||||
"UserID": "6c405053-27e3-484a-9ad7-bcb64e7bfde6",
|
||||
"OrganizationID": "00000000-0000-0000-0000-000000000000",
|
||||
"Ip": "{IPNet:{IP:\u003cnil\u003e Mask:\u003cnil\u003e} Valid:false}",
|
||||
"UserAgent": "{String: Valid:false}",
|
||||
"ResourceType": "workspace_build",
|
||||
"ResourceID": "ca5647e0-ef50-4202-a246-717e04447380",
|
||||
"ResourceTarget": "",
|
||||
"Action": "start",
|
||||
"Diff": {},
|
||||
"StatusCode": 200,
|
||||
"AdditionalFields": {
|
||||
"workspace_name": "linux-container",
|
||||
"build_number": "9",
|
||||
"build_reason": "initiator",
|
||||
"workspace_owner": ""
|
||||
},
|
||||
"RequestID": "bb791ac3-f6ee-4da8-8ec2-f54e87013e93",
|
||||
"ResourceIcon": ""
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Example of a [human readable](../../reference/cli/server.md#--log-human) audit
|
||||
log entry:
|
||||
|
||||
```console
|
||||
2023-06-13 03:43:29.233 [info] coderd: audit_log ID=95f7c392-da3e-480c-a579-8909f145fbe2 Time="2023-06-13T03:43:29.230422Z" UserID=6c405053-27e3-484a-9ad7-bcb64e7bfde6 OrganizationID=00000000-0000-0000-0000-000000000000 Ip=<nil> UserAgent=<nil> ResourceType=workspace_build ResourceID=988ae133-5b73-41e3-a55e-e1e9d3ef0b66 ResourceTarget="" Action=start Diff="{}" StatusCode=200 AdditionalFields="{\"workspace_name\":\"linux-container\",\"build_number\":\"7\",\"build_reason\":\"initiator\",\"workspace_owner\":\"\"}" RequestID=9682b1b5-7b9f-4bf2-9a39-9463f8e41cd6 ResourceIcon=""
|
||||
```
|
||||
|
||||
## Enabling this feature
|
||||
|
||||
This feature is only available with an enterprise license.
|
||||
[Learn more](../licensing/index.md)
|
||||
@@ -0,0 +1,186 @@
|
||||
# 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](../../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`
|
||||
|
||||
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:
|
||||
|
||||
```shell
|
||||
dd if=/dev/urandom bs=32 count=1 | base64
|
||||
```
|
||||
|
||||
- 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>
|
||||
```
|
||||
|
||||
- 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.
|
||||
|
||||
> 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`](../../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
|
||||
|
||||
> NOTE: 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.
|
||||
@@ -0,0 +1,20 @@
|
||||
# Security Advisories
|
||||
|
||||
> If you discover a vulnerability in Coder, please do not hesitate to report it
|
||||
> to us by following the instructions
|
||||
> [here](https://github.com/coder/coder/blob/main/SECURITY.md).
|
||||
|
||||
From time to time, Coder employees or other community members may discover
|
||||
vulnerabilities in the product.
|
||||
|
||||
If a vulnerability requires an immediate upgrade to mitigate a potential
|
||||
security risk, we will add it to the below table.
|
||||
|
||||
Click on the description links to view more details about each specific
|
||||
vulnerability.
|
||||
|
||||
---
|
||||
|
||||
| Description | Severity | Fix | Vulnerable Versions |
|
||||
| --------------------------------------------------------------------------------------------------------------------------------------- | -------- | -------------------------------------------------------------- | ------------------- |
|
||||
| [API tokens of deleted users not invalidated](https://github.com/coder/coder/blob/main/docs/security/0001_user_apikeys_invalidation.md) | HIGH | [v0.23.0](https://github.com/coder/coder/releases/tag/v0.23.0) | v0.8.25 - v0.22.2 |
|
||||
@@ -0,0 +1,113 @@
|
||||
# Secrets
|
||||
|
||||
<blockquote class="info">
|
||||
This article explains how to use secrets in a workspace. To authenticate the
|
||||
workspace provisioner, see <a href="/admin/auth">this</a>.
|
||||
</blockquote>
|
||||
|
||||
Coder is open-minded about how you get your secrets into your workspaces.
|
||||
|
||||
## Wait a minute...
|
||||
|
||||
Your first stab at secrets with Coder should be your local method. You can do
|
||||
everything you can locally and more with your Coder workspace, so whatever
|
||||
workflow and tools you already use to manage secrets may be brought over.
|
||||
|
||||
Often, this workflow is simply:
|
||||
|
||||
1. Give your users their secrets in advance
|
||||
1. Your users write them to a persistent file after they've built their
|
||||
workspace
|
||||
|
||||
[Template parameters](../templates/extending-templates/parameters.md) are a
|
||||
dangerous way to accept secrets. We show parameters in cleartext around the
|
||||
product. Assume anyone with view access to a workspace can also see its
|
||||
parameters.
|
||||
|
||||
## SSH Keys
|
||||
|
||||
Coder generates SSH key pairs for each user. This can be used as an
|
||||
authentication mechanism for git providers or other tools. Within workspaces,
|
||||
git will attempt to use this key within workspaces via the `$GIT_SSH_COMMAND`
|
||||
environment variable.
|
||||
|
||||
Users can view their public key in their account settings:
|
||||
|
||||

|
||||
|
||||
> Note: SSH keys are never stored in Coder workspaces, and are fetched only when
|
||||
> SSH is invoked. The keys are held in-memory and never written to disk.
|
||||
|
||||
## Dynamic Secrets
|
||||
|
||||
Dynamic secrets are attached to the workspace lifecycle and automatically
|
||||
injected into the workspace. With a little bit of up front template work, they
|
||||
make life simpler for both the end user and the security team.
|
||||
|
||||
This method is limited to
|
||||
[services with Terraform providers](https://registry.terraform.io/browse/providers),
|
||||
which excludes obscure API providers.
|
||||
|
||||
Dynamic secrets can be implemented in your template code like so:
|
||||
|
||||
```tf
|
||||
resource "twilio_iam_api_key" "api_key" {
|
||||
account_sid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
friendly_name = "Test API Key"
|
||||
}
|
||||
|
||||
resource "coder_agent" "main" {
|
||||
# ...
|
||||
env = {
|
||||
# Let users access the secret via $TWILIO_API_SECRET
|
||||
TWILIO_API_SECRET = "${twilio_iam_api_key.api_key.secret}"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
A catch-all variation of this approach is dynamically provisioning a cloud
|
||||
service account (e.g
|
||||
[GCP](https://registry.terraform.io/providers/hashicorp/google/latest/docs/resources/google_service_account_key#private_key))
|
||||
for each workspace and then making the relevant secrets available via the
|
||||
cloud's secret management system.
|
||||
|
||||
## Displaying Secrets
|
||||
|
||||
While you can inject secrets into the workspace via environment variables, you
|
||||
can also show them in the Workspace UI with
|
||||
[`coder_metadata`](https://registry.terraform.io/providers/coder/coder/latest/docs/resources/metadata).
|
||||
|
||||

|
||||
|
||||
Can be produced with
|
||||
|
||||
```tf
|
||||
resource "twilio_iam_api_key" "api_key" {
|
||||
account_sid = "ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
|
||||
friendly_name = "Test API Key"
|
||||
}
|
||||
|
||||
|
||||
resource "coder_metadata" "twilio_key" {
|
||||
resource_id = twilio_iam_api_key.api_key.id
|
||||
item {
|
||||
key = "Username"
|
||||
value = "Administrator"
|
||||
}
|
||||
item {
|
||||
key = "Password"
|
||||
value = twilio_iam_api_key.api_key.secret
|
||||
sensitive = true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Secrets Management
|
||||
|
||||
For more advanced secrets management, you can use a secrets management tool to
|
||||
store and retrieve secrets in your workspace. For example, you can use
|
||||
[HashiCorp Vault](https://www.vaultproject.io/) to inject secrets into your
|
||||
workspace.
|
||||
|
||||
Refer to our [HashiCorp Vault Integration](../integrations/vault.md) guide for
|
||||
more information on how to integrate HashiCorp Vault with Coder.
|
||||
Reference in New Issue
Block a user