supplychain

package
v0.4.0 Latest Latest
Warning

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

Go to latest
Published: Oct 15, 2026 License: Apache-2.0 Imports: 20 Imported by: 0

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

View Source
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.

View Source
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.

View Source
const (
	ActionAllow    = "allow"
	ActionWarn     = "warn"
	ActionBlock    = "block"
	ActionOverride = "override"
)

Gate actions stored on a record.

View Source
const (
	ScannerTrivy = "trivy"
	ScannerGrype = "grype"
)

Scanner names.

View Source
const (
	FormatSPDX      = "spdx"
	FormatCycloneDX = "cyclonedx"
)

SBOM document formats.

View Source
const (
	SeverityCritical = "critical"
	SeverityHigh     = "high"
	SeverityMedium   = "medium"
	SeverityLow      = "low"
	SeverityUnknown  = "unknown"
)

Severity levels, most severe first.

View Source
const (
	ScanNone        = ""
	ScanOK          = "ok"
	ScanFailed      = "failed"
	ScanUnavailable = "unavailable"
)

Scan statuses stored on a record.

Variables

View Source
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.

View Source
var ErrNotEnabled = errors.New("supplychain: scanning is not enabled for this app")

ErrNotEnabled means scanning is not enabled for the app.

View Source
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.

func ConfigFromEnv

func ConfigFromEnv(lookup func(string) (string, bool), logger *slog.Logger) Config

ConfigFromEnv reads Config from lookup (os.LookupEnv in production). Unset or malformed values fall back to the 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.

func (Counts) Total

func (c Counts) Total() int

Total is the number of findings across every severity.

type Decision

type Decision struct {
	Action string
	Reason string
}

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.

func (Decision) Blocked

func (d Decision) Blocked() bool

Blocked reports whether the release must not go live.

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 FileEntry

type FileEntry struct {
	App       string
	AttemptID string
	Size      int64
	ModTime   time.Time
}

FileEntry is one SBOM file found on disk.

type FileStore

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

FileStore keeps SBOM documents at <root>/<app>/<attempt>.sbom.json.

func NewFileStore

func NewFileStore(root string) *FileStore

NewFileStore returns a store rooted at root, which is created on first write.

func (*FileStore) List

func (f *FileStore) List() ([]FileEntry, error)

List returns every SBOM file on disk.

func (*FileStore) Path

func (f *FileStore) Path(app, attemptID string) (string, error)

Path returns the file path for an attempt, rejecting names that could escape the root.

func (*FileStore) Read

func (f *FileStore) Read(app, attemptID string) ([]byte, error)

Read returns the stored document, or ErrNotFound.

func (*FileStore) Remove

func (f *FileStore) Remove(app, attemptID string) error

Remove deletes one document; a missing file is not an error.

func (*FileStore) RemoveApp

func (f *FileStore) RemoveApp(app string) error

RemoveApp deletes every document of an app.

func (*FileStore) RemoveEntry

func (f *FileStore) RemoveEntry(e FileEntry) error

RemoveEntry deletes the file behind e.

func (*FileStore) Write

func (f *FileStore) Write(app, attemptID string, data []byte) error

Write stores data atomically.

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

func ParseGateMode(s string) (GateMode, error)

ParseGateMode validates a gate mode string; empty means off.

type LicenseCount

type LicenseCount struct {
	License string `json:"license"`
	Count   int    `json:"count"`
}

LicenseCount is how many packages carry a license.

type Option

type Option func(*Service)

Option customizes a Service.

func WithClock

func WithClock(now func() time.Time) Option

WithClock overrides the clock, for tests.

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.

func ParseSBOM

func ParseSBOM(data []byte) (format string, pkgs []Package, err error)

ParseSBOM reads an SPDX or CycloneDX JSON document into its packages.

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.

func (Record) HasSBOM

func (r Record) HasSBOM() bool

HasSBOM reports whether the SBOM document is still on disk.

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 NewSQLStore

func NewSQLStore(db *store.DB) *SQLStore

NewSQLStore wraps db.

func (*SQLStore) ArmOverride

func (s *SQLStore) ArmOverride(ctx context.Context, app, reason string, at time.Time) (bool, error)

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

func (s *SQLStore) AttemptExists(ctx context.Context, attemptID string) (bool, error)

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) DeleteApp

func (s *SQLStore) DeleteApp(ctx context.Context, app string) error

DeleteApp removes every record and the settings of an app.

func (*SQLStore) DeleteRecords

func (s *SQLStore) DeleteRecords(ctx context.Context, ids []string) error

DeleteRecords removes records by attempt ID.

func (*SQLStore) GetRecord

func (s *SQLStore) GetRecord(ctx context.Context, attemptID string) (Record, error)

GetRecord returns one record or ErrNotFound.

func (*SQLStore) GetSettings

func (s *SQLStore) GetSettings(ctx context.Context, app string) (Settings, error)

GetSettings returns the stored settings, or the default (off) when none exist.

func (*SQLStore) ListAllRecords

func (s *SQLStore) ListAllRecords(ctx context.Context) ([]Record, error)

ListAllRecords returns every record.

func (*SQLStore) ListRecords

func (s *SQLStore) ListRecords(ctx context.Context, app string) ([]Record, error)

ListRecords returns app's records, newest first.

func (*SQLStore) RecordsByAttempt

func (s *SQLStore) RecordsByAttempt(ctx context.Context, ids []string) (map[string]Record, error)

RecordsByAttempt maps attempt IDs to their records.

func (*SQLStore) SaveRecord

func (s *SQLStore) SaveRecord(ctx context.Context, r Record) error

SaveRecord upserts a record.

func (*SQLStore) SaveSettings

func (s *SQLStore) SaveSettings(ctx context.Context, st Settings) error

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 New

func New(cfg Config, deps Deps, opts ...Option) *Service

New builds a Service.

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

func (s *Service) ArmOverride(ctx context.Context, app, reason string) (Settings, error)

ArmOverride lets the next blocked release of app through once, for reason.

func (*Service) Config

func (s *Service) Config() Config

Config returns the effective configuration.

func (*Service) DeleteApp

func (s *Service) DeleteApp(ctx context.Context, app string) error

DeleteApp removes every record, file and setting of a deleted app.

func (*Service) Get

func (s *Service) Get(ctx context.Context, app, attemptID string) (Record, error)

Get returns the record of one deployment of app.

func (*Service) List

func (s *Service) List(ctx context.Context, app string) ([]Record, error)

List returns app's records, newest first.

func (*Service) Lookup

func (s *Service) Lookup(ctx context.Context, ids []string) (map[string]Record, error)

Lookup maps deploy attempt IDs to their records.

func (*Service) OpenSBOM

func (s *Service) OpenSBOM(ctx context.Context, app, attemptID string) ([]byte, Record, error)

OpenSBOM returns the stored SBOM document of one deployment.

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

func (s *Service) ScanNow(ctx context.Context, app, attemptID string) (Record, error)

ScanNow scans one deployment's stored SBOM and records the result. It never changes a release that is already live.

func (*Service) Settings

func (s *Service) Settings(ctx context.Context, app string) (Settings, error)

Settings returns the app's settings, or the default (off) when none exist.

func (*Service) Start

func (s *Service) Start(ctx context.Context)

Start runs a startup sweep and then one every SweepInterval until ctx ends.

func (*Service) Sweep

func (s *Service) Sweep(ctx context.Context)

Sweep runs every cleanup pass: records of deleted apps and deploys, SBOM retention, orphan files and orphan scanner containers.

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

type SettingsPatch struct {
	Enabled *bool
	Gate    *string
}

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.

type TypeCount

type TypeCount struct {
	Type  string `json:"type"`
	Count int    `json:"count"`
}

TypeCount is how many packages are of a type.

type Vuln

type Vuln struct {
	ID           string `json:"id"`
	Package      string `json:"package"`
	Version      string `json:"version,omitempty"`
	FixedVersion string `json:"fixed_version,omitempty"`
	Severity     string `json:"severity"`
	Title        string `json:"title,omitempty"`
}

Vuln is one finding.

Jump to

Keyboard shortcuts

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