mirror of
https://github.com/jmcorgan/fips.git
synced 2026-07-30 19:46:15 +00:00
Merge branch 'maint'
This commit is contained in:
@@ -1 +1 @@
|
|||||||
generated-configs
|
generated-configs*
|
||||||
|
|||||||
@@ -79,6 +79,12 @@ Docker service/container/hostname identifiers in this harness intentionally use
|
|||||||
`node-a` through `node-f`. For data-plane checks and operator examples, use the
|
`node-a` through `node-f`. For data-plane checks and operator examples, use the
|
||||||
explicit FIPS names such as `node-a.fips` and `node-d.fips`.
|
explicit FIPS names such as `node-a.fips` and `node-d.fips`.
|
||||||
|
|
||||||
|
The bridge network requests no subnet, so docker assigns one from its own
|
||||||
|
address pool and two concurrent runs of this harness never contend for a fixed
|
||||||
|
range. Consequently no node's IPv4 address is known before startup, and the
|
||||||
|
generated peer stanzas address each other by docker hostname (`host-a` …
|
||||||
|
`host-f`), resolved through the container's dnsmasq to docker's embedded DNS.
|
||||||
|
|
||||||
ACL paths are fixed in this branch:
|
ACL paths are fixed in this branch:
|
||||||
|
|
||||||
- `/etc/fips/peers.allow`
|
- `/etc/fips/peers.allow`
|
||||||
@@ -93,7 +99,9 @@ Mounted ACL files in this harness:
|
|||||||
|
|
||||||
Generated fixture location:
|
Generated fixture location:
|
||||||
|
|
||||||
- `testing/acl-allowlist/generated-configs/`
|
- `testing/acl-allowlist/generated-configs/`, or
|
||||||
|
`generated-configs<suffix>/` when `FIPS_CI_NAME_SUFFIX` is set, which is how
|
||||||
|
concurrent runs keep their fixtures apart
|
||||||
|
|
||||||
Inspect peer state:
|
Inspect peer state:
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
networks:
|
networks:
|
||||||
|
# No subnet is requested: docker assigns one from the daemon's address pool.
|
||||||
|
# A fixed request is honoured verbatim, so two runs asking for the same range
|
||||||
|
# collide on "Pool overlaps" — which is why the generated peer addresses are
|
||||||
|
# docker hostnames (host-a … host-f) rather than literal IPs. Docker's
|
||||||
|
# embedded DNS is per-network, so the same hostname in two concurrent runs
|
||||||
|
# resolves inside each run's own subnet.
|
||||||
|
#
|
||||||
|
# That removes one of the two obstacles to running this suite twice at once,
|
||||||
|
# not both. The compose project name is still fixed, so two runs that do not
|
||||||
|
# set COMPOSE_PROJECT_NAME share a project, and the second `up` recreates the
|
||||||
|
# first's containers while either `down` removes both. Nothing scopes it for
|
||||||
|
# this suite: it is run by hand only, so the caller has to. Do not read the
|
||||||
|
# floating subnet as making concurrent bare runs safe.
|
||||||
acl-net:
|
acl-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
labels:
|
labels:
|
||||||
- "com.corganlabs.fips-ci=1"
|
- "com.corganlabs.fips-ci=1"
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: 172.31.0.0/24
|
|
||||||
|
|
||||||
x-fips-common: &fips-common
|
x-fips-common: &fips-common
|
||||||
build:
|
build:
|
||||||
@@ -32,14 +42,13 @@ services:
|
|||||||
hostname: host-a
|
hostname: host-a
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-a/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-a/peers.allow:/etc/fips/peers.allow:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/peers.allow:/etc/fips/peers.allow:ro
|
||||||
- ./generated-configs/node-a/peers.deny:/etc/fips/peers.deny:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/peers.deny:/etc/fips/peers.deny:ro
|
||||||
- ./generated-configs/node-a/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-a/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
acl-net:
|
- acl-net
|
||||||
ipv4_address: 172.31.0.10
|
|
||||||
|
|
||||||
service-b:
|
service-b:
|
||||||
<<: *fips-common
|
<<: *fips-common
|
||||||
@@ -47,14 +56,13 @@ services:
|
|||||||
hostname: host-b
|
hostname: host-b
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-b/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-b/peers.allow:/etc/fips/peers.allow:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/peers.allow:/etc/fips/peers.allow:ro
|
||||||
- ./generated-configs/node-b/peers.deny:/etc/fips/peers.deny:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/peers.deny:/etc/fips/peers.deny:ro
|
||||||
- ./generated-configs/node-b/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-b/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
acl-net:
|
- acl-net
|
||||||
ipv4_address: 172.31.0.11
|
|
||||||
|
|
||||||
service-c:
|
service-c:
|
||||||
<<: *fips-common
|
<<: *fips-common
|
||||||
@@ -62,14 +70,13 @@ services:
|
|||||||
hostname: host-c
|
hostname: host-c
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-c/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-c/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-c/peers.allow:/etc/fips/peers.allow:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-c/peers.allow:/etc/fips/peers.allow:ro
|
||||||
- ./generated-configs/node-c/peers.deny:/etc/fips/peers.deny:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-c/peers.deny:/etc/fips/peers.deny:ro
|
||||||
- ./generated-configs/node-c/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-c/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-c/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-c/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
acl-net:
|
- acl-net
|
||||||
ipv4_address: 172.31.0.12
|
|
||||||
|
|
||||||
service-d:
|
service-d:
|
||||||
<<: *fips-common
|
<<: *fips-common
|
||||||
@@ -77,14 +84,13 @@ services:
|
|||||||
hostname: host-d
|
hostname: host-d
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-d/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-d/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-d/peers.allow:/etc/fips/peers.allow:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-d/peers.allow:/etc/fips/peers.allow:ro
|
||||||
- ./generated-configs/node-d/peers.deny:/etc/fips/peers.deny:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-d/peers.deny:/etc/fips/peers.deny:ro
|
||||||
- ./generated-configs/node-d/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-d/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-d/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-d/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
acl-net:
|
- acl-net
|
||||||
ipv4_address: 172.31.0.13
|
|
||||||
|
|
||||||
service-e:
|
service-e:
|
||||||
<<: *fips-common
|
<<: *fips-common
|
||||||
@@ -92,12 +98,11 @@ services:
|
|||||||
hostname: host-e
|
hostname: host-e
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-e/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-e/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-e/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-e/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-e/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-e/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
acl-net:
|
- acl-net
|
||||||
ipv4_address: 172.31.0.14
|
|
||||||
|
|
||||||
service-f:
|
service-f:
|
||||||
<<: *fips-common
|
<<: *fips-common
|
||||||
@@ -105,9 +110,8 @@ services:
|
|||||||
hostname: host-f
|
hostname: host-f
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-f/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-f/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-f/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-f/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-f/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-f/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
acl-net:
|
- acl-net
|
||||||
ipv4_address: 172.31.0.15
|
|
||||||
|
|||||||
@@ -3,7 +3,12 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
GENERATED_DIR="$SCRIPT_DIR/generated-configs"
|
|
||||||
|
# Scoped by the per-run suffix because this directory is wiped and rewritten
|
||||||
|
# below: two runs sharing one output directory would delete each other's
|
||||||
|
# fixtures out from under running containers. Unset (a bare hand run, or the
|
||||||
|
# GitHub-hosted path) it collapses to the historical "generated-configs".
|
||||||
|
GENERATED_DIR="$SCRIPT_DIR/generated-configs${FIPS_CI_NAME_SUFFIX:-}"
|
||||||
|
|
||||||
write_file() {
|
write_file() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
@@ -23,6 +28,10 @@ node-f npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Peers are addressed by the docker hostname the compose file assigns
|
||||||
|
# (host-a … host-f), not by IP. The network requests no subnet so that two
|
||||||
|
# concurrent runs cannot collide on one address range, which means no node's
|
||||||
|
# address is knowable before `docker compose up`.
|
||||||
echo "Generating ACL allowlist fixtures..."
|
echo "Generating ACL allowlist fixtures..."
|
||||||
rm -rf "$GENERATED_DIR"
|
rm -rf "$GENERATED_DIR"
|
||||||
|
|
||||||
@@ -48,31 +57,31 @@ peers:
|
|||||||
alias: "node-b"
|
alias: "node-b"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.11:2121"
|
addr: "host-b:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||||
alias: "node-c"
|
alias: "node-c"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.12:2121"
|
addr: "host-c:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||||
alias: "node-d"
|
alias: "node-d"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.13:2121"
|
addr: "host-d:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
- npub: "npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6"
|
- npub: "npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6"
|
||||||
alias: "node-e"
|
alias: "node-e"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.14:2121"
|
addr: "host-e:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
- npub: "npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c"
|
- npub: "npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c"
|
||||||
alias: "node-f"
|
alias: "node-f"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.15:2121"
|
addr: "host-f:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -113,19 +122,19 @@ peers:
|
|||||||
alias: "node-a"
|
alias: "node-a"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.10:2121"
|
addr: "host-a:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||||
alias: "node-c"
|
alias: "node-c"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.12:2121"
|
addr: "host-c:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||||
alias: "node-d"
|
alias: "node-d"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.13:2121"
|
addr: "host-d:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -166,7 +175,7 @@ peers:
|
|||||||
alias: "node-a"
|
alias: "node-a"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.10:2121"
|
addr: "host-a:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -209,7 +218,7 @@ peers:
|
|||||||
alias: "node-a"
|
alias: "node-a"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.10:2121"
|
addr: "host-a:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -252,7 +261,7 @@ peers:
|
|||||||
alias: "node-a"
|
alias: "node-a"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.10:2121"
|
addr: "host-a:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -282,7 +291,7 @@ peers:
|
|||||||
alias: "node-a"
|
alias: "node-a"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.31.0.10:2121"
|
addr: "host-a:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
+9
-4
@@ -398,15 +398,20 @@ ci_teardown() {
|
|||||||
--images "$CI_IMAGE_TEST $CI_IMAGE_APP" \
|
--images "$CI_IMAGE_TEST $CI_IMAGE_APP" \
|
||||||
--veth-suffixes "${_suffixes[*]}" >/dev/null || true
|
--veth-suffixes "${_suffixes[*]}" >/dev/null || true
|
||||||
|
|
||||||
# 3. The static suite's generated configs are per-run (a shared directory
|
# 3. The static and firewall suites' generated configs are per-run (a
|
||||||
# would let concurrent runs overwrite each other's node configs), so
|
# shared directory would let concurrent runs overwrite each other's node
|
||||||
# they are this run's to remove. Only on a green run: after a failure
|
# configs), so they are this run's to remove. Only on a green run: after
|
||||||
# they are the evidence of what the failing nodes were actually
|
# a failure they are the evidence of what the failing nodes were actually
|
||||||
# configured with. Guarded on a non-empty suffix too, since without one
|
# configured with. Guarded on a non-empty suffix too, since without one
|
||||||
# the path is the unscoped working directory a developer uses by hand,
|
# the path is the unscoped working directory a developer uses by hand,
|
||||||
# which is not ours to delete.
|
# which is not ours to delete.
|
||||||
|
#
|
||||||
|
# Every suite whose configs become per-run owes a line here. acl-allowlist
|
||||||
|
# also generates per-run now but is not in this runner's suite list, so
|
||||||
|
# this teardown never creates its directory and must not remove one.
|
||||||
if [[ $run_status -eq 0 && -n "${CI_RUN_NAME_SUFFIX:-}" ]]; then
|
if [[ $run_status -eq 0 && -n "${CI_RUN_NAME_SUFFIX:-}" ]]; then
|
||||||
rm -rf "$SCRIPT_DIR/static/generated-configs${CI_RUN_NAME_SUFFIX}"
|
rm -rf "$SCRIPT_DIR/static/generated-configs${CI_RUN_NAME_SUFFIX}"
|
||||||
|
rm -rf "$SCRIPT_DIR/firewall/generated-configs${CI_RUN_NAME_SUFFIX}"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
generated-configs
|
generated-configs*
|
||||||
|
|||||||
@@ -24,10 +24,18 @@ actively DROP'd by the fips chain (not silently unrouted).
|
|||||||
|
|
||||||
Two FIPS nodes peered over UDP on a Docker bridge network:
|
Two FIPS nodes peered over UDP on a Docker bridge network:
|
||||||
|
|
||||||
| Container | Hostname | docker IPv4 | Firewall |
|
| Container | Hostname | Firewall |
|
||||||
|-------------------------|----------|---------------|----------|
|
|-------------------------|----------|----------|
|
||||||
| `fips-fw-container-a` | `host-a` | 172.32.0.10 | none (probe) |
|
| `fips-fw-container-a` | `host-a` | none (probe) |
|
||||||
| `fips-fw-container-b` | `host-b` | 172.32.0.11 | `fips.nft` + drop-in |
|
| `fips-fw-container-b` | `host-b` | `fips.nft` + drop-in |
|
||||||
|
|
||||||
|
The bridge network requests no subnet, so docker assigns one from its own
|
||||||
|
address pool and two concurrent runs never contend for a fixed range. No
|
||||||
|
node's IPv4 address is therefore known before startup, and the generated peer
|
||||||
|
stanzas address each other by docker hostname, resolved through the
|
||||||
|
container's dnsmasq to docker's embedded DNS. The firewall assertions
|
||||||
|
themselves are unaffected: they run over the fips0 overlay, whose addresses
|
||||||
|
are derived from the node npubs.
|
||||||
|
|
||||||
`node-b` mounts the production `packaging/common/fips.nft` read-only at
|
`node-b` mounts the production `packaging/common/fips.nft` read-only at
|
||||||
`/etc/fips/fips.nft`, plus a drop-in at `/etc/fips/fips.d/services.nft`
|
`/etc/fips/fips.nft`, plus a drop-in at `/etc/fips/fips.d/services.nft`
|
||||||
@@ -108,4 +116,6 @@ docker compose -f testing/firewall/docker-compose.yml down
|
|||||||
|
|
||||||
## Generated fixture location
|
## Generated fixture location
|
||||||
|
|
||||||
`testing/firewall/generated-configs/` (gitignored).
|
`testing/firewall/generated-configs/` (gitignored), or
|
||||||
|
`generated-configs<suffix>/` when `FIPS_CI_NAME_SUFFIX` is set, which is how
|
||||||
|
concurrent runs keep their fixtures apart.
|
||||||
|
|||||||
@@ -1,11 +1,21 @@
|
|||||||
networks:
|
networks:
|
||||||
|
# No subnet is requested: docker assigns one from the daemon's address pool.
|
||||||
|
# A fixed request is honoured verbatim, so two runs asking for the same range
|
||||||
|
# collide on "Pool overlaps" — which is why the generated peer addresses are
|
||||||
|
# docker hostnames (host-a, host-b) rather than literal IPs. Docker's embedded
|
||||||
|
# DNS is per-network, so the same hostname in two concurrent runs resolves
|
||||||
|
# inside each run's own subnet.
|
||||||
|
#
|
||||||
|
# That removes one of the two obstacles to running this suite twice at once,
|
||||||
|
# not both. The compose project name is still fixed, so two runs that do not
|
||||||
|
# set COMPOSE_PROJECT_NAME share a project, and the second `up` recreates the
|
||||||
|
# first's containers while either `down` removes both. The local CI runner
|
||||||
|
# scopes it externally (run_firewall in ci-local.sh); a bare hand run does
|
||||||
|
# not. Do not read the floating subnet as making concurrent bare runs safe.
|
||||||
fw-net:
|
fw-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
labels:
|
labels:
|
||||||
- "com.corganlabs.fips-ci=1"
|
- "com.corganlabs.fips-ci=1"
|
||||||
ipam:
|
|
||||||
config:
|
|
||||||
- subnet: 172.32.0.0/24
|
|
||||||
|
|
||||||
x-fips-common: &fips-common
|
x-fips-common: &fips-common
|
||||||
build:
|
build:
|
||||||
@@ -31,12 +41,11 @@ services:
|
|||||||
hostname: host-a
|
hostname: host-a
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-a/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-a/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-a/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-a/fips.key:/etc/fips/fips.key:ro
|
||||||
networks:
|
networks:
|
||||||
fw-net:
|
- fw-net
|
||||||
ipv4_address: 172.32.0.10
|
|
||||||
|
|
||||||
service-b:
|
service-b:
|
||||||
<<: *fips-common
|
<<: *fips-common
|
||||||
@@ -44,11 +53,10 @@ services:
|
|||||||
hostname: host-b
|
hostname: host-b
|
||||||
volumes:
|
volumes:
|
||||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||||
- ./generated-configs/node-b/hosts:/etc/fips/hosts:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/hosts:/etc/fips/hosts:ro
|
||||||
- ./generated-configs/node-b/fips.yaml:/etc/fips/fips.yaml:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/fips.yaml:/etc/fips/fips.yaml:ro
|
||||||
- ./generated-configs/node-b/fips.key:/etc/fips/fips.key:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/fips.key:/etc/fips/fips.key:ro
|
||||||
- ../../packaging/common/fips.nft:/etc/fips/fips.nft:ro
|
- ../../packaging/common/fips.nft:/etc/fips/fips.nft:ro
|
||||||
- ./generated-configs/node-b/fips.d:/etc/fips/fips.d:ro
|
- ./generated-configs${FIPS_CI_NAME_SUFFIX:-}/node-b/fips.d:/etc/fips/fips.d:ro
|
||||||
networks:
|
networks:
|
||||||
fw-net:
|
- fw-net
|
||||||
ipv4_address: 172.32.0.11
|
|
||||||
|
|||||||
@@ -9,7 +9,12 @@
|
|||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
GENERATED_DIR="$SCRIPT_DIR/generated-configs"
|
|
||||||
|
# Scoped by the per-run suffix because this directory is wiped and rewritten
|
||||||
|
# below: two runs sharing one output directory would delete each other's
|
||||||
|
# fixtures out from under running containers. Unset (a bare hand run, or the
|
||||||
|
# GitHub-hosted path) it collapses to the historical "generated-configs".
|
||||||
|
GENERATED_DIR="$SCRIPT_DIR/generated-configs${FIPS_CI_NAME_SUFFIX:-}"
|
||||||
|
|
||||||
# Deterministic test identities (mirrors the acl-allowlist style).
|
# Deterministic test identities (mirrors the acl-allowlist style).
|
||||||
NPUB_A="npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
NPUB_A="npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||||
@@ -32,6 +37,10 @@ node-b $NPUB_B
|
|||||||
EOF
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Peers are addressed by the docker hostname the compose file assigns (host-a,
|
||||||
|
# host-b), not by IP. The network requests no subnet so that two concurrent
|
||||||
|
# runs cannot collide on one address range, which means neither node's address
|
||||||
|
# is knowable before `docker compose up`.
|
||||||
echo "Generating firewall fixtures..."
|
echo "Generating firewall fixtures..."
|
||||||
rm -rf "$GENERATED_DIR"
|
rm -rf "$GENERATED_DIR"
|
||||||
|
|
||||||
@@ -58,7 +67,7 @@ peers:
|
|||||||
alias: "node-b"
|
alias: "node-b"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.32.0.11:2121"
|
addr: "host-b:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
@@ -89,7 +98,7 @@ peers:
|
|||||||
alias: "node-a"
|
alias: "node-a"
|
||||||
addresses:
|
addresses:
|
||||||
- transport: udp
|
- transport: udp
|
||||||
addr: "172.32.0.10:2121"
|
addr: "host-a:2121"
|
||||||
connect_policy: auto_connect
|
connect_policy: auto_connect
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
|
|||||||
@@ -81,21 +81,46 @@ wait_for_fips0() {
|
|||||||
fail "$container fips0 did not come up within ${timeout}s"
|
fail "$container fips0 did not come up within ${timeout}s"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Connected-peer count for a container, or the empty string if it did not
|
||||||
|
# answer.
|
||||||
|
#
|
||||||
|
# Empty is deliberately distinct from a real 0. An `|| echo 0` fallback here
|
||||||
|
# would make "the container is unreachable, or its daemon never came up" and
|
||||||
|
# "the daemon answered, and the answer was zero" the same value, so any caller
|
||||||
|
# expecting zero would be satisfied on the first iteration without the
|
||||||
|
# property it is checking ever being observed. No caller in this file expects
|
||||||
|
# zero today, which is exactly why the fallback has to go now rather than when
|
||||||
|
# one is added. Same shape as the acl-allowlist suite's read_connected_peers.
|
||||||
|
read_connected_peers() {
|
||||||
|
local container="$1"
|
||||||
|
docker exec "$container" fipsctl show peers 2>/dev/null \
|
||||||
|
| python3 -c 'import json,sys; data=json.load(sys.stdin); print(sum(1 for p in data.get("peers", []) if p.get("connectivity") == "connected"))' 2>/dev/null \
|
||||||
|
|| true
|
||||||
|
}
|
||||||
|
|
||||||
# Wait for the peer count on a container to reach the expected value.
|
# Wait for the peer count on a container to reach the expected value.
|
||||||
wait_for_peers_exact() {
|
wait_for_peers_exact() {
|
||||||
local container="$1"
|
local container="$1"
|
||||||
local expected_count="$2"
|
local expected_count="$2"
|
||||||
local timeout="${3:-30}"
|
local timeout="${3:-30}"
|
||||||
|
|
||||||
|
local count="" answered=false
|
||||||
for _ in $(seq 1 "$timeout"); do
|
for _ in $(seq 1 "$timeout"); do
|
||||||
local count
|
count=$(read_connected_peers "$container")
|
||||||
count=$(docker exec "$container" fipsctl show peers 2>/dev/null \
|
if [ -n "$count" ]; then
|
||||||
| python3 -c 'import json,sys; data=json.load(sys.stdin); print(sum(1 for p in data.get("peers", []) if p.get("connectivity") == "connected"))' 2>/dev/null || echo 0)
|
answered=true
|
||||||
if [ "$count" -eq "$expected_count" ]; then
|
if [ "$count" -eq "$expected_count" ]; then
|
||||||
return 0
|
return 0
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
sleep 1
|
sleep 1
|
||||||
done
|
done
|
||||||
fail "$container did not reach $expected_count connected peers in ${timeout}s"
|
|
||||||
|
if [ "$answered" = false ]; then
|
||||||
|
fail "$container never answered a peer query in ${timeout}s, so a count of $expected_count was never actually observed"
|
||||||
|
fi
|
||||||
|
docker exec "$container" fipsctl show peers >&2 || true
|
||||||
|
fail "$container did not reach $expected_count connected peers in ${timeout}s (last answer: $count)"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Resolve `<npub>.fips` inside a container and print the AAAA answer.
|
# Resolve `<npub>.fips` inside a container and print the AAAA answer.
|
||||||
@@ -230,14 +255,20 @@ log "Case (b): node-b initiates outbound TCP, expects reply via conntrack"
|
|||||||
# node-b → node-a:8000 on the fips overlay. node-a has http.server on
|
# node-b → node-a:8000 on the fips overlay. node-a has http.server on
|
||||||
# [::]:8000 and is NOT firewalled, so this is purely a test of node-b's
|
# [::]:8000 and is NOT firewalled, so this is purely a test of node-b's
|
||||||
# outbound + ct state established,related path on the way back.
|
# outbound + ct state established,related path on the way back.
|
||||||
|
#
|
||||||
|
# mktemp rather than a fixed /tmp name: two concurrent runs of this suite
|
||||||
|
# would otherwise share one host file, and either one's `rm` between the
|
||||||
|
# other's write and read leaves an empty read that fails the http_code check
|
||||||
|
# for a reason that has nothing to do with the firewall.
|
||||||
|
CURL_OUT="$(mktemp)"
|
||||||
set +e
|
set +e
|
||||||
docker exec "$CONTAINER_B" curl -6 --silent --max-time 5 \
|
docker exec "$CONTAINER_B" curl -6 --silent --max-time 5 \
|
||||||
--output /dev/null --write-out '%{http_code}' \
|
--output /dev/null --write-out '%{http_code}' \
|
||||||
"http://[${ADDR_A}]:${OUTBOUND_TARGET_PORT}/" >/tmp/fw_b_rc 2>/dev/null
|
"http://[${ADDR_A}]:${OUTBOUND_TARGET_PORT}/" >"$CURL_OUT" 2>/dev/null
|
||||||
RC=$?
|
RC=$?
|
||||||
set -e
|
set -e
|
||||||
HTTP_CODE="$(cat /tmp/fw_b_rc 2>/dev/null || true)"
|
HTTP_CODE="$(cat "$CURL_OUT" 2>/dev/null || true)"
|
||||||
rm -f /tmp/fw_b_rc
|
rm -f "$CURL_OUT"
|
||||||
if [ "$RC" -ne 0 ]; then
|
if [ "$RC" -ne 0 ]; then
|
||||||
fail "(b) outbound from node-b failed (curl rc=$RC, http=$HTTP_CODE) — conntrack reply path broken"
|
fail "(b) outbound from node-b failed (curl rc=$RC, http=$HTTP_CODE) — conntrack reply path broken"
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -180,6 +180,47 @@ check "case4: returns 0 with 4 args" "$c4_rc_ok" "rc=$rc"
|
|||||||
c4_hold_ok=1; echo "$out" | grep -q "$HOLD_MSG" && c4_hold_ok=0
|
c4_hold_ok=1; echo "$out" | grep -q "$HOLD_MSG" && c4_hold_ok=0
|
||||||
check "case4: default slack triggered near-converged hold" "$c4_hold_ok"
|
check "case4: default slack triggered near-converged hold" "$c4_hold_ok"
|
||||||
|
|
||||||
|
# --- Case 5: wait_for_peers refuses a floor of zero -------------------
|
||||||
|
#
|
||||||
|
# The reader inside wait_for_peers falls back to 0 when a container does
|
||||||
|
# not answer. That is safe against a floor of 1 or more, where 0 reads as
|
||||||
|
# "not converged yet", and unsafe against a floor of 0, where the first
|
||||||
|
# read from a dead container satisfies the wait immediately. This case is
|
||||||
|
# the break-what-it-guards check for the rejection: drive the guard with a
|
||||||
|
# floor of 0 and confirm it refuses, then drive the same dead container
|
||||||
|
# with a floor of 1 and confirm the refusal is specific to the dangerous
|
||||||
|
# input rather than a blanket failure.
|
||||||
|
#
|
||||||
|
# `docker` is stubbed to fail, which is what an unreachable container looks
|
||||||
|
# like to this reader, so no container or network is involved and the suite
|
||||||
|
# stays hermetic.
|
||||||
|
echo
|
||||||
|
echo "== Case 5: wait_for_peers refuses a zero floor =="
|
||||||
|
docker() { return 1; }
|
||||||
|
|
||||||
|
out=$(wait_for_peers stub-container 0 2 2>&1); rc=$?
|
||||||
|
echo "$out"
|
||||||
|
c5_reject_ok=1; [ "$rc" -eq 2 ] && c5_reject_ok=0
|
||||||
|
check "case5: floor of 0 is refused" "$c5_reject_ok" "rc=$rc"
|
||||||
|
c5_msg_ok=1; echo "$out" | grep -q "refusing a minimum" && c5_msg_ok=0
|
||||||
|
check "case5: refusal names the reason" "$c5_msg_ok"
|
||||||
|
# The pre-guard behaviour, asserted so a regression is visible rather than
|
||||||
|
# quiet: without the rejection this returned 0 on its first iteration
|
||||||
|
# against a container that never answered.
|
||||||
|
c5_notpass_ok=1; [ "$rc" -ne 0 ] && c5_notpass_ok=0
|
||||||
|
check "case5: floor of 0 does not report success" "$c5_notpass_ok" "rc=$rc"
|
||||||
|
|
||||||
|
start=$SECONDS
|
||||||
|
out=$(wait_for_peers stub-container 1 2 2>&1); rc=$?
|
||||||
|
elapsed=$((SECONDS - start))
|
||||||
|
echo "$out"
|
||||||
|
c5_floor1_ok=1; [ "$rc" -eq 1 ] && c5_floor1_ok=0
|
||||||
|
check "case5: floor of 1 times out rather than being refused" "$c5_floor1_ok" "rc=$rc"
|
||||||
|
c5_polled_ok=1; [ "$elapsed" -ge 2 ] && c5_polled_ok=0
|
||||||
|
check "case5: floor of 1 polled its full budget" "$c5_polled_ok" "elapsed=${elapsed}s >= 2s"
|
||||||
|
|
||||||
|
unset -f docker
|
||||||
|
|
||||||
# --- Summary ----------------------------------------------------------
|
# --- Summary ----------------------------------------------------------
|
||||||
echo
|
echo
|
||||||
echo "=============================================="
|
echo "=============================================="
|
||||||
|
|||||||
@@ -1,44 +1,47 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
# Shared convergence wait helpers for FIPS integration tests.
|
# Shared convergence wait helpers for FIPS integration tests.
|
||||||
#
|
#
|
||||||
# Source this file to get wait_for_links(), wait_for_peers(), and
|
# Source this file to get wait_for_peers() and wait_until_connected().
|
||||||
# wait_until_connected().
|
|
||||||
#
|
#
|
||||||
# Usage:
|
# Usage:
|
||||||
# source "$(dirname "$0")/../../lib/wait-converge.sh"
|
# source "$(dirname "$0")/../../lib/wait-converge.sh"
|
||||||
# wait_for_links <container> <min_links> [timeout_secs]
|
|
||||||
# wait_for_peers <container> <min_peers> [timeout_secs]
|
# wait_for_peers <container> <min_peers> [timeout_secs]
|
||||||
# wait_until_connected <ping_fn> <max_secs> <stall_secs> [poll_secs] \
|
# wait_until_connected <ping_fn> <max_secs> <stall_secs> [poll_secs] \
|
||||||
# [near_converged_slack]
|
# [near_converged_slack]
|
||||||
|
#
|
||||||
# Wait until a container has at least min_links active links.
|
# There was a wait_for_links() here. It was removed rather than kept for
|
||||||
# Returns 0 on success, 1 on timeout.
|
# symmetry: it had no caller anywhere in the tree on any branch, and its
|
||||||
wait_for_links() {
|
# reader carried the same failure-to-zero fallback wait_for_peers does. An
|
||||||
local container="$1"
|
# uncalled helper cannot be wrong today, so the risk was that the first
|
||||||
local min_links="$2"
|
# caller to appear would inherit the hazard below without the reasoning
|
||||||
local timeout="${3:-30}"
|
# that goes with it. `git log` has the implementation if one is needed.
|
||||||
|
|
||||||
for i in $(seq 1 "$timeout"); do
|
|
||||||
local count
|
|
||||||
count=$(docker exec "$container" fipsctl show links 2>/dev/null \
|
|
||||||
| python3 -c "import sys,json; print(len(json.load(sys.stdin).get('links',[])))" 2>/dev/null || echo 0)
|
|
||||||
if [ "$count" -ge "$min_links" ]; then
|
|
||||||
echo " $container: $count link(s) after ${i}s"
|
|
||||||
return 0
|
|
||||||
fi
|
|
||||||
sleep 1
|
|
||||||
done
|
|
||||||
echo " $container: TIMEOUT waiting for $min_links link(s) after ${timeout}s"
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Wait until a container has at least min_peers connected peers.
|
# Wait until a container has at least min_peers connected peers.
|
||||||
# Returns 0 on success, 1 on timeout.
|
# Returns 0 on success, 1 on timeout.
|
||||||
|
#
|
||||||
|
# The read below falls back to 0 when the container does not answer, which
|
||||||
|
# is safe ONLY because this is a floor: a fallback of 0 reads as "not
|
||||||
|
# converged yet", the loop keeps polling, and a container that never answers
|
||||||
|
# times out and returns 1. That safety is a property of the comparison, not
|
||||||
|
# of the reader.
|
||||||
|
#
|
||||||
|
# A minimum of 0 inverts it. `[ 0 -ge 0 ]` is true, so the first read from a
|
||||||
|
# dead container would satisfy the wait immediately and the caller would
|
||||||
|
# proceed as though convergence had been observed. No caller passes 0, and
|
||||||
|
# rejecting it here means none can start to — which is cheaper than auditing
|
||||||
|
# every future caller, and is why this is a hard error rather than a warning.
|
||||||
|
# A caller that genuinely wants to assert "exactly zero peers" needs a reader
|
||||||
|
# that distinguishes no-answer from zero, not this floor.
|
||||||
wait_for_peers() {
|
wait_for_peers() {
|
||||||
local container="$1"
|
local container="$1"
|
||||||
local min_peers="$2"
|
local min_peers="$2"
|
||||||
local timeout="${3:-30}"
|
local timeout="${3:-30}"
|
||||||
|
|
||||||
|
if [ "$min_peers" -lt 1 ]; then
|
||||||
|
echo " wait_for_peers: refusing a minimum of $min_peers for $container — a floor of 0 is satisfied by a container that never answered" >&2
|
||||||
|
return 2
|
||||||
|
fi
|
||||||
|
|
||||||
for i in $(seq 1 "$timeout"); do
|
for i in $(seq 1 "$timeout"); do
|
||||||
local count
|
local count
|
||||||
count=$(docker exec "$container" fipsctl show peers 2>/dev/null \
|
count=$(docker exec "$container" fipsctl show peers 2>/dev/null \
|
||||||
|
|||||||
Reference in New Issue
Block a user