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 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 Worst(findings []api.Finding, n int) []api.Finding
- type Auditor
- type Clock
- type Counts
- type Placed
- type PolicyLoader
- type Report
- type Scanner
- 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) Report(ctx context.Context, appID, specID string) (Report, error)
- func (s *Service) Reuse(ctx context.Context, appID, specID, source string) (state.Scan, bool, error)
- func (s *Service) Scan(ctx context.Context, req api.ScanRequest, principal audit.Event) (state.Scan, 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 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.
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 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"`
// 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 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) 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 string) (state.Scan, bool, error)
Reuse finds a successful scan of this app's source by the scanner that would run now, and makes it the scan of specID: a deploy of that source 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 a scan by another scanner does not count: 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).
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" )