2022-04-25 18:57:59 +00:00
package audit
import (
2023-03-10 19:59:42 +00:00
"fmt"
2023-07-06 12:43:32 +00:00
"os"
2022-04-25 18:57:59 +00:00
"reflect"
2023-07-06 12:43:32 +00:00
"runtime"
2022-04-25 18:57:59 +00:00
2023-08-18 18:55:43 +00:00
"github.com/coder/coder/v2/coderd/database"
"github.com/coder/coder/v2/codersdk"
2022-04-25 18:57:59 +00:00
)
2023-01-27 16:50:21 +00:00
// This mapping creates a relationship between an Auditable Resource
// and the Audit Actions we track for that resource.
// It is important to maintain this mapping when adding a new Auditable Resource to the
// AuditableResources map (below) as our documentation - generated in scripts/auditdocgen/main.go -
// depends upon it.
var AuditActionMap = map [ string ] [ ] codersdk . AuditAction {
2023-02-14 21:34:13 +00:00
"GitSSHKey" : { codersdk . AuditActionCreate } ,
"Template" : { codersdk . AuditActionWrite , codersdk . AuditActionDelete } ,
"TemplateVersion" : { codersdk . AuditActionCreate , codersdk . AuditActionWrite } ,
"User" : { codersdk . AuditActionCreate , codersdk . AuditActionWrite , codersdk . AuditActionDelete } ,
"Workspace" : { codersdk . AuditActionCreate , codersdk . AuditActionWrite , codersdk . AuditActionDelete } ,
"WorkspaceBuild" : { codersdk . AuditActionStart , codersdk . AuditActionStop } ,
"Group" : { codersdk . AuditActionCreate , codersdk . AuditActionWrite , codersdk . AuditActionDelete } ,
2023-04-12 18:46:16 +00:00
"APIKey" : { codersdk . AuditActionLogin , codersdk . AuditActionLogout , codersdk . AuditActionRegister , codersdk . AuditActionCreate , codersdk . AuditActionDelete } ,
2023-02-14 21:34:13 +00:00
"License" : { codersdk . AuditActionCreate , codersdk . AuditActionDelete } ,
2023-01-27 16:50:21 +00:00
}
2022-04-25 18:57:59 +00:00
type Action string
const (
// ActionIgnore ignores diffing for the field.
ActionIgnore = "ignore"
// ActionTrack includes the value in the diff if the value changed.
ActionTrack = "track"
// ActionSecret includes a zero value of the same type if the value changed.
// It lets you indicate that a value changed, but without leaking its
// contents.
ActionSecret = "secret"
)
// Table is a map of struct names to a map of field names that indicate that
// field's AuditType.
type Table map [ string ] map [ string ] Action
// AuditableResources contains a definitive list of all auditable resources and
2023-03-10 19:59:42 +00:00
// which fields are auditable. All resource types must be valid audit.Auditable
// types.
var AuditableResources = auditMap ( auditableResourcesTypes )
var auditableResourcesTypes = map [ any ] map [ string ] Action {
2022-05-16 16:20:11 +00:00
& database . GitSSHKey { } : {
"user_id" : ActionTrack ,
"created_at" : ActionIgnore , // Never changes, but is implicit and not helpful in a diff.
"updated_at" : ActionIgnore , // Changes, but is implicit and not helpful in a diff.
"private_key" : ActionSecret , // We don't want to expose private keys in diffs.
"public_key" : ActionTrack , // Public keys are ok to expose in a diff.
} ,
& database . Template { } : {
2023-08-29 18:35:05 +00:00
"id" : ActionTrack ,
"created_at" : ActionIgnore , // Never changes, but is implicit and not helpful in a diff.
"updated_at" : ActionIgnore , // Changes, but is implicit and not helpful in a diff.
"organization_id" : ActionIgnore , /// Never changes.
"deleted" : ActionIgnore , // Changes, but is implicit when a delete event is fired.
"name" : ActionTrack ,
"display_name" : ActionTrack ,
"provisioner" : ActionTrack ,
"active_version_id" : ActionTrack ,
"description" : ActionTrack ,
"icon" : ActionTrack ,
"default_ttl" : ActionTrack ,
"max_ttl" : ActionTrack ,
2023-12-15 08:27:56 +00:00
"use_max_ttl" : ActionTrack ,
2023-10-13 16:57:18 +00:00
"autostart_block_days_of_week" : ActionTrack ,
2023-08-29 18:35:05 +00:00
"autostop_requirement_days_of_week" : ActionTrack ,
"autostop_requirement_weeks" : ActionTrack ,
"created_by" : ActionTrack ,
"created_by_username" : ActionIgnore ,
"created_by_avatar_url" : ActionIgnore ,
"group_acl" : ActionTrack ,
"user_acl" : ActionTrack ,
"allow_user_autostart" : ActionTrack ,
"allow_user_autostop" : ActionTrack ,
"allow_user_cancel_workspace_jobs" : ActionTrack ,
"failure_ttl" : ActionTrack ,
"time_til_dormant" : ActionTrack ,
"time_til_dormant_autodelete" : ActionTrack ,
2023-10-18 22:07:21 +00:00
"require_active_version" : ActionTrack ,
2023-11-20 19:16:18 +00:00
"deprecated" : ActionTrack ,
2024-02-13 14:31:20 +00:00
"max_port_sharing_level" : ActionTrack ,
2024-02-13 07:00:35 +00:00
"activity_bump" : ActionTrack ,
2022-05-16 16:20:11 +00:00
} ,
& database . TemplateVersion { } : {
2023-09-29 19:13:20 +00:00
"id" : ActionTrack ,
"template_id" : ActionTrack ,
"organization_id" : ActionIgnore , // Never changes.
"created_at" : ActionIgnore , // Never changes, but is implicit and not helpful in a diff.
"updated_at" : ActionIgnore , // Changes, but is implicit and not helpful in a diff.
"name" : ActionTrack ,
"message" : ActionIgnore , // Never changes after creation.
"readme" : ActionTrack ,
"job_id" : ActionIgnore , // Not helpful in a diff because jobs aren't tracked in audit logs.
"created_by" : ActionTrack ,
"external_auth_providers" : ActionIgnore , // Not helpful because this can only change when new versions are added.
"created_by_avatar_url" : ActionIgnore ,
"created_by_username" : ActionIgnore ,
2023-10-10 15:52:42 +00:00
"archived" : ActionTrack ,
2022-05-16 16:20:11 +00:00
} ,
2022-04-25 18:57:59 +00:00
& database . User { } : {
2023-07-20 13:35:41 +00:00
"id" : ActionTrack ,
"email" : ActionTrack ,
"username" : ActionTrack ,
"hashed_password" : ActionSecret , // Do not expose a users hashed password.
"created_at" : ActionIgnore , // Never changes.
"updated_at" : ActionIgnore , // Changes, but is implicit and not helpful in a diff.
"status" : ActionTrack ,
"rbac_roles" : ActionTrack ,
"login_type" : ActionTrack ,
"avatar_url" : ActionIgnore ,
"last_seen_at" : ActionIgnore ,
"deleted" : ActionTrack ,
"quiet_hours_schedule" : ActionTrack ,
2023-12-08 21:59:53 +00:00
"theme_preference" : ActionIgnore ,
2024-01-17 12:20:45 +00:00
"name" : ActionTrack ,
2022-04-25 18:57:59 +00:00
} ,
& database . Workspace { } : {
2022-05-16 16:20:11 +00:00
"id" : ActionTrack ,
2022-04-25 18:57:59 +00:00
"created_at" : ActionIgnore , // Never changes.
"updated_at" : ActionIgnore , // Changes, but is implicit and not helpful in a diff.
2022-05-16 16:20:11 +00:00
"owner_id" : ActionTrack ,
2022-10-21 14:34:24 +00:00
"organization_id" : ActionIgnore , // Never changes.
2022-05-16 16:20:11 +00:00
"template_id" : ActionTrack ,
2022-04-25 18:57:59 +00:00
"deleted" : ActionIgnore , // Changes, but is implicit when a delete event is fired.
2022-05-16 16:20:11 +00:00
"name" : ActionTrack ,
"autostart_schedule" : ActionTrack ,
2022-05-19 19:09:27 +00:00
"ttl" : ActionTrack ,
2022-09-02 00:08:51 +00:00
"last_used_at" : ActionIgnore ,
2023-08-24 18:25:54 +00:00
"dormant_at" : ActionTrack ,
2023-07-21 03:01:11 +00:00
"deleting_at" : ActionTrack ,
2023-10-06 09:27:12 +00:00
"automatic_updates" : ActionTrack ,
2024-01-24 13:39:19 +00:00
"favorite" : ActionTrack ,
2022-04-25 18:57:59 +00:00
} ,
2022-10-25 19:34:48 +00:00
& database . WorkspaceBuild { } : {
2023-07-25 13:14:38 +00:00
"id" : ActionIgnore ,
"created_at" : ActionIgnore ,
"updated_at" : ActionIgnore ,
"workspace_id" : ActionIgnore ,
"template_version_id" : ActionTrack ,
"build_number" : ActionIgnore ,
"transition" : ActionIgnore ,
"initiator_id" : ActionIgnore ,
"provisioner_state" : ActionIgnore ,
"job_id" : ActionIgnore ,
"deadline" : ActionIgnore ,
"reason" : ActionIgnore ,
"daily_cost" : ActionIgnore ,
"max_deadline" : ActionIgnore ,
"initiator_by_avatar_url" : ActionIgnore ,
"initiator_by_username" : ActionIgnore ,
2022-10-25 19:34:48 +00:00
} ,
2023-01-18 20:13:39 +00:00
& database . AuditableGroup { } : {
"id" : ActionTrack ,
"name" : ActionTrack ,
2023-08-02 15:53:06 +00:00
"display_name" : ActionTrack ,
2023-01-18 20:13:39 +00:00
"organization_id" : ActionIgnore , // Never changes.
"avatar_url" : ActionTrack ,
"quota_allowance" : ActionTrack ,
"members" : ActionTrack ,
2023-08-08 16:37:49 +00:00
"source" : ActionIgnore ,
2023-01-18 20:13:39 +00:00
} ,
2023-02-06 20:12:50 +00:00
& database . APIKey { } : {
"id" : ActionIgnore ,
"hashed_secret" : ActionIgnore ,
2023-03-17 17:41:44 +00:00
"user_id" : ActionTrack ,
"last_used" : ActionTrack ,
"expires_at" : ActionTrack ,
"created_at" : ActionTrack ,
2023-02-06 20:12:50 +00:00
"updated_at" : ActionIgnore ,
"login_type" : ActionIgnore ,
"lifetime_seconds" : ActionIgnore ,
"ip_address" : ActionIgnore ,
"scope" : ActionIgnore ,
2023-03-02 17:39:38 +00:00
"token_name" : ActionIgnore ,
2023-02-06 20:12:50 +00:00
} ,
2023-06-30 12:38:48 +00:00
& database . AuditOAuthConvertState { } : {
"created_at" : ActionTrack ,
"expires_at" : ActionTrack ,
"from_login_type" : ActionTrack ,
"to_login_type" : ActionTrack ,
"user_id" : ActionTrack ,
} ,
2023-11-28 17:15:17 +00:00
& database . HealthSettings { } : {
"id" : ActionIgnore ,
"dismissed_healthchecks" : ActionTrack ,
} ,
2023-02-14 21:34:13 +00:00
// TODO: track an ID here when the below ticket is completed:
// https://github.com/coder/coder/pull/6012
& database . License { } : {
"id" : ActionIgnore ,
"uploaded_at" : ActionTrack ,
"jwt" : ActionIgnore ,
"exp" : ActionTrack ,
"uuid" : ActionTrack ,
} ,
2023-04-04 20:07:29 +00:00
& database . WorkspaceProxy { } : {
2023-04-17 19:57:21 +00:00
"id" : ActionTrack ,
"name" : ActionTrack ,
"display_name" : ActionTrack ,
"icon" : ActionTrack ,
"url" : ActionTrack ,
"wildcard_hostname" : ActionTrack ,
"created_at" : ActionTrack ,
"updated_at" : ActionIgnore ,
"deleted" : ActionIgnore ,
"token_hashed_secret" : ActionSecret ,
2023-07-26 16:21:04 +00:00
"derp_enabled" : ActionTrack ,
2023-08-02 14:35:06 +00:00
"derp_only" : ActionTrack ,
2023-07-26 16:21:04 +00:00
"region_id" : ActionTrack ,
2023-11-21 11:21:25 +00:00
"version" : ActionTrack ,
2023-04-04 20:07:29 +00:00
} ,
2024-02-26 23:52:08 +00:00
& database . OAuth2ProviderApp { } : {
"id" : ActionIgnore ,
"created_at" : ActionIgnore ,
"updated_at" : ActionIgnore ,
"name" : ActionTrack ,
"icon" : ActionTrack ,
"callback_url" : ActionTrack ,
} ,
& database . OAuth2ProviderAppSecret { } : {
"id" : ActionIgnore ,
"created_at" : ActionIgnore ,
"last_used_at" : ActionIgnore ,
"hashed_secret" : ActionIgnore ,
"display_secret" : ActionIgnore ,
"app_id" : ActionIgnore ,
"secret_prefix" : ActionIgnore ,
} ,
2023-03-10 19:59:42 +00:00
}
2022-04-25 18:57:59 +00:00
// auditMap converts a map of struct pointers to a map of struct names as
// strings. It's a convenience wrapper so that structs can be passed in by value
// instead of manually typing struct names as strings.
func auditMap ( m map [ any ] map [ string ] Action ) Table {
out := make ( Table , len ( m ) )
for k , v := range m {
2023-03-10 19:59:42 +00:00
tableKey , tableValue := entry ( k , v )
out [ tableKey ] = tableValue
2022-04-25 18:57:59 +00:00
}
return out
}
2023-03-10 19:59:42 +00:00
// entry is a helper function that checks the json tags to make sure all fields
// are tracked. And no excess fields are tracked.
func entry ( v any , f map [ string ] Action ) ( string , map [ string ] Action ) {
vt := reflect . TypeOf ( v )
for vt . Kind ( ) == reflect . Ptr {
vt = vt . Elem ( )
}
// This should never happen because audit.Audible only allows structs in
// its union.
if vt . Kind ( ) != reflect . Struct {
panic ( fmt . Sprintf ( "audit table entry value must be a struct, got %T" , v ) )
}
name := structName ( vt )
// Use the flattenStructFields to recurse anonymously embedded structs
vv := reflect . ValueOf ( v )
diffs , err := flattenStructFields ( vv , vv )
if err != nil {
panic ( fmt . Sprintf ( "audit table entry type %T failed to flatten" , v ) )
}
fcpy := make ( map [ string ] Action , len ( f ) )
for k , v := range f {
fcpy [ k ] = v
}
for _ , d := range diffs {
jsonTag := d . FieldType . Tag . Get ( "json" )
if jsonTag == "-" {
// This field is explicitly ignored.
continue
}
if _ , ok := fcpy [ jsonTag ] ; ! ok {
2023-07-06 12:43:32 +00:00
_ , _ = fmt . Fprintf ( os . Stderr , "ERROR: Audit table entry missing action for field %q in type %q\nPlease update the auditable resource types in: %s\n" , d . FieldType . Name , name , self ( ) )
//nolint:revive
os . Exit ( 1 )
2023-03-10 19:59:42 +00:00
}
delete ( fcpy , jsonTag )
}
// If there are any fields left in fcpy, they are extra fields that don't
// exist in the struct. Don't track them.
if len ( fcpy ) > 0 {
panic ( fmt . Sprintf ( "audit table entry has extra actions for type %q: %v" , name , fcpy ) )
}
return structName ( vt ) , f
}
2022-04-25 18:57:59 +00:00
func ( t Action ) String ( ) string {
return string ( t )
}
2023-07-06 12:43:32 +00:00
func self ( ) string {
//nolint:dogsled
_ , file , _ , _ := runtime . Caller ( 1 )
return file
}