diff --git a/coderd/audit/diff.go b/coderd/audit/diff.go index 56ac9f88cc..b8139bb63b 100644 --- a/coderd/audit/diff.go +++ b/coderd/audit/diff.go @@ -31,9 +31,7 @@ type Auditable interface { database.NotificationTemplate | idpsync.OrganizationSyncSettings | idpsync.GroupSyncSettings | - idpsync.RoleSyncSettings | - database.WorkspaceAgent | - database.WorkspaceApp + idpsync.RoleSyncSettings } // 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 ae6a57e6c2..a973bdb915 100644 --- a/coderd/audit/request.go +++ b/coderd/audit/request.go @@ -131,10 +131,6 @@ 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)) } @@ -197,10 +193,6 @@ 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)) } @@ -254,10 +246,6 @@ 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)) } @@ -314,10 +302,6 @@ 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 e6fa985038..13dbc9ccd8 100644 --- a/coderd/audit_test.go +++ b/coderd/audit_test.go @@ -15,6 +15,7 @@ import ( "github.com/coder/coder/v2/coderd/audit" "github.com/coder/coder/v2/coderd/coderdtest" "github.com/coder/coder/v2/coderd/database" + "github.com/coder/coder/v2/coderd/database/dbgen" "github.com/coder/coder/v2/coderd/rbac" "github.com/coder/coder/v2/codersdk" "github.com/coder/coder/v2/provisioner/echo" @@ -531,3 +532,112 @@ func completeWithAgentAndApp() *echo.Responses { }, } } + +// TestDeprecatedConnEvents tests the deprecated connection and disconnection +// events in the audit logs. These events are no longer created, but need to be +// returned by the API. +func TestDeprecatedConnEvents(t *testing.T) { + t.Parallel() + var ( + ctx = context.Background() + client, _, api = coderdtest.NewWithAPI(t, &coderdtest.Options{IncludeProvisionerDaemon: true}) + user = coderdtest.CreateFirstUser(t, client) + 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) + + type additionalFields struct { + audit.AdditionalFields + ConnectionType string `json:"connection_type"` + } + + sshFields := additionalFields{ + AdditionalFields: audit.AdditionalFields{ + WorkspaceName: workspace.Name, + BuildNumber: "999", + BuildReason: "initiator", + WorkspaceOwner: workspace.OwnerName, + WorkspaceID: workspace.ID, + }, + ConnectionType: "SSH", + } + + sshFieldsBytes, err := json.Marshal(sshFields) + require.NoError(t, err) + + appFields := audit.AdditionalFields{ + WorkspaceName: workspace.Name, + // Deliberately empty + BuildNumber: "", + BuildReason: "", + WorkspaceOwner: workspace.OwnerName, + WorkspaceID: workspace.ID, + } + + appFieldsBytes, err := json.Marshal(appFields) + require.NoError(t, err) + + dbgen.AuditLog(t, api.Database, database.AuditLog{ + OrganizationID: user.OrganizationID, + Action: database.AuditActionConnect, + ResourceType: database.ResourceTypeWorkspaceAgent, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, + ResourceTarget: workspace.LatestBuild.Resources[0].Agents[0].Name, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + AdditionalFields: sshFieldsBytes, + }) + + dbgen.AuditLog(t, api.Database, database.AuditLog{ + OrganizationID: user.OrganizationID, + Action: database.AuditActionDisconnect, + ResourceType: database.ResourceTypeWorkspaceAgent, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].ID, + ResourceTarget: workspace.LatestBuild.Resources[0].Agents[0].Name, + Time: time.Date(2022, 8, 15, 14, 35, 0o0, 100, time.UTC), // 2022-8-15 14:35:00 + AdditionalFields: sshFieldsBytes, + }) + + dbgen.AuditLog(t, api.Database, database.AuditLog{ + OrganizationID: user.OrganizationID, + UserID: user.UserID, + Action: database.AuditActionOpen, + ResourceType: database.ResourceTypeWorkspaceApp, + ResourceID: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].ID, + ResourceTarget: workspace.LatestBuild.Resources[0].Agents[0].Apps[0].Slug, + Time: time.Date(2022, 8, 15, 14, 30, 45, 100, time.UTC), // 2022-8-15 14:30:45 + AdditionalFields: appFieldsBytes, + }) + + connLog, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{ + SearchQuery: "action:connect", + }) + require.NoError(t, err) + require.Len(t, connLog.AuditLogs, 1) + var sshOutFields additionalFields + err = json.Unmarshal(connLog.AuditLogs[0].AdditionalFields, &sshOutFields) + require.NoError(t, err) + require.Equal(t, sshFields, sshOutFields) + + dcLog, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{ + SearchQuery: "action:disconnect", + }) + require.NoError(t, err) + require.Len(t, dcLog.AuditLogs, 1) + err = json.Unmarshal(dcLog.AuditLogs[0].AdditionalFields, &sshOutFields) + require.NoError(t, err) + require.Equal(t, sshFields, sshOutFields) + + openLog, err := client.AuditLogs(ctx, codersdk.AuditLogsRequest{ + SearchQuery: "action:open", + }) + require.NoError(t, err) + require.Len(t, openLog.AuditLogs, 1) + var appOutFields audit.AdditionalFields + err = json.Unmarshal(openLog.AuditLogs[0].AdditionalFields, &appOutFields) + require.NoError(t, err) + require.Equal(t, appFields, appOutFields) +} diff --git a/coderd/database/dbgen/dbgen.go b/coderd/database/dbgen/dbgen.go index 9720050a43..d5693afe98 100644 --- a/coderd/database/dbgen/dbgen.go +++ b/coderd/database/dbgen/dbgen.go @@ -65,7 +65,7 @@ func AuditLog(t testing.TB, db database.Store, seed database.AuditLog) database. Action: takeFirst(seed.Action, database.AuditActionCreate), Diff: takeFirstSlice(seed.Diff, []byte("{}")), StatusCode: takeFirst(seed.StatusCode, 200), - AdditionalFields: takeFirstSlice(seed.Diff, []byte("{}")), + AdditionalFields: takeFirstSlice(seed.AdditionalFields, []byte("{}")), RequestID: takeFirst(seed.RequestID, uuid.New()), ResourceIcon: takeFirst(seed.ResourceIcon, ""), }) diff --git a/docs/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md index af033d02df..4d66260fb2 100644 --- a/docs/admin/security/audit-logs.md +++ b/docs/admin/security/audit-logs.md @@ -30,8 +30,6 @@ 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_namefalse
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
use_classic_parameter_flowtrue
user_acltrue
| | TemplateVersion
create, write | |
FieldTracked
archivedtrue
created_atfalse
created_bytrue
created_by_avatar_urlfalse
created_by_namefalse
created_by_usernamefalse
external_auth_providersfalse
has_ai_taskfalse
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
is_systemtrue
last_seen_atfalse
login_typetrue
nametrue
one_time_passcode_expires_attrue
quiet_hours_scheduletrue
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| -| WorkspaceAgent
connect, disconnect | |
FieldTracked
api_key_scopefalse
api_versionfalse
architecturefalse
auth_instance_idfalse
auth_tokenfalse
connection_timeout_secondsfalse
created_atfalse
deletedfalse
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
parent_idfalse
ready_atfalse
resource_idfalse
resource_metadatafalse
started_atfalse
subsystemsfalse
troubleshooting_urlfalse
updated_atfalse
versionfalse
| -| WorkspaceApp
open, close | |
FieldTracked
agent_idfalse
commandfalse
created_atfalse
display_groupfalse
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
ai_task_sidebar_app_idfalse
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
has_ai_taskfalse
idfalse
initiator_by_avatar_urlfalse
initiator_by_namefalse
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
| @@ -91,16 +89,16 @@ log entry: "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", + "caller": "/home/coder/coder/enterprise/audit/backends/slog.go:38", + "func": "github.com/coder/coder/v2/enterprise/audit/backends.(*SlogExporter).ExportStruct", "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}", + "Ip": null, + "UserAgent": null, "ResourceType": "workspace_build", "ResourceID": "ca5647e0-ef50-4202-a246-717e04447380", "ResourceTarget": "", diff --git a/enterprise/audit/table.go b/enterprise/audit/table.go index 2a563946dc..6c1f907abf 100644 --- a/enterprise/audit/table.go +++ b/enterprise/audit/table.go @@ -27,8 +27,6 @@ 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, codersdk.AuditActionDisconnect}, - "WorkspaceApp": {codersdk.AuditActionOpen, codersdk.AuditActionClose}, } type Action string @@ -343,63 +341,6 @@ var auditableResourcesTypes = map[any]map[string]Action{ "field": ActionTrack, "mapping": ActionTrack, }, - &database.WorkspaceAgent{}: { - "id": ActionIgnore, - "created_at": ActionIgnore, - "updated_at": ActionIgnore, - "name": ActionIgnore, - "first_connected_at": ActionIgnore, - "last_connected_at": ActionIgnore, - "disconnected_at": ActionIgnore, - "resource_id": ActionIgnore, - "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, - "parent_id": ActionIgnore, - "api_key_scope": ActionIgnore, - "deleted": ActionIgnore, - }, - &database.WorkspaceApp{}: { - "id": ActionIgnore, - "created_at": ActionIgnore, - "agent_id": ActionIgnore, - "display_name": ActionIgnore, - "icon": ActionIgnore, - "command": ActionIgnore, - "url": ActionIgnore, - "healthcheck_url": ActionIgnore, - "healthcheck_interval": ActionIgnore, - "healthcheck_threshold": ActionIgnore, - "health": ActionIgnore, - "subdomain": ActionIgnore, - "sharing_level": ActionIgnore, - "slug": ActionIgnore, - "external": ActionIgnore, - "display_group": ActionIgnore, - "display_order": ActionIgnore, - "hidden": ActionIgnore, - "open_in": ActionIgnore, - }, } // auditMap converts a map of struct pointers to a map of struct names as