audit

package module
v0.10.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 4, 2026 License: MIT Imports: 1 Imported by: 0

README

audit

An audit trail an application owns: one record format, one write path, an immutable archive, and projections for security, billing and history.

An installation belongs to one application and runs in its namespace, rendered by its chart. The application emits through a library; the receiver, the writer and the query service are separate Deployments, so the application holds no credentials for the archive and a fix never rebuilds it.

flowchart LR
  subgraph app["application pod"]
    E["emit library<br/>validates, block or async"]
  end
  E -- "ack = durable" --> R["receiver"]
  R -- "direct mode" --> W["writer"]
  R -- "stream mode" --> N[("JetStream")] --> W
  W --> S3[("Object-Locked bucket<br/>THE RECORD")]
  W -- "dedupe" --> PG[("database")]
  O["indexer<br/>(audit-observe)"] -- "follows by cursor" --> S3
  O -- "index — rebuildable" --> PG
  D["verify, nightly"] --> S3
  Q["query service"] --> PG
  Q --> S3
  UI["Audit page in the<br/>application's console"] --> Q

Who it is for

An application team that already has, or can provision, a Postgres database, an S3-compatible bucket with Object Lock and a reference clock — the things Before either shape lists.

It deliberately does not install: a central, multi-tenant audit service (an installation belongs to one application, 0011); its own message bus (stream mode consumes the application's existing JetStream); or the Audit page itself, which lives in the application's own console and only calls the query service.

The model

An installation is one deployment of this chart, in one application's namespace, in one of the two shapes (direct or stream). It writes records — validated against the application's catalogue — through a receiver into the archive (an Object-Locked bucket) and an index (Postgres, rebuildable, not itself evidence), which an indexer (audit-observe) writes by following the bucket. A query service reads the index and the archive back out, behind grants the installation's values declare.

Install and a worked example

This chart is instantiated, not deployed standalone: an installation belongs to one application and is rendered by that application's own chart, which takes this one as a dependency (0011).

# the application chart's Chart.yaml
dependencies:
  - name: audit
    version: 0.4.0
    repository: oci://ghcr.io/truvity/charts

Then the application's own values.yaml sets what this chart reads under the audit: key. The direct shape, abridged from a worked example (charts/audit/examples/direct.yaml, rendered as one of this repository's golden fixtures):

audit:
  mode: direct
  replicas: 2
  profiles:
    security:
      presets: [security]
  externalIdentifiersAreOpaque: true
  workloadIdentity:
    issuers:
      - url: https://oidc.example.com/id/CLUSTER
    workloads:
      - subject: system:serviceaccount:app:api
        source: app
  migrate:
    enabled: true
    config:
      database:
        url: postgres://audit@db.example.com:5432/audit?sslmode=verify-full
        passwordEnv: AUDIT_DATABASE_PASSWORD
      reader: audit_query
    secretEnv:
      - {name: AUDIT_DATABASE_PASSWORD, secretName: audit-db, key: password}
  writer:
    config:                          # audit-writer's own configuration file
      deployment: /etc/audit/deployment.yaml
      workloads: /etc/audit/workloads.yaml
      replicas: 2
      archive:
        bucket: {name: audit-eu-example-1, region: eu-example-1}
        prefix: audit/app
        kmsKey: alias/audit-archive
      database:
        url: postgres://audit@db.example.com:5432/audit?sslmode=verify-full
        passwordEnv: AUDIT_DATABASE_PASSWORD
    secretEnv:                       # the Secret behind that variable name
      - {name: AUDIT_DATABASE_PASSWORD, secretName: audit-db, key: password}
  # query, and the verify, purge and clock-sync jobs, each take a
  # `config:` the same way; see the example file below

Each config: is the binary's own configuration file, validated against the schema in schemas/config/ both when the chart renders and when the process starts; a secret is named there and supplied by secretEnv. The keys are in the configuration reference.

stream mode's example is the same shape with a JetStream receiver; both render as golden fixtures tests/golden/audit/example-direct.yaml and tests/golden/audit/example-stream.yaml, so the example it is abridged from is proven to render, not just plausible.

The two shapes

shape for how a record becomes durable
direct an internal service, or a cluster with no stream the receiver puts the object, then acknowledges
stream a product: many pods, metering, quotas the receiver publishes, writers consume and put

Both write the same archive and are verified by the same command. There is no shape that puts the writer inside the application (why).

The two extensions

extension what it adds needs
billing rollups at index time, an immutable monthly statement a metering profile
usage quotas a usage consumer, a counter cache, an hourly reconciler stream mode

Consumers

repo surface
truvity/gitops chart audit
A second, non-AWS estate chart audit
truvity/cloudflare Go, from r2broker

Neighbours

  • access-roster ↔ openbao ↔ audit. access-roster is the issuer; openbao is a relying party (its own docs/integrations/access-roster.md); audit is the record every one of them writes.

Documentation

you want to read
see how it fits together Architecture — the parts, the catalogue, what an acknowledgement means, what can be lost
connect an application Integrating — the catalogue, one constructor per action, the CI check, the Audit page
run one in a cluster Deployment — what to prepare, then direct or stream
record what your application does Emitting — deliveries, registration, the Go emitter; examples/emit
search the trail, or audit it Reading — grants, the API, the clients, audit verify; examples/read
know which presets to compose Presets policy, then presets/
understand why it is built this way why, concepts, the decisions

The rule that makes this repository public

Mechanism only: nothing in this repository may name a real organisation, cluster, account, team, person, incident or internal ticket (CONTRIBUTING). Every chart value that names a cluster, an account, a hostname or a secret path is an input with a neutral default — the consuming estate supplies the particulars from its own, private repository. hack/leak-canary.sh enforces it on tracked files, and just check runs it.

This repository follows the shared component contract.

Status

part state
Record, catalogues, presets, audit validate / check-emitters built
Go emitter: block and async; Connect and JetStream sinks built
Writer: split per profile, Object Lock where a profile demands it, index, dead letters, legal holds, retention addenda built
Two store tiers on any S3-compatible store: record (Object Lock in compliance mode) and attested (no lock) (0014) built
Query service: search, facets, get, export, tail; JWT with declarative grants built
The v1 bucket layout and audit verify, which checks every record object against the bucket contract; signers (key file, AWS KMS, OpenBAO transit) for the seals that will follow built; seals are not
Pseudonymisation keys: local and OpenBAO transit, off by default (0013) built
Helm chart: mode, receiver, writer, query service, the three jobs, the extension toggles built; a golden per shape, and every documented example rendered
@truvity/audit: query client, sentences, React hooks and view built; consumed from a release tag (github:truvity/audit#vX.Y.Z), not from a registry
TypeScript emitter designed, not built
Billing statement, usage consumer, reconciler designed, not built
Exporters (OCSF, ECS, Parquet), adapters designed, not built

What it is not

  • Not an application log pipeline. Logs keep flowing through the observability stack. This records what happened, who did it, to what, with what outcome, and keeps it for as long as a framework requires.
  • Not a SIEM. It exports to one.
  • Not a wallet transaction log. A digital identity wallet's own log lives on the user's device and is invisible to the provider by law. This component sees operational events only.

Development

Tools come from devbox.json through direnv; devbox shell puts them on PATH. just check is the gate — it needs nothing but this checkout: no C toolchain, no network. It builds, tests, lints, renders the chart against every golden under tests/golden/audit/ and every refusal in tests/invalid/audit/refusals.txt, and runs the leak canary.

just chart regenerates the chart's goldens (review the diff before committing); just drift-ts regenerates the TypeScript client's generated code. just race, just conformance and the other recipes that need a C toolchain, Docker, or the network are separate, for the same reason: a gate that fails because of what somebody else's environment lacks is a gate people learn to ignore. See CONTRIBUTING for the rest.

Releasing

A pushed v* tag runs release.yaml, the shared release-public workflow (truvity/ci-workflows): it builds the toolchain archives and, through ko, the three images the chart deploys, and packages and pushes the audit chart to oci://ghcr.io/truvity/charts/audit at the tag's version. charts/audit/Chart.yaml's committed version: 0.0.0 / appVersion: "0.0.0" are placeholders the release stamps over — never bump them by hand. The CLI is also published as a Nix flake release asset, for pinning audit verify the way other repositories pin accessctl.

Licence

MIT. See LICENSE.

Documentation

Overview

Package audit carries the files this repository publishes as data: the framework presets, the proto and its generated JSON Schema, and the binaries' configuration schemas. The meta-schemas and the common catalogue are the SDK's (github.com/truvity/audit/sdk).

They are embedded so that a deployment gets them from the binary it already runs, rather than from files it has to ship and keep in step.

Index

Constants

This section is empty.

Variables

View Source
var ConfigSchemas embed.FS

ConfigSchemas are the schemas of each binary's configuration file, under schemas/config/. They are generated by `just config-schemas` from internal/config and committed, so that a deployment can validate its file without this repository's code.

View Source
var Generated embed.FS

Generated holds what this build published for readers outside Go: the record's JSON Schema, with the proto's comments carried as descriptions. The writer puts a copy of it in the archive beside the records it describes.

View Source
var Presets embed.FS

Presets are the framework presets under presets/.

View Source
var Proto embed.FS

Proto is the schema of record itself. The writer copies the proto of a record's major beside the record's JSON Schema on first use, so the archive keeps the meaning of every field and not only its shape.

Functions

This section is empty.

Types

This section is empty.

Directories

Path Synopsis
Package authn is the server side of the SDK's auth package: the JWT authenticator, which verifies a bearer token against the issuers a deployment trusts, and the access-roster grants preset.
Package authn is the server side of the SDK's auth package: the JWT authenticator, which verifies a bearer token against the issuers a deployment trusts, and the access-roster grants preset.
cmd
audit command
Command audit is the tool that holds a deployment to the contracts this repository publishes: it validates catalogues and presets, explains what a profile keeps, and checks that the code and the catalogue still agree.
Command audit is the tool that holds a deployment to the contracts this repository publishes: it validates catalogues and presets, explains what a profile keeps, and checks that the code and the catalogue still agree.
audit-notary command
Command audit-notary seals the archive.
Command audit-notary seals the archive.
audit-notary-lambda command
Command audit-notary-lambda is the notary as an AWS Lambda function invoked hourly by an EventBridge Scheduler schedule.
Command audit-notary-lambda is the notary as an AWS Lambda function invoked hourly by an EventBridge Scheduler schedule.
audit-observe command
Command audit-observe is the indexer: it turns the archive into the search index by following the bucket (docs/decisions/0020).
Command audit-observe is the indexer: it turns the archive into the search index by following the bucket (docs/decisions/0020).
audit-query command
Command audit-query answers questions about what was written.
Command audit-query answers questions about what was written.
audit-writer command
Command audit-writer is an installation's front door and its write path, which are one process in a small installation and two in a busy one.
Command audit-writer is an installation's front door and its write path, which are one process in a small installation and two in a busy one.
audit-writer-lambda command
Command audit-writer-lambda is the write path as an AWS Lambda function behind an SQS event source mapping.
Command audit-writer-lambda is the write path as an AWS Lambda function behind an SQS event source mapping.
protoc-gen-audit-jsonschema command
Command protoc-gen-audit-jsonschema is the buf plugin that writes the record's JSON Schema.
Command protoc-gen-audit-jsonschema is the buf plugin that writes the record's JSON Schema.
dedupe
dynamodbdedupe
Package dynamodbdedupe is the writer's record of what it has written, kept in a DynamoDB table, for a writer that has no database: a function platform, where the writer is a Lambda and Postgres is somebody else's service.
Package dynamodbdedupe is the writer's record of what it has written, kept in a DynamoDB table, for a writer that has no database: a function platform, where the writer is a Lambda and Postgres is somebody else's service.
deploy
pulumi module
e2e
fixture
Package fixture stands in for the platform on the local kind box (see truvity/policy's hack/kind/README.md and docs/decisions/0005-kind-is-the-gate.md there): a Postgres database and its two roles, a JetStream stream, an S3 bucket — the things a real deployment's platform would already have provisioned before `helm install audit` ever runs.
Package fixture stands in for the platform on the local kind box (see truvity/policy's hack/kind/README.md and docs/decisions/0005-kind-is-the-gate.md there): a Postgres database and its two roles, a JetStream stream, an S3 bucket — the things a real deployment's platform would already have provisioned before `helm install audit` ever runs.
fixture/cmd/resolve command
Command resolve prints e2e/fixture's names as shell variable assignments, so apply.sh and every hack script that installs onto the same names can `eval` this rather than repeating them.
Command resolve prints e2e/fixture's names as shell variable assignments, so apply.sh and every hack script that installs onto the same names can `eval` this rather than repeating them.
examples
emit command
Command emit is an example application that records what it does into the audit trail.
Command emit is an example application that records what it does into the audit trail.
read command
Command read is an example of reading the audit trail from Go: a search, paged to the end with its cursor, and one record read with where it came from.
Command read is an example of reading the audit trail from Go: a search, paged to the end with its cursor, and one record read with where it came from.
hack
goreleaser-snapshot-config command
Command goreleaser-snapshot-config derives a single-architecture copy of .goreleaser.yaml for the local and CI loops that build straight into the kind box's own registry (see hack/example-snapshot.sh).
Command goreleaser-snapshot-config derives a single-architecture copy of .goreleaser.yaml for the local and CI loops that build straight into the kind box's own registry (see hack/example-snapshot.sh).
Package index is the projection a search reads.
Package index is the projection a search reads.
indextest
Package indextest is the one corpus every searcher is asked about.
Package indextest is the one corpus every searcher is asked about.
postgres
Package postgres is the default index and the shared deduplication store.
Package postgres is the default index and the shared deduplication store.
s3scan
Package s3scan answers queries by reading the archive, with no index at all.
Package s3scan answers queries by reading the archive, with no index at all.
internal
authtest
Package authtest is an identity provider for tests: a discovery document and a key set served over a real listener, so the code under test runs its actual discovery and key fetch rather than being handed a key.
Package authtest is an identity provider for tests: a discovery document and a key set served over a real listener, so the code under test runs its actual discovery and key fetch rather than being handed a key.
bucketcontract
Package bucketcontract checks a bucket against the v1 bucket contract, docs/reference/bucket-contract.md: the key grammar, the envelope of every line, the metadata of every object, its sha256, the hash of every record, and the order keys sort in.
Package bucketcontract checks a bucket against the v1 bucket contract, docs/reference/bucket-contract.md: the key grammar, the envelope of every line, the metadata of every object, its sha256, the hash of every record, and the order keys sort in.
buildinfo
Package buildinfo says which build this process is.
Package buildinfo says which build this process is.
cli
Package cli is the implementation behind cmd/audit.
Package cli is the implementation behind cmd/audit.
clock
Package clock measures how far this machine's clock is from UTC.
Package clock measures how far this machine's clock is from UTC.
config
Package config is what each binary of this repository is configured with: one typed configuration per binary, read from one file and validated against a JSON Schema before anything starts.
Package config is what each binary of this repository is configured with: one typed configuration per binary, read from one file and validated against a JSON Schema before anything starts.
config/gen command
Command gen writes the configuration schemas into schemas/config/, and the chart's values schema, which embeds them, into charts/audit/.
Command gen writes the configuration schemas into schemas/config/, and the chart's values schema, which embeds them, into charts/audit/.
config/schema
Package schema builds the JSON Schema of each binary's configuration file.
Package schema builds the JSON Schema of each binary's configuration file.
corpus
Package corpus reads the example records in testdata/records, for tests that hold a transport or an encoder to them.
Package corpus reads the example records in testdata/records, for tests that hold a transport or an encoder to them.
hold
Package hold places and releases legal holds over the archive.
Package hold places and releases legal holds over the archive.
identity
Package identity is the way back from a pseudonym to the person, for the cases the law requires and only for them.
Package identity is the way back from a pseudonym to the person, for the cases the law requires and only for them.
merkle
Package merkle is the Merkle tree of docs/reference/bucket-contract.md: the Merkle Tree Hash of RFC 6962 section 2.1, over SHA-256, and the audit path that proves one leaf is in it (RFC 9162 section 2.1.3 for the checking).
Package merkle is the Merkle tree of docs/reference/bucket-contract.md: the Merkle Tree Hash of RFC 6962 section 2.1, over SHA-256, and the audit path that proves one leaf is in it (RFC 9162 section 2.1.3 for the checking).
observe
Package observe is the third part of an installation: it turns the archive into the search index by following the bucket (docs/decisions/0020).
Package observe is the third part of an installation: it turns the archive into the search index by following the bucket (docs/decisions/0020).
observe/observetest
Package observetest is what the cursor indexer is held to, whatever it is run over: the same cases against the in-memory archive, a real S3 and a real Postgres, because the claims are about the listing and the transaction and both are the places a double is kinder than the real thing.
Package observetest is what the cursor indexer is held to, whatever it is run over: the same cases against the in-memory archive, a real S3 and a real Postgres, because the claims are about the listing and the transaction and both are the places a double is kinder than the real thing.
pgtest
Package pgtest gives a test its own Postgres schema.
Package pgtest gives a test its own Postgres schema.
query
Package query is the read service: it turns a request into a closed query, narrows it to what the caller was granted, asks a searcher, and records that the read happened.
Package query is the read service: it turns a request into a closed query, narrows it to what the caller was granted, asks a searcher, and records that the read happened.
recobj
Package recobj is the format of a record object: the one object an ingest batch becomes, as docs/reference/bucket-contract.md specifies it.
Package recobj is the format of a record object: the one object an ingest batch becomes, as docs/reference/bucket-contract.md specifies it.
registry
Package registry holds the catalogues a deployment has registered.
Package registry holds the catalogues a deployment has registered.
s3test
Package s3test gives a test a real S3 to walk.
Package s3test gives a test a real S3 to walk.
schemagen
Package schemagen turns the record's protobuf descriptor into a JSON Schema of the form this project writes.
Package schemagen turns the record's protobuf descriptor into a JSON Schema of the form this project writes.
seal
Package seal is the format of the three signed statements of docs/reference/bucket-contract.md — seals, delegations and revocations — the keys that sign them, and how a verifier decides whether to believe one.
Package seal is the format of the three signed statements of docs/reference/bucket-contract.md — seals, delegations and revocations — the keys that sign them, and how a verifier decides whether to believe one.
telemetry
Package telemetry is the OpenTelemetry metrics and traces this repository's processes publish.
Package telemetry is the OpenTelemetry metrics and traces this repository's processes publish.
ulid
Package ulid makes the identifiers that end a record object's key.
Package ulid makes the identifiers that end a record object's key.
writer
Package writer turns records into the copies the profiles keep, and puts those copies where they are kept.
Package writer turns records into the copies the profiles keep, and puts those copies where they are kept.
Package keys holds the two kinds of key this system needs and the interfaces a deployment plugs its own behind.
Package keys holds the two kinds of key this system needs and the interfaces a deployment plugs its own behind.
Package preset reads the framework presets and composes them into profiles.
Package preset reads the framework presets and composes them into profiles.
Package query is the query service as a library: search, facets, get, export, tail and resolve over what the writer wrote, behind the grants a caller holds, with every read recorded in the trail it reads.
Package query is the query service as a library: search, facets, get, export, tail and resolve over what the writer wrote, behind the grants a caller holds, with every read recorded in the trail it reads.
sdk module
sink
natssink
Package natssink publishes records to a NATS JetStream stream, and consumes them again on the other side.
Package natssink publishes records to a NATS JetStream stream, and consumes them again on the other side.
sqssink
Package sqssink publishes records to an Amazon SQS queue, and consumes them again on the other side.
Package sqssink publishes records to an Amazon SQS queue, and consumes them again on the other side.
Package sinkserver is the receiving side of the sink: the SinkService handler the writer mounts, and the Receiver that checks a caller and a batch before handing it to a Sink.
Package sinkserver is the receiving side of the sink: the SinkService handler the writer mounts, and the Receiver that checks a caller and a batch before handing it to a Sink.
Package store is the object store the archive lives in.
Package store is the object store the archive lives in.
s3store
Package s3store keeps the archive in an S3 bucket.
Package s3store keeps the archive in an S3 bucket.
storetest
Package storetest holds the store a test writes to.
Package storetest holds the store a test writes to.
Package wire is how this repository's services speak JSON.
Package wire is how this repository's services speak JSON.
Package writer is the audit writer as a library: the thing that takes records, splits them into the copies their profiles keep, pseudonymises, and puts them into the locked archive.
Package writer is the audit writer as a library: the thing that takes records, splits them into the copies their profiles keep, pseudonymises, and puts them into the locked archive.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL