Documentation
¶
Overview ¶
Package sealed builds what the sealed coverage tier runs against: advisory databases generated from one file of our own text, a container with no network, the expectations a scenario states, and the comparison between those and what a scan produced.
The tier exists to assert exact results. A test against the live databases can only assert structure, because an advisory published tomorrow changes today's answer. Against a database generated from test/integration/testdata/ecosystems/advisories.yaml, a scan either produces the findings the scenario names or it has a bug, and running it with no network makes any fetch the design did not account for fail rather than drift.
Index ¶
- Constants
- func AWSAccessKeyID() (string, error)
- func Check(exp Expected, anns []Annotation, got []Finding) ([]string, error)
- func CheckErrors(exp []ErrorExpectation, report []byte) ([]string, error)
- func CheckInit(exp, got InitExpectation) []string
- func CheckPresent(exp Expected, anns []Annotation, got []Finding) ([]string, error)
- func CheckRequests(want, never []string, log []byte) []string
- func Commits(repo string) ([]string, error)
- func CopyTree(src, dst string) error
- func Diff(want, got string) string
- func Drift(recorded, observed Structure) []string
- func LinkTree(src, dst string) error
- func Lost(recorded, observed Structure) []string
- func RunReplacements(work string, start time.Time) map[string]string
- func SemgrepRuleIDs(path string) ([]string, error)
- func ServeAndRun(addr, root, logPath string, argv []string, stdout, stderr io.Writer) int
- func ServedHandler(root string, log io.Writer) http.Handler
- func ServedImage(scenario string) string
- func TrivyRanOffline(log []byte) (missing []string, ran int)
- func ValidateSARIF(raw []byte) error
- func WriteGoVulnDB(home string, advs Advisories, fetchedAt time.Time) error
- func WriteGrypeDB(home string, advs Advisories, builtAt time.Time) error
- func WriteRetireRepo(home string, advs Advisories, fetchedAt time.Time) error
- func WriteServedImage(served, name, tag, src string) error
- func WriteTrivyDB(cacheDir string, advs Advisories) error
- type Advisories
- type Advisory
- type Annotation
- type ComponentExpectation
- type Container
- type ErrorExpectation
- type Expected
- type FileStructure
- type Finding
- type FindingExpectation
- type InitExpectation
- type InitScanExpectation
- type Normalizer
- type PackageStructure
- type RunOptions
- type Scenario
- type SecretExpectation
- type Structure
Constants ¶
const Cleared = "<cleared>"
Cleared is what a normalized field is replaced with.
const FailingToolMessage = "sealed fixture: this program failed on purpose"
FailingToolMessage is what a program replaced by Fail writes before exiting 2.
const FixtureSuffix = ".fixture"
FixtureSuffix marks a dependency manifest stored under another name. A requirements.txt, go.mod or package-lock.json anywhere in this repository is read by the forge's dependency graph and reported as a dependency of Draugr, vulnerabilities included; stored with the suffix it is a file nothing parses, and Prepare restores its name in the copy a scan reads.
const (
GrypeSchemaModel = 6
)
The database schema the pinned Grype reads, as model, revision and addition. `grype version` prints the model as "Supported DB Schema". A Grype that moves to another model refuses this database rather than reading it wrongly; bump these with the tool and regenerate.
const Image = "ubuntu:24.04@sha256:008173c23f95b170204355c12626cb5a965d779a7e1283b09e9cffbb1bf33ca3"
Image is the container the sealed tier runs in, pinned by digest. It supplies only the root of a filesystem: /usr, /etc and /opt are the host's, mounted read-only, so the tools, interpreters and libraries inside are the ones installed on the machine running the test, and the only thing the container takes away is the network.
const SemgrepDefaultRefusal = "cannot run offline: config.controls.sast.semgrep.config is unset"
SemgrepDefaultRefusal is text the sast control's error carries when Semgrep refuses to run offline on the default ruleset, which is what a descriptor naming no ruleset asks for.
const ServedAddr = "127.0.0.1:18080"
ServedAddr is where the sealed server listens inside the container. A fixed port is safe because every container has a network namespace of its own, with nothing else in it.
const ServedURL = "http://" + ServedAddr
ServedURL is ServedAddr as a URL, for a descriptor or a tool setting that points at it.
const TrivySchemaVersion = 2
TrivySchemaVersion is the database schema the pinned Trivy reads. A Trivy that moves to another fails to open this database rather than reading it wrongly; bump it with the tool and regenerate.
const WorkdirFiles = "workdir"
WorkdirFiles is the directory in a scenario whose files are copied beside the descriptor, for an option that names a file by a path relative to where Draugr runs: a Gitleaks ruleset, a directory of Rego checks.
Variables ¶
This section is empty.
Functions ¶
func AWSAccessKeyID ¶
AWSAccessKeyID returns a random string in the shape of an AWS access key id, which no AWS account issued. Regenerated until its entropy clears the threshold secret scanners apply, so a low-entropy draw cannot make a scenario fail by chance.
func Check ¶
func Check(exp Expected, anns []Annotation, got []Finding) ([]string, error)
Check compares what a scan reported with what the scenario expects, and returns one line per difference: an expected result that is missing, a result nothing expected, and a result on a line an annotation marks as one its rule must not report.
func CheckErrors ¶
func CheckErrors(exp []ErrorExpectation, report []byte) ([]string, error)
CheckErrors compares the controls a report says could not run with the ones the scenario expects to fail, one line per difference. A control that errors unexpectedly is a difference, and so is an expected failure that ran, or failed for another reason.
func CheckInit ¶
func CheckInit(exp, got InitExpectation) []string
CheckInit compares what `draugr init` wrote with what the scenario expects, one line per difference.
func CheckPresent ¶ added in v0.135.0
func CheckPresent(exp Expected, anns []Annotation, got []Finding) ([]string, error)
CheckPresent holds a scan made against real advisory databases to what the scenario expects, one line per expected result it did not report. The databases are not ours here, so the match is on what they cannot move: a package finding is matched on control, scanner, location and package whatever advisory it carries, an expected reachability verdict asks only that one was reached, and a result nothing expected is allowed.
func CheckRequests ¶ added in v0.135.0
CheckRequests returns a problem for every entry of want that no line of the request log starts with, and for every line that starts with an entry of never.
func Commits ¶ added in v0.135.0
Commits lists the commits of a repository Prepare made. Each holds a generated secret, so its id is particular to one run, and a history scan writes it into every finding it makes.
func CopyTree ¶ added in v0.135.0
CopyTree copies every file under src into dst, restoring the name of each one stored with FixtureSuffix. A file already in dst is replaced.
func Diff ¶
Diff renders the lines that differ between two documents, as removals and additions around the longest run of lines they share, capped at a screenful.
func Drift ¶ added in v0.135.0
Drift compares a recorded structure with an observed one, one line per difference: `-` for what the recording has and the scan no longer found, `+` for what the scan found that it does not.
func LinkTree ¶ added in v0.135.0
LinkTree makes dst a copy of the tree at src in which every regular file is a hard link to the original, so a home holding several gigabytes of advisory databases can be given to each scenario in the time it takes to create the directories. Where a link cannot be made, across filesystems for one, the file is copied. Symbolic links are recreated as they are.
func Lost ¶ added in v0.135.0
Lost names every ecosystem and every control the recording has findings for and the observed structure has none for. That is never drift: the fixtures pin versions with known advisories, so a language or a control going to zero is a scanner that stopped reading something, which looks like success and must fail instead of being written down as the new expectation.
func RunReplacements ¶
RunReplacements are the strings particular to one sealed run: its directory and the dates the run could have stamped.
func SemgrepRuleIDs ¶ added in v0.135.0
SemgrepRuleIDs are the ids of the rules a Semgrep rules file declares.
func ServeAndRun ¶ added in v0.135.0
ServeAndRun serves root on addr, logging requests to logPath, runs argv once the listener is up, and returns the exit code argv ended with. The server lives exactly as long as the command, so nothing outlives the container it started in.
func ServedHandler ¶ added in v0.135.0
ServedHandler serves the files under root to GET and HEAD, the way a Maven repository, a rule registry or a container registry answers, and appends a line to log for every request, whatever its method, so a scenario can assert what a scanner asked for and what it never sent.
A registry client reads a manifest's type from Content-Type rather than from the document, so a file under /v2/<name>/manifests/ is served with the mediaType it declares and the digest a client checks it against.
func ServedImage ¶ added in v0.135.0
ServedImage is the reference a scenario's image/ tree is served under, for a descriptor to name.
func TrivyRanOffline ¶ added in v0.135.0
TrivyRanOffline reads the log `draugr scan --log-file` writes and returns every Trivy invocation that reads dependencies without --offline-scan. Without that flag Trivy resolves a pom.xml's dependencies against Maven Central during the scan, the one request --skip-db-update does not stop. The second result counts the invocations read, so a caller can tell "every one carried the flag" from "there were none".
func ValidateSARIF ¶
ValidateSARIF checks a document against the SARIF 2.1.0 schema.
The published schema declares JSON Schema draft-04, which the validator does not read. The two differences that matter are its "$schema" and its top-level "id", renamed "$id" in later drafts; the schema uses no other draft-04 construct, so both are rewritten on load and nothing else is.
func WriteGoVulnDB ¶
func WriteGoVulnDB(home string, advs Advisories, fetchedAt time.Time) error
WriteGoVulnDB writes the Go advisories in advs as a Go vulnerability database in the offline layout govulncheck's -db reads (index/db.json, index/modules.json, index/vulns.json, ID/<id>.json) into home/.draugr/feeds/govulndb, and records it in the feed manifest as fetched at fetchedAt, which is where a scan looks for it.
func WriteGrypeDB ¶ added in v0.135.0
func WriteGrypeDB(home string, advs Advisories, builtAt time.Time) error
WriteGrypeDB writes a Grype vulnerability database holding advs into home/.cache/grype/db/<model>, the directory Grype reads when HOME is home, with the import record Grype checks the file against.
builtAt is the build time the database records. Grype refuses a database built more than five days before the scan, so it is the start of the run rather than a fixed date.
func WriteRetireRepo ¶
func WriteRetireRepo(home string, advs Advisories, fetchedAt time.Time) error
WriteRetireRepo writes the js advisories in advs as a retire.js repository at home/.draugr/data/retirejs/jsrepository.json, with the cache index naming it, which is the copy Draugr hands retire.js with --jsrepo when offline. Each library is identified by its file name and by a banner comment.
func WriteServedImage ¶ added in v0.135.0
WriteServedImage writes the tree under src as a single-layer linux/amd64 OCI image into served, laid out the way ServedHandler answers the registry API: the manifest under /v2/<name>/manifests/<tag> and each blob under /v2/<name>/blobs/<digest>. The layer is written with fixed timestamps and owners, so the image, and every digest a report records, is the same on every run.
func WriteTrivyDB ¶
func WriteTrivyDB(cacheDir string, advs Advisories) error
WriteTrivyDB writes a Trivy vulnerability database holding advs into cacheDir/db, the layout `trivy --cache-dir <cacheDir> --skip-db-update` reads. Advisories in an ecosystem Trivy does not cover (js) are left out.
Types ¶
type Advisories ¶
type Advisories struct {
Advisories []Advisory `yaml:"advisories"`
}
Advisories is the parsed advisories file.
func LoadAdvisories ¶
func LoadAdvisories(path string) (Advisories, error)
LoadAdvisories reads and checks an advisories file.
func (Advisories) For ¶
func (a Advisories) For(ecosystem string) []Advisory
For returns the advisories in one ecosystem, in file order.
type Advisory ¶
type Advisory struct {
// ID is the identifier a scanner reports: a CVE where there is one.
ID string `yaml:"id"`
// GHSA is the GitHub advisory identifier, for an advisory whose CVE is a separate record. The
// Grype database then keys the advisory by it and lists ID as its related CVE, with the CVE's
// own record beside it, the way Grype's GitHub and NVD providers store one. Grype reports the
// GHSA with byCve off and the CVE with it on; the other databases report ID either way.
GHSA string `yaml:"ghsa,omitempty"`
// GoID is the Go vulnerability database's own identifier, for an advisory govulncheck reads.
GoID string `yaml:"goID,omitempty"`
// Ecosystem is the package ecosystem: pip, npm, go, rubygems, cargo, composer, nuget, maven,
// or js for a library retire.js identifies by its file.
Ecosystem string `yaml:"ecosystem"`
// Package is the name the ecosystem gives the package.
Package string `yaml:"package"`
// Fixed is the first version without the vulnerability; every earlier version is affected.
Fixed string `yaml:"fixed"`
// Severity is CRITICAL, HIGH, MEDIUM or LOW.
Severity string `yaml:"severity"`
// Title is a one-line summary, written for the fixture.
Title string `yaml:"title"`
// Symbols maps a Go package path to the functions the vulnerability is in, which is what
// govulncheck follows to decide whether it is reachable.
Symbols map[string][]string `yaml:"symbols,omitempty"`
// CWE is the weakness, which retire.js requires on every entry.
CWE string `yaml:"cwe,omitempty"`
}
Advisory is one vulnerability the sealed databases carry, in the terms every generator needs.
type Annotation ¶
type Annotation struct {
// File is relative to the repository, with any fixture suffix removed.
File string
// Line is the line the annotation is about: the first line after it that is neither blank nor
// another annotation.
Line int
Rule string
// Want is true for `ruleid:` and false for `ok:`.
Want bool
}
Annotation is an expectation written in a fixture's own code, the way Semgrep tests its rules: a comment naming a rule that must (`ruleid:`) or must not (`ok:`) report the next line of code.
func ReadAnnotations ¶
func ReadAnnotations(dir string) ([]Annotation, error)
ReadAnnotations collects the annotations in every file under dir, skipping vendor/ and node_modules/, which hold other people's code.
func WithoutRules ¶ added in v0.135.0
func WithoutRules(anns []Annotation, rules []string) []Annotation
WithoutRules is anns less the annotations for rules, for a scan in which the scanner that declares them did not run.
type ComponentExpectation ¶
type ComponentExpectation struct {
Name string `yaml:"name"`
// Repositories are the repository urls, relative to the descriptor.
Repositories []string `yaml:"repositories"`
// Paths and Ignore are the scope its repositories declare. Empty means the whole repository.
Paths []string `yaml:"paths"`
Ignore []string `yaml:"ignore"`
}
ComponentExpectation is one component `draugr init` declares.
type Container ¶
type Container struct {
// Work is the one writable directory, mounted at the same path inside. The run's HOME is
// Work/home, so every cache and dataset Draugr reads is the one the test put there.
Work string
// HostHome is the invoking user's home directory, mounted read-only for the tools installed
// under it.
HostHome string
// Path is the PATH inside the container.
Path string
// Extra are further read-only mounts, such as a GOROOT outside the directories already mounted.
Extra []string
// Env is added to the environment inside the container.
Env map[string]string
// UID and GID are who the container runs as, so what it writes belongs to the invoking user.
UID, GID int
// Server, when set, is the serve helper built from ./serve. Every command then runs beside
// ServedHandler on ServedAddr, offering the files under Served and logging each request to
// RequestLog.
Server, Served, RequestLog string
}
Container describes one sealed run.
func HostContainer ¶
HostContainer describes a sealed run over work using the invoking user's tools: PATH as it is, behind the tools Draugr installed and the directory of the semgrep on PATH.
Semgrep's CLI runs a second program, pysemgrep, by looking it up on PATH. Beside the semgrep binary is the one from the same installation; a stale one elsewhere on PATH belongs to another interpreter, which the host may find through its own home directory and the container cannot.
func (*Container) Fail ¶
Fail replaces tool inside the container with a program that writes FailingToolMessage to stderr and exits 2.
type ErrorExpectation ¶
type ErrorExpectation struct {
Control string `yaml:"control"`
// Contains is text the control's error must include.
Contains string `yaml:"contains"`
}
ErrorExpectation is one control that must fail to run.
type Expected ¶
type Expected struct {
// Init is what `draugr init` writes for the repository.
Init InitExpectation `yaml:"init"`
// Secrets are credentials the harness writes into the repository before it is committed.
// Each must be reported by the secrets control.
Secrets []SecretExpectation `yaml:"secrets"`
// Findings are every other result the scan must report, and together with the secrets and the
// fixture's inline sast annotations, every result it may report.
Findings []FindingExpectation `yaml:"findings"`
// Sealed changes what the container provides and how the scan runs.
Sealed RunOptions `yaml:"sealed"`
// Errors are the controls that must fail to run, each with text its error must contain. Every
// other control must run.
Errors []ErrorExpectation `yaml:"errors"`
// Proves names the options whose effect the findings assert, as <scanner>.<option> for a
// scanner's option (gosec.include) or <control>.<option> for a control's own (licenses.deny).
// Each must be set in the scenario's descriptor, and the findings must differ because of it.
Proves []string `yaml:"proves"`
// Requests are requests the sealed server must have received by the end of the scan, each the
// start of a line of its log, "METHOD request-uri", for a scenario whose findings depend on
// something a scanner fetched from it.
Requests []string `yaml:"requests"`
// NeverRequested are line starts no request in the log may have, "DELETE " for every delete.
NeverRequested []string `yaml:"neverRequested"`
// InitScan is where a scan with the descriptor init wrote departs from Findings and Errors.
InitScan InitScanExpectation `yaml:"initScan"`
}
Expected is a scenario's expected.yaml: what `draugr init` proposes for the repository and what a sealed scan of it reports.
func (Expected) ForInitScan ¶ added in v0.135.0
ForInitScan is e as a scan with init's descriptor must match it.
func (Expected) InitRefusesSemgrep ¶ added in v0.135.0
InitRefusesSemgrep reports whether a scan with init's descriptor has Semgrep refuse to run. init names no ruleset, Semgrep's default is fetched from its registry, and an offline scan refuses a ruleset it would fetch, so the sast control reports SemgrepDefaultRefusal wherever init enables it, Semgrep is on PATH and the scan runs with --offline.
func (Expected) LeavesFieldsUnwritten ¶ added in v0.134.0
LeavesFieldsUnwritten reports whether a scan of the scenario leaves out fields the normalizers clear: a control that failed to start writes no scanner version, and a scan whose results are all about whole files, or that finds nothing, has no line to take a fingerprint from. Secrets are written on line 1, except one found only in history, which is a result about a commit rather than a line of the tree.
type FileStructure ¶ added in v0.135.0
FileStructure is one file and the control/scanner pairs that reported a result in it.
type Finding ¶
type Finding struct {
Control string
Tool string
Rule string
File string
Line int
Package string
Component string
Reachability string
// Historical is the SARIF result's history mark: the finding comes from a commit rather than
// the tree.
Historical bool
// Suppressed is who set the finding aside, as the SARIF suppression's origin names it: saga,
// vex, tool or scanner. Empty for a finding nothing set aside.
Suppressed string
}
Finding is one SARIF result, reduced to what an expectation can name.
type FindingExpectation ¶
type FindingExpectation struct {
Control string `yaml:"control"`
// Tool is the scanner that reported it, as the SARIF names it.
Tool string `yaml:"tool"`
Rule string `yaml:"rule"`
// Location is file:line relative to the repository, or the file alone for a result about a
// whole file.
Location string `yaml:"location"`
// Package is "ecosystem name version", for a finding about a dependency.
Package string `yaml:"package,omitempty"`
// Component is the component the finding was reported for. Empty matches any, which is enough
// where one component scans the repository; two components scanning one file report it twice,
// and only the component tells those two findings apart.
Component string `yaml:"component,omitempty"`
// Reachability is the verdict reachability analysis reached, where it ran.
Reachability string `yaml:"reachability,omitempty"`
// Historical says the finding comes from the repository's history rather than its tree, and
// the SARIF result has to carry the mark.
Historical bool `yaml:"historical,omitempty"`
// Suppressed is the origin of the suppression the finding has to carry: saga, vex, tool or
// scanner. Empty means the finding has to arrive active.
Suppressed string `yaml:"suppressed,omitempty"`
}
FindingExpectation is one result a scan must report.
type InitExpectation ¶
type InitExpectation struct {
// Controls are the controls it enables, in any order.
Controls []string `yaml:"controls"`
// Scanners are the scanners it enables inside a control beyond the control's default, as
// control.scanner (sast.gosec, sca.retirejs), in any order. Empty means none.
Scanners []string `yaml:"scanners"`
// Reachability are the reachability analyzers it turns on. Empty means none.
Reachability []string `yaml:"reachability"`
// Specs are the API documents it proposes as a host's spec, in the hosts block it writes
// commented out. Empty means none.
Specs []string `yaml:"specs"`
// Components are the components it declares.
Components []ComponentExpectation `yaml:"components"`
// PerDirectory is what `draugr init --per-directory` writes, for a scenario whose tree has
// directories with their own dependency files. Unset skips that run.
PerDirectory *InitExpectation `yaml:"perDirectory"`
}
InitExpectation is the descriptor `draugr init` writes.
func ObserveInit ¶
func ObserveInit(path string) (InitExpectation, error)
ObserveInit reads the descriptor `draugr init` wrote, through the loader a scan uses, as the controls and scanners it enables and the components it declares, and from its text, the API documents it proposes as a host's spec.
type InitScanExpectation ¶ added in v0.135.0
type InitScanExpectation struct {
// Skip is why a scan with init's descriptor is not compared, for a scenario whose descriptor
// is the subject: options init never writes, or components declared by hand. It is logged.
Skip string `yaml:"skip"`
// Unreported are rules from findings that init's descriptor does not report, because they
// need an option the hand-written descriptor sets and init leaves at its default.
Unreported []string `yaml:"unreported"`
// Errors replace the scenario's errors for this scan, where init enables a different set of
// controls from the ones the scenario's failure reaches. Unset keeps the scenario's; empty
// means every control runs. Semgrep's offline refusal is added to either, where
// InitRefusesSemgrep says it applies.
Errors *[]ErrorExpectation `yaml:"errors"`
}
InitScanExpectation is where a scan with the descriptor `draugr init` wrote departs from the scenario's findings and errors. Every scenario is scanned both ways, so a finding that depends on a descriptor option init does not write has to be named here, and a default that stops reaching one fails the scenario.
type Normalizer ¶
type Normalizer struct {
// Clear are paths to fields replaced with Cleared. "*" matches every element of an array or
// every value of an object. Each path must match at least one field.
Clear [][]string
// Sort are paths to arrays sorted by their elements' JSON.
Sort [][]string
// Replace maps a string to what it becomes, in every string value.
Replace map[string]string
// AllowMissing accepts a Clear path that matches nothing. For a run where a control failed to
// start, which leaves no scanner version and no finding for that control to clear.
AllowMissing bool
}
Normalizer makes a report comparable across runs: it clears the fields that vary by machine or by moment, replaces run-specific strings wherever they appear, and sorts the arrays whose order depends on which job finished first.
A path that matches nothing is an error rather than a no-op. A field that moved would otherwise stop being cleared without anybody noticing, and the golden would start pinning a value that changes on the next run, or would stop pinning anything at the old path.
func ReportNormalizer ¶
func ReportNormalizer(replace map[string]string) Normalizer
ReportNormalizer is the normalizer for Draugr's report.json.
func SARIFNormalizer ¶
func SARIFNormalizer(replace map[string]string) Normalizer
SARIFNormalizer is the normalizer for Draugr's results.sarif.
type PackageStructure ¶ added in v0.135.0
type PackageStructure struct {
Name string `yaml:"name"`
Version string `yaml:"version"`
// In is the file the package was reported in, relative to the repository.
In string `yaml:"in"`
// FoundBy are the control/scanner pairs that reported it, sorted.
FoundBy []string `yaml:"foundBy,flow"`
}
PackageStructure is one package at one location and the scanners that reported it.
type RunOptions ¶
type RunOptions struct {
// WithoutTool is a program removed from PATH inside the container.
WithoutTool string `yaml:"withoutTool"`
// FailingTool is a program replaced by one that writes FailingToolMessage and exits 2.
FailingTool string `yaml:"failingTool"`
// WithoutTrivyDB leaves Trivy's vulnerability database unwritten.
WithoutTrivyDB bool `yaml:"withoutTrivyDB"`
// GoVulnDBAge is how long before the run the local Go vulnerability database is recorded as
// fetched, as a Go duration. Empty means at the start of the run.
GoVulnDBAge string `yaml:"goVulnDBAge"`
// WithoutOffline scans without --offline, for a scenario whose scanner resolves something from
// the sealed server, which the flag would stop it asking for. The container still has no
// network beyond its own loopback.
WithoutOffline bool `yaml:"withoutOffline"`
}
RunOptions change what a sealed run provides. Most take something away, so a scenario can assert that its absence is reported rather than passed.
func (RunOptions) ScanFlags ¶ added in v0.135.0
func (o RunOptions) ScanFlags() []string
ScanFlags are the flags a sealed scan runs with beyond its descriptor and output.
func (RunOptions) TakeAway ¶ added in v0.135.0
func (o RunOptions) TakeAway(home string, now time.Time) error
TakeAway applies a scenario's data options to a home prepared with real databases: WithoutTrivyDB removes Trivy's vulnerability database, and GoVulnDBAge records the Go vulnerability database as fetched that long before now. The options that concern a program, WithoutTool and FailingTool, belong to the Container and are not applied here.
A file TakeAway changes is removed and written anew rather than edited in place, so a home made by LinkTree never writes through a hard link into the copy it was linked from.
type Scenario ¶
type Scenario struct {
// Name is the directory name, which is also the component and the repository's directory.
Name string
// Dir is the scenario directory.
Dir string
// Expected is the parsed expected.yaml.
Expected Expected
}
Scenario is one directory under ecosystems/: a repository to scan, the descriptor to scan it with, and what the scan must produce.
func LoadScenario ¶
LoadScenario reads the scenario in dir.
func (Scenario) CopyIfPresent ¶ added in v0.135.0
CopyIfPresent copies the scenario's directory name into dst with CopyTree, and does nothing when the scenario has no such directory.
func (Scenario) CopyWorkdir ¶ added in v0.135.0
CopyWorkdir copies the scenario's workdir/ into work, restoring the names of its manifests. A scenario without one copies nothing.
func (Scenario) Prepare ¶
Prepare copies the scenario's repository to work/<name>, restores the names of its manifests, writes the secrets the scenario asks for, and commits the result. A secret marked removed is deleted again in a second commit. It returns the repository directory.
The secrets are generated here and never committed to this repository: a credential-shaped string in a public tree is refused by push protection and reported by every scanner that reads it, including the one being tested.
func (Scenario) ServeImage ¶ added in v0.135.0
ServeImage writes the scenario's image/ tree into served as ServedImage(s.Name), and does nothing for a scenario without one.
type SecretExpectation ¶
type SecretExpectation struct {
// File is where the harness writes it, relative to the repository. It is written on line 1.
File string `yaml:"file"`
// Rules are the rules that must report it. Empty means nothing may report it, which is how a
// scenario writes a credential outside every component's paths and holds every component to
// leaving it alone.
Rules []string `yaml:"rules"`
// Components are the components that must each report it, once per rule. Empty means one
// report from any component, which is enough where one component scans the repository. Where
// several share it, a secret reported under a component whose paths do not hold it is a
// credential filed with a team that cannot rotate it, and only naming the owner catches that.
Components []string `yaml:"components,omitempty"`
// Removed deletes the file in a second commit, so the secret is in the repository's history
// and not in its tree, and each report of it has to carry the history mark.
Removed bool `yaml:"removed"`
}
SecretExpectation is one generated credential.
type Structure ¶ added in v0.135.0
type Structure struct {
// Packages maps an ecosystem (npm, gomod, pip) to the packages found in it.
Packages map[string][]PackageStructure `yaml:"packages,omitempty"`
// Files are the files a result with no package was reported in: a secret, a SAST result, a
// misconfiguration.
Files []FileStructure `yaml:"files,omitempty"`
}
Structure is what a scan found, reduced to the part an advisory published tomorrow does not change: each package and the scanners that reported it, grouped by ecosystem, and each file a result without a package was reported in. It is what the live tier asserts, because against the live databases the advisories themselves move every day.
func LoadStructure ¶ added in v0.135.0
LoadStructure reads a structure file. A file that does not exist is reported as absent rather than as an empty structure, because a scenario nobody has recorded is not one that finds nothing.