Documentation
¶
Overview ¶
Package extension provides a Forge-compatible extension for Chronicle.
Overview ¶
Extension wraps the entire Chronicle stack — chronicle.Chronicle, compliance.Engine, retention.Enforcer, and handler.API — as a single forge.Extension that can be registered into any Forge application with one call.
Lifecycle ¶
- New(opts...) — create the extension (no I/O yet)
- Extension.Register(fapp) — called by Forge during startup; builds the Chronicle instance, wires all sub-components, registers HTTP routes into the Forge router (unless disabled), and provides chronicle.Emitter in the DI container
- Extension.Start(ctx) — run store migrations (unless disabled) and launch the background schedulers: retention, and checkpointing when the checkpoints block is enabled
- Extension.Stop(ctx) — cancel the schedulers' shared context
- Extension.Health(ctx) — delegates to store.Ping for readiness probes
Store Requirement ¶
The extension requires a store.Store (the composite interface, not chronicle.Storer). Provide it via WithStore:
ext := extension.New(extension.WithStore(myStore))
Internally, [Register] calls store.NewAdapter to bridge store.Store into chronicle.Storer before constructing chronicle.New.
DI Pattern ¶
After [Register], other Forge extensions receive the chronicle.Emitter interface through Vessel (the Forge DI container):
vessel.Invoke(app.Container(), func(emitter chronicle.Emitter) {
// emit audit events without importing Chronicle internals
emitter.Info(ctx, "login", "session", id).Category("auth").Record()
})
Or access the emitter directly:
emitter := ext.Emitter()
Accessors ¶
- Extension.Chronicle — the underlying chronicle.Chronicle instance
- Extension.Emitter — chronicle.Emitter for DI injection
- Extension.ComplianceEngine — the compliance.Engine
- Extension.RetentionEnforcer — the retention.Enforcer
- Extension.API — the handler.API (21 HTTP endpoints)
- Extension.Handler — http.Handler for standalone use outside Forge
Options ¶
- WithStore(s) — required; the composite store backend
- WithBatchSize(n) — event batch size (default 100)
- WithFlushInterval(d) — batch flush interval (default 1s)
- WithCryptoErasure(enabled) — enable GDPR AES-256-GCM crypto-erasure
- WithRetentionInterval(d) — how often retention policies are enforced (default 24h; set to 0 to disable)
- WithArchiveSink(s) — sink.Sink used for archival during retention
- [WithLogger](l) — *slog.Logger (default: slog.Default())
- WithDisableRoutes(true) — skip HTTP route registration
- WithDisableMigrate(true) — skip automatic schema migration on Start
Package extension provides a Forge-compatible extension for Chronicle. It implements the forge.Extension interface so Chronicle can be mounted into any Forge app with automatic route registration, DI injection, metrics, and tracing.
Configuration can be provided programmatically via Option functions or via YAML configuration files under "extensions.chronicle" or "chronicle" keys.
Index ¶
- Constants
- Variables
- type AuthConfig
- type CheckpointConfig
- type Config
- type DashboardConfig
- type Extension
- func (e *Extension) API() *handler.API
- func (e *Extension) Chronicle() *chronicle.Chronicle
- func (e *Extension) ComplianceEngine() *compliance.Engine
- func (e *Extension) Emitter() chronicle.Emitter
- func (e *Extension) Handler() http.Handler
- func (e *Extension) Health(ctx context.Context) error
- func (e *Extension) Register(fapp forge.App) error
- func (e *Extension) RegisterContractContributor(disp *dispatcher.Dispatcher, reg dashcontract.Registry, ...) error
- func (e *Extension) RegisterRoutes(router forge.Router)
- func (e *Extension) RetentionEnforcer() *retention.Enforcer
- func (e *Extension) Start(ctx context.Context) error
- func (e *Extension) Stop(_ context.Context) error
- type KeyConfig
- type Option
- func WithArchiveSink(s sink.Sink) Option
- func WithAuth(provider string, readScopes, writeScopes, adminScopes []string) Option
- func WithBasePath(path string) Option
- func WithBatchSize(n int) Option
- func WithCheckpoints(cfg CheckpointConfig) Option
- func WithConfig(cfg Config) Option
- func WithCryptoErasure(enabled bool) Option
- func WithDashboardMutations() Optiondeprecated
- func WithDigestScheme(scheme string) Option
- func WithDisableMigrate() Option
- func WithDisableRoutes() Option
- func WithFlushInterval(d time.Duration) Option
- func WithGroveDatabase(name string) Option
- func WithGroveKV(name string) Option
- func WithKeyProvider(p keys.Provider) Option
- func WithKeyStore(ks crypto.KeyStore) Option
- func WithRequireConfig(require bool) Option
- func WithRetentionInterval(d time.Duration) Option
- func WithStore(s store.Store) Option
- func WithUnauthenticatedAPI() Option
- type TamperEvidenceConfig
Constants ¶
const ( ExtensionName = "chronicle" ExtensionDescription = "Immutable audit trail with hash chains, GDPR erasure, and compliance reporting" ExtensionVersion = "0.1.0" )
Extension metadata.
Variables ¶
var ( // ErrAuthNotConfigured is returned when the admin API would be mounted with // no authentication and the operator has not said that is intentional. // // The API can permanently destroy audit data: POST /v1/retention/enforce // purges events, DELETE /v1/retention/:id disables retention, and // POST /v1/erasures cannot be undone. Chronicle's scope check keeps tenants // out of each other's data but does not gate these operations, so an // unauthenticated mount means any caller that reaches the routes can destroy // an app's history. ErrAuthNotConfigured = errors.New( "chronicle: admin API would be mounted without authentication. " + "Set auth.provider to a registered forge auth provider (with " + "auth.read_scopes / auth.write_scopes / auth.admin_scopes), or set " + "auth.allow_unauthenticated: true to accept that any caller reaching " + "these routes can purge audit history, or disable_routes: true to not " + "mount the API at all", ) // ErrKeyStoreRequired is returned when crypto-erasure is enabled without a // key store to hold the per-subject keys. // // The key store decides whether sealed events can ever be read again, so it // cannot be defaulted: an in-memory one would silently make every event // unreadable after a restart, which looks identical to having erased every // subject. ErrKeyStoreRequired = errors.New( "chronicle: enable_crypto_erasure requires a key store; " + "pass extension.WithKeyStore(...) with a durable implementation, or " + "disable enable_crypto_erasure", ) // the forge auth registry is not in the DI container, so nothing could // enforce it. Failing here rather than mounting unprotected routes keeps a // misconfiguration from silently becoming an open API. ErrAuthProviderUnavailable = errors.New( "chronicle: auth.provider is set but no forge auth registry is " + "registered in the container; add the forge auth extension so the " + "provider can be resolved", ) // ErrKeyProviderRequired is returned when a keyed digest is configured with // no key source. ErrKeyProviderRequired = errors.New( "chronicle: tamper_evidence.digest is hmac but no key source was configured; " + "set tamper_evidence.keys.provider and path, or pass extension.WithKeyProvider", ) // ErrStoreCannotReceiveHasher is returned when tamper_evidence.digest is // hmac and the store passed to WithStore is of a type chronicle does not // recognise and cannot be given the chain after construction. // // WithStore bypasses buildStoreFromGroveDB, which is the only place a // pg/sqlite store otherwise gets WithHasher. A store that recomputes the // digest inside its own Append and never receives the configured chain // keeps re-linking every event under a default plain chain while Chronicle // writes HMAC digests -- silently, since the writes still succeed. // // Chronicle's own mongo, redis and memory backends do not recompute, so // they are accepted as they are; they were never the problem and have no // WithHasher option to point anyone at. Any other type is refused, because // whether it recomputes cannot be determined from outside and guessing // wrong produces the silent unkeyed chain above. ErrStoreCannotReceiveHasher = errors.New( "chronicle: tamper_evidence.digest is hmac but the store passed to WithStore " + "is not a store chronicle can hand the keyed chain to; give it a " + "SetHasher(*hash.Chain) method (chronicle's pg and sqlite stores have one) " + "so it re-links under the configured chain, or drop WithStore and let " + "chronicle build the store itself with the chain already wired in", ) // ErrCheckpointSignerRequired is returned when checkpoints.enabled is true // and no ed25519 signing key is configured or supplied. // // A checkpoint without a signature asserts nothing: anyone who can write to // the store could write a fabricated one claiming any hash they like. So a // deployment that turns checkpointing on without naming a key source // refuses to start rather than silently taking none, or worse, an unsigned // artifact that looks authoritative. ErrCheckpointSignerRequired = errors.New( "chronicle: checkpoints.enabled is true but no signing key is configured; " + "set checkpoints.signer.provider and checkpoints.signer.path, or pass " + "extension.WithKeyProvider", ) // ErrCheckpointsUnsupportedByStore is returned when checkpoints.enabled is // true and the resolved store cannot persist checkpoints durably enough to // be a root of trust. // // store/redis is the concrete case today: it is positioned as a // read-through cache layer, and a checkpoint that can be evicted proves // nothing. Starting anyway and simply taking no checkpoints would be // exactly the silent gap signed checkpoints exist to close, so the // extension refuses to start instead. ErrCheckpointsUnsupportedByStore = errors.New( "chronicle: checkpoints.enabled is true but the resolved store does not " + "support checkpoints; use a backend that can persist them durably " + "(pg, sqlite, mongo, or memory), or set checkpoints.enabled: false", ) )
Errors returned while configuring the extension.
Functions ¶
This section is empty.
Types ¶
type AuthConfig ¶
type AuthConfig struct {
// Provider is the name of a forge auth provider registered in the auth
// extension's registry. Empty means no authentication.
Provider string `json:"provider" mapstructure:"provider" yaml:"provider"`
// ReadScopes are required to observe: list and fetch events, stats, reports,
// policies and archives, and verify a chain.
ReadScopes []string `json:"read_scopes" mapstructure:"read_scopes" yaml:"read_scopes"`
// WriteScopes are required to create records that destroy nothing: saving a
// retention policy, generating a compliance report.
WriteScopes []string `json:"write_scopes" mapstructure:"write_scopes" yaml:"write_scopes"`
// AdminScopes are required for the irreversible operations: enforcing
// retention, deleting a policy, and requesting an erasure.
AdminScopes []string `json:"admin_scopes" mapstructure:"admin_scopes" yaml:"admin_scopes"`
// AllowUnauthenticated mounts the API with no authentication at all.
//
// Only appropriate when something in front of Chronicle already
// authenticates every request, or in local development. Setting it is an
// explicit acknowledgement that any caller reaching these routes can purge
// audit history.
AllowUnauthenticated bool `json:"allow_unauthenticated" mapstructure:"allow_unauthenticated" yaml:"allow_unauthenticated"`
}
AuthConfig configures authentication and authorisation for the admin API.
The API exposes operations that permanently destroy audit data — enforcing retention purges events, deleting a policy silently disables retention, and an erasure request cannot be undone. Chronicle's per-request scope check only stops one tenant reaching another's data; it does nothing to stop an authenticated caller from destroying their own app's history. So the extension refuses to start unless either Provider is set or AllowUnauthenticated is explicitly true.
func (AuthConfig) Configured ¶
func (c AuthConfig) Configured() bool
Configured reports whether an auth provider was named.
func (AuthConfig) Validate ¶
func (c AuthConfig) Validate(routesEnabled bool) error
Validate checks that the operator made a deliberate choice about API access.
type CheckpointConfig ¶
type CheckpointConfig struct {
// Enabled turns on periodic checkpointing.
Enabled bool `json:"enabled" mapstructure:"enabled" yaml:"enabled"`
// EveryEvents takes a checkpoint once a stream has gained this many events.
EveryEvents int `json:"every_events" mapstructure:"every_events" yaml:"every_events"`
// EveryInterval takes one at least this often, whatever the volume.
//
// The interval matters more than it looks: the window between checkpoints
// is exactly the span an attacker can still rewrite, so a quiet stream that
// never reaches EveryEvents would otherwise sit unprotected indefinitely.
EveryInterval time.Duration `json:"every_interval" mapstructure:"every_interval" yaml:"every_interval"`
// Signer configures where the ed25519 signing key comes from.
Signer KeyConfig `json:"signer" mapstructure:"signer" yaml:"signer"`
}
CheckpointConfig controls periodic signed checkpoints.
A checkpoint is a signed statement about where a stream's chain stood. Keying the digest stops per-event forgery; a checkpoint is what makes a later rewrite of already-recorded events provable, because the assertion cannot be restated without the signing key.
func (CheckpointConfig) Validate ¶
func (c CheckpointConfig) Validate(hasSigner bool) error
Validate checks that enabled checkpointing has somewhere to get a key.
hasSigner is a runtime fact the config alone cannot see: a keys.Provider supplied programmatically via WithKeyProvider satisfies checkpoints.signer the same way it satisfies tamper_evidence.keys, but nothing in Signer's fields would show that. A no-argument Validate would wrongly reject that documented path; see AuthConfig.Validate(routesEnabled bool) for the same shape applied to a different runtime fact.
Signer.Provider is checked against the exact set this extension implements ("", meaning take one from WithKeyProvider, or "file") rather than merely requiring it be non-empty. A value like "kms", "vault", a typo, or a capitalised "File" used to pass this check, leave no provider resolved, and reach checkpoint.NewEd25519Signer(nil) -- which then panicked the process on the scheduler's first tick instead of failing here at Register.
type Config ¶
type Config struct {
// DisableRoutes prevents HTTP route registration.
DisableRoutes bool `json:"disable_routes" mapstructure:"disable_routes" yaml:"disable_routes"`
// DisableMigrate prevents auto-migration on start.
DisableMigrate bool `json:"disable_migrate" mapstructure:"disable_migrate" yaml:"disable_migrate"`
// BasePath is the URL prefix for chronicle routes (default: "/chronicle").
BasePath string `json:"base_path" mapstructure:"base_path" yaml:"base_path"`
// BatchSize is the event batch size for the Chronicle emitter.
BatchSize int `json:"batch_size" mapstructure:"batch_size" yaml:"batch_size"`
// FlushInterval is the batch flush interval.
FlushInterval time.Duration `json:"flush_interval" mapstructure:"flush_interval" yaml:"flush_interval"`
// RetentionInterval sets how often retention policies are enforced.
// Set to 0 to disable automatic retention.
RetentionInterval time.Duration `json:"retention_interval" mapstructure:"retention_interval" yaml:"retention_interval"`
// EnableCryptoErasure enables GDPR crypto-erasure support. Requires a key
// store; see [WithKeyStore] and [ErrKeyStoreRequired].
EnableCryptoErasure bool `json:"enable_crypto_erasure" mapstructure:"enable_crypto_erasure" yaml:"enable_crypto_erasure"`
// GroveDatabase is the name of a grove.DB registered in the DI container.
// When set, the extension resolves this named database and auto-constructs
// the appropriate store based on the driver type (pg/sqlite/mongo).
// When empty and WithGroveDatabase was called, the default (unnamed) DB is used.
GroveDatabase string `json:"grove_database" mapstructure:"grove_database" yaml:"grove_database"`
// GroveKV is the name of a grove/kv.Store registered in the DI container.
// When set, the extension resolves this named KV store and constructs
// a Redis-backed Chronicle store. When empty and WithGroveKV was called,
// the default (unnamed) KV store is used.
GroveKV string `json:"grove_kv" mapstructure:"grove_kv" yaml:"grove_kv"`
// Auth controls who may call the admin API.
Auth AuthConfig `json:"auth" mapstructure:"auth" yaml:"auth"`
// TamperEvidence selects how the hash chain resists rewriting: plain
// (default) or HMAC-keyed. See [TamperEvidenceConfig].
TamperEvidence TamperEvidenceConfig `json:"tamper_evidence" mapstructure:"tamper_evidence" yaml:"tamper_evidence"`
// Checkpoints controls periodic signed checkpoints over each stream's
// chain. See [CheckpointConfig]. Leaving this unset (the default) records
// none: nothing about a keyed digest replaces the need for a checkpoint,
// since an attacker with write access to the store can rewrite both the
// event and its stream's pin.
Checkpoints CheckpointConfig `json:"checkpoints" mapstructure:"checkpoints" yaml:"checkpoints"`
// Dashboard configures how the contract dashboard scopes a session that
// arrives with no app or tenant claims. See [DashboardConfig].
Dashboard DashboardConfig `json:"dashboard" mapstructure:"dashboard" yaml:"dashboard"`
// DashboardMutations has no effect.
//
// Deprecated: dashboard writes are governed by the scopes the contract
// declares on each command, chronicle.write and chronicle.admin, held by
// the signed-in user. The field stays so a config file that still sets
// dashboard_mutations loads. It will be removed in a later release.
DashboardMutations bool `json:"dashboard_mutations" mapstructure:"dashboard_mutations" yaml:"dashboard_mutations"`
// RequireConfig requires config to be present in YAML files.
// If true and no config is found, Register returns an error.
RequireConfig bool `json:"-" yaml:"-"`
}
Config holds the Chronicle extension configuration. Fields can be set programmatically via Option functions or loaded from YAML configuration files (under "extensions.chronicle" or "chronicle" keys).
func DefaultConfig ¶
func DefaultConfig() Config
DefaultConfig returns a Config with sensible defaults.
type DashboardConfig ¶
type DashboardConfig struct {
// AppID is the app a session belongs to when its claims name none
// (chronicle.dashboard.app_id).
AppID string `json:"app_id" mapstructure:"app_id" yaml:"app_id"`
// TenantID is the tenant a session is narrowed to when its app came from
// AppID and its claims name no tenant (chronicle.dashboard.tenant_id). It
// applies to that config session only: a session that claims its app, even
// this one, never takes it. It cannot be set without AppID. Empty leaves
// the config session app-wide.
TenantID string `json:"tenant_id" mapstructure:"tenant_id" yaml:"tenant_id"`
}
DashboardConfig names the app and tenant a dashboard session takes when its claims carry none.
The dashboard scopes every request from the signed-in user's claims. Until the auth provider fills those in, a single-app deployment has nothing to scope by and every intent is refused. Naming the app here lets it run. A claim that is present but unreadable is still refused, and a request with no signed-in user never takes either value.
func (DashboardConfig) Validate ¶
func (c DashboardConfig) Validate() error
Validate refuses values the dashboard would store and compare exactly as written: edge whitespace, control characters, or a tenant with no app. Failing at startup beats a dashboard that boots and shows nothing.
type Extension ¶
type Extension struct {
*forge.BaseExtension
// contains filtered or unexported fields
}
Extension adapts Chronicle as a Forge extension. It implements the forge.Extension interface so Chronicle can be mounted into any Forge app.
func (*Extension) ComplianceEngine ¶
func (e *Extension) ComplianceEngine() *compliance.Engine
ComplianceEngine returns the compliance engine.
func (*Extension) Emitter ¶
Emitter returns the Chronicle Emitter for DI injection. Other extensions use this to emit audit events without importing Chronicle internals.
func (*Extension) Handler ¶
Handler returns the HTTP handler for all API routes. Convenience for standalone use outside Forge.
func (*Extension) Health ¶
Health implements forge.Extension.
func (*Extension) Register ¶
Register implements forge.Extension. It initializes the store, creates the Chronicle instance, wires up all components, and optionally registers HTTP routes into the Forge router.
func (*Extension) RegisterContractContributor ¶
func (e *Extension) RegisterContractContributor( disp *dispatcher.Dispatcher, reg dashcontract.Registry, wreg dashcontract.WardenRegistry, ) error
RegisterContractContributor implements dashboard.ContractContributorAware. It registers the chronicle contract contributor against the dashboard's own dispatcher and registries. The dashboard discovers it by asserting this interface alone, so the extension needs no other dashboard interface.
An extension that has not been through Register has no store to serve from. That is logged and skipped rather than returned, so one unstarted extension cannot fail the whole dashboard.
func (*Extension) RegisterRoutes ¶
RegisterRoutes registers all Chronicle API routes into a Forge router.
func (*Extension) RetentionEnforcer ¶
RetentionEnforcer returns the retention enforcer.
type KeyConfig ¶
type KeyConfig struct {
// Provider is "file", or empty to take a keys.Provider from [WithKeyProvider].
Provider string `json:"provider" mapstructure:"provider" yaml:"provider"`
// Path is the keyset file, for the file provider.
Path string `json:"path" mapstructure:"path" yaml:"path"`
}
KeyConfig points at key material.
type Option ¶
type Option func(*Extension)
Option configures the Chronicle Forge extension.
func WithArchiveSink ¶
WithArchiveSink sets the archive sink for retention.
func WithAuth ¶
WithAuth protects the admin API with a registered forge auth provider, requiring the given scopes per operation class.
Classes are graded by blast radius: read observes, write creates records that destroy nothing, and admin covers the irreversible operations (enforcing retention, deleting a policy, requesting an erasure). Pass nil for a class to require authentication without any particular scope.
extension.WithAuth("jwt",
[]string{"chronicle:read"},
[]string{"chronicle:write"},
[]string{"chronicle:admin"},
)
func WithBasePath ¶
WithBasePath sets the URL prefix for chronicle routes.
func WithCheckpoints ¶
func WithCheckpoints(cfg CheckpointConfig) Option
WithCheckpoints enables periodic signed checkpoints over each stream's chain.
A checkpoint is a signed statement about where a stream's chain stood; it is what makes a later rewrite of already-recorded events provable, because the assertion cannot be restated without the signing key. Enabling this with no signing key source -- neither cfg.Signer here nor WithKeyProvider -- fails Register with ErrCheckpointSignerRequired. Enabling it against a store that cannot persist checkpoints durably (store/redis, deliberately) fails Register with ErrCheckpointsUnsupportedByStore.
func WithConfig ¶
WithConfig sets the Forge extension configuration.
func WithCryptoErasure ¶
WithCryptoErasure enables GDPR crypto-erasure support.
Requires WithKeyStore; without one startup fails with ErrKeyStoreRequired. With both, each subject's metadata, reason and IP are encrypted before being hashed and stored, the store is wrapped so reads come back decrypted, and an erasure request destroys the subject's key. See crypto.Sealer for exactly which fields are covered and which deliberately are not.
func WithDashboardMutations
deprecated
func WithDashboardMutations() Option
WithDashboardMutations has no effect.
Deprecated: dashboard writes are governed by the scopes the contract declares on each command, chronicle.write and chronicle.admin, held by the signed-in user. This option stays so existing callers still compile. It will be removed in a later release.
func WithDigestScheme ¶
WithDigestScheme sets the digest scheme, "plain" or "hmac".
func WithDisableMigrate ¶
func WithDisableMigrate() Option
WithDisableMigrate prevents auto-migration on start.
func WithDisableRoutes ¶
func WithDisableRoutes() Option
WithDisableRoutes prevents HTTP route registration.
func WithFlushInterval ¶
WithFlushInterval sets the batch flush interval.
func WithGroveDatabase ¶
WithGroveDatabase sets the name of the grove.DB to resolve from the DI container. The extension will auto-construct the appropriate store backend (postgres/sqlite/mongo) based on the grove driver type. Pass an empty string to use the default (unnamed) grove.DB.
func WithGroveKV ¶
WithGroveKV sets the name of the grove/kv.Store to resolve from the DI container. The extension will construct a Redis-backed Chronicle store from the KV store. Pass an empty string to use the default (unnamed) grove/kv.Store.
func WithKeyProvider ¶
WithKeyProvider supplies key material directly, for deployments resolving keys from a KMS rather than a file.
func WithKeyStore ¶
WithKeyStore supplies the per-subject key store used by crypto-erasure.
Required when EnableCryptoErasure is set. The key store's durability decides whether sealed events can ever be read again: losing it is indistinguishable from erasing every subject, so an in-memory store is only suitable for tests.
func WithRequireConfig ¶
WithRequireConfig requires config to be present in YAML files. If true and no config is found, Register returns an error.
func WithRetentionInterval ¶
WithRetentionInterval sets how often retention policies are enforced. Set to 0 to disable automatic retention.
func WithStore ¶
WithStore provides the composite store for the extension.
Bypasses the extension's own store construction (WithGroveDatabase / WithGroveKV / auto-discovery), so under tamper_evidence.digest: hmac, Register needs to give this store the configured chain itself. It does so via an optional SetHasher(*hash.Chain) method, which both pgstore.Store and sqlitestore.Store implement because both recompute the digest inside Append.
Chronicle's mongo, redis and memory stores persist the digest Chronicle computed and never recompute, so they are accepted without one. Any other type fails Register with ErrStoreCannotReceiveHasher, because whether it recomputes cannot be determined from outside and guessing wrong is how a deployment ends up believing its chain is keyed while the store quietly re-links it unkeyed. Give such a store a SetHasher(*hash.Chain) method.
func WithUnauthenticatedAPI ¶
func WithUnauthenticatedAPI() Option
WithUnauthenticatedAPI mounts the admin API with no authentication.
Only appropriate when something in front of Chronicle already authenticates every request, or in local development. Without this (or WithAuth) the extension refuses to start, because the API can purge audit history. See ErrAuthNotConfigured.
type TamperEvidenceConfig ¶
type TamperEvidenceConfig struct {
// Digest is "plain" (default) or "hmac".
Digest string `json:"digest" mapstructure:"digest" yaml:"digest"`
// Keys configures where the HMAC key comes from.
Keys KeyConfig `json:"keys" mapstructure:"keys" yaml:"keys"`
}
TamperEvidenceConfig selects how the hash chain resists rewriting.
The default leaves the chain unkeyed, which is reproducible by anyone who can write to the store. Setting digest to hmac makes a digest depend on key material the database does not hold.
func (TamperEvidenceConfig) Validate ¶
func (c TamperEvidenceConfig) Validate() error
Validate checks that a keyed digest has somewhere to get its key.
Refusing here rather than at the first event means a misconfigured deployment fails at startup instead of writing unkeyed digests an operator believes are keyed.