Documentation
¶
Overview ¶
Package firewall renders cargoship's backend-neutral firewall configuration onto whichever host firewall a node runs.
A Plan describes what the cluster needs open on one node: the addresses of its peers, the pod and service CIDRs, the ports from the inventory's `.host.ports`, and the rules from the inventory's `.host.firewall.rules`. A Backend translates that Plan into one firewall's own dialect and applies it. Backends are matched to a node by Detect, so a single inventory can target a mix of firewalld, ufw, and nftables hosts.
Index ¶
Constants ¶
const (
// FirewalldService is the name of the firewalld system service.
FirewalldService = "firewalld"
)
const (
// NftablesService is the name of the nftables system service.
NftablesService = "nftables"
)
const (
// UFWService is the name of the ufw system service.
UFWService = "ufw"
)
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Backend ¶
type Backend interface {
// Name is the backend identifier, e.g. "firewalld" or "ufw".
Name() string
// Detect is true when this backend manages the firewall on h. It runs commands on the
// host, so callers should call it once per host and reuse the result.
Detect(h *cluster.ZarfHost) bool
// Apply makes the node's firewall match p, then reloads the firewall. It is
// idempotent: applying the same plan twice leaves the node in the same state, and
// rules cargoship applied on an earlier run that p no longer contains are removed.
Apply(ctx context.Context, h *cluster.ZarfHost, p Plan) error
}
Backend applies a Plan to a node using one host firewall implementation.
type Firewalld ¶
type Firewalld struct{}
Firewalld applies a Plan to a node running firewalld.
func (*Firewalld) Apply ¶
Apply writes the ipsets, service, and policy files for p, enables them, and restarts firewalld.
type Nftables ¶
type Nftables struct{}
Nftables applies a Plan to a node that manages its firewall with nftables directly, rather than through firewalld or ufw. It is the backend for hosts that never had either -- Debian and Arch nodes configured by hand, and the minimal or immutable images (CoreOS, Flatcar) that ship no firewall front end at all.
Everything cargoship writes lives in one table, `inet cargoship`, which is replaced whole in a single nft transaction on every apply. Cargoship never flushes the ruleset and never touches another table, because kube-proxy and every CNI keep their own service and policy rules in this same subsystem; a global flush would break cluster networking until they resynced.
Two things differ from the other backends. Every base chain cargoship writes has an accept policy: a default-drop policy applied from a remote phase would cut the connection cargoship is running over, the same reason cargoship never runs `ufw enable`. And an accept here ends only cargoship's chain, not the hook, so a rule in an operator's own table can still drop traffic cargoship allowed -- unlike firewalld's trusted zone, cargoship's trust is not the last word on a packet.
func (*Nftables) Apply ¶
Apply renders p as a complete ruleset, checks it, and loads it in one transaction. The table is replaced rather than edited, so rules cargoship applied on an earlier run that p no longer contains are gone once the transaction commits.
func (*Nftables) Detect ¶
Detect is true when the node persists an nftables ruleset of its own, meaning the nftables service is running or one of the distro ruleset files exists.
A node whose only nftables content comes from kube-proxy and the CNI is deliberately not a match. Every node in a running cluster has a non-empty ruleset, so matching on that would claim hosts whose operator never configured a firewall at all.
type Plan ¶
type Plan struct {
// NodeAddresses lists the private addresses of every node in the cluster. A node trusts
// all traffic from these addresses.
NodeAddresses []string
// ClusterCIDRs lists the pod and service CIDR blocks the distro engine uses. A node
// trusts all traffic from these blocks.
ClusterCIDRs []string
// Ports lists the ports the node exposes publicly, from the inventory's `.host.ports`.
Ports []cluster.ZarfHostPort
// Rules lists the backend-neutral rules from the inventory's `.host.firewall.rules`.
Rules []cluster.ZarfFirewallRule
// Policies holds the legacy firewalld-only policies from the inventory's `.host.policy`.
// Backends other than firewalld ignore it.
Policies map[string]cluster.ZarfFirewallPolicyConfig
}
Plan is the desired firewall state for a single node, expressed without reference to any particular firewall implementation.
type UFW ¶
type UFW struct{}
UFW applies a Plan to a node running ufw.
Cargoship never enables or disables ufw. A node is only configured when ufw is already active, because enabling a default-deny firewall from a remote phase would cut the connection cargoship is running over.
Two parts of the neutral model mean something slightly different here than they do on firewalld: a forward rule's ingress and egress name interfaces rather than zones, since ufw has no zones, and a rule with no address match becomes a rule from any address.
func (*UFW) Apply ¶
Apply reconciles the node's ufw rules with p and reloads ufw. Rules cargoship applied on an earlier run that p no longer contains are deleted.