compliance

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 13 Imported by: 0

Documentation

Overview

Package compliance is the shared machinery for hardening-benchmark auditing (CAPABILITY_SPEC domain 10). It is deliberately engine-agnostic at its core: benchmarks are *data* (a control catalogue with framework mappings), and the per-control pass/fail logic is injected by callers via an Assessor. That separation lets dockerbench and kubebench share one runner, one aggregation model, one waiver/drift/narrative layer, and one framework-mapping vocabulary while owning only their own evidence gathering and check functions.

The engine only enters through finding.go, which projects a compliance.Report into the unified Finding stream. Everything else here has no engine import so it stays reusable and trivially testable.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Findings

func Findings(moduleName string, rep *Report) []engine.Finding

Findings projects a report into engine findings for the given module. It emits one leading INFO summary (score + counts, always present so the module shows up in a scan even when fully compliant) followed by a finding per non-PASS control. Effective status is used so waived controls report as INFO with the waiver reason rather than as failures. Output order follows the report's deterministic control ordering.

func FrameworkCoverage

func FrameworkCoverage(controls []Control) map[Framework]int

FrameworkCoverage summarizes, across a whole report, which frameworks are exercised and how many controls touch each. It powers the "one scan feeds many audits" story in the narrative and export. Keys are returned sorted by caller (see SortedFrameworks) so callers control iteration order.

func Render

func Render(rep *ComplianceReport, format ExportFormat) ([]byte, error)

Render serializes a ComplianceReport in the requested format. Output is deterministic for a fixed report (ids derive from content, not randomness).

Types

type Assessment

type Assessment struct {
	Status   Status
	Evidence string // human-readable: what we observed and why it passed/failed
	Actual   string // machine-readable observed value (empty when N/A)
}

Assessment is what an Assessor returns for a single control.

type Assessor

type Assessor func(c Control) Assessment

Assessor evaluates one control against already-collected evidence. Modules close over their evidence and dispatch by control ID. Assessors must be pure: same evidence in, same assessment out.

type AttestationRegister

type AttestationRegister struct {
	Entries []RegisterEntry `json:"entries"`
}

AttestationRegister holds the waivers (for accepted Failed controls) and attestations (for manual/inherited controls). Both carry an owner and an expiry so accepted risk and manual evidence are auditable and time-bounded — never a permanent silent pass (COMPLIANCE_PLAN §4.4).

func LoadRegister

func LoadRegister(path string) (*AttestationRegister, error)

LoadRegister reads a JSON register from disk. A missing path yields an empty register (no waivers/attestations), not an error.

func (*AttestationRegister) Attestation

func (r *AttestationRegister) Attestation(framework, id string, now time.Time) (RegisterEntry, bool)

Attestation returns a valid (unexpired) attestation for a control, if one exists.

func (*AttestationRegister) Waiver

func (r *AttestationRegister) Waiver(framework, id string, now time.Time) (RegisterEntry, bool)

Waiver returns a valid (unexpired) waiver for a control, if one exists.

type Benchmark

type Benchmark struct {
	Code     string // short slug used in rule IDs, e.g. "docker", "k8s"
	Name     string // human title, e.g. "CIS Docker Benchmark"
	Version  string // benchmark version, e.g. "1.6.0"
	Profile  string // active profile, e.g. "self-managed", "eks"
	Controls []Control
}

Benchmark is a named, versioned control catalogue plus the profile it targets (e.g. self-managed vs a managed-Kubernetes variant). Controls are stored in the order the runner should evaluate and report them; Run re-sorts by control ID for a stable, human-sensible ordering regardless of source order.

func (Benchmark) Run

func (b Benchmark) Run(assess Assessor) *Report

Run evaluates every control in the benchmark with the assessor and returns a deterministic report: results are sorted by control id with a human-aware numeric comparison, so re-running on identical evidence is byte-identical.

type Catalog

type Catalog struct {
	// contains filtered or unexported fields
}

Catalog is the unified, normalized control catalog: every loaded pack keyed by framework, plus the crosswalk graph built from the packs' maps_to.

func LoadEmbeddedPacks

func LoadEmbeddedPacks() (*Catalog, error)

LoadEmbeddedPacks loads the packs compiled into the binary.

func LoadPacksFromDir

func LoadPacksFromDir(dir string) (*Catalog, error)

LoadPacksFromDir loads packs from a directory (for user-supplied or updated packs), falling back to nothing if the directory is empty.

func (*Catalog) Controls

func (c *Catalog) Controls(framework string) []PackControl

Controls returns a framework's controls (empty if the framework is unknown).

func (*Catalog) Frameworks

func (c *Catalog) Frameworks() []string

Frameworks lists loaded frameworks in load order.

func (*Catalog) MappedFrameworks

func (c *Catalog) MappedFrameworks(framework, id string) []crosswalkEdge

MappedFrameworks returns the set of frameworks a control crosswalks to.

func (*Catalog) Pack

func (c *Catalog) Pack(framework string) *Pack

Pack returns the pack for a framework, or nil.

type Change

type Change string

Change classifies how one control moved between two runs.

const (
	// ChangeRegressed: was compliant (PASS), now failing/warning — the alarming case.
	ChangeRegressed Change = "regressed"
	// ChangeFixed: was failing/warning, now PASS — a win to celebrate in the narrative.
	ChangeFixed Change = "fixed"
	// ChangeNew: control exists now but not in the baseline (catalogue grew or first run).
	ChangeNew Change = "new"
	// ChangeRemoved: control was in the baseline but not now (profile change).
	ChangeRemoved Change = "removed"
	// ChangeUnchanged: same effective status in both runs.
	ChangeUnchanged Change = "unchanged"
)

type ComplianceReport

type ComplianceReport struct {
	Target      string          `json:"target"`
	GeneratedAt string          `json:"generated_at"`
	ToolVersion string          `json:"tool_version"`
	Frameworks  []string        `json:"frameworks"`
	Results     []ControlResult `json:"results"`
}

ComplianceReport is the full result of a compliance run.

func RunPacks

func RunPacks(cat *Catalog, frameworks []string, rep *engine.Report, opts RunOptions) *ComplianceReport

RunPacks evaluates the enabled frameworks' controls against an engine Report and returns per-control dispositions with evidence and crosswalk. It is a pure function of (catalog, report, options): the same inputs always produce the same report.

type Control

type Control struct {
	// ID is the native benchmark control number, e.g. "2.1" for CIS Docker.
	ID string `json:"id"`
	// Title is the one-line requirement, phrased as the desired end state.
	Title string `json:"title"`
	// Section groups controls for reporting, e.g. "Docker daemon configuration".
	Section string `json:"section,omitempty"`
	// Level is the CIS profile (Level 1 baseline vs Level 2 hardening).
	Level Level `json:"level"`
	// Scored is false for "manual"/informational controls that cannot be
	// auto-evaluated with confidence; those surface as INFO for human review.
	Scored bool `json:"scored"`
	// Description explains what the control protects against (the "why").
	Description string `json:"description,omitempty"`
	// Remediation is guided prose an operator can follow to fix a failure.
	Remediation string `json:"remediation,omitempty"`
	// Frameworks maps this control onto other compliance frameworks. Every
	// control MUST carry at least one non-CIS mapping (see benchmark tests).
	Frameworks []FrameworkRef `json:"frameworks,omitempty"`
	// Fix is an optional structured, agent-appliable remediation bundle. It is
	// advisory metadata only and never changes the deterministic scan result.
	Fix *Fix `json:"fix,omitempty"`
}

Control is one benchmark requirement. A Benchmark is a catalogue of these, and framework mappings live on the control so a single scan can feed many audits (CIS + NIST + STIG + …) without hand-mapping downstream.

func (Control) References

func (c Control) References(benchmarkName string) []string

References renders a control's framework mappings into the flat []string the engine.Finding.References field expects, prefixed with the native CIS control so downstream (SARIF helpUri, tables) always shows the benchmark id first. Output is deterministic: native CIS id, then authored order, de-duplicated.

type ControlResult

type ControlResult struct {
	Framework   string              `json:"framework"`
	Version     string              `json:"version"`
	ID          string              `json:"id"`
	Title       string              `json:"title"`
	Assessment  string              `json:"assessment"`
	Disposition Disposition         `json:"disposition"`
	Evidence    Evidence            `json:"evidence"`
	MapsTo      map[string][]string `json:"maps_to,omitempty"`
	Remediation string              `json:"remediation,omitempty"`
}

ControlResult is one control's assessment across the run, including the crosswalk to every framework it satisfies.

func Gaps

func Gaps(rep *ComplianceReport) []ControlResult

Gaps returns the controls that are not fully resolved: Failed, unresolved Manual, or Unknown — the auditor's to-do list.

type Disposition

type Disposition string

Disposition is a control's resolved state per the satisfaction contract (COMPLIANCE_PLAN §3). No control may remain Unknown at release.

const (
	DispSatisfied     Disposition = "Satisfied"
	DispFailed        Disposition = "Failed"
	DispWaived        Disposition = "Waived"
	DispNotApplicable Disposition = "NotApplicable"
	DispManual        Disposition = "Manual" // awaiting attestation
	DispUnknown       Disposition = "Unknown"
)

func (Disposition) Resolved

func (d Disposition) Resolved() bool

Resolved reports whether a disposition counts toward coverage (anything but Unknown and un-attested Manual).

type Drift

type Drift struct {
	Benchmark string       `json:"benchmark"`
	Regressed []DriftEntry `json:"regressed,omitempty"`
	Fixed     []DriftEntry `json:"fixed,omitempty"`
	New       []DriftEntry `json:"new,omitempty"`
	Removed   []DriftEntry `json:"removed,omitempty"`
	ScoreFrom int          `json:"score_from"`
	ScoreTo   int          `json:"score_to"`
}

Drift is the per-control delta between an earlier baseline report and the current one. It is the raw material for "flag the specific drifted control" and for the continuous-compliance narrative's since-last-run section.

func Diff

func Diff(baseline, current *Report) *Drift

Diff compares a baseline report against the current one and returns the drift. Comparison is on effective status (waivers demoted) and keyed by control id, so it is stable across reorderings and deterministic. A nil baseline yields a drift where every current control is "new" (the honest first-run answer).

func (*Drift) HasDrift

func (d *Drift) HasDrift() bool

HasDrift reports whether anything meaningful moved (ignores unchanged and the first-run "everything is new" case, which callers usually want to treat as a baseline rather than drift).

type DriftEntry

type DriftEntry struct {
	ControlID string `json:"control_id"`
	Title     string `json:"title"`
	Change    Change `json:"change"`
	From      Status `json:"from"`
	To        Status `json:"to"`
}

DriftEntry is one control's movement between baseline and current.

type Evidence

type Evidence struct {
	Check     string `json:"check,omitempty"`
	Observed  string `json:"observed"`
	Verdict   string `json:"verdict"`
	Timestamp string `json:"timestamp"`
	Tool      string `json:"tool"`
	Target    string `json:"target,omitempty"`
}

Evidence is the machine-readable proof attached to every assessed control.

type ExportFormat

type ExportFormat string

ExportFormat names a supported compliance report serialization.

const (
	ExportJSON  ExportFormat = "json"
	ExportCSV   ExportFormat = "csv"
	ExportOSCAL ExportFormat = "oscal" // NIST OSCAL assessment-results (subset)
	ExportMD    ExportFormat = "md"    // human auditor packet
)

func ExportFormats

func ExportFormats() []ExportFormat

ExportFormats lists the supported formats.

type Fix

type Fix struct {
	// Kind is the mechanism: "daemon.json", "file-perm", "sysctl",
	// "kubelet-flag", "apiserver-flag", "manifest". Consumers switch on it.
	Kind string `json:"kind"`
	// Target is the path or object the fix applies to (e.g. "/etc/docker/daemon.json").
	Target string `json:"target"`
	// Snippet is the concrete change (a JSON fragment, a flag, a mode string).
	Snippet string `json:"snippet"`
	// DryRun describes the effect in one line for a human-readable diff preview.
	DryRun string `json:"dry_run,omitempty"`
}

Fix is a structured remediation an automation agent can propose or apply with a dry-run diff. It is intentionally declarative (what to set, where) rather than an executable script, so a human or agent reviews before acting.

type Framework

type Framework string

Framework identifies a compliance regime a control can be mapped onto. The point of the abstraction is one scan → many audits: a single control failure simultaneously satisfies the evidence needs of CIS, NIST 800-190, DISA STIG, and the higher-level regimes those roll up into.

const (
	FrameworkCIS     Framework = "CIS"          // the native benchmark
	FrameworkNIST190 Framework = "NIST-800-190" // Application Container Security Guide
	FrameworkNIST53  Framework = "NIST-800-53"  // security & privacy controls
	FrameworkSTIG    Framework = "DISA-STIG"    // DoD Security Technical Implementation Guide
	FrameworkPCI     Framework = "PCI-DSS-4.0"  // payment card industry
	FrameworkNSACISA Framework = "NSA-CISA-K8s" // Kubernetes Hardening Guidance
)

func SortedFrameworks

func SortedFrameworks(cov map[Framework]int) []Framework

SortedFrameworks returns the frameworks in a coverage map in a stable order.

type FrameworkCoverageStat

type FrameworkCoverageStat struct {
	Framework     string  `json:"framework"`
	Total         int     `json:"total"`
	Satisfied     int     `json:"satisfied"`
	Failed        int     `json:"failed"`
	Waived        int     `json:"waived"`
	NotApplicable int     `json:"not_applicable"`
	Manual        int     `json:"manual"` // unresolved manual (needs attestation)
	Unknown       int     `json:"unknown"`
	Resolved      int     `json:"resolved"` // any state but Unknown/unresolved-Manual
	CoveragePct   float64 `json:"coverage_pct"`
	AutomatedPct  float64 `json:"automated_pct"`
}

FrameworkCoverageStat summarizes how completely one framework is satisfied.

func Coverage

func Coverage(rep *ComplianceReport) []FrameworkCoverageStat

Coverage aggregates a ComplianceReport into per-framework KPIs (COMPLIANCE_PLAN §5/§8): Coverage = resolved ÷ total; Automation rate = automatically-satisfied ÷ total. Frameworks are returned sorted for deterministic output.

type FrameworkLine

type FrameworkLine struct {
	Framework Framework `json:"framework"`
	Controls  int       `json:"controls"`
}

FrameworkLine is one row of framework coverage for dashboards.

type FrameworkRef

type FrameworkRef struct {
	Framework Framework `json:"framework"`
	ID        string    `json:"id"`
}

FrameworkRef is a single mapping: "this control corresponds to <ID> in <Framework>". A control may carry several. Kept as an explicit slice (not a map) so ordering is authored and deterministic.

func Ref

func Ref(fw Framework, id string) FrameworkRef

Ref is a terse constructor used when authoring benchmark catalogues in the dockerbench/kubebench packages.

type Level

type Level int

Level is the CIS benchmark profile a control belongs to. Level 1 is the baseline every environment should meet; Level 2 is defense-in-depth that can impose operational cost. Selecting a profile lets an auditor scope a run.

const (
	// LevelUnset means the control declares no profile (treated as Level 1).
	LevelUnset Level = iota
	// Level1 is the recommended baseline (minimal operational impact).
	Level1
	// Level2 is stricter, higher-assurance hardening.
	Level2
)

func (Level) MarshalJSON

func (l Level) MarshalJSON() ([]byte, error)

MarshalJSON renders levels as "L1"/"L2" so JSON exports read like a CIS profile column rather than an opaque integer.

func (Level) String

func (l Level) String() string

type Narrative

type Narrative struct {
	Benchmark   string          `json:"benchmark"`
	Version     string          `json:"version"`
	Profile     string          `json:"profile,omitempty"`
	GeneratedAt string          `json:"generated_at"`
	Score       int             `json:"score"`
	Counts      map[string]int  `json:"counts"`
	Frameworks  []FrameworkLine `json:"frameworks"`
	Drift       *Drift          `json:"drift,omitempty"`
	TopFailures []NarrativeItem `json:"top_failures,omitempty"`
	Expiring    []Waiver        `json:"expiring_waivers,omitempty"`
}

Narrative is the structured "state of compliance" summary. It marshals to JSON for machine consumers and renders to prose via Text() for humans.

func BuildNarrative

func BuildNarrative(rep *Report, opts NarrativeOptions) *Narrative

BuildNarrative assembles the narrative from a report. It performs no I/O and reads no ambient state, so identical inputs yield an identical narrative.

func (*Narrative) Text

func (n *Narrative) Text() string

Text renders the narrative as a human-readable brief suitable for a status post. It is deterministic and side-effect free.

type NarrativeItem

type NarrativeItem struct {
	ControlID   string         `json:"control_id"`
	Title       string         `json:"title"`
	Status      Status         `json:"status"`
	Section     string         `json:"section,omitempty"`
	Remediation string         `json:"remediation,omitempty"`
	Frameworks  []FrameworkRef `json:"frameworks,omitempty"`
	Fix         *Fix           `json:"fix,omitempty"`
}

NarrativeItem is one highlighted control (a failure or a fix) with the machine-consumable context an agent needs to reason about or act on it.

type NarrativeOptions

type NarrativeOptions struct {
	Now         time.Time // injected timestamp (required for determinism)
	Baseline    *Report   // prior run, for the since-last-run drift section
	Waivers     *Waivers  // to surface soon-to-expire exceptions
	MaxFailures int       // cap on TopFailures (0 ⇒ default of 5)
}

NarrativeOptions parameterizes narrative generation. Now is mandatory and is the injected clock that keeps output deterministic in tests and reproducible in production.

type Pack

type Pack struct {
	Framework string        `json:"framework"` // e.g. "cis-docker", "pci-dss-4.0.1"
	Version   string        `json:"version"`
	Title     string        `json:"title"`
	SourceURL string        `json:"source_url"`
	Controls  []PackControl `json:"controls"`
}

Pack is one framework version as data. Adding or updating a framework is a reviewed data change, never a code change.

type PackControl

type PackControl struct {
	ID         string `json:"id"`
	Title      string `json:"title"`
	Statement  string `json:"statement,omitempty"`
	Level      int    `json:"level,omitempty"`
	Assessment string `json:"assessment"`       // automated|manual|inherited|hybrid
	Module     string `json:"module,omitempty"` // engine module that owns Check
	Check      string `json:"check,omitempty"`  // engine finding RuleID that evaluates it
	// PresentMeans flips check polarity: for most controls a matching finding is
	// a violation ("fail", the default); for evidence controls (e.g. "an SBOM was
	// generated") the finding's presence proves satisfaction ("pass").
	PresentMeans string              `json:"present_means,omitempty"`
	Expected     string              `json:"expected,omitempty"`
	Remediation  string              `json:"remediation,omitempty"`
	MapsTo       map[string][]string `json:"maps_to,omitempty"` // framework -> control ids
	References   []string            `json:"references,omitempty"`
}

PackControl is a single control: how it is assessed, which automated check evaluates it, and the crosswalk to every other framework it maps to.

type RegisterEntry

type RegisterEntry struct {
	Kind          string    `json:"kind"` // "waiver" | "attestation"
	Framework     string    `json:"framework"`
	ControlID     string    `json:"control_id"`
	Owner         string    `json:"owner"`
	Justification string    `json:"justification,omitempty"` // waivers
	Evidence      string    `json:"evidence,omitempty"`      // attestations (policy link, SOC report)
	Expires       time.Time `json:"expires"`
}

RegisterEntry is one waiver or attestation for a specific control.

type Report

type Report struct {
	Benchmark string   `json:"benchmark"`
	Version   string   `json:"version"`
	Profile   string   `json:"profile,omitempty"`
	Results   []Result `json:"results"`
}

Report is the aggregated outcome of running a benchmark. It is safe to JSON-marshal directly as an auditor-ready, control-by-control evidence export.

func (*Report) Counts

func (r *Report) Counts() map[Status]int

Counts tallies results by effective status (waivers demoted). The map always contains all five statuses so callers need not check for missing keys.

func (*Report) Failing

func (r *Report) Failing() []Result

Failing returns the results that still count against the score (FAIL, not waived), in report order — the actionable worklist.

func (*Report) FailsAt

func (r *Report) FailsAt(warnAlso bool) bool

FailsAt reports whether the report should fail a CI/CD gate: any effective FAIL fails; if warnAlso is set, effective WARN fails too. Waived controls never trip the gate.

func (*Report) Score

func (r *Report) Score() int

Score is the compliance pass rate over *scorable* results — those that landed on PASS or FAIL (effective). WARN and INFO are excluded from the denominator because they are advisory, not a hard pass/fail. Returns 0..100; a report with nothing scorable scores 100 (nothing failed).

type Result

type Result struct {
	Control  Control `json:"control"`
	Status   Status  `json:"status"`
	Evidence string  `json:"evidence,omitempty"`
	Actual   string  `json:"actual,omitempty"`
	// Waived is set when a matching, unexpired waiver suppressed a WARN/FAIL.
	// The original Status is preserved above; consumers gate on !Waived.
	Waived       bool   `json:"waived,omitempty"`
	WaiverReason string `json:"waiver_reason,omitempty"`
}

Result pairs a control with the outcome of assessing it. The full Control is embedded (not just its id) so an exported report is self-describing evidence: an auditor reading the JSON sees the requirement, the mappings, the observed value, and the verdict together, with no external lookup.

type RunOptions

type RunOptions struct {
	Now         time.Time
	ToolVersion string
	Target      string
	Register    *AttestationRegister
}

RunOptions carries the injected clock (for determinism), tool version, scanned target, and the waiver/attestation register.

type Status

type Status int

Status is the outcome of evaluating one control against collected evidence. It mirrors the PASS/WARN/FAIL/INFO vocabulary auditors expect from CIS tools.

const (
	// StatusUnknown means the control was never evaluated (a programming error
	// if it reaches a report). It sorts first so it is easy to spot.
	StatusUnknown Status = iota
	// StatusInfo covers manual-review controls and inputs we could not read.
	// Per the contract, an unreadable input degrades to INFO, never a crash.
	StatusInfo
	// StatusPass means the control's requirement is satisfied.
	StatusPass
	// StatusWarn means partially satisfied or best-practice-but-not-required.
	StatusWarn
	// StatusFail means the requirement is violated.
	StatusFail
)

func (Status) MarshalJSON

func (s Status) MarshalJSON() ([]byte, error)

MarshalJSON renders statuses as their names so JSON exports read like an auditor's control sheet rather than opaque integers.

func (Status) String

func (s Status) String() string

type Waiver

type Waiver struct {
	// Control is the native control id to suppress (e.g. "2.13"). Matching is
	// exact; a benchmark code may be given to scope cross-benchmark files.
	Control string `json:"control"`
	// Benchmark optionally scopes the waiver to one benchmark code ("docker",
	// "k8s"). Empty applies to any benchmark carrying the control id.
	Benchmark string `json:"benchmark,omitempty"`
	// Reason is the mandatory human justification recorded in the audit trail.
	Reason string `json:"reason"`
	// Owner is who accepted the risk (for accountability).
	Owner string `json:"owner,omitempty"`
	// Expires is the date the waiver stops applying (RFC3339). A zero/empty
	// value is treated as already expired — waivers must have an end date.
	Expires string `json:"expires,omitempty"`
}

Waiver records an accepted risk: a specific control is knowingly suppressed until a date, with a justification and an owner. Waivers are the auditable escape hatch demanded by every serious compliance regime — an exception that expires and re-surfaces beats a check silently disabled in code forever.

type Waivers

type Waivers struct {
	// contains filtered or unexported fields
}

Waivers is a set of waivers with an evaluation clock. Construct with the benchmark code you are scoring so Apply can honor per-benchmark scoping.

func NewWaivers

func NewWaivers(items []Waiver) *Waivers

NewWaivers wraps a slice of waivers.

func (*Waivers) Apply

func (ws *Waivers) Apply(rep *Report, benchmarkCode string, now time.Time) *Report

Apply marks results as waived in place when a matching, unexpired waiver exists at time now. It returns the report for chaining. The original Status is preserved; only Waived/WaiverReason are set, so gating and scoring use the demoted status while the export still shows the true verdict.

func (*Waivers) Expiring

func (ws *Waivers) Expiring(within time.Duration, now time.Time) []Waiver

Expiring returns waivers that expire within the given window from now, sorted by expiry then control id. This drives a "these exceptions lapse soon" nudge so accepted risk is re-reviewed rather than forgotten.

Jump to

Keyboard shortcuts

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