selfaudit

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: AGPL-3.0 Imports: 22 Imported by: 0

Documentation

Overview

Package selfaudit scores a running Redoubt installation against its own secure-by-default checklist, in the spirit of docker-bench-security (E2.7): daemon configuration, the compose bundle (socket-proxy, Traefik, platformd, buildkitd), every platform-managed container, the control-plane's own state (Owner TOTP, bootstrap token, key file mode, audit chain) and, when it runs on the host, a few daemon files.

The package has two halves. Evaluate is pure: it takes typed Inputs (a docker system.Info, the inspect results of every container, counts from the store, host file facts) and produces a Report, so the checks are unit-tested against fixture inspect JSON in testdata/. Collector gathers those Inputs from the live system using only read-only, allow-listed Docker calls (GET /info, GET /containers/json, GET /containers/{id}/json — TestSelfAuditReadOnly pins it) and store reads; it never creates, changes or removes anything.

Index

Constants

View Source
const (
	SeverityFail = "fail" // a golden-rule or plan-mandated control is off
	SeverityWarn = "warn" // a recommended hardening is missing
	SeverityInfo = "info" // informational only
)

Severity of a check that does not pass.

View Source
const (
	StatusPass = "pass"
	StatusWarn = "warn"
	StatusFail = "fail"
	StatusSkip = "skip" // could not be evaluated here (inside the container, missing input)
)

Status of one check.

View Source
const (
	BundlePlatformd   = "redoubt-platformd"
	BundleSocketProxy = "redoubt-socket-proxy"
	BundleBuildkitd   = "redoubt-buildkitd"
)

Compose-bundle container names (install/compose/docker-compose.yml); Traefik's comes from config.Config.TraefikContainer.

View Source
const (
	IDDaemonLiveRestore   = "daemon.live-restore"
	IDDaemonUserlandProxy = "daemon.userland-proxy"
	IDDaemonNNP           = "daemon.no-new-privileges"
	IDDaemonSeccomp       = "daemon.seccomp"
	IDDaemonAppArmor      = "daemon.apparmor"
	IDBundlePresent       = "bundle.present"
	IDContainersHardened  = "containers.hardened"
	IDContainersLimits    = "containers.limits"
	IDContainersConfined  = "containers.confined"
	IDSocketOnlyProxy     = "socket.only-proxy"
	IDSocketProxyNoPorts  = "socket-proxy.no-ports"
	IDPlatformdLoopback   = "platformd.loopback-only"
	IDTraefikNoDocker     = "traefik.no-docker-provider"
	IDTraefikACME         = "traefik.acme-resolver"
	IDBuildkitdNonRoot    = "buildkitd.non-root"
	IDAuthOwnerTOTP       = "auth.owner-totp"
	IDAuthBootstrapToken  = "auth.bootstrap-token" // #nosec G101 -- check id, not a credential
	IDSecretsKeyMode      = "secrets.age-key-mode"
	IDAuditChain          = "audit.chain"
)

Check IDs, stable for scripting and for the tests.

View Source
const ActionRun = "selfaudit.run"

ActionRun is the audit action recorded per run.

View Source
const DaemonJSONPath = "/etc/docker/daemon.json"

DaemonJSONPath is the daemon configuration read when the audit runs on the host.

View Source
const PermAuditRead = string(auth.PermAuditRead)

PermAuditRead mirrors auth.PermAuditRead: reading the self-audit is reading the security posture, which is the audit reader's job.

Variables

View Source
var ErrForbidden = errors.New("selfaudit: forbidden")

ErrForbidden is returned when the actor may not read the audit.

Functions

func InContainer

func InContainer() bool

InContainer reports whether the process runs inside a container (Docker creates /.dockerenv).

Types

type Actor

type Actor struct {
	Name string
	Can  func(perm string) bool
}

Actor is who requests the audit. Can nil means every permission (the bootstrap principal).

type ChainResult

type ChainResult struct {
	// Checked is false when no verifier was wired.
	Checked bool
	OK      bool
	Detail  string
	Err     error
}

ChainResult is what the audit chain verifier reported.

type ChainVerifier

type ChainVerifier interface {
	Verify(ctx context.Context) (ok bool, detail string, err error)
}

ChainVerifier walks the hash-chained audit log. It is satisfied by the audit package's chain (E2.1); platformd adapts whatever that exposes. ok is false when the chain is broken, with detail naming the first bad row; err is for failures to run the check at all.

type Check

type Check struct {
	ID       string `json:"id"`
	Title    string `json:"title"`
	Severity string `json:"severity"`
	Status   string `json:"status"`
	Detail   string `json:"detail"`
}

Check is one line of the report.

type Collector

type Collector struct {
	Docker *docker.Client
	Store  *store.Store
	Cfg    config.Config
	// Chain is optional; nil skips the audit-chain check.
	Chain ChainVerifier
	// InContainer marks a run inside the platformd container (host-only checks are skipped).
	InContainer bool
	// DaemonJSONPath overrides DaemonJSONPath (tests).
	DaemonJSONPath string
	// Now is overridable for tests.
	Now func() time.Time
}

Collector gathers Inputs from the live system. Every Docker call it makes is a GET on the allow-list (info, containers/json, containers/{id}/json); it performs no writes anywhere.

func (*Collector) Collect

func (c *Collector) Collect(ctx context.Context) (Inputs, error)

Collect gathers every input. Missing bundle containers are recorded as absent, not errors; a Docker or store failure is returned because the report would be meaningless without it.

func (*Collector) Run

func (c *Collector) Run(ctx context.Context) (Report, error)

Run collects and evaluates.

type DataFiles

type DataFiles struct {
	AgeKeyMode   fs.FileMode
	AgeKeyExists bool
	AgeKeyErr    error
	// BootstrapTokenExists is true when <data>/bootstrap-token is present.
	BootstrapTokenExists bool
	BootstrapTokenErr    error
}

DataFiles are facts about files under the data directory.

type HostFacts

type HostFacts struct {
	// DaemonJSON is the parsed /etc/docker/daemon.json; nil when the file does not exist.
	DaemonJSON map[string]any
	// DaemonJSONErr is set when the file exists but could not be read or parsed.
	DaemonJSONErr error
}

HostFacts are read from the host's filesystem (never inside the container).

type Inputs

type Inputs struct {
	Info system.Info
	// Bundle maps compose-bundle container names to their inspect result; a name that is absent
	// (or mapped to nil) was not found.
	Bundle map[string]*container.InspectResponse
	// Managed are the containers labelled redoubt.managed=true: apps, addons, one-shot jobs.
	Managed []container.InspectResponse
	Cfg     config.Config
	// InContainer is true when the audit runs inside the platformd container; host-only checks
	// are then skipped instead of reported.
	InContainer bool
	// Host holds the daemon file facts gathered on the host (nil inside the container).
	Host *HostFacts
	// Files holds facts about files under the data directory (readable from both sides).
	Files DataFiles
	// UserCount is the number of user accounts; OwnerTOTP is true when at least one enabled
	// Owner has a confirmed second factor.
	UserCount int64
	OwnerTOTP bool
	// Chain is the audit-chain verification outcome.
	Chain ChainResult
}

Inputs is everything Evaluate looks at. Collector fills it from the live system; tests fill it from fixtures.

type Report

type Report struct {
	// Score is 0..100: passes / (passes + fails + 0.5*warns), skips excluded.
	Score       int       `json:"score"`
	Checks      []Check   `json:"checks"`
	GeneratedAt time.Time `json:"generated_at"`
}

Report is the outcome of a self-audit run.

func Evaluate

func Evaluate(in Inputs, now time.Time) Report

Evaluate runs every check against in and scores the result.

func (Report) Counts

func (r Report) Counts() (pass, warn, fail, skip int)

Counts returns the number of checks per status.

func (Report) Failed

func (r Report) Failed() bool

Failed reports whether any check has StatusFail.

type Service

type Service struct {
	Collector *Collector
	Audit     audit.Sink
	Logger    *slog.Logger
	Now       func() time.Time
}

Service is the RBAC-checked entry point shared by the API, the dashboard and the CLI.

func (*Service) Run

func (s *Service) Run(ctx context.Context, actor Actor) (Report, error)

Run executes the self-audit for actor, who needs PermAuditRead. The run itself changes nothing; it is audited so that who looked at the posture, and what score they saw, is on record.

Jump to

Keyboard shortcuts

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