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 |
Field | Tracked |
| active_version_id | true |
activity_bump | true |
allow_user_autostart | true |
allow_user_autostop | true |
allow_user_cancel_workspace_jobs | true |
autostart_block_days_of_week | true |
autostop_requirement_days_of_week | true |
autostop_requirement_weeks | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_name | false |
created_by_username | false |
default_ttl | true |
deleted | false |
deprecated | true |
description | true |
display_name | true |
failure_ttl | true |
group_acl | true |
icon | true |
id | true |
max_port_sharing_level | true |
name | true |
organization_display_name | false |
organization_icon | false |
organization_id | false |
organization_name | false |
provisioner | true |
require_active_version | true |
time_til_dormant | true |
time_til_dormant_autodelete | true |
updated_at | false |
use_classic_parameter_flow | true |
user_acl | true |
|
| TemplateVersion
create, write | Field | Tracked |
| archived | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_name | false |
created_by_username | false |
external_auth_providers | false |
has_ai_task | false |
id | true |
job_id | false |
message | false |
name | true |
organization_id | false |
readme | true |
source_example_id | false |
template_id | true |
updated_at | false |
|
| User
create, write, delete | Field | Tracked |
| avatar_url | false |
created_at | false |
deleted | true |
email | true |
github_com_user_id | false |
hashed_one_time_passcode | false |
hashed_password | true |
id | true |
is_system | true |
last_seen_at | false |
login_type | true |
name | true |
one_time_passcode_expires_at | true |
quiet_hours_schedule | true |
rbac_roles | true |
status | true |
updated_at | false |
username | true |
|
-| WorkspaceAgent
connect, disconnect | Field | Tracked |
| api_key_scope | false |
api_version | false |
architecture | false |
auth_instance_id | false |
auth_token | false |
connection_timeout_seconds | false |
created_at | false |
deleted | false |
directory | false |
disconnected_at | false |
display_apps | false |
display_order | false |
environment_variables | false |
expanded_directory | false |
first_connected_at | false |
id | false |
instance_metadata | false |
last_connected_at | false |
last_connected_replica_id | false |
lifecycle_state | false |
logs_length | false |
logs_overflowed | false |
motd_file | false |
name | false |
operating_system | false |
parent_id | false |
ready_at | false |
resource_id | false |
resource_metadata | false |
started_at | false |
subsystems | false |
troubleshooting_url | false |
updated_at | false |
version | false |
|
-| WorkspaceApp
open, close | Field | Tracked |
| agent_id | false |
command | false |
created_at | false |
display_group | false |
display_name | false |
display_order | false |
external | false |
health | false |
healthcheck_interval | false |
healthcheck_threshold | false |
healthcheck_url | false |
hidden | false |
icon | false |
id | false |
open_in | false |
sharing_level | false |
slug | false |
subdomain | false |
url | false |
|
| WorkspaceBuild
start, stop | Field | Tracked |
| ai_task_sidebar_app_id | false |
build_number | false |
created_at | false |
daily_cost | false |
deadline | false |
has_ai_task | false |
id | false |
initiator_by_avatar_url | false |
initiator_by_name | false |
initiator_by_username | false |
initiator_id | false |
job_id | false |
max_deadline | false |
provisioner_state | false |
reason | false |
template_version_id | true |
template_version_preset_id | false |
transition | false |
updated_at | false |
workspace_id | false |
|
| WorkspaceProxy
| Field | Tracked |
| created_at | true |
deleted | false |
derp_enabled | true |
derp_only | true |
display_name | true |
icon | true |
id | true |
name | true |
region_id | true |
token_hashed_secret | true |
updated_at | false |
url | true |
version | true |
wildcard_hostname | true |
|
| WorkspaceTable
| Field | Tracked |
| automatic_updates | true |
autostart_schedule | true |
created_at | false |
deleted | false |
deleting_at | true |
dormant_at | true |
favorite | true |
id | true |
last_used_at | false |
name | true |
next_start_at | true |
organization_id | false |
owner_id | true |
template_id | true |
ttl | true |
updated_at | false |
|
@@ -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