From 2fa6caeb7b3829b15333b17e7e439df8838969d3 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Fri, 7 Feb 2025 14:33:24 +0000 Subject: [PATCH 1/6] feat: add workspace agent connect and app open audit types This commit adds new audit resource types for workspace agents and workspace apps, as well as connect and open actions. The idea is that we will log new audit events for connecting to the agent via editor or SSH. Likewise, we will log openings of `coder_app`s. Updates #15139 --- coderd/apidoc/docs.go | 16 ++- coderd/apidoc/swagger.json | 16 ++- coderd/audit/diff.go | 4 +- coderd/audit/request.go | 16 +++ coderd/audit_test.go | 101 +++++++++++++++++- coderd/database/dump.sql | 8 +- ..._audit_types_for_connect_and_open.down.sql | 1 + ...dd_audit_types_for_connect_and_open.up.sql | 13 +++ coderd/database/models.go | 16 ++- codersdk/audit.go | 12 +++ docs/admin/security/audit-logs.md | 2 + docs/reference/api/schemas.md | 4 + enterprise/audit/table.go | 55 ++++++++++ site/src/api/typesGenerated.ts | 8 ++ 14 files changed, 256 insertions(+), 16 deletions(-) create mode 100644 coderd/database/migrations/000291_add_audit_types_for_connect_and_open.down.sql create mode 100644 coderd/database/migrations/000291_add_audit_types_for_connect_and_open.up.sql diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 2e48634c7de13..ff370b3ae1752 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -10098,7 +10098,9 @@ const docTemplate = `{ "login", "logout", "register", - "request_password_reset" + "request_password_reset", + "connect", + "open" ], "x-enum-varnames": [ "AuditActionCreate", @@ -10109,7 +10111,9 @@ const docTemplate = `{ "AuditActionLogin", "AuditActionLogout", "AuditActionRegister", - "AuditActionRequestPasswordReset" + "AuditActionRequestPasswordReset", + "AuditActionConnect", + "AuditActionOpen" ] }, "codersdk.AuditDiff": { @@ -13864,7 +13868,9 @@ const docTemplate = `{ "notification_template", "idp_sync_settings_organization", "idp_sync_settings_group", - "idp_sync_settings_role" + "idp_sync_settings_role", + "workspace_agent", + "workspace_app" ], "x-enum-varnames": [ "ResourceTypeTemplate", @@ -13888,7 +13894,9 @@ const docTemplate = `{ "ResourceTypeNotificationTemplate", "ResourceTypeIdpSyncSettingsOrganization", "ResourceTypeIdpSyncSettingsGroup", - "ResourceTypeIdpSyncSettingsRole" + "ResourceTypeIdpSyncSettingsRole", + "ResourceTypeWorkspaceAgent", + "ResourceTypeWorkspaceApp" ] }, "codersdk.Response": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 0e03555da4720..82565a6087145 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8962,7 +8962,9 @@ "login", "logout", "register", - "request_password_reset" + "request_password_reset", + "connect", + "open" ], "x-enum-varnames": [ "AuditActionCreate", @@ -8973,7 +8975,9 @@ "AuditActionLogin", "AuditActionLogout", "AuditActionRegister", - "AuditActionRequestPasswordReset" + "AuditActionRequestPasswordReset", + "AuditActionConnect", + "AuditActionOpen" ] }, "codersdk.AuditDiff": { @@ -12549,7 +12553,9 @@ "notification_template", "idp_sync_settings_organization", "idp_sync_settings_group", - "idp_sync_settings_role" + "idp_sync_settings_role", + "workspace_agent", + "workspace_app" ], "x-enum-varnames": [ "ResourceTypeTemplate", @@ -12573,7 +12579,9 @@ "ResourceTypeNotificationTemplate", "ResourceTypeIdpSyncSettingsOrganization", "ResourceTypeIdpSyncSettingsGroup", - "ResourceTypeIdpSyncSettingsRole" + "ResourceTypeIdpSyncSettingsRole", + "ResourceTypeWorkspaceAgent", + "ResourceTypeWorkspaceApp" ] }, "codersdk.Response": { diff --git a/coderd/audit/diff.go b/coderd/audit/diff.go index 98e47e91893cb..0a4c35814df0c 100644 --- a/coderd/audit/diff.go +++ b/coderd/audit/diff.go @@ -30,7 +30,9 @@ type Auditable interface { database.NotificationTemplate | idpsync.OrganizationSyncSettings | idpsync.GroupSyncSettings | - idpsync.RoleSyncSettings + idpsync.RoleSyncSettings | + database.WorkspaceAgent | + database.WorkspaceApp } // Map is a map of changed fields in an audited resource. It maps field names to diff --git a/coderd/audit/request.go b/coderd/audit/request.go index 05c18e32fd183..3ed6891f12316 100644 --- a/coderd/audit/request.go +++ b/coderd/audit/request.go @@ -128,6 +128,10 @@ func ResourceTarget[T Auditable](tgt T) string { return "Organization Group Sync" case idpsync.RoleSyncSettings: return "Organization Role Sync" + case database.WorkspaceAgent: + return typed.Name + case database.WorkspaceApp: + return typed.Slug default: panic(fmt.Sprintf("unknown resource %T for ResourceTarget", tgt)) } @@ -187,6 +191,10 @@ func ResourceID[T Auditable](tgt T) uuid.UUID { return noID // Org field on audit log has org id case idpsync.RoleSyncSettings: return noID // Org field on audit log has org id + case database.WorkspaceAgent: + return typed.ID + case database.WorkspaceApp: + return typed.ID default: panic(fmt.Sprintf("unknown resource %T for ResourceID", tgt)) } @@ -238,6 +246,10 @@ func ResourceType[T Auditable](tgt T) database.ResourceType { return database.ResourceTypeIdpSyncSettingsRole case idpsync.GroupSyncSettings: return database.ResourceTypeIdpSyncSettingsGroup + case database.WorkspaceAgent: + return database.ResourceTypeWorkspaceAgent + case database.WorkspaceApp: + return database.ResourceTypeWorkspaceApp default: panic(fmt.Sprintf("unknown resource %T for ResourceType", typed)) } @@ -291,6 +303,10 @@ func ResourceRequiresOrgID[T Auditable]() bool { return true case idpsync.RoleSyncSettings: return true + case database.WorkspaceAgent: + return true + case database.WorkspaceApp: + return true default: panic(fmt.Sprintf("unknown resource %T for ResourceRequiresOrgID", tgt)) } diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 922e2b359b506..4caed73460e57 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -17,6 +17,8 @@ import ( "github.com/coder/coder/v2/coderd/database" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/provisioner/echo" + "github.com/coder/coder/v2/provisionersdk/proto" ) func TestAuditLogs(t *testing.T) { @@ -229,12 +231,13 @@ func TestAuditLogsFilter(t *testing.T) { ctx = context.Background() client = coderdtest.New(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) user = coderdtest.CreateFirstUser(t, client) - version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, nil) + version = coderdtest.CreateTemplateVersion(t, client, user.OrganizationID, completeWithAgentAndApp()) template = coderdtest.CreateTemplate(t, client, user.OrganizationID, version.ID) ) coderdtest.AwaitTemplateVersionJobCompleted(t, client, version.ID) workspace := coderdtest.CreateWorkspace(t, client, template.ID) + workspace.LatestBuild = coderdtest.AwaitWorkspaceBuildJobCompleted(t, client, workspace.LatestBuild.ID) // Create two logs with "Create" err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ @@ -279,6 +282,28 @@ func TestAuditLogsFilter(t *testing.T) { }) require.NoError(t, err) + for _, resource := range workspace.LatestBuild.Resources { + t.Logf("Resource: %#v", resource) + } + + // Create one log with "Connect" + err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + Action: codersdk.AuditActionConnect, + ResourceType: codersdk.ResourceTypeWorkspaceAgent, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + }) + require.NoError(t, err) + + // Create one log with "Open" + err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + Action: codersdk.AuditActionOpen, + ResourceType: codersdk.ResourceTypeWorkspaceApp, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + }) + require.NoError(t, err) + // Test cases testCases := []struct { Name string @@ -309,12 +334,12 @@ func TestAuditLogsFilter(t *testing.T) { { Name: "FilterByEmail", SearchQuery: "email:" + coderdtest.FirstUserParams.Email, - ExpectedResult: 5, + ExpectedResult: 7, }, { Name: "FilterByUsername", SearchQuery: "username:" + coderdtest.FirstUserParams.Username, - ExpectedResult: 5, + ExpectedResult: 7, }, { Name: "FilterByResourceID", @@ -366,6 +391,16 @@ func TestAuditLogsFilter(t *testing.T) { SearchQuery: "resource_type:workspace_build action:start build_reason:initiator", ExpectedResult: 1, }, + { + Name: "FilterOnWorkspaceAgentConnect", + SearchQuery: "resource_type:workspace_agent action:connect", + ExpectedResult: 1, + }, + { + Name: "FilterOnWorkspaceAppOpen", + SearchQuery: "resource_type:workspace_app action:open", + ExpectedResult: 1, + }, } for _, testCase := range testCases { @@ -387,3 +422,63 @@ func TestAuditLogsFilter(t *testing.T) { } }) } + +func completeWithAgentAndApp() *echo.Responses { + return &echo.Responses{ + Parse: echo.ParseComplete, + ProvisionPlan: []*proto.Response{ + { + Type: &proto.Response_Plan{ + Plan: &proto.PlanComplete{ + Resources: []*proto.Resource{ + { + Type: "compute", + Name: "main", + Agents: []*proto.Agent{ + { + Name: "smith", + OperatingSystem: "linux", + Architecture: "i386", + Apps: []*proto.App{ + { + Slug: "app", + DisplayName: "App", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + ProvisionApply: []*proto.Response{ + { + Type: &proto.Response_Apply{ + Apply: &proto.ApplyComplete{ + Resources: []*proto.Resource{ + { + Type: "compute", + Name: "main", + Agents: []*proto.Agent{ + { + Name: "smith", + OperatingSystem: "linux", + Architecture: "i386", + Apps: []*proto.App{ + { + Slug: "app", + DisplayName: "App", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 20e7d14b57d01..abe52e57fbd08 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -25,7 +25,9 @@ CREATE TYPE audit_action AS ENUM ( 'login', 'logout', 'register', - 'request_password_reset' + 'request_password_reset', + 'connect', + 'open' ); CREATE TYPE automatic_updates AS ENUM ( @@ -201,7 +203,9 @@ CREATE TYPE resource_type AS ENUM ( 'notification_template', 'idp_sync_settings_organization', 'idp_sync_settings_group', - 'idp_sync_settings_role' + 'idp_sync_settings_role', + 'workspace_agent', + 'workspace_app' ); CREATE TYPE startup_script_behavior AS ENUM ( diff --git a/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.down.sql b/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.down.sql new file mode 100644 index 0000000000000..35020b349fc4e --- /dev/null +++ b/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.down.sql @@ -0,0 +1 @@ +-- No-op, enum values can't be dropped. diff --git a/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.up.sql b/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.up.sql new file mode 100644 index 0000000000000..b894a45eaf443 --- /dev/null +++ b/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.up.sql @@ -0,0 +1,13 @@ +-- Add new audit types for connect and open actions. +ALTER TYPE audit_action + ADD VALUE IF NOT EXISTS 'connect'; +ALTER TYPE audit_action + ADD VALUE IF NOT EXISTS 'disconnect'; +ALTER TYPE resource_type + ADD VALUE IF NOT EXISTS 'workspace_agent'; +ALTER TYPE audit_action + ADD VALUE IF NOT EXISTS 'open'; +ALTER TYPE audit_action + ADD VALUE IF NOT EXISTS 'close'; +ALTER TYPE resource_type + ADD VALUE IF NOT EXISTS 'workspace_app'; diff --git a/coderd/database/models.go b/coderd/database/models.go index fc11e1f4f5ebe..38ec09f65610b 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -147,6 +147,8 @@ const ( AuditActionLogout AuditAction = "logout" AuditActionRegister AuditAction = "register" AuditActionRequestPasswordReset AuditAction = "request_password_reset" + AuditActionConnect AuditAction = "connect" + AuditActionOpen AuditAction = "open" ) func (e *AuditAction) Scan(src interface{}) error { @@ -194,7 +196,9 @@ func (e AuditAction) Valid() bool { AuditActionLogin, AuditActionLogout, AuditActionRegister, - AuditActionRequestPasswordReset: + AuditActionRequestPasswordReset, + AuditActionConnect, + AuditActionOpen: return true } return false @@ -211,6 +215,8 @@ func AllAuditActionValues() []AuditAction { AuditActionLogout, AuditActionRegister, AuditActionRequestPasswordReset, + AuditActionConnect, + AuditActionOpen, } } @@ -1608,6 +1614,8 @@ const ( ResourceTypeIdpSyncSettingsOrganization ResourceType = "idp_sync_settings_organization" ResourceTypeIdpSyncSettingsGroup ResourceType = "idp_sync_settings_group" ResourceTypeIdpSyncSettingsRole ResourceType = "idp_sync_settings_role" + ResourceTypeWorkspaceAgent ResourceType = "workspace_agent" + ResourceTypeWorkspaceApp ResourceType = "workspace_app" ) func (e *ResourceType) Scan(src interface{}) error { @@ -1668,7 +1676,9 @@ func (e ResourceType) Valid() bool { ResourceTypeNotificationTemplate, ResourceTypeIdpSyncSettingsOrganization, ResourceTypeIdpSyncSettingsGroup, - ResourceTypeIdpSyncSettingsRole: + ResourceTypeIdpSyncSettingsRole, + ResourceTypeWorkspaceAgent, + ResourceTypeWorkspaceApp: return true } return false @@ -1698,6 +1708,8 @@ func AllResourceTypeValues() []ResourceType { ResourceTypeIdpSyncSettingsOrganization, ResourceTypeIdpSyncSettingsGroup, ResourceTypeIdpSyncSettingsRole, + ResourceTypeWorkspaceAgent, + ResourceTypeWorkspaceApp, } } diff --git a/codersdk/audit.go b/codersdk/audit.go index 307eeb275b61c..66b43d67bafe9 100644 --- a/codersdk/audit.go +++ b/codersdk/audit.go @@ -37,6 +37,8 @@ const ( ResourceTypeIdpSyncSettingsOrganization ResourceType = "idp_sync_settings_organization" ResourceTypeIdpSyncSettingsGroup ResourceType = "idp_sync_settings_group" ResourceTypeIdpSyncSettingsRole ResourceType = "idp_sync_settings_role" + ResourceTypeWorkspaceAgent ResourceType = "workspace_agent" + ResourceTypeWorkspaceApp ResourceType = "workspace_app" ) func (r ResourceType) FriendlyString() string { @@ -87,6 +89,10 @@ func (r ResourceType) FriendlyString() string { return "settings" case ResourceTypeIdpSyncSettingsRole: return "settings" + case ResourceTypeWorkspaceAgent: + return "workspace agent" + case ResourceTypeWorkspaceApp: + return "workspace app" default: return "unknown" } @@ -104,6 +110,8 @@ const ( AuditActionLogout AuditAction = "logout" AuditActionRegister AuditAction = "register" AuditActionRequestPasswordReset AuditAction = "request_password_reset" + AuditActionConnect AuditAction = "connect" + AuditActionOpen AuditAction = "open" ) func (a AuditAction) Friendly() string { @@ -126,6 +134,10 @@ func (a AuditAction) Friendly() string { return "registered" case AuditActionRequestPasswordReset: return "password reset requested" + case AuditActionConnect: + return "connected" + case AuditActionOpen: + return "opened" default: return "unknown" } diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index 2131e7746d2d6..81c47d7d27216 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -29,6 +29,8 @@ We track the following resources: | Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| | TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| | User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| +| WorkspaceAgent
connect | |
FieldTracked
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idtrue
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
nametrue
operating_systemfalse
ready_atfalse
resource_idtrue
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| +| WorkspaceApp
open | |
FieldTracked
agent_idtrue
commandfalse
created_atfalse
display_nametrue
display_orderfalse
externalfalse
healthfalse
healthcheck_intervalfalse
healthcheck_thresholdfalse
healthcheck_urlfalse
hiddenfalse
iconfalse
idfalse
open_infalse
sharing_levelfalse
slugtrue
subdomainfalse
urlfalse
| | WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| | WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 7b2759e281f8e..3d3ea83874c79 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -554,6 +554,8 @@ | `logout` | | `register` | | `request_password_reset` | +| `connect` | +| `open` | ## codersdk.AuditDiff @@ -5358,6 +5360,8 @@ Git clone makes use of this by parsing the URL from: 'Username for "https://gith | `idp_sync_settings_organization` | | `idp_sync_settings_group` | | `idp_sync_settings_role` | +| `workspace_agent` | +| `workspace_app` | ## codersdk.Response diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index d43b2e224e374..96d90e5c02caa 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -27,6 +27,8 @@ var AuditActionMap = map[string][]codersdk.AuditAction{ "Group": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete}, "APIKey": {codersdk.AuditActionLogin, codersdk.AuditActionLogout, codersdk.AuditActionRegister, codersdk.AuditActionCreate, codersdk.AuditActionDelete}, "License": {codersdk.AuditActionCreate, codersdk.AuditActionDelete}, + "WorkspaceAgent": {codersdk.AuditActionConnect}, + "WorkspaceApp": {codersdk.AuditActionOpen}, } type Action string @@ -307,6 +309,59 @@ var auditableResourcesTypes = map[any]map[string]Action{ "field": ActionTrack, "mapping": ActionTrack, }, + &database.WorkspaceAgent{}: { + "id": ActionTrack, + "created_at": ActionIgnore, + "updated_at": ActionIgnore, + "name": ActionTrack, + "first_connected_at": ActionIgnore, + "last_connected_at": ActionIgnore, + "disconnected_at": ActionIgnore, + "resource_id": ActionTrack, + "auth_token": ActionIgnore, + "auth_instance_id": ActionIgnore, + "architecture": ActionIgnore, + "environment_variables": ActionIgnore, + "operating_system": ActionIgnore, + "instance_metadata": ActionIgnore, + "resource_metadata": ActionIgnore, + "directory": ActionIgnore, + "version": ActionIgnore, + "last_connected_replica_id": ActionIgnore, + "connection_timeout_seconds": ActionIgnore, + "troubleshooting_url": ActionIgnore, + "motd_file": ActionIgnore, + "lifecycle_state": ActionIgnore, + "expanded_directory": ActionIgnore, + "logs_length": ActionIgnore, + "logs_overflowed": ActionIgnore, + "started_at": ActionIgnore, + "ready_at": ActionIgnore, + "subsystems": ActionIgnore, + "display_apps": ActionIgnore, + "api_version": ActionIgnore, + "display_order": ActionIgnore, + }, + &database.WorkspaceApp{}: { + "id": ActionIgnore, + "created_at": ActionIgnore, + "agent_id": ActionTrack, + "display_name": ActionTrack, + "icon": ActionIgnore, + "command": ActionIgnore, + "url": ActionIgnore, + "healthcheck_url": ActionIgnore, + "healthcheck_interval": ActionIgnore, + "healthcheck_threshold": ActionIgnore, + "health": ActionIgnore, + "subdomain": ActionIgnore, + "sharing_level": ActionIgnore, + "slug": ActionTrack, + "external": ActionIgnore, + "display_order": ActionIgnore, + "hidden": ActionIgnore, + "open_in": ActionIgnore, + }, } // auditMap converts a map of struct pointers to a map of struct names as diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 09541c9767b89..6d4fb5982fc44 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -115,10 +115,12 @@ export interface AssignableRoles extends Role { // From codersdk/audit.go export type AuditAction = + | "connect" | "create" | "delete" | "login" | "logout" + | "open" | "register" | "request_password_reset" | "start" @@ -126,10 +128,12 @@ export type AuditAction = | "write"; export const AuditActions: AuditAction[] = [ + "connect", "create", "delete", "login", "logout", + "open", "register", "request_password_reset", "start", @@ -1997,6 +2001,8 @@ export type ResourceType = | "template_version" | "user" | "workspace" + | "workspace_agent" + | "workspace_app" | "workspace_build" | "workspace_proxy"; @@ -2021,6 +2027,8 @@ export const ResourceTypes: ResourceType[] = [ "template_version", "user", "workspace", + "workspace_agent", + "workspace_app", "workspace_build", "workspace_proxy", ]; From 78be514d8af9335b3b06424ab12ce73ab53b3f85 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Tue, 11 Feb 2025 12:12:43 +0000 Subject: [PATCH 2/6] fix migration --- ...n.sql => 000293_add_audit_types_for_connect_and_open.down.sql} | 0 ....up.sql => 000293_add_audit_types_for_connect_and_open.up.sql} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename coderd/database/migrations/{000291_add_audit_types_for_connect_and_open.down.sql => 000293_add_audit_types_for_connect_and_open.down.sql} (100%) rename coderd/database/migrations/{000291_add_audit_types_for_connect_and_open.up.sql => 000293_add_audit_types_for_connect_and_open.up.sql} (100%) diff --git a/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.down.sql b/coderd/database/migrations/000293_add_audit_types_for_connect_and_open.down.sql similarity index 100% rename from coderd/database/migrations/000291_add_audit_types_for_connect_and_open.down.sql rename to coderd/database/migrations/000293_add_audit_types_for_connect_and_open.down.sql diff --git a/coderd/database/migrations/000291_add_audit_types_for_connect_and_open.up.sql b/coderd/database/migrations/000293_add_audit_types_for_connect_and_open.up.sql similarity index 100% rename from coderd/database/migrations/000291_add_audit_types_for_connect_and_open.up.sql rename to coderd/database/migrations/000293_add_audit_types_for_connect_and_open.up.sql From 6adf82d57613e8372d8de43e019762b52f085e95 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 12 Feb 2025 14:21:01 +0000 Subject: [PATCH 3/6] add disconnect/close and support request_id filter --- coderd/apidoc/docs.go | 8 +++-- coderd/apidoc/swagger.json | 8 +++-- coderd/audit.go | 2 +- coderd/audit_test.go | 49 ++++++++++++++++++++++++--- coderd/database/dbmem/dbmem.go | 5 ++- coderd/database/dump.sql | 4 ++- coderd/database/modelqueries.go | 1 + coderd/database/models.go | 8 ++++- coderd/database/queries.sql.go | 12 +++++-- coderd/database/queries/auditlogs.sql | 6 ++++ coderd/searchquery/search.go | 1 + codersdk/audit.go | 7 ++++ docs/reference/api/schemas.md | 2 ++ enterprise/audit/table.go | 16 ++++----- 14 files changed, 107 insertions(+), 22 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index ff370b3ae1752..6c14832131832 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -10100,7 +10100,9 @@ const docTemplate = `{ "register", "request_password_reset", "connect", - "open" + "disconnect", + "open", + "close" ], "x-enum-varnames": [ "AuditActionCreate", @@ -10113,7 +10115,9 @@ const docTemplate = `{ "AuditActionRegister", "AuditActionRequestPasswordReset", "AuditActionConnect", - "AuditActionOpen" + "AuditActionDisconnect", + "AuditActionOpen", + "AuditActionClose" ] }, "codersdk.AuditDiff": { diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index 82565a6087145..ba1737603b208 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -8964,7 +8964,9 @@ "register", "request_password_reset", "connect", - "open" + "disconnect", + "open", + "close" ], "x-enum-varnames": [ "AuditActionCreate", @@ -8977,7 +8979,9 @@ "AuditActionRegister", "AuditActionRequestPasswordReset", "AuditActionConnect", - "AuditActionOpen" + "AuditActionDisconnect", + "AuditActionOpen", + "AuditActionClose" ] }, "codersdk.AuditDiff": { diff --git a/coderd/audit.go b/coderd/audit.go index f764094782a2f..72be70754c2ea 100644 --- a/coderd/audit.go +++ b/coderd/audit.go @@ -159,7 +159,7 @@ func (api *API) generateFakeAuditLog(rw http.ResponseWriter, r *http.Request) { Diff: diff, StatusCode: http.StatusOK, AdditionalFields: params.AdditionalFields, - RequestID: uuid.Nil, // no request ID to attach this to + RequestID: params.RequestID, ResourceIcon: "", OrganizationID: params.OrganizationID, }) diff --git a/coderd/audit_test.go b/coderd/audit_test.go index 4caed73460e57..f610f2d86009d 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -286,23 +286,44 @@ func TestAuditLogsFilter(t *testing.T) { t.Logf("Resource: %#v", resource) } - // Create one log with "Connect" + // Create one log with "Connect" and "Disconect". + connectRequestID := uuid.New() err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ Action: codersdk.AuditActionConnect, + RequestID: connectRequestID, ResourceType: codersdk.ResourceTypeWorkspaceAgent, ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) - // Create one log with "Open" + err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + Action: codersdk.AuditActionDisconnect, + RequestID: connectRequestID, + ResourceType: codersdk.ResourceTypeWorkspaceAgent, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, + Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 + }) + require.NoError(t, err) + + // Create one log with "Open" and "Close". + openRequestID := uuid.New() err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ Action: codersdk.AuditActionOpen, + RequestID: openRequestID, ResourceType: codersdk.ResourceTypeWorkspaceApp, ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) + err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ + Action: codersdk.AuditActionClose, + RequestID: openRequestID, + ResourceType: codersdk.ResourceTypeWorkspaceApp, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, + Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 + }) + require.NoError(t, err) // Test cases testCases := []struct { @@ -334,12 +355,12 @@ func TestAuditLogsFilter(t *testing.T) { { Name: "FilterByEmail", SearchQuery: "email:" + coderdtest.FirstUserParams.Email, - ExpectedResult: 7, + ExpectedResult: 9, }, { Name: "FilterByUsername", SearchQuery: "username:" + coderdtest.FirstUserParams.Username, - ExpectedResult: 7, + ExpectedResult: 9, }, { Name: "FilterByResourceID", @@ -396,11 +417,31 @@ func TestAuditLogsFilter(t *testing.T) { SearchQuery: "resource_type:workspace_agent action:connect", ExpectedResult: 1, }, + { + Name: "FilterOnWorkspaceAgentDisconnect", + SearchQuery: "resource_type:workspace_agent action:disconnect", + ExpectedResult: 1, + }, + { + Name: "FilterOnWorkspaceAgentConnectionRequestID", + SearchQuery: "resource_type:workspace_agent request_id:" + connectRequestID.String(), + ExpectedResult: 2, + }, { Name: "FilterOnWorkspaceAppOpen", SearchQuery: "resource_type:workspace_app action:open", ExpectedResult: 1, }, + { + Name: "FilterOnWorkspaceAppClose", + SearchQuery: "resource_type:workspace_app action:close", + ExpectedResult: 1, + }, + { + Name: "FilterOnWorkspaceAppOpenRequestID", + SearchQuery: "resource_type:workspace_app request_id:" + openRequestID.String(), + ExpectedResult: 2, + }, } for _, testCase := range testCases { diff --git a/coderd/database/dbmem/dbmem.go b/coderd/database/dbmem/dbmem.go index 21c40233718ef..cb183bd7e0df1 100644 --- a/coderd/database/dbmem/dbmem.go +++ b/coderd/database/dbmem/dbmem.go @@ -12370,10 +12370,13 @@ func (q *FakeQuerier) GetAuthorizedAuditLogsOffset(ctx context.Context, arg data arg.OffsetOpt-- continue } + if arg.RequestID != uuid.Nil && arg.RequestID != alog.RequestID { + continue + } if arg.OrganizationID != uuid.Nil && arg.OrganizationID != alog.OrganizationID { continue } - if arg.Action != "" && !strings.Contains(string(alog.Action), arg.Action) { + if arg.Action != "" && string(alog.Action) != arg.Action { continue } if arg.ResourceType != "" && !strings.Contains(string(alog.ResourceType), arg.ResourceType) { diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index abe52e57fbd08..44bf68a36eb40 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -27,7 +27,9 @@ CREATE TYPE audit_action AS ENUM ( 'register', 'request_password_reset', 'connect', - 'open' + 'disconnect', + 'open', + 'close' ); CREATE TYPE automatic_updates AS ENUM ( diff --git a/coderd/database/modelqueries.go b/coderd/database/modelqueries.go index 78f6285e3c11a..4c323fd91c1de 100644 --- a/coderd/database/modelqueries.go +++ b/coderd/database/modelqueries.go @@ -467,6 +467,7 @@ func (q *sqlQuerier) GetAuthorizedAuditLogsOffset(ctx context.Context, arg GetAu arg.DateFrom, arg.DateTo, arg.BuildReason, + arg.RequestID, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/models.go b/coderd/database/models.go index 38ec09f65610b..9ddcba7897699 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -148,7 +148,9 @@ const ( AuditActionRegister AuditAction = "register" AuditActionRequestPasswordReset AuditAction = "request_password_reset" AuditActionConnect AuditAction = "connect" + AuditActionDisconnect AuditAction = "disconnect" AuditActionOpen AuditAction = "open" + AuditActionClose AuditAction = "close" ) func (e *AuditAction) Scan(src interface{}) error { @@ -198,7 +200,9 @@ func (e AuditAction) Valid() bool { AuditActionRegister, AuditActionRequestPasswordReset, AuditActionConnect, - AuditActionOpen: + AuditActionDisconnect, + AuditActionOpen, + AuditActionClose: return true } return false @@ -216,7 +220,9 @@ func AllAuditActionValues() []AuditAction { AuditActionRegister, AuditActionRequestPasswordReset, AuditActionConnect, + AuditActionDisconnect, AuditActionOpen, + AuditActionClose, } } diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 2d7fe83296deb..d472566849b2b 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -558,6 +558,12 @@ WHERE workspace_builds.reason::text = $11 ELSE true END + -- Filter request_id + AND CASE + WHEN $12 :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + audit_logs.request_id = $12 + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedAuditLogsOffset -- @authorize_filter @@ -567,9 +573,9 @@ LIMIT -- a limit of 0 means "no limit". The audit log table is unbounded -- in size, and is expected to be quite large. Implement a default -- limit of 100 to prevent accidental excessively large queries. - COALESCE(NULLIF($13 :: int, 0), 100) + COALESCE(NULLIF($14 :: int, 0), 100) OFFSET - $12 + $13 ` type GetAuditLogsOffsetParams struct { @@ -584,6 +590,7 @@ type GetAuditLogsOffsetParams struct { DateFrom time.Time `db:"date_from" json:"date_from"` DateTo time.Time `db:"date_to" json:"date_to"` BuildReason string `db:"build_reason" json:"build_reason"` + RequestID uuid.UUID `db:"request_id" json:"request_id"` OffsetOpt int32 `db:"offset_opt" json:"offset_opt"` LimitOpt int32 `db:"limit_opt" json:"limit_opt"` } @@ -624,6 +631,7 @@ func (q *sqlQuerier) GetAuditLogsOffset(ctx context.Context, arg GetAuditLogsOff arg.DateFrom, arg.DateTo, arg.BuildReason, + arg.RequestID, arg.OffsetOpt, arg.LimitOpt, ) diff --git a/coderd/database/queries/auditlogs.sql b/coderd/database/queries/auditlogs.sql index 115bdcd4c8f6f..52efc40c73738 100644 --- a/coderd/database/queries/auditlogs.sql +++ b/coderd/database/queries/auditlogs.sql @@ -117,6 +117,12 @@ WHERE workspace_builds.reason::text = @build_reason ELSE true END + -- Filter request_id + AND CASE + WHEN @request_id :: uuid != '00000000-0000-0000-0000-000000000000'::uuid THEN + audit_logs.request_id = @request_id + ELSE true + END -- Authorize Filter clause will be injected below in GetAuthorizedAuditLogsOffset -- @authorize_filter diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index a4fe5d4775d6c..16d89a1586b40 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -33,6 +33,7 @@ func AuditLogs(ctx context.Context, db database.Store, query string) (database.G const dateLayout = "2006-01-02" parser := httpapi.NewQueryParamParser() filter := database.GetAuditLogsOffsetParams{ + RequestID: parser.UUID(values, uuid.Nil, "request_id"), ResourceID: parser.UUID(values, uuid.Nil, "resource_id"), ResourceTarget: parser.String(values, "", "resource_target"), Username: parser.String(values, "", "username"), diff --git a/codersdk/audit.go b/codersdk/audit.go index 66b43d67bafe9..1df5bd2d10e2c 100644 --- a/codersdk/audit.go +++ b/codersdk/audit.go @@ -111,7 +111,9 @@ const ( AuditActionRegister AuditAction = "register" AuditActionRequestPasswordReset AuditAction = "request_password_reset" AuditActionConnect AuditAction = "connect" + AuditActionDisconnect AuditAction = "disconnect" AuditActionOpen AuditAction = "open" + AuditActionClose AuditAction = "close" ) func (a AuditAction) Friendly() string { @@ -136,8 +138,12 @@ func (a AuditAction) Friendly() string { return "password reset requested" case AuditActionConnect: return "connected" + case AuditActionDisconnect: + return "disconnected" case AuditActionOpen: return "opened" + case AuditActionClose: + return "closed" default: return "unknown" } @@ -196,6 +202,7 @@ type CreateTestAuditLogRequest struct { Time time.Time `json:"time,omitempty" format:"date-time"` BuildReason BuildReason `json:"build_reason,omitempty" enums:"autostart,autostop,initiator"` OrganizationID uuid.UUID `json:"organization_id,omitempty" format:"uuid"` + RequestID uuid.UUID `json:"request_id,omitempty" format:"uuid"` } // AuditLogs retrieves audit logs from the given page. diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 3d3ea83874c79..76d468ee91082 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -555,7 +555,9 @@ | `register` | | `request_password_reset` | | `connect` | +| `disconnect` | | `open` | +| `close` | ## codersdk.AuditDiff diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 96d90e5c02caa..b9367a6038e85 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -27,8 +27,8 @@ var AuditActionMap = map[string][]codersdk.AuditAction{ "Group": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete}, "APIKey": {codersdk.AuditActionLogin, codersdk.AuditActionLogout, codersdk.AuditActionRegister, codersdk.AuditActionCreate, codersdk.AuditActionDelete}, "License": {codersdk.AuditActionCreate, codersdk.AuditActionDelete}, - "WorkspaceAgent": {codersdk.AuditActionConnect}, - "WorkspaceApp": {codersdk.AuditActionOpen}, + "WorkspaceAgent": {codersdk.AuditActionConnect, codersdk.AuditActionDisconnect}, + "WorkspaceApp": {codersdk.AuditActionOpen, codersdk.AuditActionClose}, } type Action string @@ -310,14 +310,14 @@ var auditableResourcesTypes = map[any]map[string]Action{ "mapping": ActionTrack, }, &database.WorkspaceAgent{}: { - "id": ActionTrack, + "id": ActionIgnore, "created_at": ActionIgnore, "updated_at": ActionIgnore, - "name": ActionTrack, + "name": ActionIgnore, "first_connected_at": ActionIgnore, "last_connected_at": ActionIgnore, "disconnected_at": ActionIgnore, - "resource_id": ActionTrack, + "resource_id": ActionIgnore, "auth_token": ActionIgnore, "auth_instance_id": ActionIgnore, "architecture": ActionIgnore, @@ -345,8 +345,8 @@ var auditableResourcesTypes = map[any]map[string]Action{ &database.WorkspaceApp{}: { "id": ActionIgnore, "created_at": ActionIgnore, - "agent_id": ActionTrack, - "display_name": ActionTrack, + "agent_id": ActionIgnore, + "display_name": ActionIgnore, "icon": ActionIgnore, "command": ActionIgnore, "url": ActionIgnore, @@ -356,7 +356,7 @@ var auditableResourcesTypes = map[any]map[string]Action{ "health": ActionIgnore, "subdomain": ActionIgnore, "sharing_level": ActionIgnore, - "slug": ActionTrack, + "slug": ActionIgnore, "external": ActionIgnore, "display_order": ActionIgnore, "hidden": ActionIgnore, From a790c5ea6bba21f3964dbd7867165957c8462e8a Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 12 Feb 2025 14:27:12 +0000 Subject: [PATCH 4/6] cleanup --- coderd/apidoc/docs.go | 4 ++++ coderd/apidoc/swagger.json | 4 ++++ coderd/audit_test.go | 4 ---- docs/admin/security/audit-logs.md | 4 ++-- docs/reference/api/schemas.md | 2 ++ site/src/api/typesGenerated.ts | 5 +++++ 6 files changed, 17 insertions(+), 6 deletions(-) diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go index 6c14832131832..7dcfdbe982de1 100644 --- a/coderd/apidoc/docs.go +++ b/coderd/apidoc/docs.go @@ -10778,6 +10778,10 @@ const docTemplate = `{ "type": "string", "format": "uuid" }, + "request_id": { + "type": "string", + "format": "uuid" + }, "resource_id": { "type": "string", "format": "uuid" diff --git a/coderd/apidoc/swagger.json b/coderd/apidoc/swagger.json index ba1737603b208..0dfaf26184cb1 100644 --- a/coderd/apidoc/swagger.json +++ b/coderd/apidoc/swagger.json @@ -9591,6 +9591,10 @@ "type": "string", "format": "uuid" }, + "request_id": { + "type": "string", + "format": "uuid" + }, "resource_id": { "type": "string", "format": "uuid" diff --git a/coderd/audit_test.go b/coderd/audit_test.go index f610f2d86009d..ec54aab96da33 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -282,10 +282,6 @@ func TestAuditLogsFilter(t *testing.T) { }) require.NoError(t, err) - for _, resource := range workspace.LatestBuild.Resources { - t.Logf("Resource: %#v", resource) - } - // Create one log with "Connect" and "Disconect". connectRequestID := uuid.New() err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index 81c47d7d27216..5c6a6e6a802a1 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -29,8 +29,8 @@ We track the following resources: | Template
write, delete | |
FieldTracked
active_version_idtrue
activity_bumptrue
allow_user_autostarttrue
allow_user_autostoptrue
allow_user_cancel_workspace_jobstrue
autostart_block_days_of_weektrue
autostop_requirement_days_of_weektrue
autostop_requirement_weekstrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
default_ttltrue
deletedfalse
deprecatedtrue
descriptiontrue
display_nametrue
failure_ttltrue
group_acltrue
icontrue
idtrue
max_port_sharing_leveltrue
nametrue
organization_display_namefalse
organization_iconfalse
organization_idfalse
organization_namefalse
provisionertrue
require_active_versiontrue
time_til_dormanttrue
time_til_dormant_autodeletetrue
updated_atfalse
user_acltrue
| | TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_usernamefalse
external_auth_providersfalse
idtrue
job_idfalse
messagefalse
nametrue
organization_idfalse
readmetrue
source_example_idfalse
template_idtrue
updated_atfalse
| | User
create, write, delete | |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
github_com_user_idfalse
hashed_one_time_passcodefalse
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
theme_preferencefalse
updated_atfalse
usernametrue
| -| WorkspaceAgent
connect | |
FieldTracked
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idtrue
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
nametrue
operating_systemfalse
ready_atfalse
resource_idtrue
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| -| WorkspaceApp
open | |
FieldTracked
agent_idtrue
commandfalse
created_atfalse
display_nametrue
display_orderfalse
externalfalse
healthfalse
healthcheck_intervalfalse
healthcheck_thresholdfalse
healthcheck_urlfalse
hiddenfalse
iconfalse
idfalse
open_infalse
sharing_levelfalse
slugtrue
subdomainfalse
urlfalse
| +| WorkspaceAgent
connect, disconnect | |
FieldTracked
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
directoryfalse
disconnected_atfalse
display_appsfalse
display_orderfalse
environment_variablesfalse
expanded_directoryfalse
first_connected_atfalse
idfalse
instance_metadatafalse
last_connected_atfalse
last_connected_replica_idfalse
lifecycle_statefalse
logs_lengthfalse
logs_overflowedfalse
motd_filefalse
namefalse
operating_systemfalse
ready_atfalse
resource_idfalse
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| +| WorkspaceApp
open, close | |
FieldTracked
agent_idfalse
commandfalse
created_atfalse
display_namefalse
display_orderfalse
externalfalse
healthfalse
healthcheck_intervalfalse
healthcheck_thresholdfalse
healthcheck_urlfalse
hiddenfalse
iconfalse
idfalse
open_infalse
sharing_levelfalse
slugfalse
subdomainfalse
urlfalse
| | WorkspaceBuild
start, stop | |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_usernamefalse
initiator_idfalse
job_idfalse
max_deadlinefalse
provisioner_statefalse
reasonfalse
template_version_idtrue
template_version_preset_idfalse
transitionfalse
updated_atfalse
workspace_idfalse
| | WorkspaceProxy
| |
FieldTracked
created_attrue
deletedfalse
derp_enabledtrue
derp_onlytrue
display_nametrue
icontrue
idtrue
nametrue
region_idtrue
token_hashed_secrettrue
updated_atfalse
urltrue
versiontrue
wildcard_hostnametrue
| | WorkspaceTable
| |
FieldTracked
automatic_updatestrue
autostart_scheduletrue
created_atfalse
deletedfalse
deleting_attrue
dormant_attrue
favoritetrue
idtrue
last_used_atfalse
nametrue
next_start_attrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md index 76d468ee91082..f892c27e00d55 100644 --- a/docs/reference/api/schemas.md +++ b/docs/reference/api/schemas.md @@ -1318,6 +1318,7 @@ This is required on creation to enable a user-flow of validating a template work ], "build_reason": "autostart", "organization_id": "7c60d51f-b44e-4682-87d6-449835ea4de6", + "request_id": "266ea41d-adf5-480b-af50-15b940c2b846", "resource_id": "4d5215ed-38bb-48ed-879a-fdb9ca58522f", "resource_type": "template", "time": "2019-08-24T14:15:22Z" @@ -1332,6 +1333,7 @@ This is required on creation to enable a user-flow of validating a template work | `additional_fields` | array of integer | false | | | | `build_reason` | [codersdk.BuildReason](#codersdkbuildreason) | false | | | | `organization_id` | string | false | | | +| `request_id` | string | false | | | | `resource_id` | string | false | | | | `resource_type` | [codersdk.ResourceType](#codersdkresourcetype) | false | | | | `time` | string | false | | | diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index 6d4fb5982fc44..012214a5124ac 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -115,9 +115,11 @@ export interface AssignableRoles extends Role { // From codersdk/audit.go export type AuditAction = + | "close" | "connect" | "create" | "delete" + | "disconnect" | "login" | "logout" | "open" @@ -128,9 +130,11 @@ export type AuditAction = | "write"; export const AuditActions: AuditAction[] = [ + "close", "connect", "create", "delete", + "disconnect", "login", "logout", "open", @@ -409,6 +413,7 @@ export interface CreateTestAuditLogRequest { readonly time?: string; readonly build_reason?: BuildReason; readonly organization_id?: string; + readonly request_id?: string; } // From codersdk/apikey.go From 469e2cda01bc4d085e5b9d76d505d1c4c828a3b3 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Wed, 12 Feb 2025 14:53:03 +0000 Subject: [PATCH 5/6] add orgid to test data --- coderd/audit_test.go | 99 +++++++++++++++++++++++++------------------- 1 file changed, 56 insertions(+), 43 deletions(-) diff --git a/coderd/audit_test.go b/coderd/audit_test.go index ec54aab96da33..18bcd78b38807 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -32,7 +32,8 @@ func TestAuditLogs(t *testing.T) { user := coderdtest.CreateFirstUser(t, client) err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - ResourceID: user.UserID, + ResourceID: user.UserID, + OrganizationID: user.OrganizationID, }) require.NoError(t, err) @@ -56,7 +57,8 @@ func TestAuditLogs(t *testing.T) { client2, user2 := coderdtest.CreateAnotherUser(t, client, user.OrganizationID, rbac.RoleOwner()) err := client2.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - ResourceID: user2.ID, + ResourceID: user2.ID, + OrganizationID: user.OrganizationID, }) require.NoError(t, err) @@ -125,6 +127,7 @@ func TestAuditLogs(t *testing.T) { ResourceType: codersdk.ResourceTypeWorkspaceBuild, ResourceID: workspace.LatestBuild.ID, AdditionalFields: wriBytes, + OrganizationID: user.OrganizationID, }) require.NoError(t, err) @@ -160,7 +163,8 @@ func TestAuditLogs(t *testing.T) { // Add an extra audit log in another organization err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - ResourceID: owner.UserID, + ResourceID: owner.UserID, + OrganizationID: uuid.New(), }) require.NoError(t, err) @@ -241,83 +245,92 @@ func TestAuditLogsFilter(t *testing.T) { // Create two logs with "Create" err := client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionCreate, - ResourceType: codersdk.ResourceTypeTemplate, - ResourceID: template.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionCreate, + ResourceType: codersdk.ResourceTypeTemplate, + ResourceID: template.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionCreate, - ResourceType: codersdk.ResourceTypeUser, - ResourceID: user.UserID, - Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionCreate, + ResourceType: codersdk.ResourceTypeUser, + ResourceID: user.UserID, + Time: time.Date(2022, 8, 16, 14, 30, 45, 100, time.UTC), // 2022-8-16 14:30:45 }) require.NoError(t, err) // Create one log with "Delete" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionDelete, - ResourceType: codersdk.ResourceTypeUser, - ResourceID: user.UserID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionDelete, + ResourceType: codersdk.ResourceTypeUser, + ResourceID: user.UserID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Start" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionStart, - ResourceType: codersdk.ResourceTypeWorkspaceBuild, - ResourceID: workspace.LatestBuild.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionStart, + ResourceType: codersdk.ResourceTypeWorkspaceBuild, + ResourceID: workspace.LatestBuild.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Stop" err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionStop, - ResourceType: codersdk.ResourceTypeWorkspaceBuild, - ResourceID: workspace.LatestBuild.ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionStop, + ResourceType: codersdk.ResourceTypeWorkspaceBuild, + ResourceID: workspace.LatestBuild.ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) // Create one log with "Connect" and "Disconect". connectRequestID := uuid.New() err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionConnect, - RequestID: connectRequestID, - ResourceType: codersdk.ResourceTypeWorkspaceAgent, - ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionConnect, + RequestID: connectRequestID, + ResourceType: codersdk.ResourceTypeWorkspaceAgent, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionDisconnect, - RequestID: connectRequestID, - ResourceType: codersdk.ResourceTypeWorkspaceAgent, - ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, - Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionDisconnect, + RequestID: connectRequestID, + ResourceType: codersdk.ResourceTypeWorkspaceAgent, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, + Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 }) require.NoError(t, err) // Create one log with "Open" and "Close". openRequestID := uuid.New() err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionOpen, - RequestID: openRequestID, - ResourceType: codersdk.ResourceTypeWorkspaceApp, - ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, - Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionOpen, + RequestID: openRequestID, + ResourceType: codersdk.ResourceTypeWorkspaceApp, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 }) require.NoError(t, err) err = client.CreateTestAuditLog(ctx, codersdk.CreateTestAuditLogRequest{ - Action: codersdk.AuditActionClose, - RequestID: openRequestID, - ResourceType: codersdk.ResourceTypeWorkspaceApp, - ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, - Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 + OrganizationID: user.OrganizationID, + Action: codersdk.AuditActionClose, + RequestID: openRequestID, + ResourceType: codersdk.ResourceTypeWorkspaceApp, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, + Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 }) require.NoError(t, err) From ba0fdcdcf1e400bcb6e276d08cfdd3d22f2efa15 Mon Sep 17 00:00:00 2001 From: Mathias Fredriksson Date: Mon, 17 Feb 2025 12:52:52 +0000 Subject: [PATCH 6/6] document query params in search.go --- coderd/searchquery/search.go | 14 ++++++++++++++ coderd/searchquery/search_test.go | 5 +++++ 2 files changed, 19 insertions(+) diff --git a/coderd/searchquery/search.go b/coderd/searchquery/search.go index 16d89a1586b40..849dd7f584947 100644 --- a/coderd/searchquery/search.go +++ b/coderd/searchquery/search.go @@ -19,6 +19,20 @@ import ( // AuditLogs requires the database to fetch an organization by name // to convert to organization uuid. +// +// Supported query parameters: +// +// - request_id: UUID (can be used to search for associated audits e.g. connect/disconnect or open/close) +// - resource_id: UUID +// - resource_target: string +// - username: string +// - email: string +// - date_from: string (date in format "2006-01-02") +// - date_to: string (date in format "2006-01-02") +// - organization: string (organization UUID or name) +// - resource_type: string (enum) +// - action: string (enum) +// - build_reason: string (enum) func AuditLogs(ctx context.Context, db database.Store, query string) (database.GetAuditLogsOffsetParams, []codersdk.ValidationError) { // Always lowercase for all searches. query = strings.ToLower(query) diff --git a/coderd/searchquery/search_test.go b/coderd/searchquery/search_test.go index 91d285afbd8ec..0a8e08e3d45fe 100644 --- a/coderd/searchquery/search_test.go +++ b/coderd/searchquery/search_test.go @@ -344,6 +344,11 @@ func TestSearchAudit(t *testing.T) { ResourceTarget: "foo", }, }, + { + Name: "RequestID", + Query: "request_id:foo", + ExpectedErrorContains: "valid uuid", + }, } for _, c := range testCases {