feat(coderd): add audit resource for tasks (#20301)

Updates coder/internal#976
This commit is contained in:
Mathias Fredriksson
2025-10-15 16:13:59 +00:00
committed by GitHub
parent 03d285db26
commit 408b09a1f2
14 changed files with 65 additions and 7 deletions
+4 -2
View File
@@ -17247,7 +17247,8 @@ const docTemplate = `{
"idp_sync_settings_group",
"idp_sync_settings_role",
"workspace_agent",
"workspace_app"
"workspace_app",
"task"
],
"x-enum-varnames": [
"ResourceTypeTemplate",
@@ -17274,7 +17275,8 @@ const docTemplate = `{
"ResourceTypeIdpSyncSettingsGroup",
"ResourceTypeIdpSyncSettingsRole",
"ResourceTypeWorkspaceAgent",
"ResourceTypeWorkspaceApp"
"ResourceTypeWorkspaceApp",
"ResourceTypeTask"
]
},
"codersdk.Response": {
+4 -2
View File
@@ -15759,7 +15759,8 @@
"idp_sync_settings_group",
"idp_sync_settings_role",
"workspace_agent",
"workspace_app"
"workspace_app",
"task"
],
"x-enum-varnames": [
"ResourceTypeTemplate",
@@ -15786,7 +15787,8 @@
"ResourceTypeIdpSyncSettingsGroup",
"ResourceTypeIdpSyncSettingsRole",
"ResourceTypeWorkspaceAgent",
"ResourceTypeWorkspaceApp"
"ResourceTypeWorkspaceApp",
"ResourceTypeTask"
]
},
"codersdk.Response": {
+19
View File
@@ -420,6 +420,14 @@ func (api *API) auditLogIsResourceDeleted(ctx context.Context, alog database.Get
api.Logger.Error(ctx, "unable to fetch oauth2 app secret", slog.Error(err))
}
return false
case database.ResourceTypeTask:
task, err := api.Database.GetTaskByID(ctx, alog.AuditLog.ResourceID)
if xerrors.Is(err, sql.ErrNoRows) {
return true
} else if err != nil {
api.Logger.Error(ctx, "unable to fetch task", slog.Error(err))
}
return task.DeletedAt.Valid && task.DeletedAt.Time.Before(time.Now())
default:
return false
}
@@ -496,6 +504,17 @@ func (api *API) auditLogResourceLink(ctx context.Context, alog database.GetAudit
}
return fmt.Sprintf("/deployment/oauth2-provider/apps/%s", secret.AppID)
case database.ResourceTypeTask:
task, err := api.Database.GetTaskByID(ctx, alog.AuditLog.ResourceID)
if err != nil {
return ""
}
workspace, err := api.Database.GetWorkspaceByID(ctx, task.WorkspaceID.UUID)
if err != nil {
return ""
}
return fmt.Sprintf("/tasks/%s/%s", workspace.OwnerName, task.Name)
default:
return ""
}
+2 -1
View File
@@ -31,7 +31,8 @@ type Auditable interface {
database.NotificationTemplate |
idpsync.OrganizationSyncSettings |
idpsync.GroupSyncSettings |
idpsync.RoleSyncSettings
idpsync.RoleSyncSettings |
database.TaskTable
}
// Map is a map of changed fields in an audited resource. It maps field names to
+8
View File
@@ -131,6 +131,8 @@ func ResourceTarget[T Auditable](tgt T) string {
return "Organization Group Sync"
case idpsync.RoleSyncSettings:
return "Organization Role Sync"
case database.TaskTable:
return typed.Name
default:
panic(fmt.Sprintf("unknown resource %T for ResourceTarget", tgt))
}
@@ -193,6 +195,8 @@ 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.TaskTable:
return typed.ID
default:
panic(fmt.Sprintf("unknown resource %T for ResourceID", tgt))
}
@@ -246,6 +250,8 @@ func ResourceType[T Auditable](tgt T) database.ResourceType {
return database.ResourceTypeIdpSyncSettingsRole
case idpsync.GroupSyncSettings:
return database.ResourceTypeIdpSyncSettingsGroup
case database.TaskTable:
return database.ResourceTypeTask
default:
panic(fmt.Sprintf("unknown resource %T for ResourceType", typed))
}
@@ -302,6 +308,8 @@ func ResourceRequiresOrgID[T Auditable]() bool {
return true
case idpsync.RoleSyncSettings:
return true
case database.TaskTable:
return true
default:
panic(fmt.Sprintf("unknown resource %T for ResourceRequiresOrgID", tgt))
}
+2 -1
View File
@@ -460,7 +460,8 @@ CREATE TYPE resource_type AS ENUM (
'idp_sync_settings_role',
'workspace_agent',
'workspace_app',
'prebuilds_settings'
'prebuilds_settings',
'task'
);
CREATE TYPE startup_script_behavior AS ENUM (
@@ -0,0 +1 @@
-- Nothing to do
@@ -0,0 +1 @@
ALTER TYPE resource_type ADD VALUE IF NOT EXISTS 'task';
+4 -1
View File
@@ -2670,6 +2670,7 @@ const (
ResourceTypeWorkspaceAgent ResourceType = "workspace_agent"
ResourceTypeWorkspaceApp ResourceType = "workspace_app"
ResourceTypePrebuildsSettings ResourceType = "prebuilds_settings"
ResourceTypeTask ResourceType = "task"
)
func (e *ResourceType) Scan(src interface{}) error {
@@ -2733,7 +2734,8 @@ func (e ResourceType) Valid() bool {
ResourceTypeIdpSyncSettingsRole,
ResourceTypeWorkspaceAgent,
ResourceTypeWorkspaceApp,
ResourceTypePrebuildsSettings:
ResourceTypePrebuildsSettings,
ResourceTypeTask:
return true
}
return false
@@ -2766,6 +2768,7 @@ func AllResourceTypeValues() []ResourceType {
ResourceTypeWorkspaceAgent,
ResourceTypeWorkspaceApp,
ResourceTypePrebuildsSettings,
ResourceTypeTask,
}
}
+3
View File
@@ -44,6 +44,7 @@ const (
// Deprecated: Workspace App connections are now included in the
// connection log.
ResourceTypeWorkspaceApp ResourceType = "workspace_app"
ResourceTypeTask ResourceType = "task"
)
func (r ResourceType) FriendlyString() string {
@@ -100,6 +101,8 @@ func (r ResourceType) FriendlyString() string {
return "workspace agent"
case ResourceTypeWorkspaceApp:
return "workspace app"
case ResourceTypeTask:
return "task"
default:
return "unknown"
}
+1
View File
@@ -32,6 +32,7 @@ We track the following resources:
| OrganizationSyncSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>assign_default</td><td>true</td></tr><tr><td>field</td><td>true</td></tr><tr><td>mapping</td><td>true</td></tr></tbody></table> |
| PrebuildsSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>id</td><td>false</td></tr><tr><td>reconciliation_paused</td><td>true</td></tr></tbody></table> |
| RoleSyncSettings<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>field</td><td>true</td></tr><tr><td>mapping</td><td>true</td></tr></tbody></table> |
| TaskTable<br><i></i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>created_at</td><td>false</td></tr><tr><td>deleted_at</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>owner_id</td><td>true</td></tr><tr><td>prompt</td><td>true</td></tr><tr><td>template_parameters</td><td>true</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>workspace_id</td><td>true</td></tr></tbody></table> |
| Template<br><i>write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>active_version_id</td><td>true</td></tr><tr><td>activity_bump</td><td>true</td></tr><tr><td>allow_user_autostart</td><td>true</td></tr><tr><td>allow_user_autostop</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</td><td>true</td></tr><tr><td>autostart_block_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_days_of_week</td><td>true</td></tr><tr><td>autostop_requirement_weeks</td><td>true</td></tr><tr><td>cors_behavior</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_name</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>deprecated</td><td>true</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</td><td>true</td></tr><tr><td>failure_ttl</td><td>true</td></tr><tr><td>group_acl</td><td>true</td></tr><tr><td>icon</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>max_port_sharing_level</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_display_name</td><td>false</td></tr><tr><td>organization_icon</td><td>false</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>organization_name</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>require_active_version</td><td>true</td></tr><tr><td>time_til_dormant</td><td>true</td></tr><tr><td>time_til_dormant_autodelete</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>use_classic_parameter_flow</td><td>true</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
| TemplateVersion<br><i>create, write</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>archived</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>created_by_avatar_url</td><td>false</td></tr><tr><td>created_by_name</td><td>false</td></tr><tr><td>created_by_username</td><td>false</td></tr><tr><td>external_auth_providers</td><td>false</td></tr><tr><td>has_ai_task</td><td>false</td></tr><tr><td>has_external_agent</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>message</td><td>false</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>readme</td><td>true</td></tr><tr><td>source_example_id</td><td>false</td></tr><tr><td>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
| User<br><i>create, write, delete</i> | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody> | <tr><td>avatar_url</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>true</td></tr><tr><td>email</td><td>true</td></tr><tr><td>github_com_user_id</td><td>false</td></tr><tr><td>hashed_one_time_passcode</td><td>false</td></tr><tr><td>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>is_system</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>one_time_passcode_expires_at</td><td>true</td></tr><tr><td>quiet_hours_schedule</td><td>true</td></tr><tr><td>rbac_roles</td><td>true</td></tr><tr><td>status</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>username</td><td>true</td></tr></tbody></table> |
+1
View File
@@ -7367,6 +7367,7 @@ Only certain features set these fields: - FeatureManagedAgentLimit|
| `idp_sync_settings_role` |
| `workspace_agent` |
| `workspace_app` |
| `task` |
## codersdk.Response
+13
View File
@@ -27,6 +27,7 @@ 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},
"Task": {codersdk.AuditActionCreate, codersdk.AuditActionWrite, codersdk.AuditActionDelete},
}
type Action string
@@ -347,6 +348,18 @@ var auditableResourcesTypes = map[any]map[string]Action{
"field": ActionTrack,
"mapping": ActionTrack,
},
&database.TaskTable{}: {
"id": ActionTrack,
"organization_id": ActionIgnore, // Never changes.
"owner_id": ActionTrack,
"name": ActionTrack,
"workspace_id": ActionTrack,
"template_version_id": ActionTrack,
"template_parameters": ActionTrack,
"prompt": ActionTrack,
"created_at": ActionIgnore, // Never changes.
"deleted_at": ActionIgnore, // Changes, but is implicit when a delete event is fired.
},
}
// auditMap converts a map of struct pointers to a map of struct names as
+2
View File
@@ -3060,6 +3060,7 @@ export type ResourceType =
| "organization"
| "organization_member"
| "prebuilds_settings"
| "task"
| "template"
| "template_version"
| "user"
@@ -3087,6 +3088,7 @@ export const ResourceTypes: ResourceType[] = [
"organization",
"organization_member",
"prebuilds_settings",
"task",
"template",
"template_version",
"user",