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

policy

package
v0.5.1 Latest Latest
Warning

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

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

Documentation

Overview

Package policy is the authored policy domain: services, address groups, and rulesets as operators write them, the admission rules every mutation passes before it is persisted, and the persistence interface the store implements (ADR-0018).

The authored model mirrors the authored layer of the wire contract (ADR-0015) with one addition the contract does not need on the wire: a rule may permit services by reference to a named service definition as well as inline, and the renderer expands the reference. Admission is where v1's inbound-only scope is enforced (ADR-0010): an outbound rule is rejected here with a precise error, and nothing downstream ever sees one.

Index

Constants

View Source
const MaxLabelLength = 63

MaxLabelLength bounds a label key and a label value, in bytes.

Variables

View Source
var (
	// ErrBadLabelKey is a key outside the grammar.
	ErrBadLabelKey = errors.New("policy: label key must be 1 to 63 letters, digits, '.', '_', '-', or '/', beginning and ending with a letter or digit")
	// ErrBadLabelValue is a value outside the grammar.
	ErrBadLabelValue = errors.New("policy: label value must be 1 to 63 letters, digits, '.', '_', or '-', beginning and ending with a letter or digit")
	// ErrDuplicateLabelKey is a key given twice in one label set.
	ErrDuplicateLabelKey = errors.New("policy: label key appears more than once")
)
View Source
var (
	ErrServiceUnknown      = errors.New("policy: service does not exist")
	ErrAddressGroupUnknown = errors.New("policy: address group does not exist")
	ErrRulesetUnknown      = errors.New("policy: ruleset does not exist")
	ErrRuleUnknown         = errors.New("policy: rule does not exist in this ruleset")
	ErrInUse               = errors.New("policy: object is referenced by a rule and cannot be deleted")
	ErrDuplicateName       = errors.New("policy: an object of this kind already has that name")
	ErrDuplicateRuleID     = errors.New("policy: a rule with that id already exists")
)

Store errors.

View Source
var (
	ErrDirectionOutbound    = errors.New("policy: outbound rules are not admitted in this version; only DIRECTION_INBOUND is compiled (ADR-0010)")
	ErrDirectionUnspecified = errors.New("policy: rule direction must be stated; DIRECTION_UNSPECIFIED is not a direction")
	ErrBadDirection         = errors.New("policy: direction must be inbound or outbound")
	ErrEmptySelector        = errors.New("policy: selector is empty; an empty selector matches nothing, so state the breadth explicitly")
	ErrEmptyLabelKey        = errors.New("policy: label key must not be empty")
	ErrEmptyLabelValues     = errors.New("policy: label match lists no values; a match with no values can never be satisfied")
	ErrBadPortRange         = errors.New("policy: port range must satisfy start <= end <= 65535")
	ErrBadPortSpec          = errors.New("policy: port must be a number or a start-end range")
	ErrPortsOnPortless      = errors.New("policy: protocol has no ports; list none")
	ErrBadProtocol          = errors.New("policy: protocol must be tcp, udp, or icmp")
	ErrBadCIDR              = errors.New("policy: address is not a valid CIDR")
	ErrUnknownService       = errors.New("policy: rule references a service that does not exist")
	ErrUnknownAddressGroup  = errors.New("policy: rule references an address group that does not exist")
	ErrBadPeer              = errors.New("policy: peer must be exactly one of a workload selector, an address group, or a CIDR")
	ErrNoPeers              = errors.New("policy: rule lists no peers; a rule with no peers permits nothing")
	ErrNoServices           = errors.New("policy: rule lists no services; a rule with no services permits nothing")
	ErrNoEntries            = errors.New("policy: service defines no entries")
	ErrNoCIDRs              = errors.New("policy: address group lists no addresses")
	ErrEmptyName            = errors.New("policy: name must not be empty")
	ErrBadID                = errors.New("policy: id is not a UUID")
	ErrDuplicateRule        = errors.New("policy: a rule id appears more than once in the ruleset")
)

Admission errors. Each is wrapped in a *ValidationError that names the offending field, so callers can test the kind with errors.Is and show the operator exactly what to fix.

View Source
var ErrNoDocument = errors.New("policy: a ruleset document is required")

ErrNoDocument is returned when a command expects a document and none is given.

View Source
var ErrVersionMismatch = errors.New("policy: the object has changed since it was read")

ErrVersionMismatch is returned by a conditional write whose expected version is not the object's current one. The error carrying it is a *VersionMismatchError naming the current version.

Functions

func AssignIDs

func AssignIDs(rs *Ruleset)

AssignIDs gives a ruleset that has no id a fresh one, and does the same for each of its rules, as a create does. A dry run calls it too, so a rule authored without an id renders as the rule saving it would create.

func CheckLabelKey added in v0.2.0

func CheckLabelKey(key string) error

CheckLabelKey admits a label key: ErrEmptyLabelKey when it is empty, ErrBadLabelKey when it is outside the grammar, nil otherwise.

func CheckLabelValue added in v0.2.0

func CheckLabelValue(value string) error

CheckLabelValue admits a label value: ErrBadLabelValue when it is empty or outside the grammar, nil otherwise.

func DirectionName

func DirectionName(d innerwallv1.Direction) string

DirectionName is the inverse of ParseDirection.

func FormatEntrySpec

func FormatEntrySpec(e ServiceEntry) string

FormatEntrySpec is the inverse of EntrySpecDoc.

func FormatPortSpec

func FormatPortSpec(p PortRange) string

FormatPortSpec is the inverse of ParsePortSpec.

func FormatVersion

func FormatVersion(v int64) string

FormatVersion returns the token for the stored version v.

func MatchWorkloads

func MatchWorkloads(s Selector, index map[identity.WorkloadID]map[string]string) []identity.WorkloadID

MatchWorkloads resolves a selector against an index of every workload's current labels and returns the ids it matches, sorted. It is the one scope resolution: the renderer's scope match (Matches) applied to the registry, which the read model's scoped reads, the selector preview, and the bulk mode change all call, so a preview and the change it precedes cannot resolve differently.

func ModeName

ModeName is the inverse of ParseMode.

func ParseDirection

func ParseDirection(s string) (innerwallv1.Direction, error)

ParseDirection parses a direction name.

func ParseMode

func ParseMode(s string) (innerwallv1.EnforcementMode, error)

ParseMode parses an enforcement mode name.

func ParseProtocol

func ParseProtocol(s string) (innerwallv1.Protocol, error)

ParseProtocol parses a protocol name.

func ProtocolName

func ProtocolName(p innerwallv1.Protocol) string

ProtocolName is the inverse of ParseProtocol.

func ResolveID

func ResolveID(s string, byName map[string]uuid.UUID) (uuid.UUID, bool)

ResolveID accepts an id or a name and returns the id.

func RuleEqual

func RuleEqual(a, b *Rule) bool

RuleEqual reports whether two rules say the same thing: same direction, enablement, description, peers, services, and entries, in order. Timestamps and versions are not compared; they are a consequence of content changing.

func ValidateAddressGroup

func ValidateAddressGroup(g *AddressGroup) error

ValidateAddressGroup admits an address group. CIDRs are checked to parse; the store persists them in canonical masked form.

func ValidateLabelSet added in v0.2.0

func ValidateLabelSet(labels []LabelPair) error

ValidateLabelSet admits a label set (a token's, or a workload's whole set in a label edit): every key and value in the grammar, no key twice. Findings are located at labels[i] in the order given, with the offending text quoted so whitespace in it shows.

func ValidateRule

func ValidateRule(r *Rule, refs References) error

ValidateRule admits one rule on its own, reporting paths relative to the rule.

func ValidateRuleset

func ValidateRuleset(rs *Ruleset, refs References) error

ValidateRuleset admits a ruleset: direction (inbound only), selectors (never empty), peers (well-formed, references resolve), services (references resolve, ranges valid), rule ids (distinct). It checks everything it can about a ruleset in isolation plus the references it is given; uniqueness of the name is the store's constraint. A ruleset converted from a document carries what the conversion could not read, and those faults are reported here in the same pass as everything else, so a name that resolves to nothing never hides a bad port range.

func ValidateSelector

func ValidateSelector(s Selector) error

ValidateSelector admits a selector on its own: never empty, every key and value in the label grammar, every key with at least one value.

func ValidateService

func ValidateService(s *Service) error

ValidateService admits a service definition and normalizes nothing: what is persisted is what was validated. A service converted from a document carries what the conversion could not read, reported here in the same pass as everything else.

Types

type AddressGroup

type AddressGroup struct {
	ID        uuid.UUID
	Name      string
	CIDRs     []string
	CreatedAt time.Time
	UpdatedAt time.Time
	// Version is the object's write count: 1 when created, advanced by
	// the store with every write.
	Version int64
}

AddressGroup is a named set of CIDRs for peers that are not managed workloads.

type Authoring

type Authoring struct {
	Store    Store
	Renderer Renderer
	// Now is the clock; time.Now if nil.
	Now func() time.Time
}

Authoring is the mutation surface for the authored model: admission, then persistence, then rendering. The command line and the operator surface go through it and nothing else writes the authored tables; admission runs here, so the two transports cannot diverge on what is admitted.

Updates and deletes take the version the caller last read (expect) and refuse with a *VersionMismatchError when the object has moved; an empty expect writes unconditionally, which is the command line's default.

func (*Authoring) CreateAddressGroup

func (a *Authoring) CreateAddressGroup(ctx context.Context, g *AddressGroup) error

CreateAddressGroup admits and persists a new address group, assigning its id and canonicalizing its CIDRs.

func (*Authoring) CreateRule

func (a *Authoring) CreateRule(ctx context.Context, rulesetID uuid.UUID, r *Rule) error

CreateRule adds one rule to a ruleset. The ruleset is written as a unit, conditioned on the version it was read at, so a concurrent edit of the same ruleset is refused rather than overwritten. Findings are reported relative to the rule.

func (*Authoring) CreateRuleset

func (a *Authoring) CreateRuleset(ctx context.Context, rs *Ruleset) error

CreateRuleset admits and persists a new ruleset, assigning ids to it and to any rule that has none. Every rule is created now.

func (*Authoring) CreateService

func (a *Authoring) CreateService(ctx context.Context, s *Service) error

CreateService admits and persists a new service, assigning its id.

func (*Authoring) DeleteAddressGroup

func (a *Authoring) DeleteAddressGroup(ctx context.Context, id uuid.UUID, expect string) error

DeleteAddressGroup removes an address group no rule references.

func (*Authoring) DeleteRule

func (a *Authoring) DeleteRule(ctx context.Context, rulesetID, ruleID uuid.UUID, expect string) error

DeleteRule removes one rule from a ruleset. expect is the rule's own version.

func (*Authoring) DeleteRuleset

func (a *Authoring) DeleteRuleset(ctx context.Context, id uuid.UUID, expect string) error

DeleteRuleset removes a ruleset and its rules.

func (*Authoring) DeleteService

func (a *Authoring) DeleteService(ctx context.Context, id uuid.UUID, expect string) error

DeleteService removes a service no rule references.

func (*Authoring) References

func (a *Authoring) References(ctx context.Context) (References, error)

References loads the ids admission resolves against.

func (*Authoring) UpdateAddressGroup

func (a *Authoring) UpdateAddressGroup(ctx context.Context, g *AddressGroup, expect string) error

UpdateAddressGroup admits and persists a changed address group.

func (*Authoring) UpdateRule

func (a *Authoring) UpdateRule(ctx context.Context, rulesetID uuid.UUID, r *Rule, expect string) error

UpdateRule replaces one rule of a ruleset. expect is the rule's own version; a stale one is refused with the rule's current version.

func (*Authoring) UpdateRuleset

func (a *Authoring) UpdateRuleset(ctx context.Context, rs *Ruleset, expect string) error

UpdateRuleset admits and persists a changed ruleset. Rules that keep their id keep their provenance across the edit: their CreatedAt is carried from the persisted rule, and their UpdatedAt and Version advance only when the rule itself changed. Rules without an id are new.

func (*Authoring) UpdateService

func (a *Authoring) UpdateService(ctx context.Context, s *Service, expect string) error

UpdateService admits and persists a changed service.

type EntryDoc

type EntryDoc struct {
	Protocol string   `json:"protocol"`
	Ports    []string `json:"ports,omitempty"`
}

EntryDoc is the JSON form of a service entry: a protocol name and port specs such as "5432" or "6000-6010". No ports means every port.

func EntrySpecDoc added in v0.2.0

func EntrySpecDoc(s string) EntryDoc

EntrySpecDoc reads the command-line entry form "tcp:5432,6000-6010" or "icmp" (no ports) into its document form, unparsed: the protocol and each port spec are read by ServiceFromDoc, so a command-line entry's faults reach admission the way a document's do.

func EntryToDoc

func EntryToDoc(e ServiceEntry) EntryDoc

EntryToDoc is the document form of one entry.

type Findings

type Findings struct {
	Errors []*ValidationError
	// contains filtered or unexported fields
}

Findings is every admission failure of one object, in document order. Admission reports all of them at once so an editor can mark every field that needs attention; errors.Is and errors.As see through to each one.

func AsFindings

func AsFindings(err error) *Findings

AsFindings returns the findings an error carries, or nil when it is not an admission failure.

func (*Findings) Error

func (f *Findings) Error() string

func (*Findings) Rebase

func (f *Findings) Rebase(prefix string)

Rebase strips prefix from every finding's path, for a caller that validated a whole object on behalf of one part of it.

func (*Findings) Unwrap

func (f *Findings) Unwrap() []error

Unwrap exposes every finding to errors.Is and errors.As.

type LabelPair added in v0.2.0

type LabelPair struct {
	Key   string
	Value string
}

LabelPair is one label as it enters the system.

type Names

type Names struct {
	ServiceByName      map[string]uuid.UUID
	ServiceName        map[uuid.UUID]string
	AddressGroupByName map[string]uuid.UUID
	AddressGroupName   map[uuid.UUID]string
}

Names resolves the names a document may use to ids and back.

func NewNames

func NewNames(services []Service, groups []AddressGroup) Names

NewNames indexes services and address groups for document conversion.

type Peer

type Peer struct {
	Kind           PeerKind
	Workloads      Selector
	AddressGroupID uuid.UUID
	CIDR           string
}

Peer is the remote end of a rule: managed workloads by selector, a named address group, or a literal CIDR. Exactly one is set.

type PeerDoc

type PeerDoc struct {
	Workloads    map[string][]string `json:"workloads,omitempty"`
	AddressGroup string              `json:"address_group,omitempty"`
	CIDR         string              `json:"cidr,omitempty"`
}

PeerDoc is the JSON form of a peer; exactly one field is set.

type PeerKind

type PeerKind int

PeerKind says which field of a Peer is set.

const (
	PeerUnspecified PeerKind = iota
	PeerWorkloads
	PeerAddressGroup
	PeerCIDR
)

Peer kinds, in the order the wire contract's PeerSelector declares them.

type PortRange

type PortRange struct {
	Start uint32
	End   uint32
}

PortRange is an inclusive port range; a single port has Start == End.

func ParsePortSpec

func ParsePortSpec(s string) (PortRange, error)

ParsePortSpec parses "80" or "8000-8010" into an inclusive range. Bounds are checked by admission, not here, so a bad range reports precisely.

type References

type References struct {
	Services      map[uuid.UUID]struct{}
	AddressGroups map[uuid.UUID]struct{}
}

References is what admission needs to know about the rest of the authored model: which services and address groups exist.

type Renderer

type Renderer interface {
	Render(ctx context.Context) error
}

Renderer is what runs after every admitted mutation: it re-renders every workload's policy, persists the ones whose rendered output changed, and lets the streams push them (ADR-0018). The authoring service holds it so a mutation can never be persisted without triggering it.

type Rule

type Rule struct {
	ID          uuid.UUID
	Direction   innerwallv1.Direction
	Enabled     bool
	Description string
	Peers       []Peer
	ServiceIDs  []uuid.UUID
	Entries     []ServiceEntry
	CreatedAt   time.Time
	UpdatedAt   time.Time
	Version     int64
	// contains filtered or unexported fields
}

Rule is a single authored rule. The ruleset's scope is the local end, Peers the remote end, Direction orients the two. Services may be named by reference (ServiceIDs) or written inline (Entries); the renderer expands both into the same rendered form. CreatedAt, UpdatedAt, and Version are the rule's own: a rule that keeps its id across an edit of its ruleset keeps its CreatedAt, and its UpdatedAt and Version advance only when the rule itself changed.

func RuleFromDoc

func RuleFromDoc(rd *RuleDoc, names Names) *Rule

RuleFromDoc converts one rule document; what could not be read is carried to admission at paths relative to the rule.

type RuleDoc

type RuleDoc struct {
	ID          string     `json:"id,omitempty"`
	Direction   string     `json:"direction"`
	Enabled     *bool      `json:"enabled,omitempty"`
	Description string     `json:"description,omitempty"`
	Peers       []PeerDoc  `json:"peers"`
	Services    []string   `json:"services,omitempty"`
	Entries     []EntryDoc `json:"entries,omitempty"`
	Version     string     `json:"version,omitempty"`
	CreatedAt   string     `json:"created_at,omitempty"`
	UpdatedAt   string     `json:"updated_at,omitempty"`
}

RuleDoc is the JSON form of a rule. Services and address groups may be named by name or id; Entries are inline protocol/port specs. Version, CreatedAt, and UpdatedAt are the rule's own, written by the control plane and ignored on the way in.

func RuleToDoc

func RuleToDoc(r *Rule, names Names) *RuleDoc

RuleToDoc converts one rule to its document form.

type Ruleset

type Ruleset struct {
	ID          uuid.UUID
	Name        string
	Description string
	Enabled     bool
	Scope       Selector
	Rules       []Rule
	CreatedAt   time.Time
	UpdatedAt   time.Time
	// Version is the ruleset's write count: 1 when created, advanced by
	// the store with every write, including a write of one of its rules.
	Version int64
	// contains filtered or unexported fields
}

Ruleset is a named set of rules applied to the workloads its scope selects.

func DecodeRuleset

func DecodeRuleset(data []byte, names Names) (*Ruleset, error)

DecodeRuleset parses a JSON document into a ruleset, resolving names. It does not admit; admission does, with the store's view of what exists. What the document itself gets wrong (an id that is not a UUID, a name that resolves to nothing, a port that is not a number) travels with the ruleset, and admission reports it at the offending path in the same pass, and the same shape, as everything else it finds. The error is only for a body that is not a ruleset document at all.

func RulesetFromDoc

func RulesetFromDoc(doc *RulesetDoc, names Names) *Ruleset

RulesetFromDoc converts a parsed document. Every element of the document has its place in the result, readable or not, so the paths admission reports name the document's own elements; what could not be read is carried to admission as faults (see DecodeRuleset).

type RulesetDoc

type RulesetDoc struct {
	ID          string              `json:"id,omitempty"`
	Name        string              `json:"name"`
	Description string              `json:"description,omitempty"`
	Enabled     *bool               `json:"enabled,omitempty"`
	Scope       map[string][]string `json:"scope"`
	Rules       []RuleDoc           `json:"rules"`
	Version     string              `json:"version,omitempty"`
	CreatedAt   string              `json:"created_at,omitempty"`
	UpdatedAt   string              `json:"updated_at,omitempty"`
}

RulesetDoc is the JSON form of a ruleset. Version, CreatedAt, and UpdatedAt are written by the control plane and ignored on the way in: a document that echoes them back is accepted, and the version it names is carried by the write's condition, not by the document.

func RulesetToDoc

func RulesetToDoc(rs *Ruleset, names Names) *RulesetDoc

RulesetToDoc converts a ruleset to its document form, using names where they resolve and ids otherwise.

type Selector

type Selector map[string][]string

Selector matches workloads by label: every key must match (AND) and the label's value must be one of the listed values (OR). An empty selector matches nothing.

func (Selector) Matches

func (s Selector) Matches(labels map[string]string) bool

Matches reports whether a workload carrying labels satisfies the selector. An empty selector matches nothing.

type Service

type Service struct {
	ID        uuid.UUID
	Name      string
	Entries   []ServiceEntry
	CreatedAt time.Time
	UpdatedAt time.Time
	// Version is the object's write count: 1 when created, advanced by
	// the store with every write.
	Version int64
	// contains filtered or unexported fields
}

Service is a named, reusable set of entries.

func ServiceFromDoc added in v0.2.0

func ServiceFromDoc(doc *ServiceDoc) *Service

ServiceFromDoc converts a service document. Like a ruleset's, every entry and port keeps its place, readable or not, and what could not be read travels with the service to admission: ValidateService reports it beside its own findings in one pass, so a port that is not a number never hides an empty name.

type ServiceDoc added in v0.2.0

type ServiceDoc struct {
	Name    string     `json:"name"`
	Entries []EntryDoc `json:"entries"`
}

ServiceDoc is a service definition as a client writes it.

type ServiceEntry

type ServiceEntry struct {
	Protocol innerwallv1.Protocol
	Ports    []PortRange
}

ServiceEntry is one protocol with the ports it permits. No ports means every port of the protocol, which is the only valid form for a protocol without ports.

type Store

type Store interface {
	CreateService(ctx context.Context, s *Service) error
	UpdateService(ctx context.Context, s *Service, expect string) error
	// DeleteService returns ErrInUse when a rule references the service.
	DeleteService(ctx context.Context, id uuid.UUID, expect string) error
	GetService(ctx context.Context, id uuid.UUID) (*Service, error)
	ListServices(ctx context.Context) ([]Service, error)

	CreateAddressGroup(ctx context.Context, g *AddressGroup) error
	UpdateAddressGroup(ctx context.Context, g *AddressGroup, expect string) error
	// DeleteAddressGroup returns ErrInUse when a rule references the group.
	DeleteAddressGroup(ctx context.Context, id uuid.UUID, expect string) error
	GetAddressGroup(ctx context.Context, id uuid.UUID) (*AddressGroup, error)
	ListAddressGroups(ctx context.Context) ([]AddressGroup, error)

	CreateRuleset(ctx context.Context, rs *Ruleset) error
	// UpdateRuleset replaces the ruleset and all of its rules, persisting
	// each rule's timestamps as given.
	UpdateRuleset(ctx context.Context, rs *Ruleset, expect string) error
	DeleteRuleset(ctx context.Context, id uuid.UUID, expect string) error
	GetRuleset(ctx context.Context, id uuid.UUID) (*Ruleset, error)
	ListRulesets(ctx context.Context) ([]Ruleset, error)
}

Store is the persistence the authored model needs. Every method is implemented with hand-written SQL in internal/store (ADR-0006). Writes of a composite object (a service with its entries, a ruleset with its rules) are atomic: the object is either fully persisted or not at all.

Updates and deletes are conditional: expect is the version token the caller last read (FormatVersion of its Version), compared byte-exact against the stored version's form, and the write applies only when the object still holds it, refusing otherwise with a *VersionMismatchError that names the current version. An empty expect writes unconditionally. The check is made inside the write, so two callers holding the same version cannot both succeed. Every update advances the stored version by one and sets the new value on the object it was given.

type ValidationError

type ValidationError struct {
	// Path locates the failure, e.g. "rules[1].peers[0].cidr".
	Path string
	// Err is one of the admission errors above.
	Err error
	// Detail is the offending value or a specific explanation, if any.
	Detail string
}

ValidationError is an admission failure at a named location in the authored object.

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Message

func (e *ValidationError) Message() string

Message is the finding for a person: the rule's explanation with the offending value, without the package prefix a log line carries.

func (*ValidationError) Rule

func (e *ValidationError) Rule() string

Rule is the stable name of the admission rule the finding violates.

func (*ValidationError) Unwrap

func (e *ValidationError) Unwrap() error

type VersionMismatchError

type VersionMismatchError struct {
	Current string
}

VersionMismatchError is a refused conditional write with the version the object holds now, so the caller can re-read and decide.

func (*VersionMismatchError) Error

func (e *VersionMismatchError) Error() string

func (*VersionMismatchError) Unwrap

func (e *VersionMismatchError) Unwrap() error

Jump to

Keyboard shortcuts

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