diff --git a/cli/testdata/coder_server_--help.golden b/cli/testdata/coder_server_--help.golden index 1444f80961..c83d3aa273 100644 --- a/cli/testdata/coder_server_--help.golden +++ b/cli/testdata/coder_server_--help.golden @@ -696,6 +696,27 @@ updating, and deleting workspace resources. Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this. +RETENTION OPTIONS: +Configure data retention policies for various database tables. Retention +policies automatically purge old data to reduce database size and improve +performance. Setting a retention duration to 0 disables automatic purging for +that data type. + + --api-keys-retention duration, $CODER_API_KEYS_RETENTION (default: 7d) + How long expired API keys are retained before being deleted. Keeping + expired keys allows the backend to return a more helpful error when a + user tries to use an expired key. Set to 0 to disable automatic + deletion of expired keys. + + --audit-logs-retention duration, $CODER_AUDIT_LOGS_RETENTION (default: 0) + How long audit log entries are retained. Set to 0 to disable (keep + indefinitely). We advise keeping audit logs for at least a year, and + in accordance with your compliance requirements. + + --connection-logs-retention duration, $CODER_CONNECTION_LOGS_RETENTION (default: 0) + How long connection log entries are retained. Set to 0 to disable + (keep indefinitely). + TELEMETRY OPTIONS: Telemetry is critical to our ability to improve Coder. We strip all personal information before sending data to our servers. Please only disable telemetry diff --git a/cli/testdata/server-config.yaml.golden b/cli/testdata/server-config.yaml.golden index 8b254fe295..36ab491274 100644 --- a/cli/testdata/server-config.yaml.golden +++ b/cli/testdata/server-config.yaml.golden @@ -742,3 +742,22 @@ aibridge: # (token, prompt, tool use). # (default: 60d, type: duration) retention: 1440h0m0s +# Configure data retention policies for various database tables. Retention +# policies automatically purge old data to reduce database size and improve +# performance. Setting a retention duration to 0 disables automatic purging for +# that data type. +retention: + # How long audit log entries are retained. Set to 0 to disable (keep + # indefinitely). We advise keeping audit logs for at least a year, and in + # accordance with your compliance requirements. + # (default: 0, type: duration) + audit_logs: 0s + # How long connection log entries are retained. Set to 0 to disable (keep + # indefinitely). + # (default: 0, type: duration) + connection_logs: 0s + # How long expired API keys are retained before being deleted. Keeping expired + # keys allows the backend to return a more helpful error when a user tries to use + # an expired key. Set to 0 to disable automatic deletion of expired keys. + # (default: 7d, type: duration) + api_keys: 168h0m0s diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index f80959192d..658bda7c1e 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -14302,6 +14302,9 @@ const docTemplate = `{ "redirect_to_access_url": { "type": "boolean" }, + "retention": { + "$ref": "#/definitions/codersdk.RetentionConfig" + }, "scim_api_key": { "type": "string" }, @@ -17728,6 +17731,23 @@ const docTemplate = `{ } } }, + "codersdk.RetentionConfig": { + "type": "object", + "properties": { + "api_keys": { + "description": "APIKeys controls how long expired API keys are retained before being deleted.\nKeys are only deleted if they have been expired for at least this duration.\nDefaults to 7 days to preserve existing behavior.", + "type": "integer" + }, + "audit_logs": { + "description": "AuditLogs controls how long audit log entries are retained.\nSet to 0 to disable (keep indefinitely).", + "type": "integer" + }, + "connection_logs": { + "description": "ConnectionLogs controls how long connection log entries are retained.\nSet to 0 to disable (keep indefinitely).", + "type": "integer" + } + } + }, "codersdk.Role": { "type": "object", "properties": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 8d54e2b310..1c3976a96b 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -12886,6 +12886,9 @@ "redirect_to_access_url": { "type": "boolean" }, + "retention": { + "$ref": "#/definitions/codersdk.RetentionConfig" + }, "scim_api_key": { "type": "string" }, @@ -16190,6 +16193,23 @@ } } }, + "codersdk.RetentionConfig": { + "type": "object", + "properties": { + "api_keys": { + "description": "APIKeys controls how long expired API keys are retained before being deleted.\nKeys are only deleted if they have been expired for at least this duration.\nDefaults to 7 days to preserve existing behavior.", + "type": "integer" + }, + "audit_logs": { + "description": "AuditLogs controls how long audit log entries are retained.\nSet to 0 to disable (keep indefinitely).", + "type": "integer" + }, + "connection_logs": { + "description": "ConnectionLogs controls how long connection log entries are retained.\nSet to 0 to disable (keep indefinitely).", + "type": "integer" + } + } + }, "codersdk.Role": { "type": "object", "properties": { diff --git a/codersdk/deployment.go b/codersdk/deployment.go index 7b09e3a83c..464ae7ab2b 100644 --- a/codersdk/deployment.go +++ b/codersdk/deployment.go @@ -501,6 +501,7 @@ type DeploymentValues struct { WebTerminalRenderer serpent.String `json:"web_terminal_renderer,omitempty" typescript:",notnull"` AllowWorkspaceRenames serpent.Bool `json:"allow_workspace_renames,omitempty" typescript:",notnull"` Healthcheck HealthcheckConfig `json:"healthcheck,omitempty" typescript:",notnull"` + Retention RetentionConfig `json:"retention,omitempty" typescript:",notnull"` CLIUpgradeMessage serpent.String `json:"cli_upgrade_message,omitempty" typescript:",notnull"` TermsOfServiceURL serpent.String `json:"terms_of_service_url,omitempty" typescript:",notnull"` Notifications NotificationsConfig `json:"notifications,omitempty" typescript:",notnull"` @@ -813,6 +814,23 @@ type HealthcheckConfig struct { ThresholdDatabase serpent.Duration `json:"threshold_database" typescript:",notnull"` } +// RetentionConfig contains configuration for data retention policies. +// These settings control how long various types of data are retained in the database +// before being automatically purged. Setting a value to 0 disables retention for that +// data type (data is kept indefinitely). +type RetentionConfig struct { + // AuditLogs controls how long audit log entries are retained. + // Set to 0 to disable (keep indefinitely). + AuditLogs serpent.Duration `json:"audit_logs" typescript:",notnull"` + // ConnectionLogs controls how long connection log entries are retained. + // Set to 0 to disable (keep indefinitely). + ConnectionLogs serpent.Duration `json:"connection_logs" typescript:",notnull"` + // APIKeys controls how long expired API keys are retained before being deleted. + // Keys are only deleted if they have been expired for at least this duration. + // Defaults to 7 days to preserve existing behavior. + APIKeys serpent.Duration `json:"api_keys" typescript:",notnull"` +} + type NotificationsConfig struct { // The upper limit of attempts to send a notification. MaxSendAttempts serpent.Int64 `json:"max_send_attempts" typescript:",notnull"` @@ -1180,6 +1198,11 @@ func (c *DeploymentValues) Options() serpent.OptionSet { Name: "AI Bridge", YAML: "aibridge", } + deploymentGroupRetention = serpent.Group{ + Name: "Retention", + Description: "Configure data retention policies for various database tables. Retention policies automatically purge old data to reduce database size and improve performance. Setting a retention duration to 0 disables automatic purging for that data type.", + YAML: "retention", + } ) httpAddress := serpent.Option{ @@ -3363,6 +3386,40 @@ Write out the current server config as YAML to stdout.`, YAML: "retention", Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), }, + // Retention settings + { + Name: "Audit Logs Retention", + Description: "How long audit log entries are retained. Set to 0 to disable (keep indefinitely). We advise keeping audit logs for at least a year, and in accordance with your compliance requirements.", + Flag: "audit-logs-retention", + Env: "CODER_AUDIT_LOGS_RETENTION", + Value: &c.Retention.AuditLogs, + Default: "0", + Group: &deploymentGroupRetention, + YAML: "audit_logs", + Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), + }, + { + Name: "Connection Logs Retention", + Description: "How long connection log entries are retained. Set to 0 to disable (keep indefinitely).", + Flag: "connection-logs-retention", + Env: "CODER_CONNECTION_LOGS_RETENTION", + Value: &c.Retention.ConnectionLogs, + Default: "0", + Group: &deploymentGroupRetention, + YAML: "connection_logs", + Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), + }, + { + Name: "API Keys Retention", + Description: "How long expired API keys are retained before being deleted. Keeping expired keys allows the backend to return a more helpful error when a user tries to use an expired key. Set to 0 to disable automatic deletion of expired keys.", + Flag: "api-keys-retention", + Env: "CODER_API_KEYS_RETENTION", + Value: &c.Retention.APIKeys, + Default: "7d", + Group: &deploymentGroupRetention, + YAML: "api_keys", + Annotations: serpent.Annotations{}.Mark(annotationFormatDuration, "true"), + }, { Name: "Enable Authorization Recordings", Description: "All api requests will have a header including all authorization calls made during the request. " + diff --git a/codersdk/deployment_test.go b/codersdk/deployment_test.go index 0b46320892..8b811480d5 100644 --- a/codersdk/deployment_test.go +++ b/codersdk/deployment_test.go @@ -703,3 +703,65 @@ func TestNotificationsCanBeDisabled(t *testing.T) { }) } } + +func TestRetentionConfigParsing(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + environment []serpent.EnvVar + expectedAuditLogs time.Duration + expectedConnectionLogs time.Duration + expectedAPIKeys time.Duration + }{ + { + name: "Defaults", + environment: []serpent.EnvVar{}, + expectedAuditLogs: 0, + expectedConnectionLogs: 0, + expectedAPIKeys: 7 * 24 * time.Hour, // 7 days default + }, + { + name: "IndividualRetentionSet", + environment: []serpent.EnvVar{ + {Name: "CODER_AUDIT_LOGS_RETENTION", Value: "30d"}, + {Name: "CODER_CONNECTION_LOGS_RETENTION", Value: "60d"}, + {Name: "CODER_API_KEYS_RETENTION", Value: "14d"}, + }, + expectedAuditLogs: 30 * 24 * time.Hour, + expectedConnectionLogs: 60 * 24 * time.Hour, + expectedAPIKeys: 14 * 24 * time.Hour, + }, + { + name: "AllRetentionSet", + environment: []serpent.EnvVar{ + {Name: "CODER_AUDIT_LOGS_RETENTION", Value: "365d"}, + {Name: "CODER_CONNECTION_LOGS_RETENTION", Value: "30d"}, + {Name: "CODER_API_KEYS_RETENTION", Value: "0"}, + }, + expectedAuditLogs: 365 * 24 * time.Hour, + expectedConnectionLogs: 30 * 24 * time.Hour, + expectedAPIKeys: 0, // Explicitly disabled + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + + dv := codersdk.DeploymentValues{} + opts := dv.Options() + + err := opts.SetDefaults() + require.NoError(t, err) + + err = opts.ParseEnv(tt.environment) + require.NoError(t, err) + + assert.Equal(t, tt.expectedAuditLogs, dv.Retention.AuditLogs.Value(), "audit logs retention mismatch") + assert.Equal(t, tt.expectedConnectionLogs, dv.Retention.ConnectionLogs.Value(), "connection logs retention mismatch") + assert.Equal(t, tt.expectedAPIKeys, dv.Retention.APIKeys.Value(), "api keys retention mismatch") + }) + } +} diff --git a/docs/reference/api/general.md b/docs/reference/api/general.md index 2f30b4c1d6..39793443ca 100644 --- a/docs/reference/api/general.md +++ b/docs/reference/api/general.md @@ -463,6 +463,11 @@ curl -X GET http://coder-server:8080/api/v2/deployment/config \ "disable_all": true }, "redirect_to_access_url": true, + "retention": { + "api_keys": 0, + "audit_logs": 0, + "connection_logs": 0 + }, "scim_api_key": "string", "session_lifetime": { "default_duration": 0, diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 7f88558c75..f58285a584 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -3147,6 +3147,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "disable_all": true }, "redirect_to_access_url": true, + "retention": { + "api_keys": 0, + "audit_logs": 0, + "connection_logs": 0 + }, "scim_api_key": "string", "session_lifetime": { "default_duration": 0, @@ -3663,6 +3668,11 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o "disable_all": true }, "redirect_to_access_url": true, + "retention": { + "api_keys": 0, + "audit_logs": 0, + "connection_logs": 0 + }, "scim_api_key": "string", "session_lifetime": { "default_duration": 0, @@ -3808,6 +3818,7 @@ CreateWorkspaceRequest provides options for creating a new workspace. Only one o | `proxy_trusted_origins` | array of string | false | | | | `rate_limit` | [codersdk.RateLimitConfig](#codersdkratelimitconfig) | false | | | | `redirect_to_access_url` | boolean | false | | | +| `retention` | [codersdk.RetentionConfig](#codersdkretentionconfig) | false | | | | `scim_api_key` | string | false | | | | `session_lifetime` | [codersdk.SessionLifetime](#codersdksessionlifetime) | false | | | | `ssh_keygen_algorithm` | string | false | | | @@ -7506,6 +7517,24 @@ Only certain features set these fields: - FeatureManagedAgentLimit| | `message` | string | false | | Message is an actionable message that depicts actions the request took. These messages should be fully formed sentences with proper punctuation. Examples: - "A user has been created." - "Failed to create a user." | | `validations` | array of [codersdk.ValidationError](#codersdkvalidationerror) | false | | Validations are form field-specific friendly error messages. They will be shown on a form field in the UI. These can also be used to add additional context if there is a set of errors in the primary 'Message'. | +## codersdk.RetentionConfig + +```json +{ + "api_keys": 0, + "audit_logs": 0, + "connection_logs": 0 +} +``` + +### Properties + +| Name | Type | Required | Restrictions | Description | +|-------------------|---------|----------|--------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `api_keys` | integer | false | | Api keys controls how long expired API keys are retained before being deleted. Keys are only deleted if they have been expired for at least this duration. Defaults to 7 days to preserve existing behavior. | +| `audit_logs` | integer | false | | Audit logs controls how long audit log entries are retained. Set to 0 to disable (keep indefinitely). | +| `connection_logs` | integer | false | | Connection logs controls how long connection log entries are retained. Set to 0 to disable (keep indefinitely). | + ## codersdk.Role ```json diff --git a/docs/reference/cli/server.md b/docs/reference/cli/server.md index 8f86ea7248..a173c043b0 100644 --- a/docs/reference/cli/server.md +++ b/docs/reference/cli/server.md @@ -1770,3 +1770,36 @@ Whether to inject Coder's MCP tools into intercepted AI Bridge requests (require | Default | 60d | Length of time to retain data such as interceptions and all related records (token, prompt, tool use). + +### --audit-logs-retention + +| | | +|-------------|------------------------------------------| +| Type | duration | +| Environment | $CODER_AUDIT_LOGS_RETENTION | +| YAML | retention.audit_logs | +| Default | 0 | + +How long audit log entries are retained. Set to 0 to disable (keep indefinitely). We advise keeping audit logs for at least a year, and in accordance with your compliance requirements. + +### --connection-logs-retention + +| | | +|-------------|-----------------------------------------------| +| Type | duration | +| Environment | $CODER_CONNECTION_LOGS_RETENTION | +| YAML | retention.connection_logs | +| Default | 0 | + +How long connection log entries are retained. Set to 0 to disable (keep indefinitely). + +### --api-keys-retention + +| | | +|-------------|----------------------------------------| +| Type | duration | +| Environment | $CODER_API_KEYS_RETENTION | +| YAML | retention.api_keys | +| Default | 7d | + +How long expired API keys are retained before being deleted. Keeping expired keys allows the backend to return a more helpful error when a user tries to use an expired key. Set to 0 to disable automatic deletion of expired keys. diff --git a/enterprise/cli/testdata/coder_server_--help.golden b/enterprise/cli/testdata/coder_server_--help.golden index 5ed217752b..ec8f4b8cd6 100644 --- a/enterprise/cli/testdata/coder_server_--help.golden +++ b/enterprise/cli/testdata/coder_server_--help.golden @@ -697,6 +697,27 @@ updating, and deleting workspace resources. Number of provisioner daemons to create on start. If builds are stuck in queued state for a long time, consider increasing this. +RETENTION OPTIONS: +Configure data retention policies for various database tables. Retention +policies automatically purge old data to reduce database size and improve +performance. Setting a retention duration to 0 disables automatic purging for +that data type. + + --api-keys-retention duration, $CODER_API_KEYS_RETENTION (default: 7d) + How long expired API keys are retained before being deleted. Keeping + expired keys allows the backend to return a more helpful error when a + user tries to use an expired key. Set to 0 to disable automatic + deletion of expired keys. + + --audit-logs-retention duration, $CODER_AUDIT_LOGS_RETENTION (default: 0) + How long audit log entries are retained. Set to 0 to disable (keep + indefinitely). We advise keeping audit logs for at least a year, and + in accordance with your compliance requirements. + + --connection-logs-retention duration, $CODER_CONNECTION_LOGS_RETENTION (default: 0) + How long connection log entries are retained. Set to 0 to disable + (keep indefinitely). + TELEMETRY OPTIONS: Telemetry is critical to our ability to improve Coder. We strip all personal information before sending data to our servers. Please only disable telemetry diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 456d1a737a..400ad8bb79 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1778,6 +1778,7 @@ export interface DeploymentValues { readonly web_terminal_renderer?: string; readonly allow_workspace_renames?: boolean; readonly healthcheck?: HealthcheckConfig; + readonly retention?: RetentionConfig; readonly cli_upgrade_message?: string; readonly terms_of_service_url?: string; readonly notifications?: NotificationsConfig; @@ -4156,6 +4157,32 @@ export interface Response { readonly validations?: readonly ValidationError[]; } +// From codersdk/deployment.go +/** + * RetentionConfig contains configuration for data retention policies. + * These settings control how long various types of data are retained in the database + * before being automatically purged. Setting a value to 0 disables retention for that + * data type (data is kept indefinitely). + */ +export interface RetentionConfig { + /** + * AuditLogs controls how long audit log entries are retained. + * Set to 0 to disable (keep indefinitely). + */ + readonly audit_logs: number; + /** + * ConnectionLogs controls how long connection log entries are retained. + * Set to 0 to disable (keep indefinitely). + */ + readonly connection_logs: number; + /** + * APIKeys controls how long expired API keys are retained before being deleted. + * Keys are only deleted if they have been expired for at least this duration. + * Defaults to 7 days to preserve existing behavior. + */ + readonly api_keys: number; +} + // From codersdk/roles.go /** * Role is a longer form of SlimRole that includes permissions details.