policy

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: 12 Imported by: 0

Documentation

Overview

Package policy is a deterministic, dependency-free policy-as-code engine for docker-security. It compiles a versioned policy document (rules written in a small boolean expression language) and evaluates it against a unified Input — scan findings, image identity, workload security context, and supply-chain attestation state — to produce an allow / warn / deny Decision.

The engine is the shared core behind two enforcement points: a CI gate (the internal/modules/policy Module, shift-left over scan results) and a Kubernetes ValidatingWebhook (internal/admission). Both call the same Evaluate, so a rule behaves identically in a pipeline and at admission time.

Two properties are load-bearing. First, evaluation is pure: it reads only its Input and an injected clock (for waiver expiry), never the wall clock or a random source, so the same inputs always yield the same Decision — that is what makes policy testable as code. Second, it fails closed: a malformed policy or an evaluation error is never silently treated as "allow"; callers surface it and, in admission, deny.

Index

Constants

This section is empty.

Variables

View Source
var ErrNotCompiled = errors.New("policy: engine not compiled")

ErrNotCompiled is returned by helpers that require a compiled engine.

Functions

This section is empty.

Types

type AttestationState

type AttestationState interface {
	// Signed reports whether the artifact carries a verified signature.
	Signed() bool
	// Verified reports whether an attestation of the given predicate-type URI
	// was present and verified.
	Verified(predicateType string) bool
	// VerifiedPredicates lists all verified predicate-type URIs.
	VerifiedPredicates() []string
}

AttestationState is the minimal supply-chain view the policy engine needs. It is deliberately tiny so the verify/attest packages can satisfy it with a thin adapter (a master action) without the policy engine depending on them, keeping the two phases decoupled and independently testable.

type Case

type Case struct {
	Name string `json:"name"`
	// Now is the evaluation time (RFC3339). Empty uses the suite's default,
	// keeping waiver-expiry tests deterministic and independent of the wall clock.
	Now string `json:"now,omitempty"`
	// Input is the world to evaluate.
	Input CaseInput `json:"input"`
	// Expect is the required aggregate decision.
	Expect DecisionType `json:"expect"`
	// ExpectFiring, when set, is the exact set of rule ids that must fire
	// unwaived (order-independent). Nil means "do not check individual rules".
	ExpectFiring []string `json:"expect_firing,omitempty"`
}

Case is one policy test: given Input at time Now, expect Decision.

type CaseInput

type CaseInput struct {
	Findings []FindingJSON     `json:"findings,omitempty"`
	Image    Image             `json:"image,omitempty"`
	Workload Workload          `json:"workload,omitempty"`
	Attest   StaticAttestation `json:"attest,omitempty"`
	Licenses []string          `json:"licenses,omitempty"`
	Packages []string          `json:"packages,omitempty"`
}

CaseInput is the JSON-friendly form of an Input (string severities, a static attestation state) that a suite file can express directly.

type CaseResult

type CaseResult struct {
	Name   string       `json:"name"`
	Pass   bool         `json:"pass"`
	Want   DecisionType `json:"want"`
	Got    DecisionType `json:"got"`
	Detail string       `json:"detail,omitempty"`
}

CaseResult is the outcome of one case.

type DecisionType

type DecisionType string

DecisionType is the aggregate verdict for an evaluation.

const (
	// DecisionAllow means nothing blocking or warning fired.
	DecisionAllow DecisionType = "allow"
	// DecisionWarn means a warn rule fired (or a deny fired under audit mode),
	// but nothing blocks.
	DecisionWarn DecisionType = "warn"
	// DecisionDeny means a deny rule fired in enforce mode and was not waived
	// or overridden by an explicit allow.
	DecisionDeny DecisionType = "deny"
)

func (DecisionType) Blocks

func (d DecisionType) Blocks() bool

Blocks reports whether this decision should stop the workload/pipeline.

type Effect

type Effect string

Effect is what a matched rule does to the decision.

const (
	// EffectDeny blocks in enforce mode (and would-block in audit mode).
	EffectDeny Effect = "deny"
	// EffectWarn surfaces a warning but never blocks.
	EffectWarn Effect = "warn"
	// EffectAllow is an explicit allowance: a matched allow rule exempts the
	// input from denial, expressing a carve-out ("allow this base image even
	// though a later rule would deny it").
	EffectAllow Effect = "allow"
)

type Engine

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

Engine is a compiled, ready-to-evaluate policy. It is immutable and safe for concurrent use: Evaluate reads only its rules and the per-call Input.

func Compile

func Compile(p *Policy) (*Engine, error)

Compile validates and compiles a policy into an Engine. It aggregates every rule error into one message so an author fixes all problems in a single pass rather than one recompile at a time.

func CompileBytes

func CompileBytes(data []byte) (*Engine, error)

CompileBytes is a convenience that parses then compiles a JSON policy.

func (*Engine) Evaluate

func (e *Engine) Evaluate(in *Input, now time.Time) *Result

Evaluate runs the compiled policy against an Input at time now (injected so waiver expiry is deterministic). It never returns an error: a rule that fails to evaluate is recorded on its RuleResult and treated as fail-closed, because a gate that crashed is a gate an attacker just walked through.

func (*Engine) Explain

func (e *Engine) Explain(res *Result, in *Input) *Explanation

Explain renders a Result into an Explanation. It is a pure projection of the already-computed result plus the input (used to recover the predicate values), so calling it never changes a decision. It classifies rules through the same Denials/Warnings/WaivedRules accessors the CI gate and admission layer use, so an explanation can never disagree with the decision it explains.

func (*Engine) Policy

func (e *Engine) Policy() *Policy

Policy returns the source policy document.

func (*Engine) RunSuite

func (e *Engine) RunSuite(cases []Case, defaultNow time.Time) SuiteResult

RunSuite evaluates every case against the compiled engine. defaultNow is used for cases that do not pin their own Now.

type Explanation

type Explanation struct {
	Policy   string            `json:"policy"`
	Decision DecisionType      `json:"decision"`
	Summary  string            `json:"summary"`
	Denials  []RuleExplanation `json:"denials,omitempty"`
	Warnings []RuleExplanation `json:"warnings,omitempty"`
	Waived   []RuleExplanation `json:"waived,omitempty"`
	// Remediation is the de-duplicated set of actions that, if taken, would
	// clear the current denials — the agent's to-do list to get admitted.
	Remediation []string  `json:"remediation,omitempty"`
	EvaluatedAt time.Time `json:"evaluated_at"`
}

Explanation is the structured, model-consumable account of a decision.

type FindingJSON

type FindingJSON struct {
	RuleID      string            `json:"rule_id"`
	Module      string            `json:"module"`
	Severity    string            `json:"severity"`
	Title       string            `json:"title"`
	Description string            `json:"description,omitempty"`
	Resource    string            `json:"resource,omitempty"`
	Remediation string            `json:"remediation,omitempty"`
	References  []string          `json:"references,omitempty"`
	Metadata    map[string]string `json:"metadata,omitempty"`
}

FindingJSON mirrors engine.Finding for decoding, with a string severity.

type Image

type Image struct {
	Reference  string `json:"reference,omitempty"`
	Registry   string `json:"registry,omitempty"`
	Repository string `json:"repository,omitempty"`
	Tag        string `json:"tag,omitempty"`
	Digest     string `json:"digest,omitempty"`
}

Image is the identity of the artifact under policy.

type Input

type Input struct {
	// Findings are the security findings under judgement (from a scan Report).
	Findings []engine.Finding
	// Image identifies the artifact being gated.
	Image Image
	// Workload is the Kubernetes security context, when gating a manifest.
	Workload Workload
	// Attest is the supply-chain verification state. It is an interface so the
	// policy engine never imports the attestation package — Phase 2 supplies an
	// adapter; tests and the CI gate use StaticAttestation.
	Attest AttestationState
	// Licenses lists SPDX license identifiers discovered in the artifact's SBOM.
	Licenses []string
	// Packages lists package names discovered in the artifact's SBOM.
	Packages []string
}

Input is the immutable world a policy sees. It is assembled by a caller (the CI gate builds it from a scan Report; the admission webhook builds it from an AdmissionReview) and then evaluated without further I/O.

type Kind

type Kind uint8

Kind is the runtime type tag of a Value.

const (
	KindBool Kind = iota
	KindNum
	KindStr
	KindList
)

func (Kind) String

func (k Kind) String() string

type Mode

type Mode string

Mode selects enforcement strength for the whole policy.

const (
	// ModeEnforce lets deny rules block. This is the default.
	ModeEnforce Mode = "enforce"
	// ModeAudit downgrades every would-be denial to a warning, so a new policy
	// can be rolled out and observed before it starts blocking deploys.
	ModeAudit Mode = "audit"
)

type Policy

type Policy struct {
	// Version is the policy schema version. Only "1" is understood today; an
	// explicit version means a future engine can refuse or migrate old files.
	Version string `json:"version"`
	// Name identifies the policy in decisions and reports.
	Name string `json:"name"`
	// Description is a one-line human summary.
	Description string `json:"description,omitempty"`
	// Mode is enforce (default) or audit.
	Mode Mode `json:"mode,omitempty"`
	// Rules are evaluated in document order; order only affects report/explain
	// ordering, not the final decision (deny always wins over warn).
	Rules []Rule `json:"rules"`
	// Waivers are documented, expiring exceptions.
	Waivers []Waiver `json:"waivers,omitempty"`
}

Policy is a versioned, reviewable set of rules plus governed waivers.

func Parse

func Parse(data []byte) (*Policy, error)

Parse decodes a policy document from JSON without compiling it. Use Compile to get an evaluable engine (which also validates rule expressions).

type ReportJSON

type ReportJSON struct {
	TargetType string        `json:"target_type"`
	Target     string        `json:"target"`
	Findings   []FindingJSON `json:"findings"`
}

ReportJSON is the subset of engine.Report the policy engine consumes.

func LoadReport

func LoadReport(data []byte) (*ReportJSON, error)

LoadReport decodes a scan report JSON document.

func (*ReportJSON) EngineFindings

func (r *ReportJSON) EngineFindings() []engine.Finding

EngineFindings returns the report's findings in the engine model.

type Result

type Result struct {
	Policy   string       `json:"policy"`
	Mode     Mode         `json:"mode"`
	Decision DecisionType `json:"decision"`
	// Rules holds every rule's outcome in policy document order.
	Rules []RuleResult `json:"rules"`
	// EvaluatedAt is the injected evaluation time (for waiver expiry). It is
	// recorded so a decision is self-describing and auditable.
	EvaluatedAt time.Time `json:"evaluated_at"`
}

Result is the full outcome of evaluating a policy against one Input.

func (*Result) Denials

func (r *Result) Denials() []RuleResult

Denials returns the rule results that actually blocked admission. Because a block is a property of the aggregate decision (an allow override or audit mode can neutralize a would-be deny), this is empty unless Decision is deny — so a caller never emits a "violation" that contradicts an allow verdict.

func (*Result) Firing

func (r *Result) Firing(includeWaived bool) []RuleResult

Firing returns the rule results that matched (optionally including waived).

func (*Result) WaivedRules

func (r *Result) WaivedRules() []RuleResult

WaivedRules returns the firings that a matching waiver suppressed.

func (*Result) Warnings

func (r *Result) Warnings() []RuleResult

Warnings returns rule results surfaced as warnings: matched warn rules, plus any rule that fired or errored but did not block (audit mode or an allow override). Waived firings are excluded.

type Rule

type Rule struct {
	// ID is a stable, human-meaningful identifier, unique within the policy.
	ID string `json:"id"`
	// Description explains the intent for reviewers.
	Description string `json:"description,omitempty"`
	// Match is the boolean condition; the rule fires when it evaluates to true.
	Match string `json:"match"`
	// Effect is deny, warn, or allow.
	Effect Effect `json:"effect"`
	// Severity is the finding severity when this rule fires. Empty defaults from
	// the effect (deny -> HIGH, warn -> MEDIUM).
	Severity string `json:"severity,omitempty"`
	// Message is the human explanation attached to a firing.
	Message string `json:"message,omitempty"`
	// Remediation tells an operator (or an agent) how to become compliant.
	Remediation string `json:"remediation,omitempty"`
	// References are standards/citations (CIS, NIST, internal runbooks).
	References []string `json:"references,omitempty"`
}

Rule is one policy statement: when Match evaluates true, Effect applies.

type RuleExplanation

type RuleExplanation struct {
	RuleID       string            `json:"rule_id"`
	Effect       Effect            `json:"effect"`
	Severity     string            `json:"severity,omitempty"`
	Reason       string            `json:"reason,omitempty"`
	Condition    string            `json:"condition"`
	Facts        map[string]string `json:"facts,omitempty"`
	Remediation  string            `json:"remediation,omitempty"`
	References   []string          `json:"references,omitempty"`
	Waived       bool              `json:"waived,omitempty"`
	WaiverReason string            `json:"waiver_reason,omitempty"`
}

RuleExplanation explains one rule's firing, including the concrete input values that made it fire ("facts") so the reason is verifiable, not asserted.

type RuleResult

type RuleResult struct {
	RuleID      string   `json:"rule_id"`
	Description string   `json:"description,omitempty"`
	Effect      Effect   `json:"effect"`
	Matched     bool     `json:"matched"`
	Message     string   `json:"message,omitempty"`
	Remediation string   `json:"remediation,omitempty"`
	References  []string `json:"references,omitempty"`
	// Severity is the finding severity name for a matched deny/warn.
	Severity string `json:"severity,omitempty"`
	// Waived is set when a matching, unexpired waiver suppressed this firing.
	Waived bool `json:"waived,omitempty"`
	// WaiverReason carries the waiver's justification and expiry when waived.
	WaiverReason string `json:"waiver_reason,omitempty"`
	// Error, when set, is the evaluation error for this rule (fail-closed:
	// a rule that errors is treated as if it denied).
	Error string `json:"error,omitempty"`
}

RuleResult records one rule's outcome for a specific evaluation.

type Scope

type Scope struct {
	// ImagePattern is a regexp matched against the image reference. Empty = any.
	ImagePattern string `json:"image_pattern,omitempty"`
	// Registry is an exact registry host match. Empty = any.
	Registry string `json:"registry,omitempty"`
}

Scope narrows a waiver to specific artifacts, so "we accept this CVE on the legacy image" does not silently excuse it everywhere.

type StaticAttestation

type StaticAttestation struct {
	IsSigned   bool     `json:"signed,omitempty"`
	Predicates []string `json:"predicates,omitempty"`
}

StaticAttestation is a plain-data AttestationState. The CI gate builds one from a scan Report's verification verdict; tests construct it directly. A nil AttestationState is treated as "nothing verified" (the fail-closed default).

func InferAttestation

func InferAttestation(findings []engine.Finding) StaticAttestation

InferAttestation derives supply-chain state from a combined scan report without importing the verify module: it reads the verification verdict finding that the verify module emits (metadata verdict=PASSED and a comma-separated verified_levels). This lets a policy reference `signed` / `verified(...)` when the scan already ran verification, and otherwise reports nothing verified — the fail-closed default. The verify rule namespace ("DS-RAT-SUP-") is matched by prefix so a rename of a specific rule id does not silently break inference.

func (StaticAttestation) Signed

func (s StaticAttestation) Signed() bool

func (StaticAttestation) Verified

func (s StaticAttestation) Verified(predicateType string) bool

func (StaticAttestation) VerifiedPredicates

func (s StaticAttestation) VerifiedPredicates() []string

type Suite

type Suite struct {
	// Policy is the path to the policy under test, relative to the suite file.
	Policy string `json:"policy"`
	// Cases are the individual test cases.
	Cases []Case `json:"cases"`
}

Suite is a policy test file.

func ParseSuite

func ParseSuite(data []byte) (*Suite, error)

ParseSuite decodes a suite file.

type SuiteResult

type SuiteResult struct {
	Results []CaseResult `json:"results"`
	Passed  int          `json:"passed"`
	Failed  int          `json:"failed"`
}

SuiteResult aggregates a run.

func (SuiteResult) OK

func (s SuiteResult) OK() bool

OK reports whether every case passed.

type Value

type Value struct {
	Kind Kind
	// contains filtered or unexported fields
}

Value is a single dynamically-typed policy value.

func Bool

func Bool(b bool) Value

func List

func List(vs []Value) Value

func Num

func Num(n float64) Value

func Str

func Str(s string) Value

func (Value) AsBool

func (v Value) AsBool() (bool, error)

AsBool returns the boolean value, or an error if the value is not a bool. The final result of a rule's match expression must be a bool; using a number or string in a boolean position is a policy error, not a coercion, so mistakes surface at evaluation rather than silently passing a gate.

func (Value) Contains

func (v Value) Contains(x Value) (bool, error)

Contains reports whether a list value contains x. It is the backing for the in(x, list) builtin and returns an error if the receiver is not a list.

func (Value) Display

func (v Value) Display() string

Display renders a value for human-readable messages and explanations.

func (Value) Equal

func (v Value) Equal(o Value) bool

Equal reports value equality. Cross-kind comparison is defined (and false) rather than an error, so `registry == 5` is simply false instead of aborting a whole policy evaluation. Lists compare element-wise.

func (Value) Less

func (v Value) Less(o Value) (bool, error)

Less orders two values for the <, <=, >, >= operators. Ordering is only defined for two numbers or two strings; anything else is a typed error so an author cannot accidentally compare a bool against a number and get a meaningless answer that flips a gate.

type Waiver

type Waiver struct {
	// RuleID is the rule to suppress. Matching is exact.
	RuleID string `json:"rule_id"`
	// Reason is the mandatory 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 when the waiver stops applying (RFC3339, or a bare YYYY-MM-DD
	// treated as end-of-day UTC). A missing or unparseable value is treated as
	// already expired — a waiver must have an end date to be honored.
	Expires string `json:"expires"`
	// Scope optionally narrows the waiver to matching images.
	Scope Scope `json:"scope,omitempty"`
}

Waiver suppresses one rule's firing until it expires.

type Waivers

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

Waivers is an ordered set of waivers.

func NewWaivers

func NewWaivers(items []Waiver) *Waivers

NewWaivers wraps a slice of waivers.

func (*Waivers) Expiring

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

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

type Workload

type Workload struct {
	Present                  bool     `json:"present,omitempty"`
	Privileged               bool     `json:"privileged,omitempty"`
	RunAsRoot                bool     `json:"run_as_root,omitempty"`
	HostNetwork              bool     `json:"host_network,omitempty"`
	HostPID                  bool     `json:"host_pid,omitempty"`
	HostIPC                  bool     `json:"host_ipc,omitempty"`
	ReadOnlyRootFS           bool     `json:"read_only_root_fs,omitempty"`
	AllowPrivilegeEscalation bool     `json:"allow_privilege_escalation,omitempty"`
	UsesHostPath             bool     `json:"uses_host_path,omitempty"`
	Capabilities             []string `json:"capabilities,omitempty"`
	Images                   []string `json:"images,omitempty"`
}

Workload is the security-relevant projection of a Kubernetes pod spec, flat enough for a policy rule to reference directly (privileged, runs_as_root, …).

Jump to

Keyboard shortcuts

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