diff --git a/coderd/apidoc/docs.go b/coderd/apidoc/docs.go
index 2e48634c7de13..7dcfdbe982de1 100644
--- a/coderd/apidoc/docs.go
+++ b/coderd/apidoc/docs.go
@@ -10098,7 +10098,11 @@ const docTemplate = `{
"login",
"logout",
"register",
- "request_password_reset"
+ "request_password_reset",
+ "connect",
+ "disconnect",
+ "open",
+ "close"
],
"x-enum-varnames": [
"AuditActionCreate",
@@ -10109,7 +10113,11 @@ const docTemplate = `{
"AuditActionLogin",
"AuditActionLogout",
"AuditActionRegister",
- "AuditActionRequestPasswordReset"
+ "AuditActionRequestPasswordReset",
+ "AuditActionConnect",
+ "AuditActionDisconnect",
+ "AuditActionOpen",
+ "AuditActionClose"
]
},
"codersdk.AuditDiff": {
@@ -10770,6 +10778,10 @@ const docTemplate = `{
"type": "string",
"format": "uuid"
},
+ "request_id": {
+ "type": "string",
+ "format": "uuid"
+ },
"resource_id": {
"type": "string",
"format": "uuid"
@@ -13864,7 +13876,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 +13902,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..0dfaf26184cb1 100644
--- a/coderd/apidoc/swagger.json
+++ b/coderd/apidoc/swagger.json
@@ -8962,7 +8962,11 @@
"login",
"logout",
"register",
- "request_password_reset"
+ "request_password_reset",
+ "connect",
+ "disconnect",
+ "open",
+ "close"
],
"x-enum-varnames": [
"AuditActionCreate",
@@ -8973,7 +8977,11 @@
"AuditActionLogin",
"AuditActionLogout",
"AuditActionRegister",
- "AuditActionRequestPasswordReset"
+ "AuditActionRequestPasswordReset",
+ "AuditActionConnect",
+ "AuditActionDisconnect",
+ "AuditActionOpen",
+ "AuditActionClose"
]
},
"codersdk.AuditDiff": {
@@ -9583,6 +9591,10 @@
"type": "string",
"format": "uuid"
},
+ "request_id": {
+ "type": "string",
+ "format": "uuid"
+ },
"resource_id": {
"type": "string",
"format": "uuid"
@@ -12549,7 +12561,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 +12587,9 @@
"ResourceTypeNotificationTemplate",
"ResourceTypeIdpSyncSettingsOrganization",
"ResourceTypeIdpSyncSettingsGroup",
- "ResourceTypeIdpSyncSettingsRole"
+ "ResourceTypeIdpSyncSettingsRole",
+ "ResourceTypeWorkspaceAgent",
+ "ResourceTypeWorkspaceApp"
]
},
"codersdk.Response": {
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/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..18bcd78b38807 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) {
@@ -30,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)
@@ -54,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)
@@ -123,6 +127,7 @@ func TestAuditLogs(t *testing.T) {
ResourceType: codersdk.ResourceTypeWorkspaceBuild,
ResourceID: workspace.LatestBuild.ID,
AdditionalFields: wriBytes,
+ OrganizationID: user.OrganizationID,
})
require.NoError(t, err)
@@ -158,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)
@@ -229,53 +235,102 @@ 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{
- 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{
+ 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{
+ 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{
+ 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{
+ 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)
@@ -309,12 +364,12 @@ func TestAuditLogsFilter(t *testing.T) {
{
Name: "FilterByEmail",
SearchQuery: "email:" + coderdtest.FirstUserParams.Email,
- ExpectedResult: 5,
+ ExpectedResult: 9,
},
{
Name: "FilterByUsername",
SearchQuery: "username:" + coderdtest.FirstUserParams.Username,
- ExpectedResult: 5,
+ ExpectedResult: 9,
},
{
Name: "FilterByResourceID",
@@ -366,6 +421,36 @@ 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: "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 {
@@ -387,3 +472,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/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 20e7d14b57d01..44bf68a36eb40 100644
--- a/coderd/database/dump.sql
+++ b/coderd/database/dump.sql
@@ -25,7 +25,11 @@ CREATE TYPE audit_action AS ENUM (
'login',
'logout',
'register',
- 'request_password_reset'
+ 'request_password_reset',
+ 'connect',
+ 'disconnect',
+ 'open',
+ 'close'
);
CREATE TYPE automatic_updates AS ENUM (
@@ -201,7 +205,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/000293_add_audit_types_for_connect_and_open.down.sql b/coderd/database/migrations/000293_add_audit_types_for_connect_and_open.down.sql
new file mode 100644
index 0000000000000..35020b349fc4e
--- /dev/null
+++ b/coderd/database/migrations/000293_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/000293_add_audit_types_for_connect_and_open.up.sql b/coderd/database/migrations/000293_add_audit_types_for_connect_and_open.up.sql
new file mode 100644
index 0000000000000..b894a45eaf443
--- /dev/null
+++ b/coderd/database/migrations/000293_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/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 fc11e1f4f5ebe..9ddcba7897699 100644
--- a/coderd/database/models.go
+++ b/coderd/database/models.go
@@ -147,6 +147,10 @@ const (
AuditActionLogout AuditAction = "logout"
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 {
@@ -194,7 +198,11 @@ func (e AuditAction) Valid() bool {
AuditActionLogin,
AuditActionLogout,
AuditActionRegister,
- AuditActionRequestPasswordReset:
+ AuditActionRequestPasswordReset,
+ AuditActionConnect,
+ AuditActionDisconnect,
+ AuditActionOpen,
+ AuditActionClose:
return true
}
return false
@@ -211,6 +219,10 @@ func AllAuditActionValues() []AuditAction {
AuditActionLogout,
AuditActionRegister,
AuditActionRequestPasswordReset,
+ AuditActionConnect,
+ AuditActionDisconnect,
+ AuditActionOpen,
+ AuditActionClose,
}
}
@@ -1608,6 +1620,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 +1682,9 @@ func (e ResourceType) Valid() bool {
ResourceTypeNotificationTemplate,
ResourceTypeIdpSyncSettingsOrganization,
ResourceTypeIdpSyncSettingsGroup,
- ResourceTypeIdpSyncSettingsRole:
+ ResourceTypeIdpSyncSettingsRole,
+ ResourceTypeWorkspaceAgent,
+ ResourceTypeWorkspaceApp:
return true
}
return false
@@ -1698,6 +1714,8 @@ func AllResourceTypeValues() []ResourceType {
ResourceTypeIdpSyncSettingsOrganization,
ResourceTypeIdpSyncSettingsGroup,
ResourceTypeIdpSyncSettingsRole,
+ ResourceTypeWorkspaceAgent,
+ ResourceTypeWorkspaceApp,
}
}
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..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)
@@ -33,6 +47,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/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 {
diff --git a/codersdk/audit.go b/codersdk/audit.go
index 307eeb275b61c..1df5bd2d10e2c 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,10 @@ const (
AuditActionLogout AuditAction = "logout"
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 {
@@ -126,6 +136,14 @@ func (a AuditAction) Friendly() string {
return "registered"
case AuditActionRequestPasswordReset:
return "password reset requested"
+ case AuditActionConnect:
+ return "connected"
+ case AuditActionDisconnect:
+ return "disconnected"
+ case AuditActionOpen:
+ return "opened"
+ case AuditActionClose:
+ return "closed"
default:
return "unknown"
}
@@ -184,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/admin/security/audit-logs.md b/docs/admin/security/audit-logs.md
index 2131e7746d2d6..5c6a6e6a802a1 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 |
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_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 |
user_acl | true |
|
| TemplateVersion
create, write | Field | Tracked |
| archived | true |
created_at | false |
created_by | true |
created_by_avatar_url | false |
created_by_username | false |
external_auth_providers | 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 |
last_seen_at | false |
login_type | true |
name | true |
one_time_passcode_expires_at | true |
quiet_hours_schedule | true |
rbac_roles | true |
status | true |
theme_preference | false |
updated_at | false |
username | true |
|
+| WorkspaceAgent
connect, disconnect | Field | Tracked |
| api_version | false |
architecture | false |
auth_instance_id | false |
auth_token | false |
connection_timeout_seconds | false |
created_at | 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 |
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_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 |
| build_number | false |
created_at | false |
daily_cost | false |
deadline | false |
id | false |
initiator_by_avatar_url | 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 |
|
diff --git a/docs/reference/api/schemas.md b/docs/reference/api/schemas.md
index 7b2759e281f8e..f892c27e00d55 100644
--- a/docs/reference/api/schemas.md
+++ b/docs/reference/api/schemas.md
@@ -554,6 +554,10 @@
| `logout` |
| `register` |
| `request_password_reset` |
+| `connect` |
+| `disconnect` |
+| `open` |
+| `close` |
## codersdk.AuditDiff
@@ -1314,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"
@@ -1328,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 | | |
@@ -5358,6 +5364,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..b9367a6038e85 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, codersdk.AuditActionDisconnect},
+ "WorkspaceApp": {codersdk.AuditActionOpen, codersdk.AuditActionClose},
}
type Action string
@@ -307,6 +309,59 @@ 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,
+ },
+ &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_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..012214a5124ac 100644
--- a/site/src/api/typesGenerated.ts
+++ b/site/src/api/typesGenerated.ts
@@ -115,10 +115,14 @@ export interface AssignableRoles extends Role {
// From codersdk/audit.go
export type AuditAction =
+ | "close"
+ | "connect"
| "create"
| "delete"
+ | "disconnect"
| "login"
| "logout"
+ | "open"
| "register"
| "request_password_reset"
| "start"
@@ -126,10 +130,14 @@ export type AuditAction =
| "write";
export const AuditActions: AuditAction[] = [
+ "close",
+ "connect",
"create",
"delete",
+ "disconnect",
"login",
"logout",
+ "open",
"register",
"request_password_reset",
"start",
@@ -405,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
@@ -1997,6 +2006,8 @@ export type ResourceType =
| "template_version"
| "user"
| "workspace"
+ | "workspace_agent"
+ | "workspace_app"
| "workspace_build"
| "workspace_proxy";
@@ -2021,6 +2032,8 @@ export const ResourceTypes: ResourceType[] = [
"template_version",
"user",
"workspace",
+ "workspace_agent",
+ "workspace_app",
"workspace_build",
"workspace_proxy",
];