Documentation
¶
Overview ¶
Package firewall implements the node-agnostic `solo-provisioner network firewall` scope: the `inet weaver-host-firewall` nftables table that protects the bare-metal host (SSH/mgmt allowlist, ICMP policy, in-cluster host-service ports).
It is a generic primitive — it knows nothing about block/consensus/mirror/ relay nodes. Orchestration (wiring create into `kube cluster install`, teardown into `kube cluster uninstall`) is owned by the host/cluster layer (#777 → #778/#791); this package only implements the verbs.
The `inet weaver-host-firewall` table is kept deliberately separate from `inet weaver-workload-policy` (the BN workload plane). The two tables have opposite lifecycles: `inet weaver-host-firewall` is set once and rarely changes, while `inet weaver-workload-policy` churns continuously as the daemon rewrites set elements.
Index ¶
- Constants
- Variables
- func EnsureNetworkNftUnit(ctx context.Context) error
- func IsReserved(name string) bool
- func PortStrings(ports []int) []string
- type Block
- type Config
- type FileConfig
- type Manager
- func (m *Manager) Add(ctx context.Context, name string, cidrs, ports []string) error
- func (m *Manager) Apply(ctx context.Context, t *Table) error
- func (m *Manager) Config(ctx context.Context) (*FileConfig, error)
- func (m *Manager) Create(ctx context.Context, t *Table, force bool) (bool, error)
- func (m *Manager) CreateRule(ctx context.Context, r Rule, force bool) (bool, error)
- func (m *Manager) Delete(ctx context.Context) error
- func (m *Manager) DeleteRule(ctx context.Context, name string) error
- func (m *Manager) IsActive(ctx context.Context) (bool, error)
- func (m *Manager) Reapply(ctx context.Context) error
- func (m *Manager) Remove(ctx context.Context, name string, cidrs, ports []string, force bool) error
- func (m *Manager) Set(ctx context.Context, name string, cidrs, ports []string, force bool) error
- func (m *Manager) SetMany(ctx context.Context, updates []Update, force bool) error
- func (m *Manager) Show(ctx context.Context) (string, error)
- func (m *Manager) Table(_ context.Context) (*Table, error)
- type Proto
- type Rule
- func (r *Rule) AddCIDRs(cidrs []string) error
- func (r *Rule) AddPorts(ports []string) error
- func (r *Rule) RemoveCIDRs(cidrs []string)
- func (r *Rule) RemovePorts(ports []string)
- func (r *Rule) SetCIDRs(cidrs []string) error
- func (r *Rule) SetPorts(ports []string) error
- func (r *Rule) Validate() error
- type Runner
- type Table
- type Update
Constants ¶
const ( // TableName is the nftables table this package owns. TableName = "inet weaver-host-firewall" // HostNftPath is the on-disk artifact replayed at boot by the shared // solo-provisioner-network-nft.service oneshot (authored by #780). It lives // under /etc (host OS config on the root filesystem) — not /opt/solo/weaver, // which may be a late mount and would leave the firewall unloaded early at // boot. HostNftPath = "/etc/solo-provisioner/network-weaver-host-firewall.nft" // HostConfigPath is the declarative config this table is rendered from, and // the source of truth for every mutating verb. It sits beside the nft // artifact and holds exactly the schema `network firewall create --from-file` // accepts, so `show --output yaml` re-applied through --from-file is a no-op // by construction. // // A single file rather than one per rule: a change to the management // allowlist must be all-or-nothing, and a partial write across several files // could leave a host reachable by nobody. HostConfigPath = "/etc/solo-provisioner/network-weaver-host-firewall.yaml" // HostConfigPrevSuffix is appended to HostConfigPath to name the retained // previous generation of the config. Derived by suffix rather than declared // as an independent path so the two can never be pointed at different // directories. HostConfigPrevSuffix = ".prev" // HostConfigPrevPath is the generation of the config immediately before the // one currently applied, retained on every apply so a lost or truncated // state file has a recovery path that keeps named allow rules. // // Deliberately one generation deep: this is a recovery artifact, not a // version history. History belongs in the operator's own repository, holding // the output of `network firewall show --output yaml`. // // It is written only when the config it replaces parses, so the invariant is // "absent, or a loadable config exactly one generation back" — a retained // copy that is itself corrupt would be worthless for recovery, and worse than // worthless if an operator trusted it. HostConfigPrevPath = HostConfigPath + HostConfigPrevSuffix // WeaverNftPath is the inet weaver-workload-policy artifact, owned by `block node install` // (TS_2 #743). This package never writes it; it only checks for its presence // to decide whether the shared oneshot may be disabled (teardown is #791). WeaverNftPath = "/etc/solo-provisioner/network-weaver-workload-policy.nft" // NetworkNftService is the oneshot unit that loads network-weaver-host-firewall.nft at boot // and is restarted on every live mutation so the kernel and the on-disk file // are always in sync. This package authors, installs, and enables the unit; // it never disables it — that is orchestrated by `kube cluster uninstall` // (#791). The unit is extended by #780 to also load network-weaver-workload-policy.nft. NetworkNftService = "solo-provisioner-network-nft.service" // NetworkNftServiceUnitPath is the absolute path where the unit file is // installed so systemd can discover it. NetworkNftServiceUnitPath = "/usr/lib/systemd/system/" + NetworkNftService // LockDir holds the cross-command apply lock. It lives on tmpfs (/run) so it // is auto-cleared on reboot and leaves nothing behind on uninstall. LockDir = "/run/solo-provisioner/network" // LockPath is the flock acquired (LOCK_EX) for the duration of any mutating // verb, so a hand-run operator command and the daemon poll loop (#754) can // never interleave nft transactions. LockPath = "/run/solo-provisioner/network/.applying" )
const ( // RuleMgmt is the management allowlist: the only source of host-local // administrative access under the input chain's default drop. RuleMgmt = "mgmt" // RuleBlocked is the operator-curated deny list. It renders on prerouting, // input and output, so it is not expressible as an allow rule. RuleBlocked = "blocked" // RuleInCluster is the pod-CIDR-to-host-service allowance. Its address list // is auto-detected from the node's .spec.podCIDR when the operator omits it. RuleInCluster = "in_cluster" )
Reserved rule names. These three are first-class rather than operator-authored because weaver derives or defaults their content and omitting them is dangerous: an empty mgmt list locks the operator out, an absent in-cluster list breaks the cluster, and the block list renders on three hooks rather than one. Everything else is an ordinary named allow rule.
const ConfigVersion = 1
ConfigVersion is the schema version this build writes. A file may omit `version` (treated as the current schema) but may not declare a newer one: silently ignoring a field a future weaver understands could leave a host with a firewall narrower — or wider — than the file says.
const (
DefaultSSHPort = 22
)
Default flag values.
Variables ¶
var DefaultInClusterPorts = []int{6443, 4244, 7472, 10250}
DefaultInClusterPorts is the "stack set" of host-service ports opened to the in-cluster (pod) CIDR by default: the kube-apiserver (6443), the Cilium cluster-mesh / health port (4244), the kubelet read-only/metrics port (10250), and the MetalLB metrics/memberlist port (7472). Operators override with --in-cluster-ports.
var ReservedNames = []string{RuleMgmt, RuleBlocked, RuleInCluster}
ReservedNames are the rule names an `allow` entry may not take, in render order.
Functions ¶
func EnsureNetworkNftUnit ¶ added in v0.23.0
EnsureNetworkNftUnit writes the embedded service unit file to NetworkNftServiceUnitPath, then daemon-reloads and enables the unit for boot.
The on-disk unit is compared against the embedded copy, not merely stat-ed, so an already-provisioned host converges on the current unit the next time a mutation runs. Stat-and-skip would have stranded every existing host on the unit that shipped when it was first provisioned — including the missing StartLimitIntervalSec=0 that lets a run of failed applies wedge every later command behind systemd's start limit (#1002). An unchanged unit is still a fast no-op: no write, no daemon-reload.
func IsReserved ¶ added in v0.28.0
IsReserved reports whether name is one of the three reserved blocks.
func PortStrings ¶ added in v0.28.0
PortStrings converts an int port list to the string port specs a Rule holds. It is the boundary conversion for callers whose own schema is still int-typed — the --in-cluster-ports / --mgmt-ports flags and models.HostConfig — so the int-vs-range mismatch is resolved in exactly one place.
Types ¶
type Block ¶ added in v0.28.0
Block is a reserved section of the config file: the subset of Rule an operator may set on mgmt, blocked or in_cluster. It deliberately has no `name` (the section key is the name), no `proto` and no `icmp_echo` — the reserved blocks either fix those or have no use for them, and accepting the fields only to reject them in validation would suggest they mean something.
Neither field carries `omitempty`: an empty list must survive a write as `cidrs: []`, because collapsing it to an absent key would turn "render no rule" back into "derive the default" on the next load.
func (*Block) MarshalYAML ¶ added in v0.28.0
Blocked's port list is never populated: the block list drops every port, and Rule.Validate rejects a port on it. The field exists on Block only because one type serves all three reserved sections; writing it out for `blocked` would invite an operator to fill it in.
type Config ¶
type Config struct {
Runner Runner
NftPath string
ConfigPath string
LockPath string
ApplyViaService func(ctx context.Context) error
}
Config customises a Manager. The zero value is not useful; prefer NewManager. Tests inject a fake Runner, temp paths, and a no-op service func so the package builds and runs on any platform.
type FileConfig ¶ added in v0.28.0
type FileConfig struct {
Version int `yaml:"version"`
Mgmt *Block `yaml:"mgmt,omitempty"`
Blocked *Block `yaml:"blocked,omitempty"`
InCluster *Block `yaml:"in_cluster,omitempty"`
Allow []Rule `yaml:"allow,omitempty"`
}
FileConfig is the declarative form of a Table: the schema of the `network firewall create --from-file` input, of `network firewall show --output yaml`, and of the persisted config the mutating verbs load. Those three being one type is what makes the round-trip exact — the output of `show --output yaml` re-applied via `--from-file` is a no-op by construction, not by coincidence.
The reserved blocks are pointers so an absent section is distinguishable from an empty one. A file must state all three (see requireReservedBlocks): the file is the whole table, and a block left out would silently fall back to a compiled-in default — for `mgmt` that default is an empty allowlist under a default-drop policy, i.e. a lockout the operator never wrote down. A block present with an empty list renders no rule, which is how one is disabled.
func FileConfigFromTable ¶ added in v0.28.0
func FileConfigFromTable(t *Table) *FileConfig
FileConfigFromTable is the inverse of Table: the declarative view of a table, with every reserved block written out explicitly so a subsequent load resolves to the same table without consulting a default or the cluster.
func LoadConfigFile ¶ added in v0.28.0
func LoadConfigFile(path string) (*FileConfig, error)
LoadConfigFile reads and validates a declarative firewall config. Decoding is strict: an unrecognised key is an error rather than a silent no-op, since a typo in a firewall config would otherwise present as a rule that quietly never took effect.
func ParseConfig ¶ added in v0.28.0
func ParseConfig(data []byte) (*FileConfig, error)
ParseConfig decodes and validates a declarative firewall config from YAML.
func (*FileConfig) InClusterCIDRsUnset ¶ added in v0.28.0
func (c *FileConfig) InClusterCIDRsUnset() bool
InClusterCIDRsUnset reports whether the config left the in-cluster address list unspecified, so the caller knows to auto-detect the node's pod CIDR. An explicitly empty list (`in_cluster: {cidrs: []}`) is *specified* — it means "render no in-cluster rule" — and must not trigger detection.
A parsed config always has the block itself (requireReservedBlocks), so in practice this reports on the `cidrs` field alone; the nil-block arm covers a config assembled in Go rather than decoded.
func (*FileConfig) Marshal ¶ added in v0.28.0
func (c *FileConfig) Marshal() ([]byte, error)
Marshal renders the config as YAML, for `show --output yaml` and for the persisted state file.
func (*FileConfig) Table ¶ added in v0.28.0
func (c *FileConfig) Table() (*Table, error)
Table builds the Table this config describes, applying the defaults for any omitted reserved field. The in-cluster address list is the one value it cannot resolve on its own — see InClusterCIDRsUnset.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager implements the `network firewall` verbs against the `inet weaver-host-firewall` table. Every mutating verb takes the shared apply lock, atomically rewrites the on-disk artifact, and then restarts the systemd service via DBus so the kernel is updated in one consistent operation — no separate nft apply exec.
func NewManager ¶
func NewManager() *Manager
NewManager returns a Manager wired to the live kernel and the production paths.
func NewManagerWithConfig ¶
NewManagerWithConfig returns a Manager, filling any unset Config field with its production default.
func (*Manager) Add ¶ added in v0.28.0
Add adds CIDRs and/or port specs to the named rule and re-renders. Adding is idempotent: an entry already present is left alone. Growing a rule cannot empty the management allowlist, so unlike Remove/Set it takes no force.
func (*Manager) Apply ¶ added in v0.28.0
Apply replaces the whole table from a declarative config and re-renders, regardless of whether one already exists: unlike Create it is not create-if-missing. It has no production caller today (`create --from-file` goes through Create) and is retained as the exported "replace outright" entry point, exercised by the package tests.
func (*Manager) Config ¶ added in v0.28.0
func (m *Manager) Config(ctx context.Context) (*FileConfig, error)
Config returns the declarative config of the currently-configured table, for `show --output yaml`. Unlike Show it reads the persisted config rather than the kernel, so its output is the same shape that produced the ruleset — a kernel dump has already lost the distinction between an authored rule and a default, and auto-merge may have rewritten the port sets.
func (*Manager) Create ¶
Create is create-if-missing: when the table already exists and force is false, it makes no changes and returns (false, nil). force re-renders the table from the supplied flags and returns (true, nil).
func (*Manager) CreateRule ¶ added in v0.28.0
CreateRule declares a named allow rule, so a rule can be brought into existence without a config file. It is create-if-missing like Create: a name that already exists is left alone and reported as unchanged unless force is set, in which case the rule is replaced outright — the declaration states the whole rule, so every field not supplied on the redeclare returns to its default, membership and matching alike.
The rule may be declared with no members; the element verbs populate it afterwards. It renders nothing until it has both sources and a destination, so running the declare and the populate as separate commands never opens access early.
Deliberately not built on mutate: mutate always re-applies, and the already-exists path has nothing to apply — re-rendering an identical document would restart the nft unit for no reason.
func (*Manager) Delete ¶
Delete removes the inet weaver-host-firewall table and its on-disk artifact. It is idempotent. It deliberately does NOT disable the shared solo-provisioner-network-nft.service (shared with inet weaver-workload-policy) — that is orchestrated by `kube cluster uninstall` (#791).
func (*Manager) DeleteRule ¶ added in v0.28.0
DeleteRule removes one named allow rule and re-renders. The reserved blocks cannot be deleted; see Table.DeleteRule.
func (*Manager) IsActive ¶ added in v0.27.0
IsActive reports whether the inet weaver-host-firewall table is currently present in the kernel. It is a read-only probe (no lock, no mutation) used by callers that need the firewall's current on-host state, rather than a recorded decision whose absence cannot be distinguished from "disabled": common.ResolveFirewallSeed seeds the reconfigure enable/disable choice from it so an active firewall is never torn down by default, and NetworkFirewallCreate uses it to scope its rollback to a table that step actually introduced.
func (*Manager) Reapply ¶ added in v0.29.0
Reapply re-renders and re-applies the persisted config without changing it. It is what re-asserts the weaver-managed table on demand: the operator states no intent, so there is nothing to supply and nothing to override.
Implemented as a no-op mutation because that is exactly the semantics wanted — load the persisted table, dry-run it, rewrite the artifacts, restart the unit. The rendered document carries the scoped-replace prefix, so this flushes and reloads `inet weaver-host-firewall` alone and leaves any third-party table on the host untouched.
No separate "nothing is persisted" guard: load() already fails with a pointer to `create` when neither the config nor the nft artifact exists, and never falls back to a default table — applying a default-drop policy with an empty management allowlist would be a lock-out. Validation is likewise load()'s, which parses through ParseConfig and so runs Table.Validate.
func (*Manager) Remove ¶ added in v0.28.0
Remove drops CIDRs and/or port specs from the named rule and re-renders. Removing an absent entry is a no-op. force authorises removing the last address (or the last port) from the management allowlist; without it that one case is refused — see mutate.
func (*Manager) Set ¶
Set atomically replaces the named rule's address list and/or port list. force authorises replacing the management allowlist (or its port list) with an empty one; without it that case is refused — see mutate.
func (*Manager) SetMany ¶ added in v0.28.0
SetMany applies several rules' replacement membership in a single re-render, so a `set` naming more than one block lands as one nft transaction rather than several — a half-applied management allowlist is exactly the state worth avoiding here.
type Proto ¶ added in v0.28.0
type Proto string
Proto is the L4 protocol a rule matches. A rule names exactly one: nft has no combined tcp/udp dport match, so a service reachable over both families of protocol is two rules.
type Rule ¶ added in v0.28.0
type Rule struct {
Name string `yaml:"name" json:"name"`
CIDRs []string `yaml:"cidrs,omitempty" json:"cidrs,omitempty"`
Ports []string `yaml:"ports,omitempty" json:"ports,omitempty"`
// Proto defaults to ProtoTCP when empty. Meaningless on mgmt (which renders
// a fixed TCP accept plus its own ICMP type list) and on blocked (which
// drops every protocol).
Proto Proto `yaml:"proto,omitempty" json:"proto,omitempty"`
// ICMPEcho grants this rule's sources unmetered echo-request, rendered into
// the per-family ICMP chains above the rate meter. Meaningless on mgmt,
// which already carries a broader ICMP type list, and on blocked.
ICMPEcho bool `yaml:"icmp_echo,omitempty" json:"icmp_echo,omitempty"`
}
Rule is one named record in the host firewall: a source address list, a destination port list, and the protocol they apply to. The three reserved names render into fixed positions and ignore some fields (see Validate); an allow rule renders uniformly as `<family> saddr @<name> <proto> dport @<name>_ports accept`.
Ports are strings, not ints, so an inclusive range ("2379-2380") is expressible without a mixed int/string list. CIDRs may mix address families; the renderer routes each to the matching per-family set.
func (*Rule) AddCIDRs ¶ added in v0.28.0
AddCIDRs adds CIDRs to the rule, ignoring ones already present. The list is kept sorted so a render is stable regardless of the order entries arrived in.
func (*Rule) AddPorts ¶ added in v0.28.0
AddPorts adds port specs to the rule, ignoring ones already present.
func (*Rule) RemoveCIDRs ¶ added in v0.28.0
RemoveCIDRs drops CIDRs from the rule. Removing an absent entry is a no-op.
func (*Rule) RemovePorts ¶ added in v0.28.0
RemovePorts drops port specs from the rule. Removal is by exact spec, so removing "2379" from a rule holding "2379-2380" is a no-op rather than a partial range split — nft ranges are single set elements and splitting one silently would be a surprising way to change a firewall.
func (*Rule) SetCIDRs ¶ added in v0.28.0
SetCIDRs atomically replaces the rule's full address list. An empty (non-nil) slice clears it.
func (*Rule) SetPorts ¶ added in v0.28.0
SetPorts atomically replaces the rule's full port list. An empty (non-nil) slice clears it.
func (*Rule) Validate ¶ added in v0.28.0
Validate rejects any field that would be unsafe or nonsensical to render. Every untrusted token goes through pkg/sanity, so a malformed value can never break the atomic nft transaction or smuggle in nft syntax. flagFor names the CLI flag in the error so an operator sees the input they supplied rather than an internal field name.
type Runner ¶
type Runner interface {
// List returns the rendered ruleset for the inet weaver-host-firewall table
// (`nft list table inet weaver-host-firewall`).
List(ctx context.Context) (string, error)
// Check dry-runs a rendered ruleset file (`nft -c -f <path>`) without
// committing it, so a document the kernel would reject is caught before it
// becomes the persisted boot artifact.
Check(ctx context.Context, path string) error
// Delete removes the inet weaver-host-firewall table (`nft delete table inet weaver-host-firewall`).
Delete(ctx context.Context) error
// Exists reports whether the inet weaver-host-firewall table is present in the kernel.
Exists(ctx context.Context) (bool, error)
}
Runner is the seam over the system `nft` binary for read, check and delete operations. Live rule application is done by writing the on-disk artifact and restarting the systemd service via DBus — so Apply is not part of this interface. Tests substitute a fake so the package builds and unit-tests on any platform (including macOS) without touching the kernel.
func NewExecRunner ¶
func NewExecRunner() Runner
NewExecRunner resolves the nft binary path and returns a Runner that applies changes to the live kernel.
type Table ¶
type Table struct {
// Mgmt is the management allowlist (sets @mgmt_addrs / @mgmt_addrs6) and the
// ports reachable from it (@mgmt_ports). Under the input chain's default
// drop this is the only path to host-local administrative access, so an
// empty address list locks the operator out of new connections.
Mgmt Rule
// Blocked is the operator-curated deny list (sets @blocked_addrs /
// @blocked_addrs6). It is purely operator-managed for its whole lifecycle —
// nothing in this package or the daemon ever writes to it. This is
// deliberately distinct from the BN workload plane's `bn-restricted` set
// (`inet weaver-workload-policy`), which the traffic-shaper daemon
// reconciles from the block node's statusz "restricted" category; an
// operator block list needs a home the daemon never overwrites.
//
// A blocked CIDR means "blocked on this node", not "blocked from the host's
// own services": it is dropped on prerouting (which covers pod-bound
// forwarded traffic and runs ahead of conntrack), again on input, and as a
// destination on output — because blocking a peer inbound does not stop this
// host from dialing it, and the replies to a host-initiated connection are
// admitted by the input chain's established accept.
Blocked Rule
// InCluster admits host-service ports (@in_cluster_ports) from the pod CIDR
// (@in_cluster_addrs / @in_cluster_addrs6). Per design there is deliberately
// no rule here for block-node service ports: that traffic is forwarded
// rather than delivered locally, so an input rule for it would never match.
// It lives in `network policy --ports` instead.
//
// An empty address list renders no in-cluster rule at all, which is how an
// operator disables the block without deleting it.
InCluster Rule
// Allow holds the operator-authored rules, each a source list x port list x
// protocol accept. Order within the input chains is by name, so a render is
// stable across CLI invocations; evaluation order does not matter because
// every entry is an accept and none overlap a drop.
Allow []Rule
}
Table is the in-memory model of the `inet weaver-host-firewall` nftables table. It is the single source of truth that the kernel apply (via `nft -f`), the on-disk nft artifact, and the persisted YAML config are all rendered from, so no two of them can diverge.
The three reserved blocks are separate fields rather than entries in Allow because each renders into a position an allow rule cannot reach: Mgmt also feeds the ICMP chains, Blocked renders as a drop on three hooks, and InCluster's address list is auto-detected rather than authored. Every other rule is uniform, so the CLI addresses all four kinds by name through Table.Rule.
func NewTable ¶
func NewTable() *Table
NewTable returns a Table populated with the design defaults. Callers override fields from CLI flags or a config file before rendering.
func Parse ¶
Parse recovers the three reserved blocks of a Table from a rendered network-weaver-host-firewall.nft artifact. It understands only the exact formats this package renders — it is not a general nft parser.
It is the fallback path, not the normal one: the persisted YAML config is the source of truth for the mutating verbs (see Manager.load). Parse exists so a host provisioned before named allow rules existed — or one whose config file was lost — still yields its management allowlist rather than an error that leaves the operator with no way to add their address back. Named allow rules are deliberately NOT recovered here: reverse-engineering arbitrary named rules out of nft syntax would be fragile in exactly the situation where being wrong costs the most. Recovering management access is the goal; the allow rules are not recovered at all — neither their existence nor their membership — so they must be re-declared with `network firewall create-allow-rule` and then re-populated with `network firewall add`.
Both the current and the pre-allow-rules renderings are accepted, since an upgraded host still has the old artifact on disk until its first mutation.
func (*Table) DeleteRule ¶ added in v0.28.0
DeleteRule removes an allow rule. The reserved blocks cannot be deleted — they are structural, and deleting mgmt in particular would render a default-drop input chain with no way in. Emptying a block's address list is the supported way to disable it.
func (*Table) IncompleteAllowRules ¶ added in v0.28.0
IncompleteAllowRules returns the names of allow rules that are declared but do not yet render anything, in table order. Declaring a rule before populating it is supported (`network firewall create-allow-rule`), so this is a warning the manager surfaces on apply rather than a validation failure.
func (*Table) Names ¶ added in v0.28.0
Names returns every rule name in the table, reserved blocks first and allow rules sorted, for error messages that list the valid --name values.
func (*Table) Render ¶
Render produces the full `inet weaver-host-firewall` nft document for this table. The same output feeds both the kernel apply (`nft -f`) and the on-disk artifact, so the live table and the persisted file can never diverge.
func (*Table) Rule ¶ added in v0.28.0
Rule returns a pointer to the named rule so a caller can mutate it in place. It resolves the three reserved names and any allow rule through one lookup, which is what lets `network firewall add --name <x>` treat them uniformly.
func (*Table) UpsertAllow ¶ added in v0.28.0
UpsertAllow adds or replaces an allow rule, rejecting the reserved names. The list is kept sorted by name so the rendered document is independent of the order rules were authored in.
type Update ¶ added in v0.28.0
Update is one rule's replacement membership for SetMany. A nil slice leaves that dimension unchanged; an empty (non-nil) slice clears it. Proto and ICMPEcho follow the same convention with pointers, since their zero values ("" and false) are both meaningful settings rather than "not supplied".