mirror of
https://github.com/jmcorgan/fips.git
synced 2026-08-09 00:04:54 +00:00
Merge branch 'master' into next
Integrates PR #50 (peer ACL enforcement) into the XX handshake architecture. ACL enforcement points adapted for XX's deferred identity learning: InboundHandshake check moves from handle_msg1 to handle_msg3 (responder learns initiator identity), OutboundHandshake check remains in handle_msg2 (initiator learns responder identity). Borrow scopes restructured to release connection borrows before authorize_peer calls.
This commit is contained in:
@@ -0,0 +1 @@
|
||||
generated-configs
|
||||
@@ -0,0 +1,164 @@
|
||||
# ACL Allowlist Test
|
||||
|
||||
Six Docker nodes use per-node ACL files mounted at the hardcoded runtime paths:
|
||||
|
||||
- `node-a` and `node-b` carry the insider allowlist (`node-a`, `node-b`, `node-e`, `node-f`)
|
||||
- `node-c` and `node-d` each carry a broad allowlist containing every node alias
|
||||
- `node-e` and `node-f` do not mount any ACL files locally
|
||||
- every node gets a generated `/etc/fips/hosts` with aliases for `node-a` through `node-f`
|
||||
|
||||
This lets us test three different node behaviors at once:
|
||||
|
||||
- insiders (`a`, `b`) explicitly allow `a`, `b`, `e`, and `f`
|
||||
- outsiders (`c`, `d`) allow everyone locally, but still cannot join because insiders reject them
|
||||
- allowed remotes (`e`, `f`) rely on the insider ACLs and do not need local ACL files
|
||||
|
||||
## Test Identities
|
||||
|
||||
Allowed:
|
||||
|
||||
- `node-a`
|
||||
- `npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m`
|
||||
- `0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20`
|
||||
- `node-b`
|
||||
- `npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le`
|
||||
- `b102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fb0`
|
||||
|
||||
Denied:
|
||||
|
||||
- `node-c`
|
||||
- `npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6`
|
||||
- `c102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc0`
|
||||
- `node-d`
|
||||
- `npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl`
|
||||
- `d102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fd0`
|
||||
|
||||
Additional allowed:
|
||||
|
||||
- `node-e`
|
||||
- `npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6`
|
||||
- `nsec1egyrmekfw3u4l88v8zhrak9uht503s2kvn9v49tqgp6c5l2yuxgsv386l0`
|
||||
- `node-f`
|
||||
- `npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c`
|
||||
- `nsec1afh3nysthqh47awpdewcw59wvvp499f8dvlyclmnv4gvpxdk56dsa6eqsn`
|
||||
|
||||
The generated `fips.key` fixtures use a mix of bare hex and `nsec1...` values.
|
||||
FIPS accepts either format in key files.
|
||||
|
||||
## Run
|
||||
|
||||
Build the Linux binaries and test image:
|
||||
|
||||
```bash
|
||||
./testing/scripts/build.sh --no-docker
|
||||
```
|
||||
|
||||
Start the ACL test mesh:
|
||||
|
||||
```bash
|
||||
./testing/acl-allowlist/generate-configs.sh
|
||||
docker compose -f testing/acl-allowlist/docker-compose.yml up -d --build
|
||||
```
|
||||
|
||||
Or run the full integration check:
|
||||
|
||||
```bash
|
||||
./testing/acl-allowlist/test.sh
|
||||
```
|
||||
|
||||
`test.sh` regenerates the ACL fixtures automatically before starting Docker.
|
||||
The generated ACL files use alias names, and the generated hosts file makes
|
||||
those aliases resolvable at runtime.
|
||||
|
||||
The ACL harness pins the expected test entrypoint explicitly so it does not
|
||||
accidentally reuse an older `fips-test:latest` image with a different startup
|
||||
script.
|
||||
|
||||
Docker service/container/hostname identifiers in this harness intentionally use
|
||||
`service-*`, `fips-acl-container-*`, and `host-*` names so they do not collide with the logical FIPS aliases
|
||||
`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`.
|
||||
|
||||
ACL paths are fixed in this branch:
|
||||
|
||||
- `/etc/fips/peers.allow`
|
||||
- `/etc/fips/peers.deny`
|
||||
|
||||
Mounted ACL files in this harness:
|
||||
|
||||
- `node-a` and `node-b`: insider allowlist plus `ALL` deny fallback
|
||||
- `node-c` and `node-d`: broad local allowlist used by outsider nodes trying to blend in
|
||||
- `node-e` and `node-f`: no ACL files mounted
|
||||
- all nodes: `/etc/fips/hosts` aliases for `node-a` through `node-f`
|
||||
|
||||
Generated fixture location:
|
||||
|
||||
- `testing/acl-allowlist/generated-configs/`
|
||||
|
||||
Inspect peer state:
|
||||
|
||||
```bash
|
||||
docker exec fips-acl-container-a fipsctl show peers
|
||||
docker exec fips-acl-container-b fipsctl show peers
|
||||
docker exec fips-acl-container-c fipsctl show peers
|
||||
docker exec fips-acl-container-d fipsctl show peers
|
||||
docker exec fips-acl-container-e fipsctl show peers
|
||||
docker exec fips-acl-container-f fipsctl show peers
|
||||
```
|
||||
|
||||
Inspect the loaded ACL state directly:
|
||||
|
||||
```bash
|
||||
docker exec fips-acl-container-a fipsctl acl show
|
||||
```
|
||||
|
||||
Use explicit `.fips` names when checking reachability through the FIPS overlay:
|
||||
|
||||
```bash
|
||||
docker exec fips-acl-container-a ping node-d.fips
|
||||
docker exec fips-acl-container-a ping npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl.fips
|
||||
```
|
||||
|
||||
The output shows both the original ACL file entries and the resolved
|
||||
effective npub entries.
|
||||
|
||||
Expected:
|
||||
|
||||
- `node-a` sees `node-b`, `node-e`, and `node-f`
|
||||
- `node-b` sees `node-a`
|
||||
- `node-c` sees no peers
|
||||
- `node-d` sees no peers
|
||||
- `node-e` sees `node-a`
|
||||
- `node-f` sees `node-a`
|
||||
|
||||
Visible rejection logs:
|
||||
|
||||
```bash
|
||||
docker compose -f testing/acl-allowlist/docker-compose.yml logs -f service-a service-b service-c service-d service-e service-f
|
||||
```
|
||||
|
||||
On startup, `node-c` and `node-d` immediately try their configured outbound
|
||||
static connection to `node-a`. Their own ACLs permit that attempt, but the
|
||||
insider ACL on `node-a` still rejects both peers. Because `node-a` also has
|
||||
static peer stanzas for `node-c` and `node-d`, you may see both
|
||||
`outbound_connect` and `inbound_handshake` rejection messages during startup.
|
||||
The outsider-initiated path emits messages like:
|
||||
|
||||
```text
|
||||
Rejected peer by ACL ... context=inbound_handshake decision=denylist match
|
||||
```
|
||||
|
||||
Those messages are now emitted at debug level. This harness enables
|
||||
`RUST_LOG=info,fips::node=debug` so the ACL rejection details stay visible in
|
||||
test logs, and operators can temporarily raise log level the same way when
|
||||
diagnosing ACL issues locally.
|
||||
|
||||
A later `ping6` from `node-c.fips` does not emit a new `inbound_handshake` message.
|
||||
The ping uses the data-plane session path, and since no peer session to
|
||||
`node-a.fips` was established, it just times out.
|
||||
|
||||
Stop and clean up:
|
||||
|
||||
```bash
|
||||
docker compose -f testing/acl-allowlist/docker-compose.yml down
|
||||
```
|
||||
@@ -0,0 +1,111 @@
|
||||
networks:
|
||||
acl-net:
|
||||
driver: bridge
|
||||
ipam:
|
||||
config:
|
||||
- subnet: 172.31.0.0/24
|
||||
|
||||
x-fips-common: &fips-common
|
||||
build:
|
||||
context: ../docker
|
||||
image: fips-test:latest
|
||||
entrypoint: ["/usr/local/bin/entrypoint.sh"]
|
||||
cap_add:
|
||||
- NET_ADMIN
|
||||
devices:
|
||||
- /dev/net/tun:/dev/net/tun
|
||||
sysctls:
|
||||
- net.ipv6.conf.all.disable_ipv6=0
|
||||
restart: "no"
|
||||
environment:
|
||||
- FIPS_TEST_MODE=default
|
||||
- RUST_LOG=info,fips::node=debug
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
|
||||
services:
|
||||
service-a:
|
||||
<<: *fips-common
|
||||
container_name: fips-acl-container-a
|
||||
hostname: host-a
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/node-a/hosts:/etc/fips/hosts:ro
|
||||
- ./generated-configs/node-a/peers.allow:/etc/fips/peers.allow:ro
|
||||
- ./generated-configs/node-a/peers.deny:/etc/fips/peers.deny:ro
|
||||
- ./generated-configs/node-a/fips.yaml:/etc/fips/fips.yaml:ro
|
||||
- ./generated-configs/node-a/fips.key:/etc/fips/fips.key:ro
|
||||
networks:
|
||||
acl-net:
|
||||
ipv4_address: 172.31.0.10
|
||||
|
||||
service-b:
|
||||
<<: *fips-common
|
||||
container_name: fips-acl-container-b
|
||||
hostname: host-b
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/node-b/hosts:/etc/fips/hosts:ro
|
||||
- ./generated-configs/node-b/peers.allow:/etc/fips/peers.allow:ro
|
||||
- ./generated-configs/node-b/peers.deny:/etc/fips/peers.deny:ro
|
||||
- ./generated-configs/node-b/fips.yaml:/etc/fips/fips.yaml:ro
|
||||
- ./generated-configs/node-b/fips.key:/etc/fips/fips.key:ro
|
||||
networks:
|
||||
acl-net:
|
||||
ipv4_address: 172.31.0.11
|
||||
|
||||
service-c:
|
||||
<<: *fips-common
|
||||
container_name: fips-acl-container-c
|
||||
hostname: host-c
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/node-c/hosts:/etc/fips/hosts:ro
|
||||
- ./generated-configs/node-c/peers.allow:/etc/fips/peers.allow:ro
|
||||
- ./generated-configs/node-c/peers.deny:/etc/fips/peers.deny:ro
|
||||
- ./generated-configs/node-c/fips.yaml:/etc/fips/fips.yaml:ro
|
||||
- ./generated-configs/node-c/fips.key:/etc/fips/fips.key:ro
|
||||
networks:
|
||||
acl-net:
|
||||
ipv4_address: 172.31.0.12
|
||||
|
||||
service-d:
|
||||
<<: *fips-common
|
||||
container_name: fips-acl-container-d
|
||||
hostname: host-d
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/node-d/hosts:/etc/fips/hosts:ro
|
||||
- ./generated-configs/node-d/peers.allow:/etc/fips/peers.allow:ro
|
||||
- ./generated-configs/node-d/peers.deny:/etc/fips/peers.deny:ro
|
||||
- ./generated-configs/node-d/fips.yaml:/etc/fips/fips.yaml:ro
|
||||
- ./generated-configs/node-d/fips.key:/etc/fips/fips.key:ro
|
||||
networks:
|
||||
acl-net:
|
||||
ipv4_address: 172.31.0.13
|
||||
|
||||
service-e:
|
||||
<<: *fips-common
|
||||
container_name: fips-acl-container-e
|
||||
hostname: host-e
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/node-e/hosts:/etc/fips/hosts:ro
|
||||
- ./generated-configs/node-e/fips.yaml:/etc/fips/fips.yaml:ro
|
||||
- ./generated-configs/node-e/fips.key:/etc/fips/fips.key:ro
|
||||
networks:
|
||||
acl-net:
|
||||
ipv4_address: 172.31.0.14
|
||||
|
||||
service-f:
|
||||
<<: *fips-common
|
||||
container_name: fips-acl-container-f
|
||||
hostname: host-f
|
||||
volumes:
|
||||
- ../docker/resolv.conf:/etc/resolv.conf:ro
|
||||
- ./generated-configs/node-f/hosts:/etc/fips/hosts:ro
|
||||
- ./generated-configs/node-f/fips.yaml:/etc/fips/fips.yaml:ro
|
||||
- ./generated-configs/node-f/fips.key:/etc/fips/fips.key:ro
|
||||
networks:
|
||||
acl-net:
|
||||
ipv4_address: 172.31.0.15
|
||||
Executable
+306
@@ -0,0 +1,306 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
GENERATED_DIR="$SCRIPT_DIR/generated-configs"
|
||||
|
||||
write_file() {
|
||||
local path="$1"
|
||||
mkdir -p "$(dirname "$path")"
|
||||
cat > "$path"
|
||||
}
|
||||
|
||||
write_hosts_file() {
|
||||
local node="$1"
|
||||
write_file "$GENERATED_DIR/$node/hosts" <<'EOF'
|
||||
node-a npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m
|
||||
node-b npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le
|
||||
node-c npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6
|
||||
node-d npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl
|
||||
node-e npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6
|
||||
node-f npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c
|
||||
EOF
|
||||
}
|
||||
|
||||
echo "Generating ACL allowlist fixtures..."
|
||||
rm -rf "$GENERATED_DIR"
|
||||
|
||||
write_file "$GENERATED_DIR/node-a/fips.yaml" <<'EOF'
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
|
||||
peers:
|
||||
- npub: "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le"
|
||||
alias: "node-b"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.11:2121"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.12:2121"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.13:2121"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6"
|
||||
alias: "node-e"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.14:2121"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c"
|
||||
alias: "node-f"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.15:2121"
|
||||
connect_policy: auto_connect
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-a/fips.key" <<'EOF'
|
||||
0102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-a/peers.allow" <<'EOF'
|
||||
node-a
|
||||
node-b
|
||||
node-e
|
||||
node-f
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-a/peers.deny" <<'EOF'
|
||||
ALL
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-b/fips.yaml" <<'EOF'
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.10:2121"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
alias: "node-c"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.12:2121"
|
||||
connect_policy: auto_connect
|
||||
- npub: "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
alias: "node-d"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.13:2121"
|
||||
connect_policy: auto_connect
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-b/fips.key" <<'EOF'
|
||||
b102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fb0
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-b/peers.allow" <<'EOF'
|
||||
node-a
|
||||
node-b
|
||||
node-e
|
||||
node-f
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-b/peers.deny" <<'EOF'
|
||||
ALL
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-c/fips.yaml" <<'EOF'
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.10:2121"
|
||||
connect_policy: auto_connect
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-c/fips.key" <<'EOF'
|
||||
c102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fc0
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-c/peers.allow" <<'EOF'
|
||||
node-a
|
||||
node-b
|
||||
node-c
|
||||
node-d
|
||||
node-e
|
||||
node-f
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-c/peers.deny" <<'EOF'
|
||||
# Intentionally empty.
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-d/fips.yaml" <<'EOF'
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.10:2121"
|
||||
connect_policy: auto_connect
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-d/fips.key" <<'EOF'
|
||||
d102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1fd0
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-d/peers.allow" <<'EOF'
|
||||
node-a
|
||||
node-b
|
||||
node-c
|
||||
node-d
|
||||
node-e
|
||||
node-f
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-d/peers.deny" <<'EOF'
|
||||
# Intentionally empty.
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-e/fips.yaml" <<'EOF'
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.10:2121"
|
||||
connect_policy: auto_connect
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-e/fips.key" <<'EOF'
|
||||
nsec1egyrmekfw3u4l88v8zhrak9uht503s2kvn9v49tqgp6c5l2yuxgsv386l0
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-f/fips.yaml" <<'EOF'
|
||||
node:
|
||||
identity:
|
||||
persistent: true
|
||||
|
||||
tun:
|
||||
enabled: true
|
||||
name: fips0
|
||||
mtu: 1280
|
||||
|
||||
dns:
|
||||
enabled: true
|
||||
bind_addr: "127.0.0.1"
|
||||
|
||||
transports:
|
||||
udp:
|
||||
bind_addr: "0.0.0.0:2121"
|
||||
|
||||
peers:
|
||||
- npub: "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
alias: "node-a"
|
||||
addresses:
|
||||
- transport: udp
|
||||
addr: "172.31.0.10:2121"
|
||||
connect_policy: auto_connect
|
||||
EOF
|
||||
|
||||
write_file "$GENERATED_DIR/node-f/fips.key" <<'EOF'
|
||||
nsec1afh3nysthqh47awpdewcw59wvvp499f8dvlyclmnv4gvpxdk56dsa6eqsn
|
||||
EOF
|
||||
|
||||
write_hosts_file node-a
|
||||
write_hosts_file node-b
|
||||
write_hosts_file node-c
|
||||
write_hosts_file node-d
|
||||
write_hosts_file node-e
|
||||
write_hosts_file node-f
|
||||
|
||||
echo "ACL allowlist fixtures written to $GENERATED_DIR"
|
||||
Executable
+150
@@ -0,0 +1,150 @@
|
||||
#!/bin/bash
|
||||
# Integration test for the ACL allowlist harness.
|
||||
#
|
||||
# Usage: ./test.sh [--skip-build] [--keep-up]
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
TESTING_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
COMPOSE_FILE="$SCRIPT_DIR/docker-compose.yml"
|
||||
GENERATE_CONFIGS="$SCRIPT_DIR/generate-configs.sh"
|
||||
|
||||
SKIP_BUILD=false
|
||||
KEEP_UP=false
|
||||
|
||||
while [ $# -gt 0 ]; do
|
||||
case "$1" in
|
||||
--skip-build) SKIP_BUILD=true; shift ;;
|
||||
--keep-up) KEEP_UP=true; shift ;;
|
||||
*) echo "Unknown option: $1" >&2; exit 1 ;;
|
||||
esac
|
||||
done
|
||||
|
||||
cleanup() {
|
||||
if [ "$KEEP_UP" = false ]; then
|
||||
docker compose -f "$COMPOSE_FILE" down >/dev/null 2>&1 || true
|
||||
fi
|
||||
}
|
||||
|
||||
trap cleanup EXIT
|
||||
|
||||
log() {
|
||||
echo "=== $*"
|
||||
}
|
||||
|
||||
peer_npubs() {
|
||||
local container="$1"
|
||||
docker exec "$container" fipsctl show peers \
|
||||
| python3 -c 'import json,sys; data=json.load(sys.stdin); print(" ".join(sorted(p["npub"] for p in data.get("peers", []) if p.get("connectivity") == "connected")))'
|
||||
}
|
||||
|
||||
acl_field() {
|
||||
local container="$1"
|
||||
local field="$2"
|
||||
docker exec "$container" fipsctl acl show \
|
||||
| python3 -c 'import json,sys; data=json.load(sys.stdin); field=sys.argv[1]; value=data.get(field); print(" ".join(sorted(value)) if isinstance(value, list) else ("" if value is None else value))' "$field"
|
||||
}
|
||||
|
||||
assert_peer_set() {
|
||||
local container="$1"
|
||||
local expected="$2"
|
||||
local actual
|
||||
actual="$(peer_npubs "$container")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "FAIL: $container peers mismatch" >&2
|
||||
echo " expected: $expected" >&2
|
||||
echo " actual: $actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "PASS: $container peers match expected set"
|
||||
}
|
||||
|
||||
assert_acl_field() {
|
||||
local container="$1"
|
||||
local field="$2"
|
||||
local expected="$3"
|
||||
local actual
|
||||
actual="$(acl_field "$container" "$field")"
|
||||
if [ "$actual" != "$expected" ]; then
|
||||
echo "FAIL: $container ACL field $field mismatch" >&2
|
||||
echo " expected: $expected" >&2
|
||||
echo " actual: $actual" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "PASS: $container ACL field $field matches expected value"
|
||||
}
|
||||
|
||||
wait_for_peers_exact() {
|
||||
local container="$1"
|
||||
local expected_count="$2"
|
||||
local timeout="${3:-30}"
|
||||
|
||||
for _ in $(seq 1 "$timeout"); do
|
||||
local count
|
||||
count=$(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 || echo 0)
|
||||
if [ "$count" -eq "$expected_count" ]; then
|
||||
return 0
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
echo "FAIL: $container did not reach $expected_count connected peers in ${timeout}s" >&2
|
||||
docker exec "$container" fipsctl show peers >&2 || true
|
||||
exit 1
|
||||
}
|
||||
|
||||
assert_log_contains() {
|
||||
local container="$1"
|
||||
local pattern="$2"
|
||||
local logs
|
||||
logs="$(docker logs "$container" 2>&1 | python3 -c 'import re,sys; print(re.sub(r"\x1b\[[0-9;]*m", "", sys.stdin.read()), end="")' || true)"
|
||||
if ! printf '%s' "$logs" | grep -F "$pattern" >/dev/null; then
|
||||
echo "FAIL: missing log pattern in $container: $pattern" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "PASS: $container logs contain expected ACL rejection"
|
||||
}
|
||||
|
||||
if [ "$SKIP_BUILD" = false ]; then
|
||||
log "Building Linux test binaries"
|
||||
"$TESTING_DIR/scripts/build.sh" --no-docker
|
||||
fi
|
||||
|
||||
log "Generating ACL allowlist fixtures"
|
||||
"$GENERATE_CONFIGS"
|
||||
|
||||
log "Starting ACL allowlist harness"
|
||||
docker compose -f "$COMPOSE_FILE" down >/dev/null 2>&1 || true
|
||||
docker compose -f "$COMPOSE_FILE" up -d --build
|
||||
|
||||
log "Waiting for expected peer convergence"
|
||||
wait_for_peers_exact fips-acl-container-a 3 40
|
||||
wait_for_peers_exact fips-acl-container-b 1 40
|
||||
wait_for_peers_exact fips-acl-container-c 0 5
|
||||
wait_for_peers_exact fips-acl-container-d 0 5
|
||||
wait_for_peers_exact fips-acl-container-e 1 40
|
||||
wait_for_peers_exact fips-acl-container-f 1 40
|
||||
|
||||
log "Verifying peer sets"
|
||||
assert_peer_set fips-acl-container-a "npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6 npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c"
|
||||
assert_peer_set fips-acl-container-b "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
assert_peer_set fips-acl-container-c ""
|
||||
assert_peer_set fips-acl-container-d ""
|
||||
assert_peer_set fips-acl-container-e "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
assert_peer_set fips-acl-container-f "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m"
|
||||
|
||||
log "Checking alias-based ACL resolution"
|
||||
assert_acl_field fips-acl-container-a allow_file_entries "node-a node-b node-e node-f"
|
||||
assert_acl_field fips-acl-container-a allow_entries "npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6 npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c"
|
||||
assert_acl_field fips-acl-container-c allow_file_entries "node-a node-b node-c node-d node-e node-f"
|
||||
assert_acl_field fips-acl-container-c allow_entries "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6 npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl npub1sjlh2c3x9w7kjsqg2ay080n2lff2uvt325vpan33ke34rn8l5jcqawh57m npub1tdwa4vjrjl33pcjdpf2t4p027nl86xrx24g4d3avg4vwvayr3g8qhd84le npub1x5z9rwzzm26q9verutx4aajhf2zw2pyp34c6whhde2zduxqav40qgq36l6 npub1ytrut7gjncn2zfnhn56c0zgftf0w6p99gf6fu8j73hzw5603zglqc9av6c"
|
||||
|
||||
log "Checking ACL rejection logs"
|
||||
assert_log_contains fips-acl-container-a "npub1cld9yay0u24davpu6c35l4vldrhzvaq66pcqtg9a0j2cnjrn9rtsxx2pe6"
|
||||
assert_log_contains fips-acl-container-a "npub1n9lpnv0592cc2ps6nm0ca3qls642vx7yjsv35rkxqzj2vgds52sqgpverl"
|
||||
assert_log_contains fips-acl-container-a "context=inbound_handshake"
|
||||
assert_log_contains fips-acl-container-a "decision=denylist match"
|
||||
|
||||
log "ACL allowlist integration test passed"
|
||||
Reference in New Issue
Block a user