Documentation
¶
Overview ¶
Package netguard implements the design-13 security-group firewall plane. This G1 slice is read-only: it materializes the design-13 view (security group + node binding + resolved builtin zones) of a node's legacy NFTInputs baseline without mutating the store or any apply path. The G2 slice adds the compiler with a byte-parity gate against network.GenerateNFTPlan before the legacy path retires.
Index ¶
- Constants
- Variables
- func Blocking(findings []Finding) bool
- func Compile(in CompileInput) (network.NFTPlan, error)
- func CompileRuleset(in CompileInput) (string, error)
- func ExpandPortRanges(ranges []model.GuardPortRange) ([]int, error)
- func PortRanges(ports []int) []model.GuardPortRange
- func ZoneMap(zones []model.GuardZone) map[string]model.GuardZone
- type CompileInput
- type Finding
- type LegacyView
- type LintOptions
- type NodeResolver
- type SuggestInput
- type Suggestion
Constants ¶
const ( // FindingLockoutRiskSSH fires when no compiled rule can accept traffic on // any of the node's management ports. FindingLockoutRiskSSH = "lockout_risk_ssh" // FindingUnverifiedApply fires when the server has no public URL, so the // node-side apply cannot run a control-plane selfcheck after committing. FindingUnverifiedApply = "unverified_apply" // FindingManagementPortAssumed fires when the lockout check had no reported // reality to learn the node's real shell port from and fell back to tcp/22. // It is a warning rather than a block because tcp/22 is usually right; it // exists so an operator can tell a checked plan from a guessed one. FindingManagementPortAssumed = "management_port_assumed" // FindingInterfaceMissing fires when a rule matches an inbound interface // the node does not report. The public zone defaults to eth0, and twelve // fleet nodes have no eth0 (ens17, ens5, enp2s0, wlo1): every accept on // that interface matches nothing, the default drop takes over, and the // lockout check above still counts those accepts as a way in. FindingInterfaceMissing = "interface_missing" // FindingInterfaceUnverified fires when a rule matches an inbound interface // but the node has never reported which interfaces it has, so the lint // cannot tell a right name from a wrong one. It blocks rather than warns // because the wrong name is the lockout above, and a node that has not // reported is exactly the node most likely to be running an agent old // enough to have been enrolled with the eth0 guess. FindingInterfaceUnverified = "interface_unverified" SeverityBlock = "block" SeverityWarn = "warn" // ManagementPort is the port the lockout lint falls back to when the node // has never reported which port its shell daemon actually listens on. ManagementPort = 22 )
const ( SuggestionListenerMissingAllow = "listener_missing_allow" SuggestionAllowWithoutListener = "allow_without_listener" SuggestionOverlayListenerPublicAllow = "overlay_listener_public_allow" SuggestionOverlayZoneUntrusted = "overlay_zone_untrusted" SuggestionManagedTableDrift = "managed_table_drift" )
const ( // LegacyGroupPrefix namespaces the node-private security groups derived // from legacy NFTInputs baselines (design-13 §7.1). LegacyGroupPrefix = "sg-legacy-" )
const MaxExpandedPortsPerRule = 1024
MaxExpandedPortsPerRule bounds range expansion. The current renderer emits explicit port lists, so a very wide range would produce an unreadable, unreviewable ruleset. Native `from-to` nft range emission is a later renderer upgrade (design-13 L2); until then wide ranges fail closed with a named error rather than silently exploding the plan.
Variables ¶
var ErrNodeUnmanaged = errors.New("node guard binding is observe-only; adopt the node before planning")
ErrNodeUnmanaged is returned when a plan is requested for an observe-only binding. Converted legacy baselines start unmanaged: an operator must adopt a node before its firewall can be planned from the new model.
Functions ¶
func Compile ¶
func Compile(in CompileInput) (network.NFTPlan, error)
Compile lowers zones, trusted-zone accepts, per-node overrides, and attached security groups into a single network.NFTPlan.
func CompileRuleset ¶
func CompileRuleset(in CompileInput) (string, error)
CompileRuleset renders the final lattice_guard ruleset for a node.
func ExpandPortRanges ¶
func ExpandPortRanges(ranges []model.GuardPortRange) ([]int, error)
ExpandPortRanges flattens inclusive ranges into the explicit port list the current renderer emits, fail-closed on invalid or excessively wide ranges.
func PortRanges ¶
func PortRanges(ports []int) []model.GuardPortRange
PortRanges compresses a port list into sorted, deduplicated inclusive ranges: [9009,9010,9011,9013] becomes 9009-9011 and 9013. Out-of-range values are dropped rather than widened (fail-closed).
Types ¶
type CompileInput ¶
type CompileInput struct {
Binding model.NodeGuardBinding
// Groups in binding order. The caller resolves Binding.GroupIDs.
Groups []model.SecurityGroup
// Zones by id, including the builtin zones resolved for this node.
Zones map[string]model.GuardZone
Resolve NodeResolver
}
CompileInput is the fully-resolved authoring state for one node.
type Finding ¶
type Finding struct {
Code string `json:"code"`
Severity string `json:"severity"`
Message string `json:"message"`
}
Finding is one lint result. Blocking findings refuse the plan unless the operator explicitly accepts the risk, which is audited.
type LegacyView ¶
type LegacyView struct {
Group model.SecurityGroup
Binding model.NodeGuardBinding
Zones []model.GuardZone
}
LegacyView is the read-only design-13 rendering of one node's legacy NFTInputs baseline.
func LegacyBaseline ¶
func LegacyBaseline(inputs model.NFTInputs) LegacyView
LegacyBaseline converts a legacy NFTInputs record into the design-13 shape: one node-private security group whose rules reference the builtin public and wireguard zones, a binding attaching that group, and the node-resolved zone definitions. Semantics are preserved exactly: legacy "wireguard ports" were port-scoped source-CIDR allows, so the wireguard zone appears as a rule remote, never as a trusted zone in Binding.ZoneIDs. Managed is false: the node stays observe-only until an operator explicitly adopts it (G2).
type LintOptions ¶
type LintOptions struct {
// PublicURLConfigured reports whether the node-side apply will be able to
// run `lattice-agent --selfcheck-controlplane` after committing.
PublicURLConfigured bool
// Reality is the node's last reported firewall reality, or nil when the
// node has never reported one. Its listeners are what let the lockout check
// protect the port the operator actually reaches this box on, instead of
// assuming every fleet member runs sshd on 22. A node whose sshd moved to
// 2222 used to pass this lint with tcp/22 open and tcp/2222 dropped, which
// is the exact plan that locks the operator out for good.
Reality *model.GuardNodeReality
}
LintOptions carries the plan-time context the compiled ruleset cannot know.
type NodeResolver ¶
NodeResolver mirrors netpolicy.NodeResolver so node remotes resolve against current fleet state at compile time.
type SuggestInput ¶ added in v0.2.3
type SuggestInput struct {
Binding model.NodeGuardBinding
Groups []model.SecurityGroup
Zones map[string]model.GuardZone
Reality model.GuardNodeReality
}
SuggestInput is the read-only state needed to compare operator intent with a low-trust node reality report. It intentionally contains no store, HTTP, or task-executor dependency so G3 can wire persistence and routes later without changing the core diff logic.
type Suggestion ¶ added in v0.2.3
type Suggestion struct {
ID string `json:"id"`
Code string `json:"code"`
Severity string `json:"severity"`
Title string `json:"title"`
Detail string `json:"detail"`
ZoneID string `json:"zone_id,omitempty"`
Interface string `json:"interface,omitempty"`
Protocol string `json:"protocol,omitempty"`
Port int `json:"port,omitempty"`
Address string `json:"address,omitempty"`
Process string `json:"process,omitempty"`
}
Suggestion is an operator-review prompt derived from reality. Suggestions are display/diff input only; accepting one is a later, audited mutation path.
func Suggest ¶ added in v0.2.3
func Suggest(in SuggestInput) ([]Suggestion, error)
Suggest compares the node's current guard intent with the latest reality snapshot and emits deterministic, de-duplicated suggestions.