remediate

package
v1.19.1 Latest Latest
Warning

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

Go to latest
Published: May 29, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Overview

Package remediate generates structured fix-it artifacts (Terraform blocks, kubectl patches, cloud-CLI commands, Ansible plays, Helm overlays, bash one-liners) from compliancekit Findings. v0.15+.

Remediation is GENERATION, not APPLICATION. Per ADR-006 the binary never mutates infrastructure on its own — `--apply-fix` is the v2.x trust gate, intentionally a separate decision. Every Snippet this package emits is something the operator copy-pastes (or wires into a PR they review). The flow is:

scanner emits Finding → Strategy registered for finding.CheckID
→ Render(finding, format) → Snippet (Content + Verify + Rollback)
→ writer drops Snippet onto disk inside the evidence pack.

ADR-011 codifies the architectural shape. Highlights:

  • One Strategy may handle multiple CheckIDs and multiple Formats.
  • A Strategy declares a RiskClass (safe/review/manual) so operators and the POA&M emitter know which findings need a human in the loop.
  • Strategies live in per-format subpackages (internal/remediate/{terraform,kubectl,awscli,gcloud,azcli,doctl, hcloud,helm,ansible,bash}) and self-register via package init.
  • The CLI side-effect-imports each subpackage in internal/cli/remediate.go; tests import only what they exercise.

What this package is NOT:

  • It does not apply fixes (ADR-006).
  • It does not run arbitrary user-supplied templates — every Strategy is hand-written Go so the safety boundary is statically auditable.
  • It does not invent CheckIDs; if a CheckID has no registered Strategy the finding falls through to the POA&M emitter (internal/remediate/poam) for manual-action capture.

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrUnknownFormat is returned by ParseFormat when the input
	// is not one of the canonical names or aliases.
	ErrUnknownFormat = errors.New("remediate: unknown format")

	// ErrNoStrategy is returned by Registry.Render when no strategy
	// handles the finding's CheckID. The CLI translates this into
	// a POA&M manual-action entry rather than a hard error.
	ErrNoStrategy = errors.New("remediate: no strategy for check_id")

	// ErrFormatUnsupported is returned by Strategy.Render when the
	// strategy does not support the requested format. The registry
	// catches this and tries the next matching strategy.
	ErrFormatUnsupported = errors.New("remediate: format unsupported by strategy")
)

Errors returned by the package + registry.

AllFormats is the canonical iteration order — bash first (fallback), then IaC, then cloud-CLI families. Stable for CLI output and tests.

View Source
var Default = NewRegistry()

Default is the process-wide registry every strategy package registers against. The CLI calls Default.RenderAll; tests build isolated registries via NewRegistry.

Functions

func Register

func Register(s Strategy)

Register installs s into Default. Strategy packages call this from init().

Types

type Format

type Format string

Format identifies one remediation output language. Operators select it via `compliancekit remediate --format=<value>` or get every format the strategies support via `--format=all`.

const (
	// FormatBash emits POSIX-sh one-liners. Always available as a
	// fallback when a more structured format isn't supported for
	// a given CheckID.
	FormatBash Format = "bash"

	// FormatTerraform emits HCL fragments suitable for dropping into
	// an existing TF root module. Strategies aim for surgical fixes
	// (`aws_s3_bucket_public_access_block`-style focused blocks)
	// rather than full module rewrites.
	FormatTerraform Format = "terraform"

	// FormatKubectl emits `kubectl patch` commands plus the YAML
	// manifest equivalent so operators using GitOps (Argo / Flux)
	// can patch their repo directly.
	FormatKubectl Format = "kubectl"

	// FormatHelm emits values.yaml overlay snippets. Useful when the
	// offending manifest is owned by a third-party Helm chart and
	// editing the rendered output would be lost on next upgrade.
	FormatHelm Format = "helm"

	// FormatAnsible emits playbook task fragments. Used by Linux/CIS
	// strategies because hosts are the audience that already ships
	// Ansible configuration management at scale.
	FormatAnsible Format = "ansible"

	// FormatAWSCLI emits `aws <service> ...` commands. Live-cloud
	// fixes for AWS findings where the operator cannot or will not
	// route through Terraform.
	FormatAWSCLI Format = "aws-cli"

	// FormatGCloud emits `gcloud <service> ...` commands.
	FormatGCloud Format = "gcloud"

	// FormatAzureCLI emits `az <service> ...` commands.
	FormatAzureCLI Format = "az-cli"

	// FormatDoctl emits `doctl <service> ...` commands. DigitalOcean.
	FormatDoctl Format = "doctl"

	// FormatHcloud emits `hcloud <service> ...` commands. Hetzner.
	FormatHcloud Format = "hcloud"
)

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat normalizes a user-supplied format string. Accepts the canonical identifiers above plus a few aliases ("tf" → terraform, "k8s" → kubectl, "aws" → aws-cli) so the CLI is forgiving without allowing typos to silently pick the wrong language.

func (Format) String

func (f Format) String() string

String implements fmt.Stringer so Format flows through fmt.Sprintf in errors and runbook lines without ceremony.

type Registry

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

Registry indexes Strategy implementations by CheckID for fast per-finding dispatch. Each subpackage in internal/remediate/<format> calls Register from its init(); the CLI side-effect-imports each subpackage so the Default registry is fully populated by start of run.

A CheckID may resolve to several strategies (e.g. one Terraform strategy + one AWS-CLI strategy for the same S3 finding). Render picks the first strategy that supports the requested Format.

func NewRegistry

func NewRegistry() *Registry

NewRegistry returns an empty Registry. Tests use this to isolate strategy registrations; production code goes through Default.

func (*Registry) Register

func (r *Registry) Register(s Strategy)

Register adds a strategy to the registry. Panics on duplicate Strategy.Name() values — a duplicate registration is a programmer error caught at init-time, not a runtime condition.

A Strategy with CheckIDs() == ["*"] is filed under wildcards and considered only when no concrete strategy claims the CheckID. A Strategy with an empty Formats() slice panics; a Strategy with no CheckIDs at all panics (there is nothing to register against).

func (*Registry) RegisteredCheckIDs

func (r *Registry) RegisteredCheckIDs() []string

RegisteredCheckIDs returns the sorted union of CheckIDs every registered strategy claims. Used by `compliancekit remediate --list` to advertise coverage and by CI to assert that every active check has at least one strategy (per issue #14 DoD).

func (*Registry) RegisteredStrategies

func (r *Registry) RegisteredStrategies() []Strategy

RegisteredStrategies returns every registered Strategy, sorted by Strategy.Name(). Used by `compliancekit remediate --list` to print the coverage table.

func (*Registry) Render

func (r *Registry) Render(f compliancekit.Finding, format Format) (Snippet, error)

Render produces a Snippet for the (finding, format) pair, trying each registered Strategy in order until one supports format. Returns ErrNoStrategy if no strategy is registered for the CheckID; returns ErrFormatUnsupported if strategies are registered but none can emit the requested format.

Strategies that recognize the finding but cannot auto-remediate (e.g. credential rotation) MUST return a Snippet with Risk=RiskManual and a populated Notes field rather than an error — the caller treats "manual" snippets as actionable POA&M input.

func (*Registry) RenderAll

func (r *Registry) RenderAll(findings []compliancekit.Finding) (snippets []Snippet, unmatched []compliancekit.Finding)

RenderAll produces a Snippet for every (finding × format) pair the registry can serve. Findings with no registered strategy are reported via the unmatched return value so callers can route them to POA&M manual-action. Formats a finding's strategies do not support are silently skipped (a CVE finding has no kubectl patch).

Order: outer loop over findings (stable), inner loop over AllFormats (canonical order). Output is deterministic for a given input slice.

func (*Registry) StrategiesFor

func (r *Registry) StrategiesFor(checkID string) []Strategy

StrategiesFor returns the strategies that handle checkID, in registration order. Returns wildcards only if no concrete strategy matches. Empty result means no remediation is generatable for that CheckID; the CLI routes such findings to POA&M manual-action.

type RiskClass

type RiskClass string

RiskClass tells the operator how much hand-holding a remediation needs. Strategies declare this; the runbook + POA&M emitter use it to route findings to the right column.

const (
	// RiskSafe means applying the snippet has no expected blast
	// radius: no service disruption, no data loss, no behavior
	// change beyond the security posture. Examples: enable
	// encryption-at-rest, attach a logging policy, set a tag.
	RiskSafe RiskClass = "safe"

	// RiskReview means applying changes visible behavior. The
	// operator should read the snippet before applying. Examples:
	// restrict a security group, narrow IAM permissions, set a
	// stricter pod-security context.
	RiskReview RiskClass = "review"

	// RiskManual means the fix cannot be expressed as a snippet
	// the operator copy-pastes — it requires out-of-band action.
	// Examples: rotate a leaked credential, renew a cert, revoke
	// an IAM user. POA&M routes these to the manual-action list.
	RiskManual RiskClass = "manual"
)

func (RiskClass) String

func (r RiskClass) String() string

String implements fmt.Stringer.

type Snippet

type Snippet struct {
	// CheckID is the finding this snippet remediates. Mirrors
	// compliancekit.Finding.CheckID exactly.
	CheckID string `json:"check_id"`

	// Format is the language of Content.
	Format Format `json:"format"`

	// Resource is a copy of the originating finding's ResourceRef.
	// Carried on the Snippet so downstream writers don't need to
	// re-join against the findings slice.
	Resource compliancekit.ResourceRef `json:"resource"`

	// Risk classifies how aggressive the change is. Operators
	// triage by risk before reading the body.
	Risk RiskClass `json:"risk"`

	// Idempotent reports whether re-applying the snippet leaves
	// system state unchanged. True for most Terraform / kubectl
	// patches; false for stateful operations like key rotation.
	Idempotent bool `json:"idempotent"`

	// Content is the executable text (HCL, bash, YAML, …). UTF-8.
	Content string `json:"content"`

	// VerifyCmd is an optional bash one-liner the operator can run
	// to confirm the fix landed. Empty when verification is not
	// expressible as a single command.
	VerifyCmd string `json:"verify_cmd,omitempty"`

	// RollbackCmd is an optional bash one-liner that undoes the
	// fix. Empty for safe / manual changes where rollback is
	// either unnecessary (safe) or non-trivial (manual).
	RollbackCmd string `json:"rollback_cmd,omitempty"`

	// Notes is short operator-facing prose: caveats, prerequisites
	// (e.g. "requires kms:CreateKey permission"), or context the
	// snippet itself doesn't communicate. Rendered above Content
	// in the runbook.
	Notes string `json:"notes,omitempty"`

	// Refs links to authoritative docs (provider guides, CIS
	// benchmark sections, CVE advisories).
	Refs []string `json:"refs,omitempty"`
}

Snippet is one remediation rendered in one Format. Strategies return Snippets from Render; writers persist them to disk inside the evidence pack.

Content is the bytes the operator applies. VerifyCmd is a single bash one-liner an operator can run AFTER applying to confirm the fix landed (e.g. `aws s3api get-public-access-block ...`). It's optional but strongly encouraged — a remediation that can't be verified is hard to trust.

type Strategy

type Strategy interface {
	// Name is a short identifier for the strategy, e.g.
	// "aws-s3-public-access". Used in error messages, debug logs,
	// and the runbook's "rendered by" footer. Lowercase, kebab-case.
	Name() string

	// CheckIDs returns the catalog IDs this strategy handles. May
	// return ["*"] to register as a fallback for unmatched IDs —
	// fallbacks are tried after exact matches and only if no
	// concrete strategy claims the CheckID.
	CheckIDs() []string

	// Formats returns the Format values this strategy can render.
	// MUST be non-empty; a strategy that can render nothing should
	// not be registered.
	Formats() []Format

	// Render produces a Snippet for the given (finding, format)
	// pair. Returns ErrFormatUnsupported when format is not in
	// Formats(); returns a Snippet with Risk=RiskManual + empty
	// Content for findings the strategy recognizes but cannot
	// auto-remediate.
	Render(f compliancekit.Finding, format Format) (Snippet, error)
}

Strategy is the contract every remediation generator implements. One Strategy can handle several CheckIDs and emit several Formats; the registry indexes by CheckID and dispatches to Render.

Implementations MUST be stateless: the same instance is reused across renders and across goroutines. Per-call state lives on the Finding and on the Snippet returned.

Directories

Path Synopsis
Package ansible implements remediate.Strategy renderers for the FormatAnsible output.
Package ansible implements remediate.Strategy renderers for the FormatAnsible output.
Package awscli implements remediate.Strategy renderers for the FormatAWSCLI output.
Package awscli implements remediate.Strategy renderers for the FormatAWSCLI output.
Package azcli implements remediate.Strategy renderers for the FormatAzureCLI output.
Package azcli implements remediate.Strategy renderers for the FormatAzureCLI output.
Package bash implements remediate.Strategy renderers for the FormatBash output.
Package bash implements remediate.Strategy renderers for the FormatBash output.
Package doctl implements remediate.Strategy renderers for the FormatDoctl output.
Package doctl implements remediate.Strategy renderers for the FormatDoctl output.
Package gcloud implements remediate.Strategy renderers for the FormatGCloud output.
Package gcloud implements remediate.Strategy renderers for the FormatGCloud output.
Package hcloud implements remediate.Strategy renderers for the FormatHcloud output.
Package hcloud implements remediate.Strategy renderers for the FormatHcloud output.
Package helm implements remediate.Strategy renderers for the FormatHelm output.
Package helm implements remediate.Strategy renderers for the FormatHelm output.
Package kubectl implements remediate.Strategy renderers for the FormatKubectl output.
Package kubectl implements remediate.Strategy renderers for the FormatKubectl output.
Package poam emits OSCAL v1.1.2 Plan of Action & Milestones (POA&M) JSON for findings whose remediation classifies as manual — either because no strategy is registered, or because the registered strategy declared RiskManual.
Package poam emits OSCAL v1.1.2 Plan of Action & Milestones (POA&M) JSON for findings whose remediation classifies as manual — either because no strategy is registered, or because the registered strategy declared RiskManual.
Package render holds small shared helpers strategy packages use to emit safe, well-formatted snippet content.
Package render holds small shared helpers strategy packages use to emit safe, well-formatted snippet content.
Package runbook writes the operator-facing artifacts of v0.15's remediation flow:
Package runbook writes the operator-facing artifacts of v0.15's remediation flow:
Package terraform implements remediate.Strategy renderers for the FormatTerraform output.
Package terraform implements remediate.Strategy renderers for the FormatTerraform output.
Package tickets files external tickets (Jira, Linear) for findings whose remediation is manual.
Package tickets files external tickets (Jira, Linear) for findings whose remediation is manual.

Jump to

Keyboard shortcuts

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