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",
];
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy