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:
Mathias Fredriksson
2025-02-17 13:02:30 +00:00
committed by GitHub
parent 42f6b716f2
commit b5329ae1cd
21 changed files with 411 additions and 43 deletions
+20 -4
View File
@@ -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": {
+20 -4
View File
@@ -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
View File
@@ -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,
})
+3 -1
View File
@@ -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
+16
View File
@@ -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
View File
@@ -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",
},
},
},
},
},
},
},
},
},
},
}
}
+4 -1
View File
@@ -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) {
+8 -2
View File
@@ -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';
+1
View File
@@ -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,
)
+20 -2
View File
@@ -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,
}
}
+10 -2
View File
@@ -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,
)
+6
View File
@@ -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
+15
View File
@@ -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"),
+5
View File
@@ -344,6 +344,11 @@ func TestSearchAudit(t *testing.T) {
ResourceTarget: "foo",
},
},
{
Name: "RequestID",
Query: "request_id:foo",
ExpectedErrorContains: "valid uuid",
},
}
for _, c := range testCases {