Documentation
¶
Overview ¶
Package usage builds and queries an in-memory index of "what RBAC verbs has each subject actually exercised within an observation window?" It is the data layer behind the least-privilege analyzer: granted permissions come from the snapshot (point-in-time), observed permissions come from the kube-apiserver audit log (history), and the analyzer diffs them to surface narrowing opportunities.
The index never persists. Audit logs are large, snapshots are small and shareable; the loader streams the log once at scan time, builds the index, and the analyzer queries it before it is dropped at process exit. The window metadata travels on each finding so the HTML report can show reviewers what data fed the verdict.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func SubjectFromUsername ¶
func SubjectFromUsername(username string) (models.SubjectRef, bool)
SubjectFromUsername parses a username string into a models.SubjectRef. ServiceAccount users are emitted by the API server as `system:serviceaccount:<namespace>:<name>` - we map that into the canonical SubjectRef shape so it aligns with snapshot-derived subjects. Any other shape (humans authenticated via OIDC/cert, internal system users, `system:anonymous`, etc.) returns ok=false; v1 of the least-privilege analyzer only considers ServiceAccount workloads.
Types ¶
type AuditEvent ¶
type AuditEvent struct {
Username string
Verb string
APIGroup string
Resource string // including any subresource appended as "resource/subresource"
StatusCode int
RequestedAt time.Time
}
AuditEvent is the normalized intermediate shape the parsers produce. Both the native kube-apiserver JSON-lines parser and the EKS CloudWatch envelope parser emit AuditEvents that the indexer consumes. Keeping a single internal shape means new audit-log sources only need a new parser, never analyzer-side changes.
type Source ¶
type Source string
Source identifies the audit-log format used to produce paths.
func ParseSource ¶
ParseSource maps the CLI flag to a Source value. Empty defaults to native.
type UsageIndex ¶
type UsageIndex struct {
WindowStart time.Time
WindowEnd time.Time
EventsProcessed int
// EventsSkipped counts events dropped during ingestion: denied responses, parse errors,
// out-of-window timestamps, non-ServiceAccount users. The total is surfaced in the HTML
// report so reviewers can sanity-check ingest health.
EventsSkipped int
NonSAUsernames int // events from human/group users; ignored by v1 but counted for transparency
// contains filtered or unexported fields
}
UsageIndex is the in-memory aggregate of audit-log observations keyed by subject and (apiGroup, resource[/subresource]). Construct one via LoadAuditLog. Queries are read-only and safe to call from multiple goroutines.
func EmptyIndex ¶
func EmptyIndex() *UsageIndex
EmptyIndex returns a usable empty index. Callers in pre-flight error paths use it so downstream code can dereference the pointer without nil-checks.
func LoadAuditLog ¶
func LoadAuditLog(paths []string, source Source, window time.Duration, now time.Time) (*UsageIndex, []string, error)
LoadAuditLog ingests one or more audit-log paths into a UsageIndex restricted to events within the last `window` duration. Paths may be individual files or directories; when a directory is supplied, every regular file inside (one level deep) is consumed. `.gz`-suffixed files are streamed through a gzip decoder so rotated logs work out of the box.
Permission/format errors on individual files are surfaced as warnings appended to the returned warnings slice and never abort the load - this mirrors the collector's "partial snapshots are still useful" posture. A nil paths slice or zero window yields an empty index without error so callers can dispatch on the resulting state.
func (*UsageIndex) HasAnyEventsFor ¶
func (i *UsageIndex) HasAnyEventsFor(subj models.SubjectRef) bool
HasAnyEventsFor reports whether the index contains any event attributed to subj within the observation window. Used by the analyzer to distinguish "subject is silent on resource X but active elsewhere" (narrow finding) from "subject is silent everywhere" (whole-Role candidate for removal).
func (*UsageIndex) Observed ¶
func (i *UsageIndex) Observed(subj models.SubjectRef, apiGroup, resource string) verbSet
Observed returns the verbs observed for subject on (apiGroup, resource). Subresources are tracked separately: a query for "pods" will NOT return verbs observed against "pods/exec". This matches Kubernetes RBAC semantics - pods and pods/exec are distinct grants and need distinct usage signals.
Returns an empty (non-nil) verbSet when nothing has been observed; callers can iterate freely without nil checks.
func (*UsageIndex) ObservedGVRs ¶
func (i *UsageIndex) ObservedGVRs(subj models.SubjectRef) []gvrKey
ObservedGVRs returns every (apiGroup, resource) coordinate that subj has touched. Used by the wildcard-expansion path: when a Role grants `verbs: ["*"]` on `apiGroups: ["*"]`, we don't enumerate every possible cluster verb - we only consider what the subject has actually exercised, which is the only signal we have for "narrower than the wildcard."