Files
computer-playbook/group_vars/all/18_swarm.yml
kevinveenbirkenbachandClaude Opus 5.5 788eabffbf fix(svc-swarm-node): drop outside traffic to non-public routing-mesh ports
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>
2026-10-21 20:56:29 +02:00

46 lines
1.5 KiB
YAML

---
DEPLOYMENT_MODE: "{{ 'swarm' if (groups['svc-swarm-node'] | default([]) | length) > 1 else 'compose' }}"
SWARM_CLUSTER_AVAILABLE: "{{ (groups['svc-swarm-node'] | default([]) | length) > 0 }}"
# Exception: a role default would be invisible to the by-path include in
# roles/svc-db-openldap/tasks/01_docker.yml, which reaches into
# roles/sys-svc-compose/tasks/utils/post_deploy.yml after the role has finished.
swarm_stack_converge_retries: 600
IS_STACK_HOST: >-
{{ DEPLOYMENT_MODE == 'compose'
or inventory_hostname in (groups['svc-swarm-manager'] | default([])) }}
IS_BACKUP_HOST: >-
{{ group_names | select('match', '^svc-bkp-') | list | length > 0 }}
STACK_HOST: >-
{{ (groups['svc-swarm-manager'] | default([]))[0] | default(inventory_hostname) }}
swarm_stack_fatal_grace: 180
SWARM_INGRESS_GUARDED: "{{ swarm_ingress_endpoints.stdout_lines | swarm_ingress_published | swarm_ingress_guarded(lookup('applications') | swarm_public_ports) }}"
SWARM_INGRESS_PEERS: "{{ swarm_ingress_nodes.stdout_lines | swarm_node_addresses }}"
swarm:
manager:
advertise_addr: >-
{{ lookup('mesh_address', STACK_HOST) if (WIREGUARD_ENABLED | bool) else '' }}
network:
encryption: true
registry:
host: "{{ (groups['svc-swarm-manager'] | default([]))[0] | default('') }}"
port: 5000
SWARM_REGISTRY_PREFIX: >-
{{
(swarm.registry.host ~ ':' ~ swarm.registry.port ~ '/')
if (
DEPLOYMENT_MODE == 'swarm'
and (swarm.registry.host | length > 0)
)
else ''
}}