Skip to content

api

import "github.com/vilaca/portal/internal/api"

Package api defines the load-bearing interfaces and DTOs that Portal modules communicate through. It is the only package depended on by every other internal package; nothing in api may import from internal/admission, internal/audit, internal/network, internal/actions, internal/engine, internal/expr, internal/sink, internal/lookup, or internal/rule.

The only external dependencies allowed are k8s.io/apimachinery (for GVK and Unstructured, which travel through Context) and Go's standard library.

Index

func ContextBuilders

func ContextBuilders() map[string]func() ContextBuilder

ContextBuilders returns a snapshot of every registered builder factory, in no particular order. The engine queries each in turn via Supports(gvk).

func RegisterAction

func RegisterAction(typ string, ctor func() Action)

RegisterAction adds an Action factory.

func RegisterContextBuilder

func RegisterContextBuilder(name string, ctor func() ContextBuilder)

RegisterContextBuilder adds a ContextBuilder factory.

func RegisterEngine

func RegisterEngine(name string, ctor func() ExpressionEngine)

RegisterEngine adds an ExpressionEngine factory. Last writer wins.

func RegisterSink

func RegisterSink(name string, ctor func() OutputSink)

RegisterSink adds an OutputSink factory.

func Sinks

func Sinks() map[string]func() OutputSink

Sinks returns a snapshot of every registered sink factory.

type Action

Action is one response capability — label, annotate, evict, patch-NP, revoke-SA-token, alertmanager. Adding a new action type is one struct + one Register call.

type Action interface {
    Type() string
    Execute(ctx context.Context, v Violation, params map[string]any) error
    Idempotent() bool
    DefaultRateLimit() time.Duration
}

func ActionFor

func ActionFor(typ string) Action

ActionFor returns a constructed Action by type, or nil if absent.

type ActionDispatcher

ActionDispatcher routes Violations to enabled Actions. It is non-blocking, applies rate-limiting per (rule, target) tuple, and consults an IdempotencyStore so re-emissions within the action's idempotency window are dropped rather than re-executed.

type ActionDispatcher interface {
    Dispatch(ctx context.Context, v Violation)
    // Drain blocks until in-flight dispatches finish or ctx is cancelled.
    Drain(ctx context.Context) error
}

type ActionSpec

ActionSpec is one entry in Rule.Actions.

type ActionSpec struct {
    Type      string         `json:"type" yaml:"type"`
    On        []Mode         `json:"on,omitempty" yaml:"on,omitempty"`
    RateLimit string         `json:"rateLimit,omitempty" yaml:"rateLimit,omitempty"`
    Params    map[string]any `json:"params,omitempty" yaml:",inline"`
}

type AdmissionRequest

AdmissionRequest is the subset of AdmissionReview.Request that rules can read.

type AdmissionRequest struct {
    Operation string
    UserInfo  UserInfo
    DryRun    bool
    OldObject *unstructured.Unstructured
}

type Context

Context is the evaluation environment a rule sees. It carries three layers:

  • Object: always populated. The raw resource as *unstructured.Unstructured, exposed to expr-lang as nested maps. Anything reachable in the K8s schema is reachable here — this is the universal escape hatch.
  • Env: the rendered expr-lang environment (a map[string]any). Pod-shaped sugar lives here under the keys "container", "spec", "securityContext", "metadata", "object", "request". Non-pod GVKs get only "object", "metadata", "request".
  • Request: admission-only request metadata (Operation, UserInfo, DryRun, OldObject).
type Context struct {
    GVK     schema.GroupVersionKind
    Object  *unstructured.Unstructured
    Env     map[string]any
    Request *AdmissionRequest
}

type ContextBuilder

ContextBuilder converts a raw unstructured resource into an evaluation Context. One ContextBuilder is responsible for one GVK family — the pod-shaped builder covers Pod plus any GVK whose spec embeds PodTemplateSpec; a fallback builder handles everything else with only Object/Env(object,metadata,request).

type ContextBuilder interface {
    Supports(gvk schema.GroupVersionKind) bool
    Build(obj *unstructured.Unstructured) (Context, error)
}

type Decision

Decision is the admission-time aggregate response across all rules that fired for one AdmissionReview request.

type Decision struct {
    Allowed  bool
    Message  string
    Warnings []string
    // Violations is the full list this request produced, for PolicyReport + metrics.
    Violations []Violation
}

type DepEntry

DepEntry is an (rule, observed-object) pair recorded as depending on some referenced object.

type DepEntry struct {
    Rule     string
    Observed ObjectRef
}

type DepRecorder

DepRecorder is the write side of the reverse-dependency index. The lookup implementation records (rule, object) → (referenced) edges; the audit loop queries the inverse on resource-change events to enqueue re-evaluation.

type DepRecorder interface {
    Record(rule string, observed, referenced ObjectRef)
    Dependents(referenced ObjectRef) []DepEntry
}

type EnforcementAction

EnforcementAction controls how admission responds to a violation.

type EnforcementAction string

const (
    EnforceDeny   EnforcementAction = "deny"
    EnforceWarn   EnforcementAction = "warn"
    EnforceDryRun EnforcementAction = "dryrun"
)

type EventMeta

EventMeta describes one event flowing through the engine. It is what EventSource produces and what ActionDispatcher / OutputSink consume alongside Violations.

type EventMeta struct {
    Source    string // "admission", "audit", "network", "api-audit-log" (v2)
    EventID   string // unique per event; used as the idempotency seed
    At        time.Time
    DryRun    bool
    Operation string // create/update/delete; "audit" or "network" outside admission
}

type EventSource

EventSource produces evaluation events. The admission webhook, the audit informer loop, and the NetworkPolicy analyser each implement EventSource and feed the same RuleEngine + ActionDispatcher pipeline.

type EventSource interface {
    Name() string
    // Start blocks until ctx is cancelled or the source errors fatally. onEvent
    // is invoked for each event; the source is responsible for any internal
    // goroutines (informers, HTTP handlers).
    Start(ctx context.Context, onEvent func(Context, EventMeta)) error
    Stop(ctx context.Context) error
}

type ExpressionEngine

ExpressionEngine compiles rule expressions into evaluable Programs. expr-lang is the default implementation; CEL/Rego/starlark can be added in v3 without touching the rule schema.

type ExpressionEngine interface {
    // Name is "expr", "cel", "rego" — used in metrics and the Rule's selector.
    Name() string
    // Compile parses one rule expression. Diagnostics include line/column when
    // available so PortalClusterRule.status.parseError is human-readable.
    Compile(expression string) (Program, error)
}

func Engine

func Engine(name string) ExpressionEngine

Engine returns a constructed ExpressionEngine by name, or nil if absent.

type IdempotencyStore

IdempotencyStore remembers recently-dispatched (rule,gvk,ns,name,actionType) tuples. Default impl is an in-memory LRU; v2 may swap in a persistent store.

type IdempotencyStore interface {
    // Seen returns true if the key has been observed within ttl, otherwise
    // records it and returns false.
    Seen(key string, ttl time.Duration) bool
}

type Lookup

Lookup is exposed to rule expressions as `cluster.\<gvk>.byName(ns, name)` and `cluster.\<gvk>.list(ns, selector)`. Backed by audit's shared informer caches at runtime; pluggable for tests.

type Lookup interface {
    // ByName fetches a single object from the cache. Returns nil if absent.
    ByName(gvk schema.GroupVersionKind, namespace, name string) (*unstructured.Unstructured, error)
    // List returns every object of gvk in namespace matching selector. Empty
    // namespace means cluster-wide.
    List(gvk schema.GroupVersionKind, namespace string, selector map[string]string) ([]*unstructured.Unstructured, error)
    // Watched reports the set of GVKs this Lookup currently has informers for.
    Watched() []schema.GroupVersionKind
}

type Matcher

Matcher narrows the set of resources a rule applies to.

type Matcher struct {
    GVK        []schema.GroupVersionKind `json:"gvk" yaml:"gvk"`
    Namespaces NamespaceSelector         `json:"namespaces,omitempty" yaml:"namespaces,omitempty"`
}

type Mode

Mode names a rule-evaluation loop. A rule may opt in to multiple modes.

type Mode string

const (
    ModeAdmission Mode = "admission"
    ModeAudit     Mode = "audit"
    ModeNetwork   Mode = "network"
    ModeRuntime   Mode = "runtime" // v2
)

type NamespaceSelector

NamespaceSelector is the include/exclude shape used by Rule.Match.Namespaces.

type NamespaceSelector struct {
    Include []string `json:"include,omitempty" yaml:"include,omitempty"`
    Exclude []string `json:"exclude,omitempty" yaml:"exclude,omitempty"`
}

type ObjectRef

ObjectRef identifies one cluster object for the dep index.

type ObjectRef struct {
    GVK       schema.GroupVersionKind
    Namespace string
    Name      string
}

type OutputSink

OutputSink writes violations somewhere visible. AlertManager, PolicyReport, Prometheus, stdout/JSON all implement this. Each sink is independently enable-able and must be safe for concurrent Emit calls.

type OutputSink interface {
    Name() string
    Emit(ctx context.Context, v Violation) error
    Close() error
}

type Program

Program is a compiled, reusable rule expression. It MUST be safe for concurrent Eval calls.

type Program interface {
    Eval(ctx Context) (bool, error)
}

type RateLimiter

RateLimiter limits dispatch frequency per opaque key. Returning false means "drop"; the dispatcher counts these into portal_actions_total{result="ratelimited"}.

type RateLimiter interface {
    Allow(key string, window time.Duration) bool
}

type Rule

Rule is the canonical parsed shape of a Portal rule, regardless of whether it originated from a folder YAML file or a PortalClusterRule / PortalRule CR.

type Rule struct {
    Name              string            `json:"name" yaml:"name"`
    Enabled           bool              `json:"enabled" yaml:"enabled"`
    Severity          Severity          `json:"severity,omitempty" yaml:"severity,omitempty"`
    Mode              []Mode            `json:"mode,omitempty" yaml:"mode,omitempty"`
    EnforcementAction EnforcementAction `json:"enforcementAction,omitempty" yaml:"enforcementAction,omitempty"`
    Match             Matcher           `json:"match" yaml:"match"`
    Expression        string            `json:"rule" yaml:"rule"`
    Alert             string            `json:"alert,omitempty" yaml:"alert,omitempty"`
    Actions           []ActionSpec      `json:"actions,omitempty" yaml:"actions,omitempty"`

    // Source identifies where this rule came from; used by status reporting and tests.
    Source RuleSource `json:"-" yaml:"-"`
}

func (Rule) HasMode

func (r Rule) HasMode(m Mode) bool

HasMode reports whether the rule opts into the given evaluation mode.

type RuleEngine

RuleEngine dispatches a Context to every rule indexed under that GVK and returns the produced Violations.

type RuleEngine interface {
    Evaluate(ctx Context, meta EventMeta) []Violation
}

type RuleIndex

RuleIndex is the read-only view of the rule store that the engine consults. Loaders (folder, CR) write into a concrete index that implements this.

type RuleIndex interface {
    // ForGVK returns the rules whose Match.GVK includes gvk, filtered by enabled.
    ForGVK(gvk schema.GroupVersionKind) []Rule
    // All returns every enabled rule. Used for static dependency extraction.
    All() []Rule
}

type RuleLoader

RuleLoader feeds parsed Rules into a central index. The folder loader walks a directory and watches it via fsnotify; the CR loader watches the two PortalClusterRule / PortalRule CRDs. Both produce the same Rule shape.

type RuleLoader interface {
    Name() string
    Start(ctx context.Context, onUpdate func(snapshot []Rule)) error
    Stop(ctx context.Context) error
}

type RuleSource

RuleSource records the origin of a Rule for diagnostics.

type RuleSource struct {
    Origin    string // "folder", "PortalClusterRule", "PortalRule"
    Path      string // file path or CR name/namespace
    UID       string // CR UID when applicable
    Generated bool   // true if produced by migrate-rules
}

type Severity

Severity classifies a rule for downstream routing and alerting.

type Severity string

const (
    SeverityInfo     Severity = "info"
    SeverityLow      Severity = "low"
    SeverityMedium   Severity = "medium"
    SeverityHigh     Severity = "high"
    SeverityCritical Severity = "critical"
)

type UserInfo

UserInfo identifies the requester at admission.

type UserInfo struct {
    Username string
    UID      string
    Groups   []string
    Extra    map[string][]string
}

type Violation

Violation is the cross-cutting DTO emitted by rule evaluation. Every output channel (admission response, PolicyReport, AlertManager, Prometheus, action dispatcher) consumes Violations.

type Violation struct {
    Rule      string                  // Rule.Name
    Severity  Severity                // copied from Rule
    GVK       schema.GroupVersionKind // resource that violated
    Namespace string
    Name      string

    Mode    Mode      // which loop produced this violation (admission/audit/network)
    Message string    // human-readable reason
    At      time.Time // creation timestamp

    // EnforcementAction tells admission how to respond. Empty outside admission.
    EnforcementAction EnforcementAction

    // Actions is the rule's action list, copied verbatim so the dispatcher
    // doesn't need to re-read the rule store. Pre-merged with the `alert:`
    // shorthand.
    Actions []ActionSpec

    // Source carries enough metadata for sinks to render context-rich messages
    // without re-fetching the object.
    Source ViolationSource
}

type ViolationSource

ViolationSource captures the originating event for downstream rendering.

type ViolationSource struct {
    EventID   string // unique per emission, used for idempotency
    Operation string // admission operation, if any
    Username  string // admission requester, if any
    Container string // when the violating context was a specific container in a multi-container Pod
}

Generated by gomarkdoc