Documentation
¶
Overview ¶
Package store persists scans, results, and related fleet records in SQLite or Postgres.
Index ¶
- Constants
- Variables
- func GenerateToken() (string, error)
- func HashToken(raw string) string
- func NewScanID() string
- func TokensEqual(a, b string) bool
- func ValidRole(r string) bool
- type APIKeyRecord
- type AckRecord
- type AlertListOptions
- type AlertRecord
- type AuditEntry
- type AuditListOptions
- type ClusterRecord
- type Driver
- type GroupRecord
- type LocationRecord
- type Postgres
- func (s *Postgres) AddClusterToGroup(ctx context.Context, group, cluster string) error
- func (s *Postgres) Close() error
- func (s *Postgres) DeleteAck(ctx context.Context, fingerprint string) error
- func (s *Postgres) DeleteClusterTag(ctx context.Context, cluster, key string) error
- func (s *Postgres) DeleteGroup(ctx context.Context, name string) error
- func (s *Postgres) DeleteLocation(ctx context.Context, cluster string) error
- func (s *Postgres) GetAPIKey(ctx context.Context, id string) (*APIKeyRecord, error)
- func (s *Postgres) GetAPIKeyByHash(ctx context.Context, hash string) (*APIKeyRecord, error)
- func (s *Postgres) GetAck(ctx context.Context, fingerprint string) (AckRecord, error)
- func (s *Postgres) GetAlert(ctx context.Context, fingerprint string) (*AlertRecord, error)
- func (s *Postgres) GetClusterHistory(ctx context.Context, cluster string, limit int) ([]ScanResultRecord, error)
- func (s *Postgres) GetClusterTags(ctx context.Context, cluster string) (map[string]string, error)
- func (s *Postgres) GetGroup(ctx context.Context, name string) (*GroupRecord, error)
- func (s *Postgres) GetLocation(ctx context.Context, cluster string) (*LocationRecord, error)
- func (s *Postgres) GetScan(ctx context.Context, id string) (*ScanRecord, error)
- func (s *Postgres) GetScanResults(ctx context.Context, scanID string) (map[string]map[string]scanner.Result, error)
- func (s *Postgres) GetScansByTimeRange(ctx context.Context, start, end time.Time) ([]ScanRecord, error)
- func (s *Postgres) IsAcked(ctx context.Context, fingerprint string) (bool, error)
- func (s *Postgres) ListAPIKeys(ctx context.Context) ([]APIKeyRecord, error)
- func (s *Postgres) ListAcks(ctx context.Context) ([]AckRecord, error)
- func (s *Postgres) ListAlerts(ctx context.Context, opts AlertListOptions) ([]AlertRecord, error)
- func (s *Postgres) ListAuditEntries(ctx context.Context, opts AuditListOptions) ([]AuditEntry, error)
- func (s *Postgres) ListClusterTags(ctx context.Context) (map[string]map[string]string, error)
- func (s *Postgres) ListClusters(ctx context.Context) ([]ClusterRecord, error)
- func (s *Postgres) ListGroups(ctx context.Context) ([]GroupRecord, error)
- func (s *Postgres) ListLocations(ctx context.Context) ([]LocationRecord, error)
- func (s *Postgres) ListScans(ctx context.Context, limit int) ([]ScanRecord, error)
- func (s *Postgres) Ping(ctx context.Context) error
- func (s *Postgres) Prune(ctx context.Context, cutoff time.Time) (int, error)
- func (s *Postgres) PruneAlerts(ctx context.Context, cutoff time.Time) (int, error)
- func (s *Postgres) PruneAuditEntries(ctx context.Context, cutoff time.Time) (int, error)
- func (s *Postgres) RemoveClusterFromGroup(ctx context.Context, group, cluster string) error
- func (s *Postgres) RevokeAPIKey(ctx context.Context, id string) error
- func (s *Postgres) SaveAPIKey(ctx context.Context, rec APIKeyRecord) error
- func (s *Postgres) SaveAck(ctx context.Context, rec AckRecord) error
- func (s *Postgres) SaveAuditEntry(ctx context.Context, rec AuditEntry) error
- func (s *Postgres) SaveGroup(ctx context.Context, name string, clusters []string) error
- func (s *Postgres) SaveScan(ctx context.Context, clusters []string, ...) (string, error)
- func (s *Postgres) SaveScanRecord(ctx context.Context, rec ScanRecord, ...) (string, error)
- func (s *Postgres) SetClusterTag(ctx context.Context, cluster, key, value string) error
- func (s *Postgres) SetLocation(ctx context.Context, loc LocationRecord) error
- func (s *Postgres) TouchAPIKey(ctx context.Context, id string, at time.Time) error
- func (s *Postgres) UpsertAlert(ctx context.Context, rec AlertRecord) error
- func (p *Postgres) Vacuum(_ context.Context) error
- type SQLite
- func (s *SQLite) AddClusterToGroup(ctx context.Context, group, cluster string) error
- func (s *SQLite) Close() error
- func (s *SQLite) DeleteAck(ctx context.Context, fingerprint string) error
- func (s *SQLite) DeleteClusterTag(ctx context.Context, cluster, key string) error
- func (s *SQLite) DeleteGroup(ctx context.Context, name string) error
- func (s *SQLite) DeleteLocation(ctx context.Context, cluster string) error
- func (s *SQLite) GetAPIKey(ctx context.Context, id string) (*APIKeyRecord, error)
- func (s *SQLite) GetAPIKeyByHash(ctx context.Context, hash string) (*APIKeyRecord, error)
- func (s *SQLite) GetAck(ctx context.Context, fingerprint string) (AckRecord, error)
- func (s *SQLite) GetAlert(ctx context.Context, fingerprint string) (*AlertRecord, error)
- func (s *SQLite) GetClusterHistory(ctx context.Context, cluster string, limit int) ([]ScanResultRecord, error)
- func (s *SQLite) GetClusterTags(ctx context.Context, cluster string) (map[string]string, error)
- func (s *SQLite) GetGroup(ctx context.Context, name string) (*GroupRecord, error)
- func (s *SQLite) GetLocation(ctx context.Context, cluster string) (*LocationRecord, error)
- func (s *SQLite) GetScan(ctx context.Context, id string) (*ScanRecord, error)
- func (s *SQLite) GetScanResults(ctx context.Context, scanID string) (map[string]map[string]scanner.Result, error)
- func (s *SQLite) GetScansByTimeRange(ctx context.Context, start, end time.Time) ([]ScanRecord, error)
- func (s *SQLite) IsAcked(ctx context.Context, fingerprint string) (bool, error)
- func (s *SQLite) ListAPIKeys(ctx context.Context) ([]APIKeyRecord, error)
- func (s *SQLite) ListAcks(ctx context.Context) ([]AckRecord, error)
- func (s *SQLite) ListAlerts(ctx context.Context, opts AlertListOptions) ([]AlertRecord, error)
- func (s *SQLite) ListAuditEntries(ctx context.Context, opts AuditListOptions) ([]AuditEntry, error)
- func (s *SQLite) ListClusterTags(ctx context.Context) (map[string]map[string]string, error)
- func (s *SQLite) ListClusters(ctx context.Context) ([]ClusterRecord, error)
- func (s *SQLite) ListGroups(ctx context.Context) ([]GroupRecord, error)
- func (s *SQLite) ListLocations(ctx context.Context) ([]LocationRecord, error)
- func (s *SQLite) ListScans(ctx context.Context, limit int) ([]ScanRecord, error)
- func (s *SQLite) Ping(ctx context.Context) error
- func (s *SQLite) Prune(ctx context.Context, cutoff time.Time) (int, error)
- func (s *SQLite) PruneAlerts(ctx context.Context, cutoff time.Time) (int, error)
- func (s *SQLite) PruneAuditEntries(ctx context.Context, cutoff time.Time) (int, error)
- func (s *SQLite) RemoveClusterFromGroup(ctx context.Context, group, cluster string) error
- func (s *SQLite) RevokeAPIKey(ctx context.Context, id string) error
- func (s *SQLite) SaveAPIKey(ctx context.Context, rec APIKeyRecord) error
- func (s *SQLite) SaveAck(ctx context.Context, rec AckRecord) error
- func (s *SQLite) SaveAuditEntry(ctx context.Context, rec AuditEntry) error
- func (s *SQLite) SaveGroup(ctx context.Context, name string, clusters []string) error
- func (s *SQLite) SaveScan(ctx context.Context, clusters []string, ...) (string, error)
- func (s *SQLite) SaveScanRecord(ctx context.Context, rec ScanRecord, ...) (string, error)
- func (s *SQLite) SetClusterTag(ctx context.Context, cluster, key, value string) error
- func (s *SQLite) SetLocation(ctx context.Context, loc LocationRecord) error
- func (s *SQLite) TouchAPIKey(ctx context.Context, id string, at time.Time) error
- func (s *SQLite) UpsertAlert(ctx context.Context, rec AlertRecord) error
- func (s *SQLite) Vacuum(ctx context.Context) error
- func (s *SQLite) VacuumInto(ctx context.Context, path string) error
- type ScanRecord
- type ScanResultRecord
- type Store
Constants ¶
const ( // RoleAdmin grants unrestricted access including key management and audit log. RoleAdmin = "admin" // RoleOperator may trigger scans and mutate findings within scope. RoleOperator = "operator" // RoleViewer may only read. RoleViewer = "viewer" )
Role enumerates the authorization levels Fleetsweeper recognizes.
const ScopeWildcard = "*"
ScopeWildcard is the cluster scope entry that grants access to every cluster.
Variables ¶
var ( // ErrStore indicates a general storage failure. ErrStore = errors.New("store") // ErrNotFound indicates the requested record does not exist. ErrNotFound = errors.New("not found") // ErrQuery indicates a failure executing a database query. ErrQuery = errors.New("query") // ErrMigrate indicates a failure applying schema migrations. ErrMigrate = errors.New("migrate") // ErrDuplicate indicates a write collided with an existing unique record. ErrDuplicate = errors.New("duplicate") // ErrInvalid indicates a write was rejected because a record failed validation. ErrInvalid = errors.New("invalid") )
Functions ¶
func GenerateToken ¶
GenerateToken returns a freshly minted token suitable for an API key. The "fsk_" prefix lets operators identify a Fleetsweeper key at a glance in logs and secret stores. The remaining 32 bytes come from crypto/rand.
func HashToken ¶
HashToken returns the lowercase-hex SHA-256 of a raw token. Used both at creation time (when persisting) and on each request (when matching).
func NewScanID ¶ added in v0.10.0
func NewScanID() string
NewScanID returns a fresh, time-sortable scan identifier in the format SaveScan generates. Callers use it to name a scan before it runs, then save it with SaveScanRecord.
func TokensEqual ¶
TokensEqual compares two raw tokens in constant time to defeat timing oracles.
Types ¶
type APIKeyRecord ¶
type APIKeyRecord struct {
// ID is the stable identifier used in audit log entries and revocation calls.
ID string `json:"id"`
// TokenHash is the lowercase-hex SHA-256 of the raw token.
TokenHash string `json:"-"`
// Name is a human-readable label for the key (for example "ci-runner").
Name string `json:"name"`
// Role is the authorization role: admin, operator, or viewer.
Role string `json:"role"`
// ClusterScope lists cluster names (or "group:<name>" entries) this key may
// act on. The single element "*" grants unrestricted access.
ClusterScope []string `json:"cluster_scope"`
// CreatedAt is when the key was minted.
CreatedAt time.Time `json:"created_at"`
// ExpiresAt, when non-zero, is when the key automatically becomes invalid.
ExpiresAt time.Time `json:"expires_at"`
// LastUsedAt is the most recent successful authentication.
LastUsedAt time.Time `json:"last_used_at"`
// RevokedAt, when non-zero, marks the key as administratively disabled.
RevokedAt time.Time `json:"revoked_at"`
// CreatedBy identifies the actor that created this key (admin key id, or
// "bootstrap" for the legacy --auth-token, or "cli" for offline creation).
CreatedBy string `json:"created_by,omitempty"`
}
APIKeyRecord is a persisted API key authorized to call the server. Tokens are never stored in plaintext: only TokenHash is persisted, and the raw token is returned to the operator exactly once at creation time.
type AckRecord ¶
type AckRecord struct {
// Fingerprint is the SHA-256 of cluster|scanner|rule.
Fingerprint string `json:"fingerprint"`
// Cluster echoes the finding's cluster scope (or "fleet").
Cluster string `json:"cluster"`
// Scanner echoes the finding's source scanner.
Scanner string `json:"scanner"`
// Title echoes the finding's title at ack time, for human context.
Title string `json:"title"`
// AckBy identifies the operator who acked, free-form.
AckBy string `json:"ack_by,omitempty"`
// Reason is the operator's stated reason for acking.
Reason string `json:"reason,omitempty"`
// SnoozeUntil, when non-zero, is when the ack expires and the finding
// resumes alerting. Zero value means "permanent until removed".
SnoozeUntil time.Time `json:"snooze_until"`
// CreatedAt is when the ack was recorded.
CreatedAt time.Time `json:"created_at"`
}
AckRecord is a single finding acknowledgement persisted in SQLite. The fingerprint is stable across scans for the same check on the same cluster (cluster + scanner + rule) so a recurring critical can be acknowledged once instead of every scan cycle.
type AlertListOptions ¶
type AlertListOptions struct {
// Limit caps the number of rows returned. Defaults to 200 when zero.
Limit int
// Cluster, when non-empty, restricts results to a single cluster.
Cluster string
// Status, when non-empty, filters by alert status ("firing"/"resolved").
Status string
// Severity, when non-empty, filters by severity label value.
Severity string
// Since, when non-zero, only returns alerts received strictly after this time.
Since time.Time
}
AlertListOptions filters AlertRecord queries.
type AlertRecord ¶
type AlertRecord struct {
// Fingerprint is the AlertManager-assigned stable identity.
Fingerprint string `json:"fingerprint"`
// Cluster is the cluster the alert applies to. Derived from the
// `cluster` label when present and falling back to the empty string
// when AlertManager doesn't carry a cluster label.
Cluster string `json:"cluster"`
// Status is "firing" or "resolved".
Status string `json:"status"`
// AlertName is the value of the alertname label.
AlertName string `json:"alertname"`
// Severity is the value of the severity label when present.
Severity string `json:"severity,omitempty"`
// Summary is the value of the summary annotation when present.
Summary string `json:"summary,omitempty"`
// StartsAt is when the alert began firing.
StartsAt time.Time `json:"starts_at"`
// EndsAt is when the alert resolved (zero while firing).
EndsAt time.Time `json:"ends_at"`
// ReceivedAt is when Fleetsweeper recorded the alert.
ReceivedAt time.Time `json:"received_at"`
// Labels carries every label AlertManager attached to the alert.
Labels map[string]string `json:"labels"`
// Annotations carries every annotation AlertManager attached to the alert.
Annotations map[string]string `json:"annotations"`
// GeneratorURL is the link back to the Prometheus rule that fired.
GeneratorURL string `json:"generator_url,omitempty"`
}
AlertRecord is one inbound AlertManager alert persisted to the alerts table. Fingerprint is AlertManager's stable per-alert identifier and is reused as the primary key so a transition from firing to resolved updates the existing row in place.
type AuditEntry ¶
type AuditEntry struct {
// ID is a time-sortable identifier.
ID string `json:"id"`
// Timestamp is when the request was received.
Timestamp time.Time `json:"timestamp"`
// ActorID identifies the api key that performed the action.
// Empty when --insecure mode allowed an anonymous call.
ActorID string `json:"actor_id,omitempty"`
// ActorName is the key's human-readable label at request time.
ActorName string `json:"actor_name,omitempty"`
// ActorRole is the role the key carried at request time.
ActorRole string `json:"actor_role,omitempty"`
// Method is the HTTP verb (POST, PUT, DELETE).
Method string `json:"method"`
// Path is the request path.
Path string `json:"path"`
// Status is the HTTP response status.
Status int `json:"status"`
// RemoteAddr is the client address as reported by the transport.
RemoteAddr string `json:"remote_addr,omitempty"`
// UserAgent is the client's User-Agent header.
UserAgent string `json:"user_agent,omitempty"`
// DurationMS is the request handler duration in milliseconds.
DurationMS int64 `json:"duration_ms"`
// Error, when non-empty, is a short message describing why the request
// failed authorization or processing.
Error string `json:"error,omitempty"`
}
AuditEntry is a single audit log row recording one mutating request. Read-only requests are not audited to keep the table volume manageable; the request log already captures those at info level.
type AuditListOptions ¶
type AuditListOptions struct {
// Limit caps the number of rows returned. Defaults to 100 when zero.
Limit int
// Since, when non-zero, only returns entries strictly newer than this time.
Since time.Time
// ActorID, when non-empty, restricts results to a single API key.
ActorID string
// MinStatus, when non-zero, only returns entries with status >= MinStatus.
// Use 400 to surface only failures.
MinStatus int
}
AuditListOptions filters AuditEntry queries.
type ClusterRecord ¶
type ClusterRecord struct {
// Name is the kubeconfig context name.
Name string `json:"name"`
// FirstSeen is when this cluster was first scanned.
FirstSeen time.Time `json:"first_seen"`
// LastSeen is when this cluster was last scanned.
LastSeen time.Time `json:"last_seen"`
// Groups lists the group names this cluster belongs to.
Groups []string `json:"groups"`
// Tags is the cluster's free-form tag map, omitted from the wire
// when no tags are set.
Tags map[string]string `json:"tags,omitempty"`
}
ClusterRecord represents a known cluster.
type Driver ¶
type Driver string
Driver identifies a supported backend.
const ( // DriverSQLite stores data in a single SQLite file. Default for single-instance // deployments. Concurrent readers are fine; writers are serialized by the // SQLite engine. DriverSQLite Driver = "sqlite" // DriverPostgres stores data in a PostgreSQL database. Use this when running // multiple fleetsweeper replicas behind a load balancer. DriverPostgres Driver = "postgres" )
func DetectDriver ¶
DetectDriver infers the driver from a DSN's prefix. URL-shaped DSNs like "postgres://..." or "postgresql://..." choose Postgres; everything else falls back to SQLite. Callers can override with --db-driver when the DSN is ambiguous.
type GroupRecord ¶
type GroupRecord struct {
// Name is the group name.
Name string `json:"name"`
// Clusters lists the cluster names in this group.
Clusters []string `json:"clusters"`
}
GroupRecord represents a cluster group.
type LocationRecord ¶
type LocationRecord struct {
// Cluster is the cluster context name.
Cluster string `json:"cluster"`
// Lat is latitude in degrees north (positive) or south (negative).
Lat float64 `json:"lat"`
// Lng is longitude in degrees east (positive) or west (negative).
Lng float64 `json:"lng"`
// Site is a human-readable label (for example "Edge site 42, Manhattan").
Site string `json:"site,omitempty"`
// Notes is free-form text the operator can use.
Notes string `json:"notes,omitempty"`
// UpdatedAt is when the record was last written.
UpdatedAt time.Time `json:"updated_at"`
}
LocationRecord is a user-supplied geographic override for a cluster. This is how operators map clusters to physical sites (on-premises sites and edge devices) that have no cloud-region label to auto-detect.
type Postgres ¶
type Postgres struct {
// contains filtered or unexported fields
}
Postgres implements Store against a PostgreSQL database. It is functionally equivalent to SQLite but tolerates concurrent writers, so multi-replica fleetsweeper deployments share a single backend. Timestamps are stored as RFC3339 TEXT and JSON columns as TEXT so the same row marshaling code as SQLite continues to work; the performance cost is negligible at the row volumes Fleetsweeper produces.
func NewPostgres ¶
NewPostgres opens a connection pool against the given DSN and applies migrations. The DSN is a standard libpq/pgx URL such as "postgres://user:pass@host:5432/db?sslmode=require".
func (*Postgres) AddClusterToGroup ¶
AddClusterToGroup adds a cluster to an existing group.
func (*Postgres) DeleteAck ¶
DeleteAck removes an acknowledgement by fingerprint. Idempotent: a missing row is not an error so callers can call from a "clear" button without worrying about state.
func (*Postgres) DeleteClusterTag ¶
DeleteClusterTag removes one key from a cluster's tag set.
func (*Postgres) DeleteGroup ¶
DeleteGroup removes a group. Cascades to group_clusters.
func (*Postgres) DeleteLocation ¶
DeleteLocation removes a manual override for a cluster.
func (*Postgres) GetAPIKey ¶
func (s *Postgres) GetAPIKey(ctx context.Context, id string) (*APIKeyRecord, error)
GetAPIKey returns the key with the given ID.
func (*Postgres) GetAPIKeyByHash ¶
func (s *Postgres) GetAPIKeyByHash(ctx context.Context, hash string) (*APIKeyRecord, error)
GetAPIKeyByHash looks up a key by its token hash. Revoked or expired keys are returned with their RevokedAt/ExpiresAt set so callers can produce the right error message.
func (*Postgres) GetAck ¶ added in v0.14.0
GetAck returns the active ack for fingerprint. Expired snoozes are pruned first, so an elapsed ack reads as missing. Returns the wrapped ErrNotFound when no row matches.
func (*Postgres) GetAlert ¶
func (s *Postgres) GetAlert(ctx context.Context, fingerprint string) (*AlertRecord, error)
GetAlert returns the alert row with the given fingerprint. Returns the wrapped ErrNotFound when no row matches.
func (*Postgres) GetClusterHistory ¶
func (s *Postgres) GetClusterHistory(ctx context.Context, cluster string, limit int) ([]ScanResultRecord, error)
GetClusterHistory returns scan results for a cluster across scans.
func (*Postgres) GetClusterTags ¶
GetClusterTags returns every tag pair on a cluster.
func (*Postgres) GetGroup ¶
func (s *Postgres) GetGroup(ctx context.Context, name string) (*GroupRecord, error)
GetGroup retrieves a group by name.
func (*Postgres) GetLocation ¶
func (s *Postgres) GetLocation(ctx context.Context, cluster string) (*LocationRecord, error)
GetLocation returns the manual override for a cluster, or nil/no error when none has been set.
func (*Postgres) GetScan ¶
func (s *Postgres) GetScan(ctx context.Context, id string) (*ScanRecord, error)
GetScan retrieves a scan record by ID.
func (*Postgres) GetScanResults ¶
func (s *Postgres) GetScanResults(ctx context.Context, scanID string) (map[string]map[string]scanner.Result, error)
GetScanResults retrieves all per-cluster scanner results for a scan.
func (*Postgres) GetScansByTimeRange ¶
func (s *Postgres) GetScansByTimeRange(ctx context.Context, start, end time.Time) ([]ScanRecord, error)
GetScansByTimeRange returns scans within a time window.
func (*Postgres) IsAcked ¶
IsAcked reports whether a finding identified by fingerprint has an active (non-expired) ack.
func (*Postgres) ListAPIKeys ¶
func (s *Postgres) ListAPIKeys(ctx context.Context) ([]APIKeyRecord, error)
ListAPIKeys returns every API key, ordered by creation time descending. Revoked keys are included so administrators can audit them.
func (*Postgres) ListAcks ¶
ListAcks returns every active ack. Expired snoozes are filtered out and removed from the database as a side effect so the table stays small.
func (*Postgres) ListAlerts ¶
func (s *Postgres) ListAlerts(ctx context.Context, opts AlertListOptions) ([]AlertRecord, error)
ListAlerts returns alerts matching opts, newest received_at first.
func (*Postgres) ListAuditEntries ¶
func (s *Postgres) ListAuditEntries(ctx context.Context, opts AuditListOptions) ([]AuditEntry, error)
ListAuditEntries returns audit entries matching opts, newest first.
func (*Postgres) ListClusterTags ¶
ListClusterTags returns every tag across the fleet, grouped by cluster.
func (*Postgres) ListClusters ¶
func (s *Postgres) ListClusters(ctx context.Context) ([]ClusterRecord, error)
ListClusters returns all known clusters with their group memberships.
func (*Postgres) ListGroups ¶
func (s *Postgres) ListGroups(ctx context.Context) ([]GroupRecord, error)
ListGroups returns all groups.
func (*Postgres) ListLocations ¶
func (s *Postgres) ListLocations(ctx context.Context) ([]LocationRecord, error)
ListLocations returns every manual override sorted by cluster name.
func (*Postgres) ListScans ¶
func (s *Postgres) ListScans(ctx context.Context, limit int) ([]ScanRecord, error)
ListScans returns scan records newest first, breaking timestamp ties by id.
func (*Postgres) Prune ¶
Prune deletes scans older than cutoff. The scan_results rows cascade. Returns the number of scans deleted.
func (*Postgres) PruneAlerts ¶
PruneAlerts removes alert rows older than cutoff. Returns rows removed.
func (*Postgres) PruneAuditEntries ¶
PruneAuditEntries deletes audit_log rows older than cutoff. Returns the number of rows removed. Idempotent; safe to call from a periodic ticker.
func (*Postgres) RemoveClusterFromGroup ¶
RemoveClusterFromGroup removes a cluster from a group.
func (*Postgres) RevokeAPIKey ¶
RevokeAPIKey marks a key as revoked. The row is retained so audit log entries referencing it remain interpretable.
func (*Postgres) SaveAPIKey ¶
func (s *Postgres) SaveAPIKey(ctx context.Context, rec APIKeyRecord) error
SaveAPIKey inserts a new API key. The ID and TokenHash must already be set; CreatedAt defaults to now when zero. Re-inserting the same ID returns ErrStore.
func (*Postgres) SaveAck ¶
SaveAck upserts an acknowledgement. The fingerprint is the primary key, so re-acking an already-acked finding refreshes its metadata in place.
func (*Postgres) SaveAuditEntry ¶
func (s *Postgres) SaveAuditEntry(ctx context.Context, rec AuditEntry) error
SaveAuditEntry inserts one audit entry. ID and Timestamp default to fresh values when unset, so callers can supply just the request-level fields.
func (*Postgres) SaveScan ¶
func (s *Postgres) SaveScan( ctx context.Context, clusters []string, results map[string]map[string]scanner.Result, ) (string, error)
SaveScan persists a complete scan with all per-cluster results under a new ID.
func (*Postgres) SaveScanRecord ¶ added in v0.10.0
func (s *Postgres) SaveScanRecord( ctx context.Context, rec ScanRecord, results map[string]map[string]scanner.Result, ) (string, error)
SaveScanRecord persists a scan under rec.ID and rec.Timestamp, generating whichever is unset.
func (*Postgres) SetClusterTag ¶
SetClusterTag upserts one key/value tag on a cluster. Keys are case-sensitive and free-form; common conventions are `env=prod`, `tier=critical`, `owner=team-a`.
func (*Postgres) SetLocation ¶
func (s *Postgres) SetLocation(ctx context.Context, loc LocationRecord) error
SetLocation upserts a manual geographic override for a cluster.
func (*Postgres) TouchAPIKey ¶
TouchAPIKey records the most recent successful authentication time. This is best-effort: a failure here does not invalidate the request.
func (*Postgres) UpsertAlert ¶
func (s *Postgres) UpsertAlert(ctx context.Context, rec AlertRecord) error
UpsertAlert inserts or updates an alert keyed by fingerprint.
type SQLite ¶
type SQLite struct {
// contains filtered or unexported fields
}
SQLite implements Store using a SQLite database.
func NewSQLite ¶
NewSQLite opens or creates a SQLite database at path and runs migrations. WAL is enabled for read/write concurrency; busy_timeout retries instead of failing under contention; foreign_keys are required for ON DELETE CASCADE to work on scan_results.
func (*SQLite) AddClusterToGroup ¶
AddClusterToGroup adds a cluster to an existing group.
func (*SQLite) DeleteAck ¶
DeleteAck removes an acknowledgement by fingerprint. Idempotent: a missing row is not an error so callers can call from a "clear" button without worrying about state.
func (*SQLite) DeleteClusterTag ¶
DeleteClusterTag removes one key from a cluster's tag set.
func (*SQLite) DeleteGroup ¶
DeleteGroup removes a group. Cascades to group_clusters.
func (*SQLite) DeleteLocation ¶
DeleteLocation removes a manual override for a cluster.
func (*SQLite) GetAPIKey ¶
func (s *SQLite) GetAPIKey(ctx context.Context, id string) (*APIKeyRecord, error)
GetAPIKey returns the key with the given ID.
func (*SQLite) GetAPIKeyByHash ¶
func (s *SQLite) GetAPIKeyByHash(ctx context.Context, hash string) (*APIKeyRecord, error)
GetAPIKeyByHash looks up a key by its token hash. Revoked or expired keys are returned with their RevokedAt/ExpiresAt set so callers can produce the right error message.
func (*SQLite) GetAck ¶ added in v0.14.0
GetAck returns the active ack for fingerprint. Expired snoozes are pruned first, so an elapsed ack reads as missing. Returns the wrapped ErrNotFound when no row matches.
func (*SQLite) GetAlert ¶
func (s *SQLite) GetAlert(ctx context.Context, fingerprint string) (*AlertRecord, error)
GetAlert returns the alert row with the given fingerprint. Returns the wrapped ErrNotFound when no row matches.
func (*SQLite) GetClusterHistory ¶
func (s *SQLite) GetClusterHistory(ctx context.Context, cluster string, limit int) ([]ScanResultRecord, error)
GetClusterHistory returns scan results for a cluster across scans.
func (*SQLite) GetClusterTags ¶
GetClusterTags returns every tag pair on a cluster.
func (*SQLite) GetGroup ¶
func (s *SQLite) GetGroup(ctx context.Context, name string) (*GroupRecord, error)
GetGroup retrieves a group by name.
func (*SQLite) GetLocation ¶
func (s *SQLite) GetLocation(ctx context.Context, cluster string) (*LocationRecord, error)
GetLocation returns the manual override for a cluster, or nil/no error when none has been set.
func (*SQLite) GetScan ¶
func (s *SQLite) GetScan(ctx context.Context, id string) (*ScanRecord, error)
GetScan retrieves a scan record by ID.
func (*SQLite) GetScanResults ¶
func (s *SQLite) GetScanResults(ctx context.Context, scanID string) (map[string]map[string]scanner.Result, error)
GetScanResults retrieves all per-cluster scanner results for a scan.
func (*SQLite) GetScansByTimeRange ¶
func (s *SQLite) GetScansByTimeRange(ctx context.Context, start, end time.Time) ([]ScanRecord, error)
GetScansByTimeRange returns scans within a time window.
func (*SQLite) IsAcked ¶
IsAcked reports whether a finding identified by fingerprint has an active (non-expired) ack.
func (*SQLite) ListAPIKeys ¶
func (s *SQLite) ListAPIKeys(ctx context.Context) ([]APIKeyRecord, error)
ListAPIKeys returns every API key, ordered by creation time descending. Revoked keys are included so administrators can audit them.
func (*SQLite) ListAcks ¶
ListAcks returns every active ack. Expired snoozes are filtered out and removed from the database as a side effect so the table stays small.
func (*SQLite) ListAlerts ¶
func (s *SQLite) ListAlerts(ctx context.Context, opts AlertListOptions) ([]AlertRecord, error)
ListAlerts returns alerts matching opts, newest received_at first.
func (*SQLite) ListAuditEntries ¶
func (s *SQLite) ListAuditEntries(ctx context.Context, opts AuditListOptions) ([]AuditEntry, error)
ListAuditEntries returns audit entries matching opts, newest first.
func (*SQLite) ListClusterTags ¶
ListClusterTags returns every tag across the fleet, grouped by cluster.
func (*SQLite) ListClusters ¶
func (s *SQLite) ListClusters(ctx context.Context) ([]ClusterRecord, error)
ListClusters returns all known clusters with their group memberships.
func (*SQLite) ListGroups ¶
func (s *SQLite) ListGroups(ctx context.Context) ([]GroupRecord, error)
ListGroups returns all groups.
func (*SQLite) ListLocations ¶
func (s *SQLite) ListLocations(ctx context.Context) ([]LocationRecord, error)
ListLocations returns every manual override sorted by cluster name.
func (*SQLite) ListScans ¶
func (s *SQLite) ListScans(ctx context.Context, limit int) ([]ScanRecord, error)
ListScans returns scan records newest first, breaking timestamp ties by id.
func (*SQLite) Prune ¶
Prune deletes scans older than cutoff. The scan_results rows cascade. Returns the number of scans deleted.
func (*SQLite) PruneAlerts ¶
PruneAlerts removes alert rows older than cutoff. Returns rows removed.
func (*SQLite) PruneAuditEntries ¶
PruneAuditEntries deletes audit_log rows older than cutoff. Returns the number of rows removed. Idempotent; safe to call from a periodic ticker.
func (*SQLite) RemoveClusterFromGroup ¶
RemoveClusterFromGroup removes a cluster from a group.
func (*SQLite) RevokeAPIKey ¶
RevokeAPIKey marks a key as revoked. The row is retained so audit log entries referencing it remain interpretable.
func (*SQLite) SaveAPIKey ¶
func (s *SQLite) SaveAPIKey(ctx context.Context, rec APIKeyRecord) error
SaveAPIKey inserts a new API key. The ID and TokenHash must already be set; CreatedAt defaults to now when zero. Re-inserting the same ID returns ErrStore.
func (*SQLite) SaveAck ¶
SaveAck upserts an acknowledgement. The fingerprint is the primary key, so re-acking an already-acked finding refreshes its metadata in place.
func (*SQLite) SaveAuditEntry ¶
func (s *SQLite) SaveAuditEntry(ctx context.Context, rec AuditEntry) error
SaveAuditEntry inserts one audit entry. ID and Timestamp default to fresh values when unset, so callers can supply just the request-level fields.
func (*SQLite) SaveScan ¶
func (s *SQLite) SaveScan( ctx context.Context, clusters []string, results map[string]map[string]scanner.Result, ) (string, error)
SaveScan persists a complete scan with all per-cluster results under a new ID.
func (*SQLite) SaveScanRecord ¶ added in v0.10.0
func (s *SQLite) SaveScanRecord( ctx context.Context, rec ScanRecord, results map[string]map[string]scanner.Result, ) (string, error)
SaveScanRecord persists a scan under rec.ID and rec.Timestamp, generating whichever is unset.
func (*SQLite) SetClusterTag ¶
SetClusterTag upserts one key/value tag on a cluster. Keys are case-sensitive and free-form; common conventions are `env=prod`, `tier=critical`, `owner=team-a`.
func (*SQLite) SetLocation ¶
func (s *SQLite) SetLocation(ctx context.Context, loc LocationRecord) error
SetLocation upserts a manual geographic override for a cluster.
func (*SQLite) TouchAPIKey ¶
TouchAPIKey records the most recent successful authentication time. This is best-effort: a failure here does not invalidate the request.
func (*SQLite) UpsertAlert ¶
func (s *SQLite) UpsertAlert(ctx context.Context, rec AlertRecord) error
UpsertAlert inserts or updates an alert keyed by fingerprint.
func (*SQLite) VacuumInto ¶
VacuumInto writes a consistent snapshot of the database to the given file path. Wraps the SQLite `VACUUM INTO` statement which captures the entire database state atomically without blocking concurrent reads. The target path must not already exist.
type ScanRecord ¶
type ScanRecord struct {
// ID is the unique, time-sortable scan identifier.
ID string `json:"id"`
// Timestamp is when the scan was executed.
Timestamp time.Time `json:"timestamp"`
// Clusters lists the cluster context names that were scanned.
Clusters []string `json:"clusters"`
// Scanners lists the scanner names that were executed.
Scanners []string `json:"scanners"`
}
ScanRecord represents a persisted scan execution.
type ScanResultRecord ¶
type ScanResultRecord struct {
// ScanID is the parent scan identifier.
ScanID string `json:"scan_id"`
// Cluster is the cluster context name.
Cluster string `json:"cluster"`
// Scanner is the scanner name.
Scanner string `json:"scanner"`
// DataJSON is the raw JSON-encoded scanner output.
DataJSON []byte `json:"data_json"`
// State is how much to trust DataJSON, as the scanner reported it.
State scanner.State `json:"state,omitempty"`
// Reason explains a State other than OK.
Reason string `json:"reason,omitempty"`
}
ScanResultRecord represents one scanner's output for one cluster in one scan.
type Store ¶
type Store interface {
// SaveScan persists a complete scan with all per-cluster results. Returns
// the generated scan ID.
SaveScan(ctx context.Context, clusters []string, results map[string]map[string]scanner.Result) (string, error)
// SaveScanRecord persists a scan under rec.ID and rec.Timestamp so a caller can hand out the
// ID before the scan finishes, or carry an existing scan across stores. An empty ID is
// generated and a zero Timestamp means now. rec.Clusters lists the scanned clusters and
// rec.Scanners is ignored in favor of the scanners present in results. Returns the scan ID.
SaveScanRecord(ctx context.Context, rec ScanRecord, results map[string]map[string]scanner.Result) (string, error)
// GetScan retrieves a scan record by ID.
GetScan(ctx context.Context, id string) (*ScanRecord, error)
// ListScans returns scan records ordered by timestamp descending.
ListScans(ctx context.Context, limit int) ([]ScanRecord, error)
// GetScanResults retrieves all per-cluster scanner results for a scan,
// reconstructed into the same map shape the scan engine produces.
GetScanResults(ctx context.Context, scanID string) (map[string]map[string]scanner.Result, error)
// ListClusters returns all known clusters.
ListClusters(ctx context.Context) ([]ClusterRecord, error)
// SaveGroup creates or updates a group with the given cluster members.
SaveGroup(ctx context.Context, name string, clusters []string) error
// GetGroup retrieves a group by name.
GetGroup(ctx context.Context, name string) (*GroupRecord, error)
// ListGroups returns all groups.
ListGroups(ctx context.Context) ([]GroupRecord, error)
// DeleteGroup removes a group.
DeleteGroup(ctx context.Context, name string) error
// AddClusterToGroup adds a cluster to an existing group.
AddClusterToGroup(ctx context.Context, group, cluster string) error
// RemoveClusterFromGroup removes a cluster from a group.
RemoveClusterFromGroup(ctx context.Context, group, cluster string) error
// GetClusterHistory returns scan results for a specific cluster across
// scans, ordered by time descending.
GetClusterHistory(ctx context.Context, cluster string, limit int) ([]ScanResultRecord, error)
// GetScansByTimeRange returns scans within a time window.
GetScansByTimeRange(ctx context.Context, start, end time.Time) ([]ScanRecord, error)
// Prune deletes scans older than cutoff. Returns the number of scans deleted.
Prune(ctx context.Context, cutoff time.Time) (int, error)
// Vacuum reclaims unused database pages.
Vacuum(ctx context.Context) error
// SetLocation upserts a manual location override for a cluster.
SetLocation(ctx context.Context, loc LocationRecord) error
// GetLocation returns a single cluster's manual override, or nil if none.
GetLocation(ctx context.Context, cluster string) (*LocationRecord, error)
// ListLocations returns every manual override.
ListLocations(ctx context.Context) ([]LocationRecord, error)
// DeleteLocation removes a manual override.
DeleteLocation(ctx context.Context, cluster string) error
// SaveAck upserts a finding acknowledgement. The fingerprint primary key
// allows the same finding to be acked once across many scan cycles.
SaveAck(ctx context.Context, rec AckRecord) error
// DeleteAck removes an acknowledgement. Idempotent.
DeleteAck(ctx context.Context, fingerprint string) error
// GetAck returns the active acknowledgement for a fingerprint. Returns the
// wrapped ErrNotFound when no row matches or its snooze has expired.
GetAck(ctx context.Context, fingerprint string) (AckRecord, error)
// ListAcks returns every active acknowledgement (expired snoozes pruned).
ListAcks(ctx context.Context) ([]AckRecord, error)
// IsAcked reports whether a finding is currently acknowledged.
IsAcked(ctx context.Context, fingerprint string) (bool, error)
// SaveAPIKey inserts a new API key. The raw token is never persisted: the
// caller must hash it and place the result in rec.TokenHash before calling.
SaveAPIKey(ctx context.Context, rec APIKeyRecord) error
// GetAPIKeyByHash looks up a key by token hash. Returns the wrapped
// ErrNotFound when no row matches.
GetAPIKeyByHash(ctx context.Context, hash string) (*APIKeyRecord, error)
// GetAPIKey returns the key with the given ID.
GetAPIKey(ctx context.Context, id string) (*APIKeyRecord, error)
// ListAPIKeys returns every API key including revoked ones, newest first.
ListAPIKeys(ctx context.Context) ([]APIKeyRecord, error)
// RevokeAPIKey marks the key with the given ID as administratively disabled.
RevokeAPIKey(ctx context.Context, id string) error
// TouchAPIKey updates the last-used-at timestamp. Best-effort; callers
// should not fail authentication on error.
TouchAPIKey(ctx context.Context, id string, at time.Time) error
// SaveAuditEntry inserts one audit log row.
SaveAuditEntry(ctx context.Context, rec AuditEntry) error
// ListAuditEntries returns audit entries matching opts, newest first.
ListAuditEntries(ctx context.Context, opts AuditListOptions) ([]AuditEntry, error)
// PruneAuditEntries deletes audit log rows older than cutoff and returns
// the number of rows removed. Used by the audit-retention ticker.
PruneAuditEntries(ctx context.Context, cutoff time.Time) (int, error)
// UpsertAlert inserts or updates an inbound AlertManager alert keyed by
// its fingerprint. Reusing the fingerprint allows the same alert to
// transition between firing/resolved without producing duplicate rows.
UpsertAlert(ctx context.Context, rec AlertRecord) error
// GetAlert returns the alert row with the given fingerprint. Returns
// ErrNotFound when no row matches.
GetAlert(ctx context.Context, fingerprint string) (*AlertRecord, error)
// ListAlerts returns alerts matching opts, newest received_at first.
ListAlerts(ctx context.Context, opts AlertListOptions) ([]AlertRecord, error)
// PruneAlerts deletes alert rows older than cutoff (received_at). Returns
// the number of rows removed.
PruneAlerts(ctx context.Context, cutoff time.Time) (int, error)
// SetClusterTag upserts a single key/value tag on a cluster.
SetClusterTag(ctx context.Context, cluster, key, value string) error
// DeleteClusterTag removes one key from a cluster's tag set. Idempotent.
DeleteClusterTag(ctx context.Context, cluster, key string) error
// GetClusterTags returns every tag pair on a cluster as a key→value
// map. Empty map when no tags are set.
GetClusterTags(ctx context.Context, cluster string) (map[string]string, error)
// ListClusterTags returns every tag across the fleet, grouped by
// cluster. Convenient for the dashboard's per-cluster render.
ListClusterTags(ctx context.Context) (map[string]map[string]string, error)
// Close releases database resources.
Close() error
}
Store persists and retrieves scan data.