mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-06 10:59:59 +00:00
In swarm mode `docker stack deploy` ignores the host IP of a short port mapping, so every `127.0.0.1:<port>:<port>` that container_ports renders for a `ports.local` entry is published by the ingress routing mesh on every node, via docker-proxy on 0.0.0.0 and [::] plus DNAT in nat PREROUTING. Databases, LDAP and application backends answered from outside the cluster. svc-swarm-node now loads an nftables table through svc-net-firewall. Its prerouting filter chain (priority mangle, ahead of Docker's dstnat) drops packets addressed to the node for any ingress-published port that no application declares under `ports.public`. Loopback, docker0, docker_gwbridge, the user bridges and the swarm nodes' own addresses stay admitted. Host-side provisioning over 127.0.0.1, containers that reach a host port through host-gateway, and node-to-node consumers such as the registry-cache mirror keep working. Both sets are read live on the manager (`docker service inspect` for `.Endpoint.Ports`, `docker node inspect` for `.Status.Addr`), so ephemeral ingress ports and nodes outside `--limit` are covered. The table is loaded on a node before it joins, reloaded on every node after the join, and reloaded after each stack deploy by the sys-svc-compose swarm handler. Refs #1089 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
33 lines
1.1 KiB
Django/Jinja
33 lines
1.1 KiB
Django/Jinja
table inet {{ FIREWALL_TABLE }}
|
|
delete table inet {{ FIREWALL_TABLE }}
|
|
table inet {{ FIREWALL_TABLE }} {
|
|
{% for protocol in ['tcp', 'udp'] %}
|
|
{% set _ports = SWARM_INGRESS_GUARDED_PORTS | selectattr('protocol', 'equalto', protocol) | map(attribute='port') | list %}
|
|
set guarded_{{ protocol }} {
|
|
type inet_service
|
|
{% if _ports | length > 0 %}
|
|
elements = { {{ _ports | join(', ') }} }
|
|
{% endif %}
|
|
}
|
|
|
|
{% endfor %}
|
|
{% for family in ['ipv4', 'ipv6'] %}
|
|
{% set _addresses = SWARM_INGRESS_PEER_ADDRESSES[family] %}
|
|
set peers_{{ family }} {
|
|
type {{ family }}_addr
|
|
{% if _addresses | length > 0 %}
|
|
elements = { {{ _addresses | join(', ') }} }
|
|
{% endif %}
|
|
}
|
|
|
|
{% endfor %}
|
|
chain prerouting {
|
|
type filter hook prerouting priority -150; policy accept;
|
|
{% for family, match in [('ipv4', 'ip'), ('ipv6', 'ip6')] %}
|
|
{% for protocol in ['tcp', 'udp'] %}
|
|
iifname != { "lo", "docker0", "docker_gwbridge" } iifname != "br-*" {{ match }} saddr != @peers_{{ family }} fib daddr type local {{ protocol }} dport @guarded_{{ protocol }} drop
|
|
{% endfor %}
|
|
{% endfor %}
|
|
}
|
|
}
|