Documentation
¶
Overview ¶
Package supplychain records the SBOM of each build, optionally scans it for known vulnerabilities in a short-lived container, and decides whether a release may go live.
Index ¶
- Constants
- Variables
- type BlockedError
- type Config
- type Counts
- type Decision
- type Deps
- type FileEntry
- type FileStore
- func (f *FileStore) List() ([]FileEntry, error)
- func (f *FileStore) Path(app, attemptID string) (string, error)
- func (f *FileStore) Read(app, attemptID string) ([]byte, error)
- func (f *FileStore) Remove(app, attemptID string) error
- func (f *FileStore) RemoveApp(app string) error
- func (f *FileStore) RemoveEntry(e FileEntry) error
- func (f *FileStore) Write(app, attemptID string, data []byte) error
- type GateMode
- type LicenseCount
- type Option
- type Package
- type Record
- type Resolver
- type Runner
- type SBOMSummary
- type SQLStore
- func (s *SQLStore) ArmOverride(ctx context.Context, app, reason string, at time.Time) (bool, error)
- func (s *SQLStore) AttemptExists(ctx context.Context, attemptID string) (bool, error)
- func (s *SQLStore) ConsumeOverride(ctx context.Context, app string, notBefore time.Time) (string, bool, error)
- func (s *SQLStore) DeleteApp(ctx context.Context, app string) error
- func (s *SQLStore) DeleteRecords(ctx context.Context, ids []string) error
- func (s *SQLStore) GetRecord(ctx context.Context, attemptID string) (Record, error)
- func (s *SQLStore) GetSettings(ctx context.Context, app string) (Settings, error)
- func (s *SQLStore) ListAllRecords(ctx context.Context) ([]Record, error)
- func (s *SQLStore) ListRecords(ctx context.Context, app string) ([]Record, error)
- func (s *SQLStore) RecordsByAttempt(ctx context.Context, ids []string) (map[string]Record, error)
- func (s *SQLStore) SaveRecord(ctx context.Context, r Record) error
- func (s *SQLStore) SaveSettings(ctx context.Context, st Settings) error
- type ScanSummary
- type Service
- func (s *Service) AfterBuild(ctx context.Context, app, attemptID string, att build.Attestations) error
- func (s *Service) ArmOverride(ctx context.Context, app, reason string) (Settings, error)
- func (s *Service) Config() Config
- func (s *Service) DeleteApp(ctx context.Context, app string) error
- func (s *Service) Get(ctx context.Context, app, attemptID string) (Record, error)
- func (s *Service) List(ctx context.Context, app string) ([]Record, error)
- func (s *Service) Lookup(ctx context.Context, ids []string) (map[string]Record, error)
- func (s *Service) OpenSBOM(ctx context.Context, app, attemptID string) ([]byte, Record, error)
- func (s *Service) SaveSettings(ctx context.Context, app string, patch SettingsPatch) (Settings, error)
- func (s *Service) ScanNow(ctx context.Context, app, attemptID string) (Record, error)
- func (s *Service) Settings(ctx context.Context, app string) (Settings, error)
- func (s *Service) Start(ctx context.Context)
- func (s *Service) Sweep(ctx context.Context)
- type Settings
- type SettingsPatch
- type Store
- type TypeCount
- type Vuln
Constants ¶
const ( EnvScanEnabled = "APP_SCAN_ENABLED" EnvScanner = "APP_SCANNER" EnvScannerImage = "APP_SCANNER_IMAGE" EnvScanTimeout = "APP_SCAN_TIMEOUT" EnvPullTimeout = "APP_SCAN_PULL_TIMEOUT" EnvScanMemoryMB = "APP_SCAN_MEMORY_MB" EnvScanCPUs = "APP_SCAN_CPUS" EnvOverrideTTL = "APP_SCAN_OVERRIDE_TTL" EnvKeepPerApp = "APP_SBOM_KEEP_PER_APP" EnvSweepInterval = "APP_SCAN_SWEEP_INTERVAL" )
Environment variables ConfigFromEnv reads.
const ( DefaultTrivyImage = "docker.io/aquasec/trivy:0.65.0" DefaultGrypeImage = "docker.io/anchore/grype:v0.99.1" )
Default scanner images. Pinned tags, override with APP_SCANNER_IMAGE.
const ( ActionAllow = "allow" ActionWarn = "warn" ActionBlock = "block" ActionOverride = "override" )
Gate actions stored on a record.
const ( ScannerTrivy = "trivy" ScannerGrype = "grype" )
Scanner names.
const ( FormatSPDX = "spdx" FormatCycloneDX = "cyclonedx" )
SBOM document formats.
const ( SeverityCritical = "critical" SeverityHigh = "high" SeverityMedium = "medium" SeverityLow = "low" SeverityUnknown = "unknown" )
Severity levels, most severe first.
const ( ScanNone = "" ScanOK = "ok" ScanFailed = "failed" )
Scan statuses stored on a record.
Variables ¶
var ( ErrInvalid = errors.New("supplychain: invalid setting") ErrNotFound = errors.New("supplychain: not found") ErrDisabled = errors.New("supplychain: scanning is disabled on this server") ErrNoSBOM = errors.New("supplychain: deployment has no SBOM") )
Sentinel errors.
var ErrNotEnabled = errors.New("supplychain: scanning is not enabled for this app")
ErrNotEnabled means scanning is not enabled for the app.
var ErrUnexpectedReport = errors.New("supplychain: unexpected scanner report")
ErrUnexpectedReport means the scanner output parsed as JSON but is not a report of the configured scanner.
Functions ¶
This section is empty.
Types ¶
type BlockedError ¶
type BlockedError struct{ Reason string }
BlockedError is returned to the deploy pipeline when the gate blocks a release.
func (*BlockedError) Error ¶
func (e *BlockedError) Error() string
type Config ¶
type Config struct {
// BuildAttest reports whether Dockerfile builds produce SBOMs at all.
BuildAttest bool
// Enabled is the server kill switch: false refuses every scan.
Enabled bool
Scanner string
Image string
Timeout time.Duration
PullTimeout time.Duration
MemoryMB int64
CPUs float64
OverrideTTL time.Duration
// KeepPerApp is how many recent SBOM documents are kept per app.
KeepPerApp int
SweepInterval time.Duration
}
Config holds every supply chain threshold. ConfigFromEnv fills defaults.
type Counts ¶
type Counts struct {
Critical int `json:"critical"`
High int `json:"high"`
Medium int `json:"medium"`
Low int `json:"low"`
Unknown int `json:"unknown"`
}
Counts is the number of findings per severity.
type Decision ¶
Decision is what the gate concluded for one release.
func Decide ¶
func Decide(mode GateMode, scan *ScanSummary, overrideReason string) Decision
Decide applies mode to a scan result. A nil scan (scanner unavailable or failed) never blocks: the gate fails open and says so. overrideReason is a non-empty operator reason that lets a blocked release through.
type Deps ¶
type Deps struct {
Store Store
Resolver Resolver
Runner Runner
// DataDir is the platform data directory; SBOMs live in DataDir/supplychain.
DataDir string
// Namespace prefixes labels and names of scan containers (the brand short name).
Namespace string
Logger *slog.Logger
}
Deps are the collaborators a Service needs.
type FileStore ¶
type FileStore struct {
// contains filtered or unexported fields
}
FileStore keeps SBOM documents at <root>/<app>/<attempt>.sbom.json.
func NewFileStore ¶
NewFileStore returns a store rooted at root, which is created on first write.
func (*FileStore) Path ¶
Path returns the file path for an attempt, rejecting names that could escape the root.
func (*FileStore) RemoveEntry ¶
RemoveEntry deletes the file behind e.
type GateMode ¶
type GateMode string
GateMode is what a scan result may do to a release.
const ( GateOff GateMode = "off" GateWarn GateMode = "warn" GateBlockOnCritical GateMode = "block_on_critical" )
Gate modes, least strict first.
func ParseGateMode ¶
ParseGateMode validates a gate mode string; empty means off.
type LicenseCount ¶
LicenseCount is how many packages carry a license.
type Package ¶
type Package struct {
Name string `json:"name"`
Version string `json:"version,omitempty"`
Type string `json:"type,omitempty"`
License string `json:"license,omitempty"`
}
Package is one component found in an SBOM.
type Record ¶
type Record struct {
AttemptID string
App string
Summary SBOMSummary
SBOMBytes int64
HasProvenance bool
GeneratedAt time.Time
ScanStatus string
Scanner string
ScanError string
ScannedAt time.Time
Scan *ScanSummary
GateAction string
GateReason string
}
Record is the per-deploy-attempt supply chain metadata.
type Resolver ¶
type Resolver interface {
AppExists(ctx context.Context, app string) (bool, error)
AttemptExists(ctx context.Context, attemptID string) (bool, error)
}
Resolver answers questions about apps and their deploy attempts.
type Runner ¶
type Runner interface {
EnsureImageID(ctx context.Context, ref string) (id string, pulled bool, err error)
RunOneShot(ctx context.Context, spec docker.OneShotSpec) (docker.OneShotResult, error)
ListContainersByLabel(ctx context.Context, label string) ([]docker.LabeledContainer, error)
Remove(ctx context.Context, id string, force bool) error
}
Runner is the Docker surface scanning uses; *docker.Client satisfies it.
type SBOMSummary ¶
type SBOMSummary struct {
Format string `json:"format"`
PackageCount int `json:"package_count"`
Types []TypeCount `json:"types,omitempty"`
Licenses []LicenseCount `json:"licenses,omitempty"`
Unlicensed int `json:"unlicensed"`
TopPackages []Package `json:"top_packages,omitempty"`
}
SBOMSummary is the compact view stored in the database.
func Summarize ¶
func Summarize(format string, pkgs []Package) SBOMSummary
Summarize builds the compact view of pkgs.
type SQLStore ¶
type SQLStore struct {
// contains filtered or unexported fields
}
SQLStore implements Store over the control plane database.
func (*SQLStore) ArmOverride ¶
ArmOverride arms a one-shot override in a single statement, and only while the stored gate is block_on_critical with scanning on. It reports whether an override was armed.
func (*SQLStore) AttemptExists ¶
AttemptExists reports whether a deploy attempt row exists.
func (*SQLStore) ConsumeOverride ¶
func (s *SQLStore) ConsumeOverride(ctx context.Context, app string, notBefore time.Time) (string, bool, error)
ConsumeOverride returns and clears an armed override newer than notBefore.
func (*SQLStore) DeleteRecords ¶
DeleteRecords removes records by attempt ID.
func (*SQLStore) GetSettings ¶
GetSettings returns the stored settings, or the default (off) when none exist.
func (*SQLStore) ListAllRecords ¶
ListAllRecords returns every record.
func (*SQLStore) ListRecords ¶
ListRecords returns app's records, newest first.
func (*SQLStore) RecordsByAttempt ¶
RecordsByAttempt maps attempt IDs to their records.
func (*SQLStore) SaveRecord ¶
SaveRecord upserts a record.
func (*SQLStore) SaveSettings ¶
SaveSettings upserts scan_enabled and scan_gate only. It never writes the override columns, and it clears a stored override when the gate is not block_on_critical, so a settings write cannot arm, restore or extend one.
type ScanSummary ¶
type ScanSummary struct {
Scanner string `json:"scanner"`
Counts Counts `json:"counts"`
// Fixable is the number of findings that have a fixed version.
Fixable int `json:"fixable"`
// Top lists the most severe, fixable-first findings.
Top []Vuln `json:"top,omitempty"`
}
ScanSummary is what a scanner reported for one SBOM.
func ParseGrype ¶
func ParseGrype(data []byte) (ScanSummary, error)
ParseGrype reads `grype -o json` output.
func ParseTrivy ¶
func ParseTrivy(data []byte) (ScanSummary, error)
ParseTrivy reads `trivy sbom --format json` output.
type Service ¶
type Service struct {
// contains filtered or unexported fields
}
Service records SBOMs, scans them on demand and gates releases. It holds no goroutine while idle apart from the optional sweep ticker.
func (*Service) AfterBuild ¶
func (s *Service) AfterBuild(ctx context.Context, app, attemptID string, att build.Attestations) error
AfterBuild is the deploy pipeline hook: it stores the build's SBOM and, when the app opted in, scans it and applies the gate. Only a gate block returns an error; every other failure is logged and the release proceeds.
func (*Service) ArmOverride ¶
ArmOverride lets the next blocked release of app through once, for reason.
func (*Service) SaveSettings ¶
func (s *Service) SaveSettings(ctx context.Context, app string, patch SettingsPatch) (Settings, error)
SaveSettings applies patch. A gate other than off requires scanning enabled.
func (*Service) ScanNow ¶
ScanNow scans one deployment's stored SBOM and records the result. It never changes a release that is already live.
func (*Service) Settings ¶
Settings returns the app's settings, or the default (off) when none exist.
type Settings ¶
type Settings struct {
App string
Enabled bool
Gate GateMode
// OverrideReason and OverrideArmedAt describe a one-shot gate override.
OverrideReason string
OverrideArmedAt time.Time
}
Settings are the per-app scan settings.
type SettingsPatch ¶
SettingsPatch changes the fields that are non-nil.
type Store ¶
type Store interface {
GetSettings(ctx context.Context, app string) (Settings, error)
// SaveSettings writes scan_enabled and scan_gate only and clears any override the new gate no longer allows.
SaveSettings(ctx context.Context, s Settings) error
// ArmOverride arms an override only while the stored gate is block_on_critical with scanning on.
ArmOverride(ctx context.Context, app, reason string, at time.Time) (bool, error)
// ConsumeOverride returns and clears the armed override if it is newer than notBefore.
ConsumeOverride(ctx context.Context, app string, notBefore time.Time) (string, bool, error)
SaveRecord(ctx context.Context, r Record) error
GetRecord(ctx context.Context, attemptID string) (Record, error)
ListRecords(ctx context.Context, app string) ([]Record, error)
ListAllRecords(ctx context.Context) ([]Record, error)
RecordsByAttempt(ctx context.Context, ids []string) (map[string]Record, error)
DeleteRecords(ctx context.Context, ids []string) error
DeleteApp(ctx context.Context, app string) error
}
Store persists supply chain metadata; *SQLStore satisfies it.