compiler

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Aug 16, 2026 License: Apache-2.0 Imports: 29 Imported by: 0

Documentation

Index

Constants

View Source
const (
	ProtocolSSH    = "ssh"
	ProtocolTLS    = "tls"
	ProtocolDNS    = "dns"
	ProtocolHTTP11 = "http/1.1"
	ProtocolHTTP2  = "http/2"
	ProtocolQUIC   = "quic"
)

Protocol tokens a protocol behavior value may name. The set is limited to client-speaks-first protocols with a fixed signature, because classification reads only a flow's first data segment. A token names what the classifier recognized, not a security property: tls/ means a TLS record layer was on the wire, and its absence says nothing (ssh and quic are both encrypted).

View Source
const (
	ModeEnforce = "enforce"
	ModeMonitor = "monitor"
)

Policy modes. The mode axis is: enforce (block in the kernel) and monitor (observe + emit findings, never block).

View Source
const MaxALPNLength = 16

MaxALPNLength is the size of the kernel's ALPN match buffer. The schema caps the tls/<alpn> suffix at this length so admission never accepts a value the classifier cannot match.

View Source
const MaxDNSNameLen = 126

MaxDNSNameLen is the longest dotted name a dns value may carry.

It is derived from the kernel's question-name key rather than from the DNS limit: the wire form of a dotted name is two bytes longer than the name (every dot becomes a length prefix, plus a leading prefix and a trailing root byte), so a 128-byte key holds 126 characters. Admitting the 253 characters DNS permits would accept names the observer can never produce, and a policy naming one would match nothing while looking correct.

View Source
const MaxPathValueLen = 127

MaxPathValueLen bounds an exec or open path value. The banned and allowed maps are keyed by char[128] filled with bpf_probe_read_kernel_str, so every key the kernel can ever look up holds at most 127 bytes plus its NUL terminator: a 128-byte value would be programmed into a key no exec or open can produce.

View Source
const ProtocolUnclassified = "unclassified"

ProtocolUnclassified is the kernel's residual classification for traffic matching no signature. It is observation vocabulary — findings, metrics, logs — never a policy value: ParseProtocolValue rejects it, so the only rule that covers unclassifiable traffic is a default deny.

View Source
const StarTarget = "*"

StarTarget is the sentinel a policy uses to mean "everything", i.e. a default-deny (in a deny list) or an allow-all (in an allow list). It applies to every behavior kind: network targets as well as open/exec paths. It is never programmed into a kernel map; consumers translate it into their own default-deny flag.

Variables

View Source
var (
	// ErrEmptyDNSValue reports a value that is empty after trimming.
	ErrEmptyDNSValue = errors.New("empty dns name")
	// ErrWildcardPositionDNSValue reports a wildcard that is not the whole
	// leftmost label: "a.*.b", "a.b.*", "ap*.b", or a bare "*." with no suffix.
	ErrWildcardPositionDNSValue = errors.New(`a wildcard is only supported as the leftmost label: use "*.example.com", an exact name, or "*" to report every name`)
	// ErrNotAHostnameDNSValue reports anything else that is not a name: a single
	// label, a malformed label, a numeric last label, an address, a URL.
	ErrNotAHostnameDNSValue = errors.New(`not a hostname, a "*.<hostname>" wildcard or "*"`)
	// ErrTooLongDNSValue reports a value too long to be matched.
	ErrTooLongDNSValue = fmt.Errorf("dns name is longer than %d characters", MaxDNSNameLen)
	// ErrNULDNSValue reports an embedded NUL, which would truncate the name
	// somewhere between here and the comparison that has to match it.
	ErrNULDNSValue = errors.New("dns name contains a NUL byte")
)

Sentinel errors returned by ParseDNSValue, terse for the same reason the network ones are: callers surface them inside their own vocabulary.

View Source
var (
	// ErrEmptyNetworkValue reports a value that is empty after trimming.
	ErrEmptyNetworkValue = errors.New("empty network target")
	// ErrIPv6NetworkValue reports an IPv6 literal or CIDR (IPv4-mapped IPv6
	// forms are unmapped and accepted, not reported).
	ErrIPv6NetworkValue = errors.New("IPv6 addresses and CIDRs are not supported")
	// ErrWildcardNetworkValue reports a wildcard such as "*.example.com",
	// distinct from the bare "*" sentinel.
	ErrWildcardNetworkValue = errors.New(`wildcards are not supported: list each address or fully qualified hostname, or use "*" to match everything`)
	// ErrNotAnIPNetworkValue reports anything else: URLs, truncated
	// addresses, and strings that are not a usable hostname either (a single
	// label, an over-long or malformed label, a numeric last label).
	ErrNotAnIPNetworkValue = errors.New(`not an IPv4 address, IPv4 CIDR, hostname or "*"`)
	// ErrServiceFormNetworkValue reports a name in the cluster DNS domain that
	// names neither a Service nor one of its endpoints: a pod A record, or an
	// incomplete name. Such a name is rejected instead of falling through to
	// Host, because resolving it from the pod's DNS answers is not what an
	// operator naming a cluster address asked for. Callers prefix the specific
	// diagnosis; see serviceFormError.
	ErrServiceFormNetworkValue = errors.New(`a cluster Service must be named "<service>.<namespace>.svc.<cluster-domain>", or one of its endpoints "<hostname>.<service>.<namespace>.svc.<cluster-domain>"`)
	// ErrServiceShortFormNetworkValue reports a cluster name with no domain,
	// such as "redis.default.svc". A pod's resolver expands it through the
	// search domains, so the question is the full name and the authored value
	// would match nothing.
	ErrServiceShortFormNetworkValue = errors.New("this cluster name is missing its DNS domain")
	// ErrServiceLabelNetworkValue reports a cluster name whose labels are
	// malformed.
	ErrServiceLabelNetworkValue = errors.New(`invalid cluster Service name: the service label must start with a letter and the namespace and hostname labels with a letter or digit, all continuing with alphanumerics or "-" and at most 63 characters`)
)

Sentinel errors returned by ParseNetworkValue. They are deliberately terse: callers that surface them to operators (admission's field errors, the egress filter's rejected-target conditions) wrap or map them into their own remedy-bearing vocabulary.

View Source
var (
	// ErrEmptyPathValue reports a value that is empty after trimming.
	ErrEmptyPathValue = errors.New("empty path value")
	// ErrNULInPathValue reports an interior NUL byte. A path key is NUL-padded
	// and read back with bpf_probe_read_kernel_str, so a value carrying its own
	// NUL keys an entry no exec or open can ever produce.
	ErrNULInPathValue = errors.New("path contains a NUL byte: kernel paths are NUL-terminated strings")
	// ErrPathValueTooLong reports a value longer than MaxPathValueLen.
	ErrPathValueTooLong = fmt.Errorf("path is longer than %d bytes: the kernel path maps cannot hold it", MaxPathValueLen)
	// ErrRelativePathValue reports a value that is not an absolute path. The
	// kernel resolves every path it can match with bpf_d_path, which always
	// yields one, so a relative value programs a key nothing can ever produce.
	ErrRelativePathValue = errors.New("path must be absolute: the kernel matches resolved paths, so a bare program name never matches")
	// ErrPaddedStarValue reports a value that trims to the StarTarget sentinel
	// without being exactly it. The sentinel switches the whole behavior to
	// default deny, so a near-miss is corrected by the author, not normalized.
	ErrPaddedStarValue = errors.New(`the default-deny wildcard must be written exactly as "*"`)
)

Sentinel errors returned by ParsePathValue. Their text reaches operators unchanged, through admission's field errors and through the rejected-path policy conditions, so each one names the remedy and not just the fault.

View Source
var (
	// ErrEmptyProtocolValue reports a value that is empty after trimming.
	ErrEmptyProtocolValue = errors.New("empty protocol target")
	// ErrInvalidALPNValue reports a tls/<alpn> suffix that is empty, longer
	// than MaxALPNLength, or not visible ASCII.
	ErrInvalidALPNValue = errors.New(`ALPN must be 1-16 visible ASCII characters, e.g. "tls/h2"`)
	// ErrNotAProtocolValue reports anything else: an unrecognized token, an
	// ALPN suffix on a token other than tls, wrong case.
	ErrNotAProtocolValue = errors.New(`not a protocol token ("ssh", "tls", "tls/<alpn>", "dns", "http/1.1", "http/2", "quic") or "*"`)
)

Sentinel errors returned by ParseProtocolValue. Their messages reach operators verbatim — admission's field errors, the rejected-target status conditions — so each carries the remedy.

View Source
var ClusterDomain = "cluster.local"

ClusterDomain is the cluster's DNS domain, the suffix that makes a value a Service name rather than an external one. It is a cluster-wide constant that every consumer of the schema has to agree on, so it is set once from the daemon's flag before any policy is compiled, never per call.

View Source
var (
	VariablesType = types.NewObjectType("nirmata.variables")
)

Functions

func IsObserveMode

func IsObserveMode(mode string) bool

IsObserveMode reports whether the mode only observes behavior and never programs deny/allow entries into the kernel.

func IsStarTarget

func IsStarTarget(raw string) bool

IsStarTarget reports whether raw is the StarTarget sentinel under the same cleaning the value schemas apply. Consumers that translate the sentinel into a default-deny flag must use this rather than comparing against StarTarget: a policy value of `" * "` is a default deny to every parser, and a raw comparison would silently downgrade it to allow-all-except-denied.

Types

type AllowDenyPair

type AllowDenyPair struct {
	// todo: maybe move default deny detection to be directly on the pair and check it
	// at compile time ?
	Allow []string
	Deny  []string
}

func (*AllowDenyPair) DiffPair

func (p *AllowDenyPair) DiffPair(target *AllowDenyPair) *AllowDenyPair

given a pair p, and a target..return another pair that represents what's in the target but not p.

func (*AllowDenyPair) HasEntries

func (p *AllowDenyPair) HasEntries() bool

type ClusterService

type ClusterService struct {
	Name      string
	Namespace string
	Hostname  string
}

ClusterService is the Service named by a cluster DNS value. Hostname is set only for a per-endpoint record, and names one endpoint of that Service rather than the Service as a whole.

type CompiledRuntimePolicy

type CompiledRuntimePolicy struct {
	ReevalInterval *time.Duration
	UID            string
	Name           string
	// contains filtered or unexported fields
}

func (*CompiledRuntimePolicy) Evaluate

Evaluate runs the policy's compiled CEL programs.

type Compiler

type Compiler interface {
	Compile(rp v1alpha1.RuntimePolicy) (*CompiledRuntimePolicy, error)
}

func NewCompiler

func NewCompiler(client dynamic.Interface, resolver ServiceResolver) (Compiler, error)

type DNSValue

type DNSValue struct {
	Star     bool
	Name     string
	Wildcard bool
}

DNSValue is the parsed form of one dns behavior value. Star is the StarTarget sentinel; otherwise Name holds the name and Wildcard reports whether it was authored as "*.<Name>", matching any subdomain of Name rather than Name itself.

func ParseDNSValue

func ParseDNSValue(raw string) (DNSValue, error)

ParseDNSValue parses one policy-authored dns behavior value: the StarTarget sentinel, an exact hostname, or a left-wildcard "*.<hostname>".

A wildcard is accepted here and rejected by ParseNetworkValue, which is the one narrowing point between the two: a dns value is only ever compared against an observed question name, while a network target has to be resolved to addresses and programmed into a kernel map, and no finite set of addresses corresponds to a wildcard. Both schemas agree on what a hostname is, in validHostname.

Name is lowercased because observed names arrive lowercased, and a case-sensitive comparison against a mixed-case policy value would never match instead of failing visibly.

type EvaluationResult

type EvaluationResult struct {
	UID       string
	Name      string
	IPs       *AllowDenyPair
	Open      *AllowDenyPair
	Exec      *AllowDenyPair
	Protocols *AllowDenyPair
	DNS       *AllowDenyPair
	AppliesTo PodTarget
	Mode      string

	// MonitorFilter narrows which of this policy's findings are reported. Nil
	// when the policy sets no spec.monitorFilter.
	MonitorFilter *MonitorFilter

	// UnresolvedServices holds the Service DNS values whose Service is absent
	// from cache, as authored.
	UnresolvedServices []string
}

type FilterActivation

type FilterActivation map[string]any

FilterActivation is the CEL activation a monitorFilter expression sees. It depends only on the event, so one event's activation is reused across every policy that event is decided against.

func NewFilterActivation

func NewFilterActivation(ev runtimeevent.Event) FilterActivation

NewFilterActivation converts an event into the CEL activation a monitorFilter expression sees.

A facts key is set only when the matching pointer is non-nil, which is what makes has(event.open) answer the kind question and what makes dereferencing the wrong arm an eval error rather than a zero value.

func (FilterActivation) WithWouldDeny

func (a FilterActivation) WithWouldDeny() FilterActivation

WithWouldDeny returns the activation for the monitor-mode counterfactual. Only the outer map and the event map are copied: the per-kind fact maps under them are shared with a, so neither activation may write through them.

type FilterDecision

type FilterDecision struct {
	Report     bool
	Expression string
	Err        error
}

FilterDecision is the outcome of running a filter over one event. Expression names the expression that decided, empty when every expression passed. A non-nil Err always comes with Report true: the filter picks what an operator sees, so a broken expression must widen what is reported rather than hide it.

type MonitorFilter

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

MonitorFilter is a policy's compiled monitorFilter expressions, in spec order.

func (*MonitorFilter) Decide

Decide reports whether the event a describes should become a finding. A nil filter reports everything.

type NetworkValue

type NetworkValue struct {
	// Star is true when the value is the StarTarget sentinel.
	Star bool
	// Addr is set for a single-address value. It is already Unmap()ed, so an
	// IPv4-mapped IPv6 literal comes back as its IPv4 form.
	Addr netip.Addr
	// Prefix is set for a CIDR value of ANY width, already unmapped and
	// Masked. Width is deliberately not checked here: ParseNetworkValue
	// defines what a value IS, and each consumer applies its own width
	// policy (see egressfilter.ParseTargets, the single narrowing point).
	Prefix netip.Prefix
	// Host is set for a hostname value, lowercased and without the root dot,
	// so "API.Example.COM." and "api.example.com" are the same value.
	Host string
	// Service is set for a cluster Service DNS name. It is parsed rather than
	// left in Host because the two resolve by different mechanisms: a Service
	// is looked up in the API server, any other name is learned from the pod's
	// own DNS answers.
	Service *ClusterService
}

NetworkValue is the parsed form of one network target string. Exactly one of the fields is meaningful: Star for the "*" sentinel, Addr for a single address, Prefix for a CIDR, Host for an external hostname, Service for a cluster Service DNS name.

func ParseNetworkValue

func ParseNetworkValue(raw string) (NetworkValue, error)

ParseNetworkValue parses one policy-authored network target string. This is the one definition of the egress target value schema: admission validation (validateBehavior), program-time expansion (egressfilter.ParseTargets) and monitor-mode matching (monitor.newNetMatcher) all consume it, so they cannot disagree about what a value is.

The value is first trimmed of surrounding whitespace, quotes and brackets (CEL list rendering and hand-written YAML both leak those). Then:

  • StarTarget ("*") yields Star
  • an IPv4 literal (or IPv4-mapped IPv6 literal) yields Addr, unmapped
  • an IPv4 CIDR (or IPv4-mapped IPv6 CIDR) of any width yields Prefix, unmapped and masked
  • "<service>.<namespace>.svc.<ClusterDomain>" yields Service
  • "<hostname>.<service>.<namespace>.svc.<ClusterDomain>" yields Service with Hostname set, naming one endpoint of it
  • any other multi-label DNS name yields Host, lowercased and stripped of its root dot
  • everything else is an error: ErrEmptyNetworkValue, ErrIPv6NetworkValue, ErrWildcardNetworkValue, ErrServiceFormNetworkValue, ErrServiceShortFormNetworkValue, ErrServiceLabelNetworkValue, or ErrNotAnIPNetworkValue

type PathValue

type PathValue struct {
	// Star is true when the value is the StarTarget sentinel.
	Star bool
	// Path is the literal path the kernel maps are keyed on, byte for byte.
	Path string
}

PathValue is the parsed form of one exec or open path value. Exactly one of the fields is meaningful: Star for the "*" sentinel, Path for a literal path.

func ParsePathValue

func ParsePathValue(raw string) (PathValue, error)

ParsePathValue parses one policy-authored exec or open path value. This is the one definition of the exec and open value schema: admission validation, program-time key derivation and monitor-mode matching all reach it through ParsePathList, so they cannot disagree about what a value is. Exec and open share it because they are programmed into the same kernel maps.

The value is trimmed of surrounding whitespace — quotes and brackets are not trimmed, unlike ParseNetworkValue, because they are legal path bytes. Then:

  • StarTarget ("*"), written exactly, yields Star; a value that merely trims to it is an error
  • anything else yields Path, a literal never split into tokens and never interpreted as a glob
  • an empty, NUL-bearing, over-long or relative value is an error

type PodTarget

type PodTarget struct {
	Pod       labels.Selector
	Namespace labels.Selector
}

PodTarget is a policy's compiled answer to "does this policy apply to this pod": the namespace selector and the pod selector must both match.

The zero value matches nothing. A selector that could not be built must never widen a policy's scope, so Matches treats a nil half as no match rather than as "unset, so everything".

func (PodTarget) Matches

func (t PodTarget) Matches(nsLabels, podLabels map[string]string) bool

type ProtocolValue

type ProtocolValue struct {
	Star     bool
	Protocol string
	ALPN     string
}

ProtocolValue is the parsed form of one protocol target string. Star is true for the "*" sentinel; otherwise Protocol holds one of the Protocol* tokens and ALPN is non-empty only for a tls/<alpn> value.

func ParseProtocolValue

func ParseProtocolValue(raw string) (ProtocolValue, error)

ParseProtocolValue parses one policy-authored protocol target string. This is the one definition of the protocol target value schema: admission validation, program-time map filling (protofilter.ParseTargets) and monitor-mode matching all consume it, so they cannot disagree about what a value is.

The value is trimmed exactly as ParseNetworkValue trims, so the two schemas agree about "*". Then:

  • StarTarget ("*") yields Star
  • a bare token (ssh, tls, dns, http/1.1, http/2, quic) yields Protocol
  • tls/<alpn> yields Protocol tls with the ALPN, which must be 1 to MaxALPNLength bytes of visible ASCII (ALPN identifiers are case-sensitive byte strings, so no folding happens)
  • everything else is ErrEmptyProtocolValue, ErrInvalidALPNValue or ErrNotAProtocolValue

type RejectedTarget

type RejectedTarget struct {
	Value  string
	Reason string
}

RejectedTarget is a policy value that could not be turned into a kernel map entry, together with the reason. Rejections are returned as typed values (never dropped, never folded into an error) so callers can log them and attach them to policy status.

func ParsePathList

func ParsePathList(values []string) (paths []string, star bool, rejected []RejectedTarget)

ParsePathList splits one behavior's path values into the three groups every consumer needs: the literal paths, whether the default-deny sentinel is present, and the values that could not be parsed. Paths are de-duplicated, preserving first-seen order.

The kernel enforcer keys its maps off this, and monitor mode matches off it, so neither can hold a path the other does not.

func (RejectedTarget) String

func (r RejectedTarget) String() string

type ServiceResolver

type ServiceResolver interface {
	// ResolveService returns the addresses for one Service, in the value
	// schema ParseNetworkValue accepts.
	//
	// found is false when the Service is absent from cache. That is reported
	// separately from an empty address list so an unresolved name and a Service
	// with no ready endpoints stay distinguishable: the first is a policy
	// naming something nonexistent, the second is a policy whose target is
	// scaled to zero.
	ResolveService(namespace, name string) (addrs []string, found bool)

	// ResolveEndpoint returns the addresses of the one endpoint of a Service
	// whose DNS hostname is hostname, for a per-endpoint record such as
	// "web-0.web.default.svc.cluster.local".
	//
	// found follows ResolveService: false when no endpoint of that Service
	// carries the hostname, which covers both a missing Service and a replica
	// that is not running.
	ResolveEndpoint(namespace, service, hostname string) (addrs []string, found bool)
}

ServiceResolver turns a cluster Service DNS name into destination addresses. It is satisfied by pkg/services, which backs it with API server informers, so a Service target resolves from watched cache state rather than from DNS.

Jump to

Keyboard shortcuts

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