Help us shape the future of Go! Take the 2026 Go Developer Survey

vulns

package
v0.6.1 Latest Latest
Warning

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

Go to latest
Published: Oct 24, 2026 License: MIT Imports: 34 Imported by: 0

README

Vulnerability monitoring

Off by default. --vuln-enabled on osctrl-tls stores software inventory; on osctrl-api it downloads advisory data, matches it and serves findings. Package inventory never leaves the deployment: the only outbound traffic is the feed downloads below, to URLs the operator controls.

Data flow

  1. Apply a vuln-linux, vuln-darwin or vuln-windows profile (GET /api/v1/vulnerabilities/profiles) to an environment's schedule. The queries run daily in snapshot mode.
  2. osctrl-tls replaces each node's node_software rows per category on every snapshot and records the OS in vuln_node_state.
  3. One osctrl-api replica (SQL lease) syncs the OSV ecosystems the fleet reports, plus the CISA KEV catalog and, with --vuln-nvd-enabled, NVD CVEs, every --vuln-sync-hours. It then re-matches nodes whose inventory or advisories changed.

Coverage

Inventory Matched against
Debian, Ubuntu (incl. non-LTS) deb_packages OSV Debian:<major>, Ubuntu:<release>[:LTS], by source package
RHEL, Rocky, AlmaLinux rpm_packages OSV Red Hat:enterprise_linux:<major>::*, Rocky Linux:<major>, AlmaLinux:<major>
python_packages OSV PyPI (PEP 503 names)
npm_packages (global) OSV npm
Windows programs, chocolatey_packages; macOS apps, homebrew_packages NVD CPE (--vuln-nvd-enabled), as possible findings

Not assessed, and counted as such rather than reported clean:

  • Alpine (osquery has no apk_packages table);
  • CentOS, SUSE and other distributions;
  • Ubuntu Pro/FIPS and RHEL EUS streams;
  • Windows programs, macOS apps, Homebrew and Chocolatey packages while --vuln-nvd-enabled is off;
  • packages in an ecosystem whose OSV feed hasn't synced yet;
  • versions without a digit.

Severity

  • Source: CVSS v3 (else v4) vectors published in the OSV record, scored locally.
  • No vector: many Debian and Ubuntu records publish none, and their severity is unknown.
  • KEV: a finding is flagged when any CVE alias of its advisory is in the CISA catalog.

NVD possible findings

With --vuln-nvd-enabled, osctrl-api also syncs CVEs from the NVD CVE API 2.0 and matches Windows programs, macOS apps, Homebrew and Chocolatey packages by product name.

  • Names: the installed name and vendor are normalized to CPE spelling. The vendor is the Windows publisher or the macOS bundle id. The names are then looked up exactly among the products stored CVEs list (vuln_cpe_products). Without a matching vendor, a product is taken only when a single vendor ships it. There is no fuzzy matching.
  • Versions are compared as dotted numbers. A version without a digit is not assessed.
  • Possible, not confirmed: these findings are shown, de-emphasized, but never alert and never count toward the posture score. KEV and severity come from the CVE.
  • Severity: an OSV record without its own CVSS borrows the highest score of an aliased NVD CVE; cvss_from names the CVE.
  • Application CVEs only (CPE part a) are stored. Kernel and OS CVEs are not, so their OSV records keep unknown severity unless they carry a CVSS.
  • Rate limits: requests are paced at NVD's documented limit, one every 6 seconds without a key; an API key gives ten times the rate. The first sync reads about 150 pages of 2,000 CVEs and stores each one, so expect an hour or more, during which the worker matches nothing else. Rate-limited pages are retried twice; a sync that still fails retries 15 minutes after it ended, and a failed first sync resumes from its next page.
  • API key: set it through VULN_NVD_API_KEY; a flag value shows in the process list. It is sent only as the apiKey header.
  • Turning it off removes the NVD data on the next worker tick: possible findings resolve and borrowed severities revert.

Alerts

A vuln_finding alert rule fires when a node gains an open confirmed finding at or above the rule's threshold:

Threshold Matches
kev Known-exploited findings only
critical Critical findings, plus all known-exploited
high High and critical, plus all known-exploited
medium Medium, high and critical, plus all known-exploited
low Low and above, plus all known-exploited
any Everything, including unknown severity

How it fires:

  • Where: the watcher runs in osctrl-tls, so it needs --alerts-enabled and --vuln-enabled there. The API refuses vuln_finding rules when vulnerability monitoring is off.
  • Grouping: findings are grouped per advisory, so one advisory affecting many nodes is one notification.
  • Cap: each rule sends at most 10 advisories per environment per hour, worst first within a sweep, plus one digest per hour for the rest. The budget lives in Redis, so it holds across sweeps and osctrl-tls replicas; an inventory rollout or a fleet-wide re-match can't page once a minute.
  • Once only: a finding alerts when it first appears, including on a node's first inventory. A reopened finding doesn't alert again.
  • Escalations: an open finding that later becomes known-exploited (CISA adds its CVE to KEV) or rises in severity alerts again, once, for the rules it newly matches: a kev rule fires when CISA lists a CVE the fleet already has, and a high rule when a medium finding becomes high. osctrl-api records each escalation in vuln_escalations (kept 30 days).
  • Restarts: Redis cursors (osctrl:alert:vuln:cursor, osctrl:alert:vuln:escalation-cursor) prevent re-alerting. A missing, corrupt or expired cursor restarts from the newest record and never replays history. Cursors expire after a week without sweeps, so re-enabling alerts after a long pause starts from now.
  • Peaks: an escalation is measured against the highest severity and KEV state the finding ever had, so falling and coming back (NVD off for a while, a truncated KEV feed) is not news. Escalations recorded before a finding was reopened stay silent, and a finding the watcher has not alerted yet alerts once with its current state.
  • Replicas: one osctrl-tls replica sweeps at a time (a Redis lock held for the sweep, released when it ends, expiring after 5 minutes if its holder dies).
  • Settling: a sweep takes only findings the previous sweep already listed, so one written by a slower match transaction is never skipped. Alerts arrive a minute later; no clocks are compared.
  • Possible findings: never alert.

Posture score

With --posture-enabled and --vuln-enabled on osctrl-api, nodes get a "Known vulnerabilities" control (ISO 27001 A.8.8):

Open confirmed findings Result
Any KEV or critical Fail at critical weight; risk level critical
Otherwise any high Fail at high weight
Otherwise any medium, low or unknown Warn
None Pass

The control is unevaluated, never passed, before advisory data has loaded and for a node without findings that has never been matched or has no assessed packages (Amazon Linux, an OS-only Windows node, an ecosystem whose feed hasn't synced). A node with no findings but some packages not assessed warns: not assessed is never clean. Packages only NVD names cover (Windows programs, macOS apps, Homebrew, Chocolatey) never count either way, and possible findings never affect the score.

Health

The health page shows an "Advisory feeds" component:

  • degraded when a feed's last error is newer than its last success;
  • unknown before the first OSV sync;
  • stale after three missed sync intervals.

Air-gapped deployments

Mirror the OSV bucket layout (<ecosystem>/all.zip, <ecosystem>/modified_id.csv, <ecosystem>/<id>.json) and the KEV JSON on an internal web server. Then set --vuln-osv-url and --vuln-kev-url. Only http and https URLs are accepted. Leave NVD off, or point --vuln-nvd-url at a mirror that serves the API's responses.

Operations

  • Downloads are capped by --vuln-max-download-mb. Archives are read in memory and never extracted.
  • Failed syncs keep existing data and retry after 15 minutes. The API reports feeds as stale after three missed intervals and loaded=false before the first OSV sync.
  • Resolved findings are kept for --vuln-retention-days.
  • Deleted nodes: rows of deleted nodes are swept hourly.
  • Log sinks: inventory results also flow to the configured log sinks, like any scheduled query.
  • Metrics: with --metrics-enabled and --vuln-enabled, osctrl-tls exports gauges read from the shared database (cached for a minute): osctrl_vuln_advisories, osctrl_vuln_feed_last_success_timestamp_seconds (per source), osctrl_vuln_findings_open (per confidence), osctrl_vuln_findings_resolved, osctrl_vuln_nodes_pending_match and osctrl_vuln_packages_not_assessed. Every osctrl-tls replica exports the same fleet-wide values: aggregate with max(), not sum(). A reading that fails or takes over 10 seconds exports nothing.
  • Rejected records: an advisory the database rejects is skipped and counted (last_skipped), not retried forever.
  • NVD on some replicas only: a replica with --vuln-nvd-enabled off keeps NVD data while another replica used NVD in the last hour, and logs a warning; set the flag the same everywhere.

Documentation

Overview

Package vulns reports which nodes run software affected by known vulnerabilities. osctrl-tls stores each node's inventory (inventory.go); osctrl-api downloads advisory data, matches it and serves the findings (worker.go, read.go). Everything is inert unless --vuln-enabled is set.

Index

Constants

View Source
const (
	CategoryOS     = "os"
	CategoryDeb    = "deb"
	CategoryRPM    = "rpm"
	CategoryPython = "python"
	CategoryNPM    = "npm"
	// CPE-only categories: matched by product name against NVD, and only
	// while --vuln-nvd-enabled is on.
	CategoryHomebrew   = "homebrew"
	CategoryPrograms   = "programs"
	CategoryApps       = "apps"
	CategoryChocolatey = "chocolatey"
)

Inventory categories: the suffix of each osctrl:vuln: scheduled query.

View Source
const (
	ConfidenceConfirmed = "confirmed"
	ConfidencePossible  = "possible"
)

Finding confidence. Confirmed is an exact package-ecosystem match against an advisory; possible is a name-and-version match against NVD CPE data, shown but never alerted on or scored.

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

Severity buckets, derived from a CVSS score.

View Source
const (
	AdvisorySourceOSV = "osv"
	AdvisorySourceNVD = "nvd"
)

Advisory sources: the feed that owns a record.

View Source
const (
	StateOpen     = "open"
	StateResolved = "resolved"
	StateAll      = "all"
)

Finding states for FindingFilter.State.

View Source
const (
	DefaultOSVURL       = "https://osv-vulnerabilities.storage.googleapis.com"
	DefaultKEVURL       = "https://www.cisa.gov/sites/default/files/feeds/known_exploited_vulnerabilities.json"
	DefaultSyncInterval = 6 * time.Hour
	DefaultMaxDownload  = int64(2048) << 20
)
View Source
const (
	DefaultNVDURL = "https://services.nvd.nist.gov/rest/json/cves/2.0"
)
View Source
const EvidenceCategory = "vulnerabilities"

EvidenceCategory is the synthetic posture category findings are scored under.

View Source
const MaxRowsPerCategory = 50000

MaxRowsPerCategory bounds one snapshot. A bigger result is rejected rather than truncated: a truncated inventory reads as "not vulnerable".

View Source
const QueryPrefix = "osctrl:vuln:"

QueryPrefix names the scheduled queries whose results are inventory.

Variables

View Source
var ErrBadFilter = errors.New("invalid filter")

ErrBadFilter is a filter value the API must reject with 400.

View Source
var ErrTooLarge = errors.New("download exceeds the size limit")

ErrTooLarge is returned when a download is bigger than the configured cap.

View Source
var ErrUnassessable = errors.New("version cannot be assessed")

ErrUnassessable means a version could not be compared. The package is reported as "not assessed", never as "not affected".

Functions

func AdvisoryKey

func AdvisoryKey(osvEcosystem string) string

AdvisoryKey normalizes an OSV affected[].package.ecosystem value to the key node packages are matched under, or "" for ecosystems osctrl does not match. Ubuntu Pro/FIPS and Red Hat EUS streams ship packages a standard install does not have, so matching them would only produce noise.

func IsVulnQuery

func IsVulnQuery(name string) bool

IsVulnQuery reports whether a scheduled query's results are inventory.

func Migrate

func Migrate(db *gorm.DB) error

Migrate creates the vulnerability tables. Called only when the feature is enabled, so a disabled deployment never has them.

func OSKey

func OSKey(platform, version, major string) string

OSKey returns the ecosystem key for a node's distro packages, in the form AdvisoryKey produces ("Debian:12", "Ubuntu:22.04", "Red Hat:9"), or "" when the OS is not one osctrl can assess. Inputs are os_version columns.

func PackageKey

func PackageKey(ecosystem, name string) string

PackageKey is the identity of a package name within an ecosystem. PyPI names are normalized per PEP 503; everything else compares exactly.

func ParseEcosystems

func ParseEcosystems(s string) []string

ParseEcosystems splits the --vuln-ecosystems value.

func Profile

func Profile(id string) (posture.PostureProfile, bool)

Profile returns one profile by id.

func Profiles

func Profiles() []posture.PostureProfile

Profiles returns the inventory schedules, one per platform, in the shape posture profiles use so the existing apply workflow can merge them.

func RegisterMetrics

func RegisterMetrics(reg prometheus.Registerer, db *gorm.DB)

RegisterMetrics registers the vulnerability gauges.

func RequestSync

func RequestSync(db *gorm.DB, at time.Time) error

RequestSync asks the worker to sync on its next tick. Used by the API.

Types

type Advisory

type Advisory struct {
	ID string `gorm:"primaryKey;type:varchar(128)" json:"id"`
	// Source is the feed that owns the record. Existing rows predate NVD
	// support and are OSV records.
	Source     string  `gorm:"type:varchar(8);default:osv;index" json:"source"`
	Summary    string  `gorm:"type:text" json:"summary"`
	Details    string  `gorm:"type:text" json:"details"`
	CVSSVector string  `gorm:"type:varchar(255)" json:"cvss_vector"`
	CVSSScore  float64 `json:"cvss_score"`
	Severity   string  `gorm:"type:varchar(16);index" json:"severity"`
	// CVSSFrom is the aliased NVD CVE an OSV record without its own CVSS
	// borrowed its score from. Empty when the score is the record's own.
	CVSSFrom  string    `gorm:"type:varchar(32);default:''" json:"cvss_from"`
	KEV       bool      `gorm:"index" json:"kev"`
	RefURLs   string    `gorm:"type:text" json:"-"` // JSON array of URLs
	Published time.Time `json:"published"`
	Modified  time.Time `json:"modified"`
}

Advisory is one vulnerability record (an OSV id such as DSA-5678-1 or RHSA-2026:1234). KEV is owned by the KEV sync, never by the record.

func (Advisory) TableName

func (Advisory) TableName() string

type AdvisoryDetail

type AdvisoryDetail struct {
	Advisory   Advisory  `json:"advisory"`
	Aliases    []string  `json:"aliases"`
	References []string  `json:"references"`
	Findings   []Finding `json:"findings"`
}

AdvisoryDetail is one advisory and the nodes in one environment it affects.

type Affected

type Affected struct {
	ID         uint   `gorm:"primarykey"`
	AdvisoryID string `gorm:"type:varchar(128);index"`
	Ecosystem  string `gorm:"type:varchar(64);index:idx_vuln_affected_pkg"`
	Package    string `gorm:"type:varchar(255);index:idx_vuln_affected_pkg"`
	Ranges     string `gorm:"type:text"` // JSON []Range
	Versions   string `gorm:"type:text"` // JSON []string
}

Affected is one (ecosystem, package) an advisory affects. Ecosystem is the normalized key from AdvisoryKey; Package is the PackageKey of the name.

func (Affected) TableName

func (Affected) TableName() string

type Alias

type Alias struct {
	AdvisoryID string `gorm:"primaryKey;type:varchar(128)"`
	Alias      string `gorm:"primaryKey;type:varchar(128);index"`
}

Alias links an advisory to another identifier for it, usually a CVE.

func (Alias) TableName

func (Alias) TableName() string

type CPEProduct

type CPEProduct struct {
	Vendor  string `gorm:"primaryKey;type:varchar(128)"`
	Product string `gorm:"primaryKey;type:varchar(128);index"`
}

CPEProduct is one vendor:product that a stored NVD CVE lists. It is the CPE dictionary name matching looks names up in: only products some CVE affects can produce a finding, so the full NVD dictionary would add nothing.

func (CPEProduct) TableName

func (CPEProduct) TableName() string

type Config

type Config struct {
	OSVURL       string
	KEVURL       string
	Ecosystems   []string // OSV directories; empty = what the fleet reports
	SyncInterval time.Duration
	Retention    time.Duration
	MaxDownload  int64
	HTTPClient   *http.Client
	// NVD CPE matching (--vuln-nvd-*). The key is a secret: it travels only
	// as a request header.
	NVDEnabled bool
	NVDURL     string
	NVDAPIKey  string
}

Config is the worker's view of the --vuln-* flags.

type CountRow

type CountRow struct {
	Name  string `json:"name"`
	Total int64  `json:"total"`
}

CountRow is one entry of a top-N list.

type Escalation

type Escalation struct {
	ID           uint   `gorm:"primarykey"`
	FindingID    uint   `gorm:"index"`
	PrevSeverity string `gorm:"type:varchar(16)"`
	PrevKEV      bool
	CreatedAt    time.Time `gorm:"index"`
}

Escalation records an open confirmed finding that became known-exploited or rose in severity: news the alert watcher reports once, by ID, like a new finding. Rows are pruned after escalationRetention.

func (Escalation) TableName

func (Escalation) TableName() string

type Event

type Event struct {
	Introduced   string `json:"introduced,omitempty"`
	Fixed        string `json:"fixed,omitempty"`
	LastAffected string `json:"last_affected,omitempty"`
	Limit        string `json:"limit,omitempty"`
}

Event is one OSV range event; exactly one field is set.

type FeedStatus

type FeedStatus struct {
	Sources         []SyncState `json:"sources"`
	LastSyncAt      *time.Time  `json:"last_sync_at"`
	SyncRequestedAt *time.Time  `json:"sync_requested_at"`
	LastSyncFailed  bool        `json:"last_sync_failed"`
	Loaded          bool        `json:"loaded"`
	Stale           bool        `json:"stale"`
}

FeedStatus is the admin view of the feeds and the worker.

type Finding

type Finding struct {
	ID               uint       `gorm:"primarykey" json:"id"`
	NodeUUID         string     `gorm:"type:varchar(64);uniqueIndex:idx_vuln_finding" json:"node_uuid"`
	EnvironmentID    uint       `gorm:"index" json:"environment_id"`
	AdvisoryID       string     `gorm:"type:varchar(128);uniqueIndex:idx_vuln_finding;index" json:"advisory_id"`
	Ecosystem        string     `gorm:"type:varchar(64);uniqueIndex:idx_vuln_finding" json:"ecosystem"`
	Package          string     `gorm:"type:varchar(255);uniqueIndex:idx_vuln_finding" json:"package"`
	InstalledVersion string     `gorm:"type:varchar(255)" json:"installed_version"`
	FixedVersion     string     `gorm:"type:varchar(255)" json:"fixed_version"`
	Confidence       string     `gorm:"type:varchar(16);index" json:"confidence"`
	Severity         string     `gorm:"type:varchar(16);index" json:"severity"`
	KEV              bool       `json:"kev"`
	FirstSeen        time.Time  `json:"first_seen"`
	LastSeen         time.Time  `json:"last_seen"`
	ResolvedAt       *time.Time `gorm:"index" json:"resolved_at"`
	// PeakSeverity and EverKEV are the highest state the finding reached:
	// escalations are measured against them, so falling and coming back is
	// not news. ReopenedAt is the last reopen: escalations recorded before
	// it are not news either.
	PeakSeverity string     `gorm:"type:varchar(16);default:''" json:"-"`
	EverKEV      bool       `gorm:"default:false" json:"-"`
	ReopenedAt   *time.Time `json:"-"`
}

Finding is one advisory affecting one package on one node. Resolved findings are kept (ResolvedAt set) so "fixed on" stays visible.

func (Finding) TableName

func (Finding) TableName() string

type FindingFilter

type FindingFilter struct {
	EnvironmentID uint
	NodeUUID      string
	Severity      string
	Confidence    string
	KEV           bool
	State         string
	Package       string
	Advisory      string
	Page          int
	PageSize      int
}

FindingFilter selects findings. EnvironmentID is mandatory.

type Inventory

type Inventory struct {
	DB *gorm.DB
	// contains filtered or unexported fields
}

Inventory stores the software nodes report through the osctrl:vuln: scheduled queries. It is the only part of this feature osctrl-tls runs.

func NewInventory

func NewInventory(db *gorm.DB) (*Inventory, error)

NewInventory migrates the vulnerability tables and returns the store.

func (*Inventory) EscalationsAfter

func (inv *Inventory) EscalationsAfter(ctx context.Context, afterID uint, limit int) ([]NewFinding, error)

EscalationsAfter lists escalations of open confirmed findings with escalation id > afterID, lowest first, at most limit. ID is the escalation's; the rest is the finding as it is now.

func (*Inventory) FindingsAfter

func (inv *Inventory) FindingsAfter(ctx context.Context, afterID uint, limit int) ([]NewFinding, error)

FindingsAfter lists open confirmed findings with id > afterID, lowest id first, at most limit, with the node's hostname (empty when the node was deleted). "Possible" findings never alert, and a finding resolved before the watcher saw it is no longer news.

func (*Inventory) Ingest

func (inv *Inventory) Ingest(nodeUUID string, envID uint, results []types.LogResultData)

Ingest stores every osctrl:vuln: snapshot in results. Differential results are ignored: the profile schedules snapshots, and an added/removed row is not a whole inventory to replace the stored one with.

func (*Inventory) LatestEscalationID

func (inv *Inventory) LatestEscalationID(ctx context.Context) (uint, error)

LatestEscalationID returns the highest escalation id, 0 when there is none.

func (*Inventory) LatestFindingID

func (inv *Inventory) LatestFindingID(ctx context.Context) (uint, error)

LatestFindingID returns the highest finding id, 0 when there is none.

type KEV

type KEV struct {
	CVEID string `gorm:"primaryKey;type:varchar(32)"`
}

KEV is one CVE in the CISA Known Exploited Vulnerabilities catalog.

func (KEV) TableName

func (KEV) TableName() string

type Matcher

type Matcher struct {
	DB *gorm.DB

	// CPE turns on name matching against NVD data (--vuln-nvd-enabled).
	CPE bool
	// contains filtered or unexported fields
}

Matcher recomputes findings from stored inventory and advisories.

func (*Matcher) MatchNode

func (m *Matcher) MatchNode(nodeUUID string) error

MatchNode recomputes one node's findings. Findings that no longer match are resolved, never deleted; ones that match again are reopened.

type NVDSource

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

NVDSource syncs CVEs that list application CPEs from the NVD CVE API 2.0, or from a mirror serving the same responses.

func (*NVDSource) Sync

func (s *NVDSource) Sync(ctx context.Context) (SyncResult, error)

Sync pages through CVEs modified since the cursor (all of them on the first run). Pages are stored as they arrive; the cursor moves only when every page is in, so a failed run is retried from the same point.

type NewFinding

type NewFinding struct {
	ID               uint
	NodeUUID         string
	Hostname         string
	EnvironmentID    uint
	AdvisoryID       string
	Package          string
	InstalledVersion string
	FixedVersion     string
	Severity         string
	KEV              bool
	Escalated        bool
	PrevSeverity     string
	PrevKEV          bool
	// FindingID is the escalated finding (ID is the escalation's own).
	FindingID uint
}

NewFinding is one open confirmed finding, or one escalation of it, for the alert watcher.

type NodeReport

type NodeReport struct {
	Findings    []Finding  `json:"findings"`
	NotAssessed int        `json:"not_assessed"`
	InventoryAt *time.Time `json:"inventory_at"`
	MatchedAt   *time.Time `json:"matched_at"`
	Loaded      bool       `json:"loaded"`
	Stale       bool       `json:"stale"`
}

NodeReport is one node's findings and assessment coverage.

type NodeSoftware

type NodeSoftware struct {
	ID            uint      `gorm:"primarykey" json:"-"`
	NodeUUID      string    `gorm:"type:varchar(64);index:idx_vuln_sw_node" json:"node_uuid"`
	EnvironmentID uint      `gorm:"index" json:"environment_id"`
	Category      string    `gorm:"type:varchar(32);index:idx_vuln_sw_node" json:"category"`
	Name          string    `gorm:"type:varchar(255)" json:"name"`
	SourceName    string    `gorm:"type:varchar(255)" json:"source_name"`
	Version       string    `gorm:"type:varchar(255)" json:"version"`
	Arch          string    `gorm:"type:varchar(32)" json:"arch"`
	Vendor        string    `gorm:"type:varchar(255)" json:"vendor"`
	LastSeen      time.Time `json:"last_seen"`
}

NodeSoftware is one installed package on one node, from the latest inventory snapshot for its category.

func (NodeSoftware) TableName

func (NodeSoftware) TableName() string

type NodeState

type NodeState struct {
	NodeUUID      string     `gorm:"primaryKey;type:varchar(64)" json:"node_uuid"`
	EnvironmentID uint       `gorm:"index" json:"environment_id"`
	OSPlatform    string     `gorm:"type:varchar(64)" json:"os_platform"`
	OSVersion     string     `gorm:"type:varchar(128)" json:"os_version"`
	OSMajor       string     `gorm:"type:varchar(16)" json:"os_major"`
	InventoryAt   time.Time  `gorm:"index" json:"inventory_at"`
	MatchedAt     *time.Time `gorm:"index" json:"matched_at"`
	NotAssessed   int        `json:"not_assessed"`
	// Assessed counts packages matched against an exact ecosystem feed
	// (OSV). It is what a clean result means for scoring; CPE matches never
	// count.
	Assessed int `json:"assessed"`
}

NodeState is per-node bookkeeping: the OS (which picks the advisory ecosystem for distro packages) and whether findings are current. A node needs matching when MatchedAt is NULL or older than InventoryAt.

func (NodeState) TableName

func (NodeState) TableName() string

type OSVSource

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

OSVSource syncs ecosystems from an OSV bucket (or a mirror of its layout): <base>/<dir>/all.zip, <base>/<dir>/modified_id.csv, <base>/<dir>/<id>.json.

func (*OSVSource) Sync

func (s *OSVSource) Sync(ctx context.Context, dir string) (SyncResult, error)

Sync brings one ecosystem directory up to date. On error the cursor does not move and nothing already stored is removed, so the next run retries.

type Range

type Range struct {
	Type   string  `json:"type"`
	Events []Event `json:"events"`
}

Range is one OSV range: events of a single type.

type Reader

type Reader struct {
	DB         *gorm.DB
	StaleAfter time.Duration
	// contains filtered or unexported fields
}

Reader answers the API's questions. Every query is scoped by environment (or node, which the handler has already checked against the environment).

func NewReader

func NewReader(db *gorm.DB, syncInterval time.Duration) *Reader

NewReader returns a reader that reports feeds as stale after three missed sync intervals.

func (*Reader) Advisory

func (r *Reader) Advisory(id string, envID uint) (AdvisoryDetail, error)

Advisory returns gorm.ErrRecordNotFound for an unknown id.

func (*Reader) Feeds

func (r *Reader) Feeds() (FeedStatus, error)

Feeds reports each feed's progress.

func (*Reader) Findings

func (r *Reader) Findings(f FindingFilter) ([]Finding, int64, error)

Findings returns one page of findings and the total matching the filter.

func (*Reader) HealthComponent

func (r *Reader) HealthComponent(ctx context.Context) health.Component

HealthComponent reports feed health: degraded when a feed is failing, unknown before the first OSV sync, stale when data is old. It reads under ctx so a stalled database cannot hang the health page.

func (*Reader) Node

func (r *Reader) Node(uuid string, envID uint) (NodeReport, error)

Node returns the open findings of one node in environment envID.

func (*Reader) ScoreEvidence

func (r *Reader) ScoreEvidence(nodeUUIDs []string) (map[string][]posture.NodePosture, error)

ScoreEvidence implements posture.EvidenceSource: one record per node with the counts of its open confirmed findings.

A node gets a record only once advisory data is loaded and the node either has confirmed findings, or its last match assessed at least one package against an exact ecosystem feed. Anything less would let an unassessed node pass (Amazon Linux, an OS-only Windows node, an ecosystem whose feed never synced, a node only NVD names cover). An evaluated node stays evaluated while an advisory sync has cleared its matched_at, so its score does not flicker after every sync.

func (*Reader) ScoringRules

func (r *Reader) ScoringRules() []posture.ScoringRule

ScoringRules implements posture.EvidenceSource.

func (*Reader) Summary

func (r *Reader) Summary(envID uint) (Summary, error)

Summary counts open findings in one environment.

type Summary

type Summary struct {
	Loaded     bool                        `json:"loaded"`
	Stale      bool                        `json:"stale"`
	BySeverity map[string]map[string]int64 `json:"by_severity"`
	KEV        int64                       `json:"kev"`
	// Possible counts open possible (NVD CPE) findings. KEV, AffectedNodes
	// and the top lists count confirmed findings only.
	Possible int64 `json:"possible"`
	// KEVPossible counts open possible findings that are known-exploited.
	KEVPossible   int64      `json:"kev_possible"`
	AffectedNodes int64      `json:"affected_nodes"`
	NotAssessed   int64      `json:"not_assessed"`
	TopAdvisories []CountRow `json:"top_advisories"`
	TopPackages   []CountRow `json:"top_packages"`
}

Summary is the environment overview.

type SyncResult

type SyncResult struct{ Written, Skipped int }

SyncResult reports what one sync processed.

type SyncState

type SyncState struct {
	Source      string     `gorm:"primaryKey;type:varchar(128)" json:"source"`
	Cursor      time.Time  `json:"cursor"`
	LastSuccess *time.Time `json:"last_success"`
	LastWritten int        `json:"last_written"`
	LastSkipped int        `json:"last_skipped"`
	LastError   string     `gorm:"type:text" json:"last_error"`
	LastErrorAt *time.Time `json:"last_error_at"`
	// ResumeIndex and ResumeStart let a full NVD sync that failed part-way
	// continue from its next page; ResumeStart is when that sync began.
	ResumeIndex int        `json:"-"`
	ResumeStart *time.Time `json:"-"`
}

SyncState is one feed's progress: "osv:<dir>" or "kev". Cursor only advances after a successful sync.

func (SyncState) TableName

func (SyncState) TableName() string

type Worker

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

Worker syncs feeds and keeps findings current. Every osctrl-api replica runs one; a lease row lets exactly one of them work at a time.

func NewWorker

func NewWorker(db *gorm.DB, cfg Config) (*Worker, error)

NewWorker migrates the tables and validates the configuration.

func (*Worker) Run

func (w *Worker) Run(ctx context.Context)

Run ticks until ctx is cancelled.

func (*Worker) Tick

func (w *Worker) Tick(ctx context.Context) error

Tick does one round of work if this replica holds the lease.

type WorkerState

type WorkerState struct {
	Name            string     `gorm:"primaryKey;type:varchar(32)" json:"-"`
	Owner           string     `gorm:"type:varchar(128)" json:"-"`
	LeaseUntil      time.Time  `json:"-"`
	SyncRequestedAt *time.Time `json:"sync_requested_at"`
	LastSyncAt      *time.Time `json:"last_sync_at"`
	LastSyncFailed  bool       `json:"last_sync_failed"`
	LastHousekeepAt *time.Time `json:"-"`
	// NVDActiveAt is the last tick of a replica with --vuln-nvd-enabled.
	// Replicas with NVD off keep NVD data while it is recent.
	NVDActiveAt *time.Time `json:"-"`
	// NVDActiveBy is the host that stamped NVDActiveAt: a host's own stamp,
	// from before it restarted with NVD off, does not delay the drop.
	NVDActiveBy string `gorm:"type:varchar(255)" json:"-"`
}

WorkerState is the single coordination row shared by every osctrl-api replica: who holds the lease and what the worker last did.

func (WorkerState) TableName

func (WorkerState) TableName() string

Jump to

Keyboard shortcuts

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