diff --git a/.vscode/settings.json b/.vscode/settings.json index 4aceda0053..667b3b9d57 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -117,6 +117,7 @@ "tailnet", "tailnettest", "Tailscale", + "tbody", "TCGETS", "tcpip", "TCSETS", @@ -128,6 +129,7 @@ "tfjson", "tfplan", "tfstate", + "thead", "tios", "tmpdir", "tparallel", diff --git a/Makefile b/Makefile index 4abf12be4b..01048aaf7b 100644 --- a/Makefile +++ b/Makefile @@ -418,6 +418,7 @@ gen: \ provisionerd/proto/provisionerd.pb.go \ site/src/api/typesGenerated.ts \ docs/admin/prometheus.md \ + docs/admin/audit-logs.md \ coderd/apidoc/swagger.json \ .prettierignore.include \ .prettierignore \ @@ -436,6 +437,7 @@ gen/mark-fresh: provisionerd/proto/provisionerd.pb.go \ site/src/api/typesGenerated.ts \ docs/admin/prometheus.md \ + docs/admin/audit-logs.md \ coderd/apidoc/swagger.json \ .prettierignore.include \ .prettierignore \ @@ -490,6 +492,11 @@ docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/me cd site yarn run format:write:only ../docs/admin/prometheus.md +docs/admin/audit-logs.md: scripts/auditdocgen/main.go enterprise/audit/table.go + go run scripts/auditdocgen/main.go + cd site + yarn run format:write:only ../docs/admin/audit-logs.md + coderd/apidoc/swagger.json: $(shell find ./scripts/apidocgen $(FIND_EXCLUSIONS) -type f) $(wildcard coderd/*.go) $(wildcard enterprise/coderd/*.go) $(wildcard codersdk/*.go) .swaggo docs/manifest.json ./scripts/apidocgen/generate.sh yarn run --cwd=site format:write:only ../docs/api ../docs/manifest.json ../coderd/apidoc/swagger.json diff --git a/docs/admin/audit-logs.md b/docs/admin/audit-logs.md index 71398362ed..892c61114c 100644 --- a/docs/admin/audit-logs.md +++ b/docs/admin/audit-logs.md @@ -5,15 +5,23 @@ their deployment. ## Tracked Events -We track **create, update and delete** events for the following resources: +We track the following resources: -- GitSSHKey -- Template -- TemplateVersion -- Workspace -- WorkspaceBuild -- User -- Group + + +| Resource | | +| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| AuditableGroup |
FieldTracked
avatar_urltrue
idtrue
memberstrue
nametrue
organization_idfalse
quota_allowancetrue
| +| GitSSHKey |
FieldTracked
created_atfalse
private_keytrue
public_keytrue
updated_atfalse
user_idtrue
| +| Organization |
FieldTracked
created_atfalse
descriptiontrue
idtrue
nametrue
updated_atfalse
| +| OrganizationMember |
FieldTracked
created_atfalse
organization_idtrue
rolestrue
updated_atfalse
user_idtrue
| +| Template |
FieldTracked
active_version_idtrue
allow_user_cancel_workspace_jobstrue
created_atfalse
created_bytrue
default_ttltrue
deletedfalse
descriptiontrue
display_nametrue
group_acltrue
icontrue
idtrue
is_privatetrue
min_autostart_intervaltrue
nametrue
organization_idfalse
provisionertrue
updated_atfalse
user_acltrue
| +| TemplateVersion |
FieldTracked
created_atfalse
created_bytrue
idtrue
job_idfalse
nametrue
organization_idfalse
readmetrue
template_idtrue
updated_atfalse
| +| User |
FieldTracked
avatar_urlfalse
created_atfalse
deletedtrue
emailtrue
hashed_passwordtrue
idtrue
last_seen_atfalse
login_typefalse
rbac_rolestrue
statustrue
updated_atfalse
usernametrue
| +| Workspace |
FieldTracked
autostart_scheduletrue
created_atfalse
deletedfalse
idtrue
last_used_atfalse
nametrue
organization_idfalse
owner_idtrue
template_idtrue
ttltrue
updated_atfalse
| +| WorkspaceBuild |
FieldTracked
build_numberfalse
created_atfalse
daily_costfalse
deadlinefalse
idfalse
initiator_idfalse
job_idfalse
provisioner_statefalse
reasonfalse
template_version_idtrue
transitionfalse
updated_atfalse
workspace_idfalse
| + + ## Filtering logs diff --git a/enterprise/audit/generate.sh b/enterprise/audit/generate.sh index 7821242b60..42d8980f43 100755 --- a/enterprise/audit/generate.sh +++ b/enterprise/audit/generate.sh @@ -15,5 +15,5 @@ PROJECT_ROOT=$(cd "$SCRIPT_DIR" && git rev-parse --show-toplevel) ( cd "$PROJECT_ROOT" - go run ./scripts/auditgen ./coderd/database "$@" + go run ./scripts/audittypegen ./coderd/database "$@" ) diff --git a/scripts/auditdocgen/main.go b/scripts/auditdocgen/main.go new file mode 100644 index 0000000000..4a304ad664 --- /dev/null +++ b/scripts/auditdocgen/main.go @@ -0,0 +1,151 @@ +package main + +import ( + "bytes" + "flag" + "log" + "os" + "sort" + "strconv" + "strings" + + "golang.org/x/xerrors" + + "github.com/coder/coder/enterprise/audit" +) + +var ( + auditDocFile string + dryRun bool + + generatorPrefix = []byte("") + generatorSuffix = []byte("") +) + +/* +* +AuditableResourcesMap is derived from audit.AuditableResources +and has the following structure: + + { + friendlyResourceName: { + fieldName1: isTracked, + fieldName2: isTracked, + ... + }, + ... + } +*/ +type AuditableResourcesMap map[string]map[string]bool + +func main() { + flag.StringVar(&auditDocFile, "audit-doc-file", "docs/admin/audit-logs.md", "Path to audit log doc file") + flag.BoolVar(&dryRun, "dry-run", false, "Dry run") + flag.Parse() + + auditableResourcesMap := readAuditableResources() + + doc, err := readAuditDoc() + if err != nil { + log.Fatal("can't read audit doc: ", err) + } + + doc, err = updateAuditDoc(doc, auditableResourcesMap) + if err != nil { + log.Fatal("can't update audit doc: ", err) + } + + if dryRun { + log.Println(string(doc)) + return + } + + err = writeAuditDoc(doc) + if err != nil { + log.Fatal("can't write updated audit doc: ", err) + } +} + +// Transforms audit.AuditableResources to AuditableResourcesMap, +// which uses friendlier language. +func readAuditableResources() AuditableResourcesMap { + auditableResourcesMap := make(AuditableResourcesMap) + + for resourceName, resourceFields := range audit.AuditableResources { + friendlyResourceName := strings.Split(resourceName, ".")[2] + fieldNameMap := make(map[string]bool) + for fieldName, action := range resourceFields { + fieldNameMap[fieldName] = action != audit.ActionIgnore + auditableResourcesMap[friendlyResourceName] = fieldNameMap + } + } + + return auditableResourcesMap +} + +func readAuditDoc() ([]byte, error) { + doc, err := os.ReadFile(auditDocFile) + if err != nil { + return nil, err + } + + return doc, nil +} + +// Writes a markdown table of audit log resources to a buffer +func updateAuditDoc(doc []byte, auditableResourcesMap AuditableResourcesMap) ([]byte, error) { + // We must sort the resources to ensure table ordering + sortedResourceNames := sortKeys(auditableResourcesMap) + + i := bytes.Index(doc, generatorPrefix) + if i < 0 { + return nil, xerrors.New("generator prefix tag not found") + } + tableStartIndex := i + len(generatorPrefix) + 1 + + j := bytes.Index(doc[tableStartIndex:], generatorSuffix) + if j < 0 { + return nil, xerrors.New("generator suffix tag not found") + } + tableEndIndex := tableStartIndex + j + + var buffer bytes.Buffer + buffer.Write(doc[:tableStartIndex]) + buffer.WriteByte('\n') + + buffer.WriteString("|Resource||\n") + buffer.WriteString("|--|-----------------|\n") + + for _, resourceName := range sortedResourceNames { + buffer.WriteString("|" + resourceName + "|") + + // We must sort the field names to ensure sub-table ordering + sortedFieldNames := sortKeys(auditableResourcesMap[resourceName]) + + for _, fieldName := range sortedFieldNames { + isTracked := auditableResourcesMap[resourceName][fieldName] + buffer.WriteString("") + } + + buffer.WriteString("
FieldTracked
" + fieldName + "" + strconv.FormatBool(isTracked) + "
\n") + } + + buffer.WriteString("\n") + buffer.Write(doc[tableEndIndex:]) + return buffer.Bytes(), nil +} + +func writeAuditDoc(doc []byte) error { + // G306: Expect WriteFile permissions to be 0600 or less + /* #nosec G306 */ + return os.WriteFile(auditDocFile, doc, 0644) +} + +func sortKeys[T any](stringMap map[string]T) []string { + var keyNames []string + for key := range stringMap { + keyNames = append(keyNames, key) + } + sort.Strings(keyNames) + return keyNames +} diff --git a/scripts/auditgen/main.go b/scripts/audittypegen/main.go similarity index 100% rename from scripts/auditgen/main.go rename to scripts/audittypegen/main.go diff --git a/scripts/check_enterprise_imports.sh b/scripts/check_enterprise_imports.sh index d89eeed1c0..4aacae53ad 100755 --- a/scripts/check_enterprise_imports.sh +++ b/scripts/check_enterprise_imports.sh @@ -9,7 +9,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh" cdroot set +e -find . -regex ".*\.go" | grep -v "./enterprise" | xargs grep -n "github.com/coder/coder/enterprise" +find . -regex ".*\.go" | grep -v "./enterprise" | grep -v "./scripts/auditdocgen/main.go" | xargs grep -n "github.com/coder/coder/enterprise" # reverse the exit code because we want this script to fail if grep finds anything. status=$? set -e