mirror of https://github.com/coder/coder.git
chore: autogenerate audit log documentation (#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
This commit is contained in:
parent
cc694a55bc
commit
e2bea2d20f
|
@ -117,6 +117,7 @@
|
||||||
"tailnet",
|
"tailnet",
|
||||||
"tailnettest",
|
"tailnettest",
|
||||||
"Tailscale",
|
"Tailscale",
|
||||||
|
"tbody",
|
||||||
"TCGETS",
|
"TCGETS",
|
||||||
"tcpip",
|
"tcpip",
|
||||||
"TCSETS",
|
"TCSETS",
|
||||||
|
@ -128,6 +129,7 @@
|
||||||
"tfjson",
|
"tfjson",
|
||||||
"tfplan",
|
"tfplan",
|
||||||
"tfstate",
|
"tfstate",
|
||||||
|
"thead",
|
||||||
"tios",
|
"tios",
|
||||||
"tmpdir",
|
"tmpdir",
|
||||||
"tparallel",
|
"tparallel",
|
||||||
|
|
7
Makefile
7
Makefile
|
@ -418,6 +418,7 @@ gen: \
|
||||||
provisionerd/proto/provisionerd.pb.go \
|
provisionerd/proto/provisionerd.pb.go \
|
||||||
site/src/api/typesGenerated.ts \
|
site/src/api/typesGenerated.ts \
|
||||||
docs/admin/prometheus.md \
|
docs/admin/prometheus.md \
|
||||||
|
docs/admin/audit-logs.md \
|
||||||
coderd/apidoc/swagger.json \
|
coderd/apidoc/swagger.json \
|
||||||
.prettierignore.include \
|
.prettierignore.include \
|
||||||
.prettierignore \
|
.prettierignore \
|
||||||
|
@ -436,6 +437,7 @@ gen/mark-fresh:
|
||||||
provisionerd/proto/provisionerd.pb.go \
|
provisionerd/proto/provisionerd.pb.go \
|
||||||
site/src/api/typesGenerated.ts \
|
site/src/api/typesGenerated.ts \
|
||||||
docs/admin/prometheus.md \
|
docs/admin/prometheus.md \
|
||||||
|
docs/admin/audit-logs.md \
|
||||||
coderd/apidoc/swagger.json \
|
coderd/apidoc/swagger.json \
|
||||||
.prettierignore.include \
|
.prettierignore.include \
|
||||||
.prettierignore \
|
.prettierignore \
|
||||||
|
@ -490,6 +492,11 @@ docs/admin/prometheus.md: scripts/metricsdocgen/main.go scripts/metricsdocgen/me
|
||||||
cd site
|
cd site
|
||||||
yarn run format:write:only ../docs/admin/prometheus.md
|
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
|
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
|
./scripts/apidocgen/generate.sh
|
||||||
yarn run --cwd=site format:write:only ../docs/api ../docs/manifest.json ../coderd/apidoc/swagger.json
|
yarn run --cwd=site format:write:only ../docs/api ../docs/manifest.json ../coderd/apidoc/swagger.json
|
||||||
|
|
|
@ -5,15 +5,23 @@ their deployment.
|
||||||
|
|
||||||
## Tracked Events
|
## Tracked Events
|
||||||
|
|
||||||
We track **create, update and delete** events for the following resources:
|
We track the following resources:
|
||||||
|
|
||||||
- GitSSHKey
|
<!-- Code generated by 'make docs/admin/audit-logs.md'. DO NOT EDIT -->
|
||||||
- Template
|
|
||||||
- TemplateVersion
|
| <b>Resource<b> | |
|
||||||
- Workspace
|
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||||
- WorkspaceBuild
|
| 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> |
|
||||||
- User
|
| 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> |
|
||||||
- Group
|
| 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> |
|
||||||
|
| 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> |
|
||||||
|
| 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> |
|
||||||
|
| 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> |
|
||||||
|
| 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> |
|
||||||
|
| 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> |
|
||||||
|
| 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> |
|
||||||
|
|
||||||
|
<!-- End generated by 'make docs/admin/audit-logs.md'. -->
|
||||||
|
|
||||||
## Filtering logs
|
## Filtering logs
|
||||||
|
|
||||||
|
|
|
@ -15,5 +15,5 @@ PROJECT_ROOT=$(cd "$SCRIPT_DIR" && git rev-parse --show-toplevel)
|
||||||
|
|
||||||
(
|
(
|
||||||
cd "$PROJECT_ROOT"
|
cd "$PROJECT_ROOT"
|
||||||
go run ./scripts/auditgen ./coderd/database "$@"
|
go run ./scripts/audittypegen ./coderd/database "$@"
|
||||||
)
|
)
|
||||||
|
|
|
@ -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("<!-- Code generated by 'make docs/admin/audit-logs.md'. DO NOT EDIT -->")
|
||||||
|
generatorSuffix = []byte("<!-- End generated by 'make docs/admin/audit-logs.md'. -->")
|
||||||
|
)
|
||||||
|
|
||||||
|
/*
|
||||||
|
*
|
||||||
|
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("|<b>Resource<b>||\n")
|
||||||
|
buffer.WriteString("|--|-----------------|\n")
|
||||||
|
|
||||||
|
for _, resourceName := range sortedResourceNames {
|
||||||
|
buffer.WriteString("|" + resourceName + "|<table><thead><tr><th>Field</th><th>Tracked</th></tr></thead><tbody>")
|
||||||
|
|
||||||
|
// 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("<tr><td>" + fieldName + "</td><td>" + strconv.FormatBool(isTracked) + "</td></tr>")
|
||||||
|
}
|
||||||
|
|
||||||
|
buffer.WriteString("</tbody></table>\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
|
||||||
|
}
|
|
@ -9,7 +9,7 @@ source "$(dirname "${BASH_SOURCE[0]}")/lib.sh"
|
||||||
cdroot
|
cdroot
|
||||||
|
|
||||||
set +e
|
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.
|
# reverse the exit code because we want this script to fail if grep finds anything.
|
||||||
status=$?
|
status=$?
|
||||||
set -e
|
set -e
|
||||||
|
|
Loading…
Reference in New Issue