mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add workspace agent connect and app open audit types (#16493)
This commit adds new audit resource types for workspace agents and workspace apps, as well as connect/disconnect and open/close actions. The idea is that we will log new audit events for connecting to the agent via SSH/editor. Likewise, we will log openings of `coder_app`s. This change also introduces support for filtering by `request_id`. Updates #15139
This commit is contained in:
Generated
+20
-4
@@ -10151,7 +10151,11 @@ const docTemplate = `{
|
||||
"login",
|
||||
"logout",
|
||||
"register",
|
||||
"request_password_reset"
|
||||
"request_password_reset",
|
||||
"connect",
|
||||
"disconnect",
|
||||
"open",
|
||||
"close"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"AuditActionCreate",
|
||||
@@ -10162,7 +10166,11 @@ const docTemplate = `{
|
||||
"AuditActionLogin",
|
||||
"AuditActionLogout",
|
||||
"AuditActionRegister",
|
||||
"AuditActionRequestPasswordReset"
|
||||
"AuditActionRequestPasswordReset",
|
||||
"AuditActionConnect",
|
||||
"AuditActionDisconnect",
|
||||
"AuditActionOpen",
|
||||
"AuditActionClose"
|
||||
]
|
||||
},
|
||||
"codersdk.AuditDiff": {
|
||||
@@ -10823,6 +10831,10 @@ const docTemplate = `{
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"resource_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
@@ -13917,7 +13929,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",
|
||||
@@ -13941,7 +13955,9 @@ const docTemplate = `{
|
||||
"ResourceTypeNotificationTemplate",
|
||||
"ResourceTypeIdpSyncSettingsOrganization",
|
||||
"ResourceTypeIdpSyncSettingsGroup",
|
||||
"ResourceTypeIdpSyncSettingsRole"
|
||||
"ResourceTypeIdpSyncSettingsRole",
|
||||
"ResourceTypeWorkspaceAgent",
|
||||
"ResourceTypeWorkspaceApp"
|
||||
]
|
||||
},
|
||||
"codersdk.Response": {
|
||||
|
||||
Generated
+20
-4
@@ -9015,7 +9015,11 @@
|
||||
"login",
|
||||
"logout",
|
||||
"register",
|
||||
"request_password_reset"
|
||||
"request_password_reset",
|
||||
"connect",
|
||||
"disconnect",
|
||||
"open",
|
||||
"close"
|
||||
],
|
||||
"x-enum-varnames": [
|
||||
"AuditActionCreate",
|
||||
@@ -9026,7 +9030,11 @@
|
||||
"AuditActionLogin",
|
||||
"AuditActionLogout",
|
||||
"AuditActionRegister",
|
||||
"AuditActionRequestPasswordReset"
|
||||
"AuditActionRequestPasswordReset",
|
||||
"AuditActionConnect",
|
||||
"AuditActionDisconnect",
|
||||
"AuditActionOpen",
|
||||
"AuditActionClose"
|
||||
]
|
||||
},
|
||||
"codersdk.AuditDiff": {
|
||||
@@ -9636,6 +9644,10 @@
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"request_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"resource_id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
@@ -12602,7 +12614,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",
|
||||
@@ -12626,7 +12640,9 @@
|
||||
"ResourceTypeNotificationTemplate",
|
||||
"ResourceTypeIdpSyncSettingsOrganization",
|
||||
"ResourceTypeIdpSyncSettingsGroup",
|
||||
"ResourceTypeIdpSyncSettingsRole"
|
||||
"ResourceTypeIdpSyncSettingsRole",
|
||||
"ResourceTypeWorkspaceAgent",
|
||||
"ResourceTypeWorkspaceApp"
|
||||
]
|
||||
},
|
||||
"codersdk.Response": {
|
||||
|
||||
+1
-1
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
+171
-26
@@ -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",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12433,10 +12433,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) {
|
||||
|
||||
Generated
+8
-2
@@ -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 (
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
-- No-op, enum values can't be dropped.
|
||||
@@ -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';
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -344,6 +344,11 @@ func TestSearchAudit(t *testing.T) {
|
||||
ResourceTarget: "foo",
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "RequestID",
|
||||
Query: "request_id:foo",
|
||||
ExpectedErrorContains: "valid uuid",
|
||||
},
|
||||
}
|
||||
|
||||
for _, c := range testCases {
|
||||
|
||||
Reference in New Issue
Block a user