Documentation
¶
Overview ¶
Package security turns a scanner's findings into a number, and that number into a decision.
Requirements §23, design 09. The split matters: the scanner knows what is wrong, this package knows what it costs and what follows, and neither learns the other's vocabulary (R-251). Everything here is arithmetic and policy — there is no I/O in the scoring, which is why it can be tested without Docker, a scanner, or a database.
Index ¶
- Constants
- func AwaitsImage(scan state.Scan) bool
- func ContentAddressed(image string) bool
- func Due(clock Clock, doc policy.Document, insecureSince *time.Time) bool
- func Fixable(findings []api.Finding) []api.Finding
- func Ranked(findings []api.Finding) []api.Finding
- func Refusal(standing Standing, worst []api.Finding) error
- func Score(findings []api.Finding) int
- func Union(parts ...api.ScanResult) api.ScanResult
- func Worst(findings []api.Finding, n int) []api.Finding
- type Auditor
- type Clock
- type Counts
- type DetectionScanner
- func (d DetectionScanner) RecordParts(ctx context.Context, req api.ScanRequest, parts []api.ScanResult, failed error)
- func (d DetectionScanner) ScanPart(ctx context.Context, req api.ScanRequest) (api.ScanResult, error)
- func (d DetectionScanner) ScanSource(ctx context.Context, req api.ScanRequest)
- func (d DetectionScanner) Scans() bool
- type Placed
- type PolicyLoader
- type Report
- type Scanner
- type Scope
- type Scores
- type Service
- func (s *Service) Allows(ctx context.Context, appID, specID string) (Standing, error)
- func (s *Service) Configured() (string, bool)
- func (s *Service) Place(ctx context.Context, scores map[string]Scores) (map[string]Placed, error)
- func (s *Service) RecordParts(ctx context.Context, req api.ScanRequest, parts []api.ScanResult, failed error, ...) (state.Scan, error)
- func (s *Service) Report(ctx context.Context, appID, specID string) (Report, error)
- func (s *Service) Reuse(ctx context.Context, appID, specID, source, image string) (state.Scan, bool, error)
- func (s *Service) Scan(ctx context.Context, req api.ScanRequest, principal audit.Event) (state.Scan, error)
- func (s *Service) ScanPart(ctx context.Context, req api.ScanRequest) (api.ScanResult, error)
- func (s *Service) Scanning(appID string) (time.Time, bool)
- type Standing
- type Verdict
Constants ¶
const ( WeightCritical = 25 WeightHigh = 10 WeightMedium = 3 WeightLow = 1 )
The weights (R-313).
One critical finding costs more than fifty low ones, deliberately. A score that averaged, or that scaled with the size of the app, would let a real problem hide behind a long tail of noise — and a threshold set against it would mean something different for every app.
`unknown` scores as low: a finding whose severity nobody has decided is not free, and is not critical either.
Variables ¶
This section is empty.
Functions ¶
func AwaitsImage ¶ added in v0.5.0
AwaitsImage reports whether a scan read the source alone, before there was a revision: detection's scan of a repository Pando builds, or Scan now on an app not yet accepted. Its score is the source's and not the app's. The app runs an image nobody has built yet, and R-311 says the score comes from what the app deploys. The findings stand; the number waits for a scan of the image.
func ContentAddressed ¶ added in v0.5.0
ContentAddressed reports whether an image reference names its content: an image ID (sha256:…) or a reference pinned by digest (repo@sha256:…). Only such a name says two images are the same image.
func Fixable ¶
Fixable returns the findings a fix exists for.
The distinction host policy may act on (R-313): "what is wrong with this app" and "what could its owner do about it today" are different questions, and an installation that only acts on the second should not be scored on the first. A finding with no fix is still real — it is hidden only where policy says the number should not count it, and the two agree because the same filter drives both.
func Ranked ¶
Ranked returns the findings worst-first.
Severity, then ID, so the same scan orders the same way twice — a list whose order depends on the scanner's output order changes under the reader for no reason.
func Refusal ¶
Refusal is the plan-time error for an app that may not be deployed.
Written here rather than at the call site so the message is the same wherever the refusal comes from, and so the three findings that cost the most travel with it — R-105: an error somebody can act on, or paste into the assistant that wrote the app.
func Union ¶ added in v0.5.0
func Union(parts ...api.ScanResult) api.ScanResult
Union is one scan's result from scans of its parts: the image's findings and the source's, in that order, with a finding both report kept once.
What a scanner given an image and a source together reports (design 09 §3): a dependency found in both costs what it costs once. Two findings are the same finding when they have the same ID at the same target — the fields every scanner fills, so nothing here reads one scanner's vocabulary (R-251). The scanner named is the parts' own, and the time is when the last part finished.
Types ¶
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 how many findings of each severity, for a sentence a person reads.
type DetectionScanner ¶ added in v0.5.0
DetectionScanner is the service as detection uses it (R-311, R-312): scans with no spec ID — there is no revision yet, and there may never be one — and never fatal. The proposal is what detection produces, and a scanner that could not read a checkout or an image is recorded as a failed scan on the app already.
func (DetectionScanner) RecordParts ¶ added in v0.5.0
func (d DetectionScanner) RecordParts(ctx context.Context, req api.ScanRequest, parts []api.ScanResult, failed error)
RecordParts records what ScanPart read as one scan of the source and the image.
func (DetectionScanner) ScanPart ¶ added in v0.5.0
func (d DetectionScanner) ScanPart(ctx context.Context, req api.ScanRequest) (api.ScanResult, error)
ScanPart scans the source or the image detection built, recording nothing: the two run at once, beside the build and the trial run.
func (DetectionScanner) ScanSource ¶ added in v0.5.0
func (d DetectionScanner) ScanSource(ctx context.Context, req api.ScanRequest)
ScanSource scans and records in one call: the checkout, and the published image the proposal runs by digest when it runs one, so a deploy of the same source and image reuses the scan.
func (DetectionScanner) Scans ¶ added in v0.5.0
func (d DetectionScanner) Scans() bool
Scans reports whether there is a scanner to scan with.
type Placed ¶
type Placed struct {
Score *int
Verdict Verdict
// Scanning says a scan of the app is running now, so a list can show it
// rather than "Not scanned" until the scan ends.
Scanning bool
}
Placed is the number this installation means, and what it means.
type PolicyLoader ¶
PolicyLoader is host policy, narrowed to the one call this needs.
type Report ¶
type Report struct {
Standing Standing `json:"standing"`
Scan *state.Scan `json:"scan,omitempty"`
Counts Counts `json:"counts"`
Worst []api.Finding `json:"worst,omitempty"`
// Scope is what the scan above read: source, image or image_and_source
// (R-311). Empty with no scan.
Scope Scope `json:"scope,omitempty"`
// AwaitingImage says the scan above read the source alone, before there
// was a revision, so its score is the source's and not the app's (R-311):
// standing.score is empty until a scan reads the image the app runs. The
// scan's own score and findings are still in scan.
AwaitingImage bool `json:"awaiting_image,omitempty"`
// Scanner is what would run, or what did. Empty when the installation has
// none configured, which is what makes a threshold inert (R-317).
Scanner string `json:"scanner,omitempty"`
// ScanningSince is when a scan of this app that has not finished began,
// and absent when none is running. Whoever started it: a deploy, detection,
// or somebody asking — so every client can show that a scan is under way
// rather than only the one that asked for it (R-261).
ScanningSince *time.Time `json:"scanning_since,omitempty"`
// IgnoringUnfixable says the score and the list above leave out findings
// with no fix, because host policy says so (R-313). Said rather than
// implied: a list somebody cannot explain the length of is a list they
// stop trusting.
IgnoringUnfixable bool `json:"ignoring_unfixable"`
}
Report is an app's security standing, with the scan behind it.
type Scanner ¶
type Scanner interface {
Scan(ctx context.Context, req api.ScanRequest) (api.ScanResult, error)
}
Scanner is what this package needs from an adapter, narrowed to the one call.
type Scope ¶ added in v0.5.0
type Scope string
Scope is what a scan read (R-311): the source, the image, or both. Said on the wire rather than left for each client to work out, so a number is never read as covering more than it does.
const ( // ScopeSource: the source alone, with no image. ScopeSource Scope = "source" // ScopeImage: an image alone — an image app, whose only source is the // image's own digest, or Scan now on a deployed app. ScopeImage Scope = "image" // ScopeImageAndSource: an image and the source it came from. ScopeImageAndSource Scope = "image_and_source" )
type Service ¶
type Service struct {
Scans *state.Scans
Deployments *state.Deployments
Registry *api.Registry
Policy PolicyLoader
Auditor Auditor
Logger *zap.Logger
// contains filtered or unexported fields
}
Service runs scans and answers "where does this app stand".
The adapter produces findings, this produces the score, records it, and tells the audit log. Nothing here knows what a CVE is and nothing in the adapter knows what a threshold is (R-251).
func (*Service) Allows ¶
Allows reports whether this revision may be deployed (R-314).
Called from the deploy path with the score of the image that was just built, and from the planner with whatever is already known about the revision.
func (*Service) Configured ¶
Configured reports whether this installation can scan at all.
func (*Service) Place ¶
Place puts a set of already-fetched scores against policy, in one pass.
For a list: the alternative is a policy load and a scanner lookup per row, which is the shape that turns a page of twenty apps into forty queries. The scores come from the caller because the list query already fetched them.
func (*Service) RecordParts ¶ added in v0.5.0
func (s *Service) RecordParts(ctx context.Context, req api.ScanRequest, parts []api.ScanResult, failed error, principal audit.Event) (state.Scan, error)
RecordParts records one scan of what req names — its source and its image together — from the results of scanning each part on its own: the union of their findings, as one scan of both would report them (Union). A part that failed fails the scan, which is recorded with no score (R-318), as Scan records one. The scan is keyed by req's source and image, so a deploy of the same source and image reuses it (R-312).
func (*Service) Report ¶
Report is where an app stands right now.
The scan of the revision it is running, not the newest scan of the app: an app whose newest scan is of a revision it is not running has not been scanned in the sense that matters.
func (*Service) Reuse ¶
func (s *Service) Reuse(ctx context.Context, appID, specID, source, image string) (state.Scan, bool, error)
Reuse finds a successful scan of this app's source and image by the scanner that would run now, and makes it the scan of specID: a deploy of that source and image uses it rather than scanning the same thing again (R-312, design 09 §4.1).
Source is what names the source exactly — a commit, an image digest, an upload's archive digest — and image is what the deploy built or pulled, by digest, or empty when it has none. The scan reused has to have read both (R-311): detection's scan of the source alone does not stand in for the image a deploy built from it, and neither does a scan of another build of the same commit. An image named by anything but its digest is never reused, since the name says nothing about what is in it. A scan by another scanner does not count either: its findings are not the ones this installation's scanner would report. Found is false when there is nothing to reuse, and the caller scans.
A scan already attached to specID is returned as it is. One attached to another revision, or to none, is recorded again against specID, so the threshold check and every screen that asks for the revision's score find it.
func (*Service) Scan ¶
func (s *Service) Scan(ctx context.Context, req api.ScanRequest, principal audit.Event) (state.Scan, error)
Scan runs a scan and records it, whatever the outcome.
A failure is recorded as a scan with no score rather than swallowed: a scanner that cannot run is a thing somebody has to fix, and silence would leave an unscannable app looking clean (R-318).
func (*Service) ScanPart ¶ added in v0.5.0
func (s *Service) ScanPart(ctx context.Context, req api.ScanRequest) (api.ScanResult, error)
ScanPart runs the scanner over one part of what an app runs — its source, or its image — and records nothing. Detection scans the source while the image builds and the image once it is built (R-311), then records the two as one scan with RecordParts. The app is shown as scanning while a part runs.
type Standing ¶
type Standing struct {
Verdict Verdict `json:"verdict"`
// Score is nil for a revision with no score, which is not a score of zero
// (R-318). The wire keeps them apart because the console has to.
Score *int `json:"score"`
Threshold int `json:"threshold"`
// Scanned is when the score was taken. Zero when there is none.
Scanned time.Time `json:"scanned,omitzero"`
// StopAt is when this app will be stopped, when it is insecure and policy
// says to stop. Zero otherwise.
StopAt time.Time `json:"stop_at,omitzero"`
}
Standing is an app's position against the installation's threshold.
func Evaluate ¶
func Evaluate(doc policy.Document, score *int, scanned time.Time, scannerConfigured bool, insecureSince *time.Time) Standing
Evaluate places one app against policy.
`score` is nil for a revision that has never been scanned, or whose only scan failed — which is not the same as a score of zero and is not treated as one (R-318).
func (Standing) Deployable ¶
Deployable reports whether a standing permits a deploy (R-314).
Unscanned blocks: with a threshold in force, "we have never looked" is not a pass. Inert does not: an installation with no scanner has nothing to look with, and a policy that blocks every deploy because a component is missing is worse than one that is visibly off.
type Verdict ¶
type Verdict string
Verdict is what policy says about an app's standing.
const ( // VerdictOK: at or above the threshold, or no threshold in force. VerdictOK Verdict = "ok" // VerdictUnscanned: a threshold is in force and this revision has no score. VerdictUnscanned Verdict = "unscanned" // VerdictInsecure: below the threshold. VerdictInsecure Verdict = "insecure" // VerdictInert: a threshold is set and no scanner is configured, so nothing // is enforced and the console says so (R-317). VerdictInert Verdict = "inert" )