Skip to content

Commit e2bea2d

Browse files
authored
chore: autogenerate audit log documentation (coder#5862)
* added script for table creation * added tags to audit-logs.md * removed log * removed empty block line * PR feedback * modify check_unstaged * third times the charm maybe * spelling * relative path * excluding from the right script this time * sorted resources to ensure table order * running make cmd * running make again * ensuring order on subtable
1 parent cc694a5 commit e2bea2d

File tree

7 files changed

+179
-11
lines changed

7 files changed

+179
-11
lines changed

.vscode/settings.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@
117117
"tailnet",
118118
"tailnettest",
119119
"Tailscale",
120+
"tbody",
120121
"TCGETS",
121122
"tcpip",
122123
"TCSETS",
@@ -128,6 +129,7 @@
128129
"tfjson",
129130
"tfplan",
130131
"tfstate",
132+
"thead",
131133
"tios",
132134
"tmpdir",
133135
"tparallel",

Makefile

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -418,6 +418,7 @@ gen: \
418418
provisionerd/proto/provisionerd.pb.go \
419419
site/src/api/typesGenerated.ts \
420420
docs/admin/prometheus.md \
421+
docs/admin/audit-logs.md \
421422
coderd/apidoc/swagger.json \
422423
.prettierignore.include \
423424
.prettierignore \
@@ -436,6 +437,7 @@ gen/mark-fresh:
436437
provisionerd/proto/provisionerd.pb.go \
437438
site/src/api/typesGenerated.ts \
438439
docs/admin/prometheus.md \
440+
docs/admin/audit-logs.md \
439441
coderd/apidoc/swagger.json \
440442
.prettierignore.include \
441443
.prettierignore \
@@ -490,6 +492,11 @@ docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/me
490492
cd site
491493
yarn run format:write:only ../docs/admin/prometheus.md
492494

495+
docs/admin/audit-logs.md: scripts/auditdocgen/main.go enterprise/audit/table.go
496+
go run scripts/auditdocgen/main.go
497+
cd site
498+
yarn run format:write:only ../docs/admin/audit-logs.md
499+
493500
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
494501
./scripts/apidocgen/generate.sh
495502
yarn run --cwd=site format:write:only ../docs/api ../docs/manifest.json ../coderd/apidoc/swagger.json

docs/admin/audit-logs.md

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,23 @@ their deployment.
55

66
## Tracked Events
77

8-
We track **create, update and delete** events for the following resources:
9-
10-
- GitSSHKey
11-
- Template
12-
- TemplateVersion
13-
- Workspace
14-
- WorkspaceBuild
15-
- User
16-
- Group
8+
We track the following resources:
9+
10+
<!-- Code generated by 'make docs/admin/audit-logs.md'. DO NOT EDIT -->
11+
12+
| <b>Resource<b> | |
13+
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
14+
| AuditableGroup | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>avatar_url</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>members</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>quota_allowance</td><td>true</td></tr></tbody></table> |
15+
| GitSSHKey | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>private_key</td><td>true</td></tr><tr><td>public_key</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
16+
| Organization | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
17+
| OrganizationMember | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>organization_id</td><td>true</td></tr><tr><td>roles</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_id</td><td>true</td></tr></tbody></table> |
18+
| Template | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>active_version_id</td><td>true</td></tr><tr><td>allow_user_cancel_workspace_jobs</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>default_ttl</td><td>true</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>description</td><td>true</td></tr><tr><td>display_name</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>is_private</td><td>true</td></tr><tr><td>min_autostart_interval</td><td>true</td></tr><tr><td>name</td><td>true</td></tr><tr><td>organization_id</td><td>false</td></tr><tr><td>provisioner</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>user_acl</td><td>true</td></tr></tbody></table> |
19+
| TemplateVersion | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>created_at</td><td>false</td></tr><tr><td>created_by</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>job_id</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>template_id</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
20+
| User | <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>hashed_password</td><td>true</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_seen_at</td><td>false</td></tr><tr><td>login_type</td><td>false</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> |
21+
| Workspace | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>autostart_schedule</td><td>true</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>deleted</td><td>false</td></tr><tr><td>id</td><td>true</td></tr><tr><td>last_used_at</td><td>false</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>template_id</td><td>true</td></tr><tr><td>ttl</td><td>true</td></tr><tr><td>updated_at</td><td>false</td></tr></tbody></table> |
22+
| WorkspaceBuild | <table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody><tr><td>build_number</td><td>false</td></tr><tr><td>created_at</td><td>false</td></tr><tr><td>daily_cost</td><td>false</td></tr><tr><td>deadline</td><td>false</td></tr><tr><td>id</td><td>false</td></tr><tr><td>initiator_id</td><td>false</td></tr><tr><td>job_id</td><td>false</td></tr><tr><td>provisioner_state</td><td>false</td></tr><tr><td>reason</td><td>false</td></tr><tr><td>template_version_id</td><td>true</td></tr><tr><td>transition</td><td>false</td></tr><tr><td>updated_at</td><td>false</td></tr><tr><td>workspace_id</td><td>false</td></tr></tbody></table> |
23+
24+
<!-- End generated by 'make docs/admin/audit-logs.md'. -->
1725

1826
## Filtering logs
1927

enterprise/audit/generate.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,5 @@ PROJECT_ROOT=$(cd "$SCRIPT_DIR" && git rev-parse --show-toplevel)
1515

1616
(
1717
cd "$PROJECT_ROOT"
18-
go run ./scripts/auditgen ./coderd/database "$@"
18+
go run ./scripts/audittypegen ./coderd/database "$@"
1919
)

scripts/auditdocgen/main.go

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"flag"
6+
"log"
7+
"os"
8+
"sort"
9+
"strconv"
10+
"strings"
11+
12+
"golang.org/x/xerrors"
13+
14+
"github.com/coder/coder/enterprise/audit"
15+
)
16+
17+
var (
18+
auditDocFile string
19+
dryRun bool
20+
21+
generatorPrefix = []byte("<!-- Code generated by 'make docs/admin/audit-logs.md'. DO NOT EDIT -->")
22+
generatorSuffix = []byte("<!-- End generated by 'make docs/admin/audit-logs.md'. -->")
23+
)
24+
25+
/*
26+
*
27+
AuditableResourcesMap is derived from audit.AuditableResources
28+
and has the following structure:
29+
30+
{
31+
friendlyResourceName: {
32+
fieldName1: isTracked,
33+
fieldName2: isTracked,
34+
...
35+
},
36+
...
37+
}
38+
*/
39+
type AuditableResourcesMap map[string]map[string]bool
40+
41+
func main() {
42+
flag.StringVar(&auditDocFile, "audit-doc-file", "docs/admin/audit-logs.md", "Path to audit log doc file")
43+
flag.BoolVar(&dryRun, "dry-run", false, "Dry run")
44+
flag.Parse()
45+
46+
auditableResourcesMap := readAuditableResources()
47+
48+
doc, err := readAuditDoc()
49+
if err != nil {
50+
log.Fatal("can't read audit doc: ", err)
51+
}
52+
53+
doc, err = updateAuditDoc(doc, auditableResourcesMap)
54+
if err != nil {
55+
log.Fatal("can't update audit doc: ", err)
56+
}
57+
58+
if dryRun {
59+
log.Println(string(doc))
60+
return
61+
}
62+
63+
err = writeAuditDoc(doc)
64+
if err != nil {
65+
log.Fatal("can't write updated audit doc: ", err)
66+
}
67+
}
68+
69+
// Transforms audit.AuditableResources to AuditableResourcesMap,
70+
// which uses friendlier language.
71+
func readAuditableResources() AuditableResourcesMap {
72+
auditableResourcesMap := make(AuditableResourcesMap)
73+
74+
for resourceName, resourceFields := range audit.AuditableResources {
75+
friendlyResourceName := strings.Split(resourceName, ".")[2]
76+
fieldNameMap := make(map[string]bool)
77+
for fieldName, action := range resourceFields {
78+
fieldNameMap[fieldName] = action != audit.ActionIgnore
79+
auditableResourcesMap[friendlyResourceName] = fieldNameMap
80+
}
81+
}
82+
83+
return auditableResourcesMap
84+
}
85+
86+
func readAuditDoc() ([]byte, error) {
87+
doc, err := os.ReadFile(auditDocFile)
88+
if err != nil {
89+
return nil, err
90+
}
91+
92+
return doc, nil
93+
}
94+
95+
// Writes a markdown table of audit log resources to a buffer
96+
func updateAuditDoc(doc []byte, auditableResourcesMap AuditableResourcesMap) ([]byte, error) {
97+
// We must sort the resources to ensure table ordering
98+
sortedResourceNames := sortKeys(auditableResourcesMap)
99+
100+
i := bytes.Index(doc, generatorPrefix)
101+
if i < 0 {
102+
return nil, xerrors.New("generator prefix tag not found")
103+
}
104+
tableStartIndex := i + len(generatorPrefix) + 1
105+
106+
j := bytes.Index(doc[tableStartIndex:], generatorSuffix)
107+
if j < 0 {
108+
return nil, xerrors.New("generator suffix tag not found")
109+
}
110+
tableEndIndex := tableStartIndex + j
111+
112+
var buffer bytes.Buffer
113+
buffer.Write(doc[:tableStartIndex])
114+
buffer.WriteByte('\n')
115+
116+
buffer.WriteString("|<b>Resource<b>||\n")
117+
buffer.WriteString("|--|-----------------|\n")
118+
119+
for _, resourceName := range sortedResourceNames {
120+
buffer.WriteString("|" + resourceName + "|<table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody>")
121+
122+
// We must sort the field names to ensure sub-table ordering
123+
sortedFieldNames := sortKeys(auditableResourcesMap[resourceName])
124+
125+
for _, fieldName := range sortedFieldNames {
126+
isTracked := auditableResourcesMap[resourceName][fieldName]
127+
buffer.WriteString("<tr><td>" + fieldName + "</td><td>" + strconv.FormatBool(isTracked) + "</td></tr>")
128+
}
129+
130+
buffer.WriteString("</tbody></table>\n")
131+
}
132+
133+
buffer.WriteString("\n")
134+
buffer.Write(doc[tableEndIndex:])
135+
return buffer.Bytes(), nil
136+
}
137+
138+
func writeAuditDoc(doc []byte) error {
139+
// G306: Expect WriteFile permissions to be 0600 or less
140+
/* #nosec G306 */
141+
return os.WriteFile(auditDocFile, doc, 0644)
142+
}
143+
144+
func sortKeys[T any](stringMap map[string]T) []string {
145+
var keyNames []string
146+
for key := range stringMap {
147+
keyNames = append(keyNames, key)
148+
}
149+
sort.Strings(keyNames)
150+
return keyNames
151+
}
File renamed without changes.

scripts/check_enterprise_imports.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
99
cdroot
1010

1111
set +e
12-
find . -regex ".*\.go" | grep -v "./enterprise" | xargs grep -n "github.com/coder/coder/enterprise"
12+
find . -regex ".*\.go" | grep -v "./enterprise" | grep -v "./scripts/auditdocgen/main.go" | xargs grep -n "github.com/coder/coder/enterprise"
1313
# reverse the exit code because we want this script to fail if grep finds anything.
1414
status=$?
1515
set -e

0 commit comments

Comments
 (0)