@celilo/e2e 0.10.3 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -28
- package/bin/e2e-status +1 -1
- package/bin/e2e-up +14 -14
- package/config/dhcp/dnsmasq.conf +3 -3
- package/config/resolver/unbound.conf +2 -2
- package/config/routing/fw-ext-routes.sh +4 -4
- package/config/routing/fw-isp-routes.sh +3 -3
- package/config/routing/fw-main-routes.sh +15 -4
- package/config/routing/management-routes.sh +11 -11
- package/config/routing/observer-setup.sh +3 -3
- package/config/routing/resolver-internal-routes.sh +5 -5
- package/config/routing/target-routes.sh +1 -1
- package/config/routing/target-setup.sh +18 -16
- package/config/socks/startup.sh +3 -3
- package/docker/Dockerfile.target-machine +15 -1
- package/docker/Dockerfile.target-machine-docker +6 -0
- package/docker/Dockerfile.target-technitium +6 -0
- package/package.json +2 -2
- package/simulators/greenwave/server.ts +1 -1
- package/simulators/greenwave/state.ts +7 -7
- package/src/address-plan.test.ts +95 -0
- package/src/cli/scaffold.ts +2 -2
- package/src/container-manager.ts +77 -42
- package/src/docker-compose-generator.ts +67 -39
- package/src/exit-cleanup.test.ts +16 -2
- package/src/fixtures.ts +12 -12
- package/src/index.ts +2 -2
- package/src/network-builder.ts +2 -2
- package/src/observer.test.ts +13 -13
- package/src/observer.ts +3 -3
- package/src/socks-proxy.ts +2 -2
- package/src/types.ts +67 -17
- package/src/vantage-helpers.ts +1 -1
- package/src/vantage.test.ts +24 -24
- package/src/zone-classifier.test.ts +25 -20
- package/src/zone-classifier.ts +5 -4
package/README.md
CHANGED
|
@@ -16,25 +16,34 @@ The network consists of 7 Docker bridge networks simulating a realistic home lab
|
|
|
16
16
|
|
|
17
17
|
| Network | Subnet | Purpose |
|
|
18
18
|
|---------|--------|---------|
|
|
19
|
-
| **internal** |
|
|
20
|
-
| **dmz** | 10.
|
|
21
|
-
| **app** | 10.
|
|
22
|
-
| **secure** | 10.
|
|
19
|
+
| **internal** | 10.226.1.0/24 | Home LAN (management, firewalls) |
|
|
20
|
+
| **dmz** | 10.226.10.0/24 | Public-facing services |
|
|
21
|
+
| **app** | 10.226.20.0/24 | Internal applications |
|
|
22
|
+
| **secure** | 10.226.30.0/24 | Sensitive services |
|
|
23
23
|
| **isp-external** | 100.100.0.0/24 | ISP network (between home and internet) |
|
|
24
24
|
| **internet-external** | 100.64.0.0/24 | Simulated internet (DNS, ACME, etc.) |
|
|
25
25
|
| **real-internet** | 172.30.0.0/24 | Bridge to actual internet (for apt, pip, etc.) |
|
|
26
26
|
|
|
27
27
|
All networks except `real-internet` have Docker IP masquerade **disabled** — traffic flows through explicit iptables routing, not Docker's NAT. The `real-internet` network has masquerade enabled to provide actual internet access for package downloads.
|
|
28
28
|
|
|
29
|
+
### Why these addresses (#539)
|
|
30
|
+
|
|
31
|
+
Every simulated **private** zone lives inside `SIM_PRIVATE_SUPERNET` (`10.226.0.0/16` — mnemonic `0xE2` = "e2"), declared once in `src/types.ts` and derived from there by the compose generator, the zone classifier and the harness's `system init`. Two reasons, both load-bearing:
|
|
32
|
+
|
|
33
|
+
1. **A leaked stack must not be able to collide with a real fleet.** The sim used to number its zones exactly like a production celilo deployment; on celilo's own forgejo-builder, which lives in the real dmz, a leaked bridge claimed the builder's own /24 and blackholed every containerized CI job's route to the forge. Teardown cannot be made reliable enough to prevent that (a SIGKILL runs no handler), so the sim moved instead.
|
|
34
|
+
2. **Nothing may be magic about production's octets.** A suite that only passes on one site's address plan is asserting an accident, not celilo's behaviour.
|
|
35
|
+
|
|
36
|
+
Don't hardcode an octet: use `zoneIp(zone, host)` / `ZONE_SUBNETS` / `ZONE_GATEWAYS`. `src/address-plan.test.ts` fails if a zone leaves the supernet, or if any retired fleet prefix reappears anywhere in the suite.
|
|
37
|
+
|
|
29
38
|
## Machines
|
|
30
39
|
|
|
31
40
|
### Fixed Infrastructure (always present)
|
|
32
41
|
|
|
33
42
|
| Machine | Role | Networks | IPs |
|
|
34
43
|
|---------|------|----------|-----|
|
|
35
|
-
| **management** | Celilo CLI, Ansible, SSH | internal |
|
|
36
|
-
| **fw-main** | iptables firewall (managed by Celilo) | internal, dmz, app, secure |
|
|
37
|
-
| **fw-isp** | Greenwave router simulator | internal, isp-external |
|
|
44
|
+
| **management** | Celilo CLI, Ansible, SSH | internal | 10.226.1.100 |
|
|
45
|
+
| **fw-main** | iptables firewall (managed by Celilo) | internal, dmz, app, secure | 10.226.1.254, 10.226.10.1, 10.226.20.1, 10.226.30.1 |
|
|
46
|
+
| **fw-isp** | Greenwave router simulator | internal, isp-external | 10.226.1.1, 100.100.0.100 |
|
|
38
47
|
| **fw-ext** | Edge router + transparent HTTPS proxy (Squid) | isp-external, internet-external, real-internet | 100.100.0.101, 100.64.0.1, 172.30.0.5 |
|
|
39
48
|
| **comcast-resolver** | Unbound recursive DNS resolver | isp-external, real-internet | 100.100.0.1, 172.30.0.4 |
|
|
40
49
|
| **root-dns** | Knot authoritative DNS (root zone) | internet-external | 100.64.0.53 |
|
|
@@ -49,9 +58,9 @@ Added per-test via the `network()` builder API. Each is an Ubuntu 22.04 containe
|
|
|
49
58
|
|
|
50
59
|
| Example | Network | IP | Purpose |
|
|
51
60
|
|---------|---------|-----|---------|
|
|
52
|
-
| caddy | dmz | 10.
|
|
53
|
-
| idp | app | 10.
|
|
54
|
-
| db | secure | 10.
|
|
61
|
+
| caddy | dmz | 10.226.10.10 | Caddy reverse proxy deployment |
|
|
62
|
+
| idp | app | 10.226.20.100 | Identity provider deployment |
|
|
63
|
+
| db | secure | 10.226.30.50 | Database deployment |
|
|
55
64
|
|
|
56
65
|
## Routing
|
|
57
66
|
|
|
@@ -59,8 +68,8 @@ Traffic flows through explicit routing chains, mimicking a real network:
|
|
|
59
68
|
|
|
60
69
|
| From | To | Path |
|
|
61
70
|
|------|----|------|
|
|
62
|
-
| management | dmz/app/secure | via fw-main (
|
|
63
|
-
| management | internet | via fw-isp (
|
|
71
|
+
| management | dmz/app/secure | via fw-main (10.226.1.254) |
|
|
72
|
+
| management | internet | via fw-isp (10.226.1.1) → fw-ext |
|
|
64
73
|
| dmz machines | internet | via fw-main → fw-isp → fw-ext |
|
|
65
74
|
| Pebble | caddy (HTTP-01 challenge) | via fw-ext → fw-isp DNAT → fw-main DNAT → caddy |
|
|
66
75
|
|
|
@@ -104,12 +113,12 @@ When called, it updates the Knot DNS zone file and signals a zone reload. DNS pr
|
|
|
104
113
|
|
|
105
114
|
### Greenwave Router
|
|
106
115
|
|
|
107
|
-
Runs on `fw-isp` (
|
|
116
|
+
Runs on `fw-isp` (10.226.1.1). A Bun HTTPS server implementing the C4000XG REST API subset:
|
|
108
117
|
- `POST /cgi/cgi_action` — Login/logout
|
|
109
118
|
- `GET /cgi/cgi_get` — Read config (public IP, port mappings)
|
|
110
119
|
- `POST /cgi/cgi_set` — Add/remove port forwarding rules
|
|
111
120
|
|
|
112
|
-
Port forwarding rules are applied via actual iptables commands, making NAT functional in the simulated network. The management HTTPS interface binds to the internal interface only (
|
|
121
|
+
Port forwarding rules are applied via actual iptables commands, making NAT functional in the simulated network. The management HTTPS interface binds to the internal interface only (10.226.1.1), not the external interface.
|
|
113
122
|
|
|
114
123
|
### Pebble (Let's Encrypt)
|
|
115
124
|
|
|
@@ -136,7 +145,7 @@ cd e2e
|
|
|
136
145
|
./bin/e2e-up --full-stack
|
|
137
146
|
|
|
138
147
|
# Custom machine spec
|
|
139
|
-
./bin/e2e-up --custom '{"dmz":{"caddy":"10.
|
|
148
|
+
./bin/e2e-up --custom '{"dmz":{"caddy":"10.226.10.10","web":"10.226.10.20"}}'
|
|
140
149
|
```
|
|
141
150
|
|
|
142
151
|
After startup, you're dropped into the management machine shell with tab completion for `celilo` commands (aliased as `c`).
|
|
@@ -166,10 +175,10 @@ cd /celilo/modules
|
|
|
166
175
|
|
|
167
176
|
# System init
|
|
168
177
|
c system init --accept-defaults \
|
|
169
|
-
network.dmz.subnet=10.
|
|
170
|
-
network.app.subnet=10.
|
|
171
|
-
network.secure.subnet=10.
|
|
172
|
-
network.internal.subnet=
|
|
178
|
+
network.dmz.subnet=10.226.10.0/24 \
|
|
179
|
+
network.app.subnet=10.226.20.0/24 \
|
|
180
|
+
network.secure.subnet=10.226.30.0/24 \
|
|
181
|
+
network.internal.subnet=10.226.1.0/24 \
|
|
173
182
|
primary_domain=iamtheinternet.org \
|
|
174
183
|
admin.email=admin@iamtheinternet.org \
|
|
175
184
|
dns.primary=100.100.0.1 \
|
|
@@ -182,17 +191,17 @@ c module import iptables
|
|
|
182
191
|
c module import caddy
|
|
183
192
|
|
|
184
193
|
# Add machines (--ssh-user root needed for non-interactive)
|
|
185
|
-
c machine add
|
|
186
|
-
c machine add
|
|
187
|
-
c machine add 10.
|
|
194
|
+
c machine add 10.226.1.1 --ssh-user root --earmark greenwave
|
|
195
|
+
c machine add 10.226.1.254 --ssh-user root --earmark iptables
|
|
196
|
+
c machine add 10.226.10.10 --ssh-user root
|
|
188
197
|
|
|
189
198
|
# Pre-configure secrets and non-derivable config
|
|
190
199
|
c module config set namecheap domains '["iamtheinternet.org"]'
|
|
191
200
|
c module secret set namecheap ddns_passwords '{"iamtheinternet.org":"test123"}'
|
|
192
|
-
c module config set greenwave router_ip
|
|
201
|
+
c module config set greenwave router_ip 10.226.1.1
|
|
193
202
|
c module secret set greenwave router_username admin
|
|
194
203
|
c module secret set greenwave router_password admin
|
|
195
|
-
c module config set iptables nat_ip
|
|
204
|
+
c module config set iptables nat_ip 10.226.1.253
|
|
196
205
|
c module config set caddy hostname www
|
|
197
206
|
c module config set caddy acme_ca https://acme-v02.api.letsencrypt.org/dir
|
|
198
207
|
|
|
@@ -249,7 +258,7 @@ describe('my deployment test', () => {
|
|
|
249
258
|
net = await CADDY_DEPLOYMENT().start();
|
|
250
259
|
|
|
251
260
|
// 2. Add machines, import modules, configure
|
|
252
|
-
await net.celilo('machine add 10.
|
|
261
|
+
await net.celilo('machine add 10.226.10.10 --ssh-user root');
|
|
253
262
|
await net.celilo('module import /celilo/modules/caddy');
|
|
254
263
|
await net.celilo('module config set caddy hostname www');
|
|
255
264
|
|
|
@@ -330,9 +339,9 @@ INFRASTRUCTURE_ONLY() // no dynamic machines
|
|
|
330
339
|
import { network } from '../src/network-builder';
|
|
331
340
|
|
|
332
341
|
const net = await network()
|
|
333
|
-
.dmz({ caddy: '10.
|
|
334
|
-
.app({ api: '10.
|
|
335
|
-
.secure({ db: '10.
|
|
342
|
+
.dmz({ caddy: '10.226.10.10', web: '10.226.10.20' })
|
|
343
|
+
.app({ api: '10.226.20.100' })
|
|
344
|
+
.secure({ db: '10.226.30.50' })
|
|
336
345
|
.start();
|
|
337
346
|
```
|
|
338
347
|
|
package/bin/e2e-status
CHANGED
|
@@ -22,7 +22,7 @@ docker compose -p "$PROJECT_NAME" exec -T comcast-resolver dig @127.0.0.1 www.ia
|
|
|
22
22
|
|
|
23
23
|
echo ""
|
|
24
24
|
echo "=== Connectivity (from management) ==="
|
|
25
|
-
for target in "fw-main:
|
|
25
|
+
for target in "fw-main:10.226.1.254" "fw-isp:10.226.1.1" "comcast-resolver:100.100.0.1" "root-dns:100.64.0.53" "namecheap-dns:100.64.0.55"; do
|
|
26
26
|
name="${target%%:*}"
|
|
27
27
|
ip="${target##*:}"
|
|
28
28
|
echo -n " $name ($ip): "
|
package/bin/e2e-up
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# ./bin/e2e-up # Infrastructure only
|
|
6
6
|
# ./bin/e2e-up --caddy # + caddy machine in dmz
|
|
7
7
|
# ./bin/e2e-up --full-stack # + caddy, idp, db
|
|
8
|
-
# ./bin/e2e-up --custom '{"dmz":{"caddy":"10.
|
|
8
|
+
# ./bin/e2e-up --custom '{"dmz":{"caddy":"10.226.10.10","web":"10.226.10.20"}}'
|
|
9
9
|
#
|
|
10
10
|
# After startup, drops you into a shell on the management machine.
|
|
11
11
|
# Use `exit` to leave the shell (network stays running).
|
|
@@ -30,9 +30,9 @@ while [[ $# -gt 0 ]]; do
|
|
|
30
30
|
echo ""
|
|
31
31
|
echo "Presets:"
|
|
32
32
|
echo " (default) Infrastructure only (management, firewalls, DNS, Pebble)"
|
|
33
|
-
echo " --caddy + caddy machine at 10.
|
|
33
|
+
echo " --caddy + caddy machine at 10.226.10.10 in dmz"
|
|
34
34
|
echo " --full-stack + caddy (dmz), idp (app), db (secure)"
|
|
35
|
-
echo " --custom JSON Custom machine spec, e.g. '{\"dmz\":{\"caddy\":\"10.
|
|
35
|
+
echo " --custom JSON Custom machine spec, e.g. '{\"dmz\":{\"caddy\":\"10.226.10.10\"}}'"
|
|
36
36
|
echo ""
|
|
37
37
|
echo "After startup, you'll be dropped into the management machine shell."
|
|
38
38
|
echo "Use ./bin/e2e-down to tear down, or ./bin/e2e-shell to reconnect."
|
|
@@ -60,7 +60,7 @@ const presets = {
|
|
|
60
60
|
},
|
|
61
61
|
'caddy': {
|
|
62
62
|
topology: 'default',
|
|
63
|
-
dmzMachines: [{ name: 'caddy', ip: '10.
|
|
63
|
+
dmzMachines: [{ name: 'caddy', ip: '10.226.10.10', zone: 'dmz' }],
|
|
64
64
|
appMachines: [],
|
|
65
65
|
secureMachines: [],
|
|
66
66
|
domain: 'iamtheinternet.org',
|
|
@@ -69,9 +69,9 @@ const presets = {
|
|
|
69
69
|
},
|
|
70
70
|
'full-stack': {
|
|
71
71
|
topology: 'default',
|
|
72
|
-
dmzMachines: [{ name: 'caddy', ip: '10.
|
|
73
|
-
appMachines: [{ name: 'idp', ip: '10.
|
|
74
|
-
secureMachines: [{ name: 'db', ip: '10.
|
|
72
|
+
dmzMachines: [{ name: 'caddy', ip: '10.226.10.10', zone: 'dmz' }],
|
|
73
|
+
appMachines: [{ name: 'idp', ip: '10.226.20.100', zone: 'app' }],
|
|
74
|
+
secureMachines: [{ name: 'db', ip: '10.226.30.50', zone: 'secure' }],
|
|
75
75
|
domain: 'iamtheinternet.org',
|
|
76
76
|
ddnsPassword: 'test123',
|
|
77
77
|
verifyRouting: false,
|
|
@@ -131,17 +131,17 @@ echo " Project: $PROJECT_NAME"
|
|
|
131
131
|
echo "================================================"
|
|
132
132
|
echo ""
|
|
133
133
|
echo "Networks:"
|
|
134
|
-
echo " internal:
|
|
135
|
-
echo " dmz: 10.
|
|
136
|
-
echo " app: 10.
|
|
137
|
-
echo " secure: 10.
|
|
134
|
+
echo " internal: 10.226.1.0/24"
|
|
135
|
+
echo " dmz: 10.226.10.0/24"
|
|
136
|
+
echo " app: 10.226.20.0/24"
|
|
137
|
+
echo " secure: 10.226.30.0/24"
|
|
138
138
|
echo " isp-external: 100.100.0.0/24"
|
|
139
139
|
echo " internet-external: 100.64.0.0/24"
|
|
140
140
|
echo ""
|
|
141
141
|
echo "Key machines:"
|
|
142
|
-
echo " management:
|
|
143
|
-
echo " fw-main:
|
|
144
|
-
echo " fw-isp:
|
|
142
|
+
echo " management: 10.226.1.100 (conductor CLI)"
|
|
143
|
+
echo " fw-main: 10.226.1.254 (iptables firewall)"
|
|
144
|
+
echo " fw-isp: 10.226.1.1 (greenwave router sim)"
|
|
145
145
|
echo " comcast-resolver: 100.100.0.1 (DNS resolver)"
|
|
146
146
|
echo " namecheap-dns: 100.64.0.55 (DDNS simulator)"
|
|
147
147
|
echo " letsencrypt: 100.64.0.100 (Pebble ACME)"
|
package/config/dhcp/dnsmasq.conf
CHANGED
|
@@ -9,13 +9,13 @@ interface=eth0
|
|
|
9
9
|
bind-interfaces
|
|
10
10
|
|
|
11
11
|
# DHCP range: .200-.220 (small range for test clients)
|
|
12
|
-
dhcp-range=
|
|
12
|
+
dhcp-range=10.226.1.200,10.226.1.220,255.255.255.0,1h
|
|
13
13
|
|
|
14
14
|
# Default gateway (the greenwave router itself)
|
|
15
|
-
dhcp-option=option:router,
|
|
15
|
+
dhcp-option=option:router,10.226.1.1
|
|
16
16
|
|
|
17
17
|
# DNS server (initially the router, updated by greenwave API)
|
|
18
|
-
dhcp-option=option:dns-server,
|
|
18
|
+
dhcp-option=option:dns-server,10.226.1.1
|
|
19
19
|
|
|
20
20
|
# Domain name / search domain
|
|
21
21
|
# dhcp-option=option:domain-name,iamtheinternet.org
|
|
@@ -32,8 +32,8 @@ server:
|
|
|
32
32
|
# sit on the `internal` network — celilo-mgr reaches a notification
|
|
33
33
|
# transport locally; a transport needing public ingress could not tell
|
|
34
34
|
# you the ingress was broken.
|
|
35
|
-
local-data: "signal-cli.lab. IN A
|
|
36
|
-
local-data: "signal-sim.lab. IN A
|
|
35
|
+
local-data: "signal-cli.lab. IN A 10.226.1.90"
|
|
36
|
+
local-data: "signal-sim.lab. IN A 10.226.1.91"
|
|
37
37
|
# signal-cli release host — the sim internet's stand-in for GitHub
|
|
38
38
|
# releases, so the signal module's deploy-time download resolves inside
|
|
39
39
|
# the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
|
|
@@ -6,10 +6,10 @@ ip route del default 2>/dev/null || true
|
|
|
6
6
|
# Default route via real-internet bridge (for Squid outbound to real internet)
|
|
7
7
|
ip route add default via 172.30.0.1
|
|
8
8
|
# Internal subnets via fw-isp
|
|
9
|
-
ip route add
|
|
10
|
-
ip route add 10.
|
|
11
|
-
ip route add 10.
|
|
12
|
-
ip route add 10.
|
|
9
|
+
ip route add 10.226.1.0/24 via 100.100.0.100 # Internal via fw-isp
|
|
10
|
+
ip route add 10.226.10.0/24 via 100.100.0.100 # dmz via fw-isp
|
|
11
|
+
ip route add 10.226.20.0/24 via 100.100.0.100 # app via fw-isp
|
|
12
|
+
ip route add 10.226.30.0/24 via 100.100.0.100 # secure via fw-isp
|
|
13
13
|
|
|
14
14
|
# IP forwarding is set via sysctls in docker-compose
|
|
15
15
|
|
|
@@ -11,9 +11,9 @@ chmod 600 /root/.ssh/authorized_keys
|
|
|
11
11
|
# Routing
|
|
12
12
|
ip route del default 2>/dev/null || true
|
|
13
13
|
ip route add default via 100.100.0.101 # Internet via fw-ext
|
|
14
|
-
ip route add 10.
|
|
15
|
-
ip route add 10.
|
|
16
|
-
ip route add 10.
|
|
14
|
+
ip route add 10.226.10.0/24 via 10.226.1.254 # dmz via fw-main
|
|
15
|
+
ip route add 10.226.20.0/24 via 10.226.1.254 # app via fw-main
|
|
16
|
+
ip route add 10.226.30.0/24 via 10.226.1.254 # secure via fw-main
|
|
17
17
|
|
|
18
18
|
# IP forwarding is set via sysctls in docker-compose
|
|
19
19
|
|
|
@@ -30,14 +30,25 @@ else
|
|
|
30
30
|
# Two-layer: no external interface on fw-main; it routes outbound to the
|
|
31
31
|
# upstream firewall (fw-isp) and MASQUERADEs toward it (egress leaves via the
|
|
32
32
|
# internal-facing interface, so there is no single WAN interface to scope to).
|
|
33
|
-
ip route add default via
|
|
34
|
-
# Protected↔protected (
|
|
35
|
-
#
|
|
33
|
+
ip route add default via 10.226.1.1
|
|
34
|
+
# Protected↔protected (the segmented zones ↔ each other) is NOT NAT'd — a real
|
|
35
|
+
# firewall routes between its segmented zones without NAT, preserving the
|
|
36
36
|
# client source so the dmz-resident resolver can serve source-based split-horizon
|
|
37
37
|
# (ISS-0156). Only protected↔internal and egress get MASQUERADE'd below. Without
|
|
38
38
|
# this RETURN, app→dmz and internal→dmz would both be SNAT'd to the dmz gateway
|
|
39
39
|
# and become indistinguishable (confirmed via live probe).
|
|
40
|
-
|
|
40
|
+
#
|
|
41
|
+
# PROTECTED_SUBNETS is passed from the compose generator (ZONE_SUBNETS). It used
|
|
42
|
+
# to be written as `-s 10.0.0.0/8 -d 10.0.0.0/8`, which meant "protected" only
|
|
43
|
+
# because `internal` happened to be numbered out of 192.168/16 and every other
|
|
44
|
+
# zone out of 10/8 — the firewall's NAT policy silently depended on the octets. With
|
|
45
|
+
# the whole sim in 10.226.0.0/16 (#539) that shorthand would have swept `internal`
|
|
46
|
+
# into the no-NAT class and handed LAN devices the protected DNS view.
|
|
47
|
+
for src in ${PROTECTED_SUBNETS:?PROTECTED_SUBNETS not set by compose}; do
|
|
48
|
+
for dst in ${PROTECTED_SUBNETS}; do
|
|
49
|
+
iptables -t nat -A POSTROUTING -s "$src" -d "$dst" -j RETURN
|
|
50
|
+
done
|
|
51
|
+
done
|
|
41
52
|
iptables -t nat -A POSTROUTING -j MASQUERADE
|
|
42
53
|
fi
|
|
43
54
|
|
|
@@ -20,29 +20,29 @@ echo "UserKnownHostsFile /dev/null" >> /root/.ssh/config
|
|
|
20
20
|
chmod 600 /root/.ssh/config
|
|
21
21
|
|
|
22
22
|
# Routing — DEFAULT_GATEWAY comes from docker-compose environment
|
|
23
|
-
# default:
|
|
24
|
-
GATEWAY="${DEFAULT_GATEWAY:-
|
|
23
|
+
# default: 10.226.1.1 (fw-isp), direct-internet: 10.226.1.254 (fw-main)
|
|
24
|
+
GATEWAY="${DEFAULT_GATEWAY:-10.226.1.1}"
|
|
25
25
|
# Next hop into the segmented zones. On the default topology celilo-mgr is on the
|
|
26
|
-
# internal LAN and reaches them via fw-main's internal leg (
|
|
26
|
+
# internal LAN and reaches them via fw-main's internal leg (10.226.1.254). When
|
|
27
27
|
# celilo-mgr lives on its OWN control-plane network (managementZone secure-mgmt),
|
|
28
28
|
# it reaches them via fw-main's leg on THAT network instead.
|
|
29
|
-
FW_MAIN="${FW_MAIN_HOP:-
|
|
29
|
+
FW_MAIN="${FW_MAIN_HOP:-10.226.1.254}"
|
|
30
30
|
ip route del default 2>/dev/null || true
|
|
31
31
|
ip route add default via "$GATEWAY"
|
|
32
|
-
ip route add 10.
|
|
33
|
-
ip route add 10.
|
|
34
|
-
ip route add 10.
|
|
32
|
+
ip route add 10.226.10.0/24 via "$FW_MAIN" # dmz via fw-main
|
|
33
|
+
ip route add 10.226.20.0/24 via "$FW_MAIN" # app via fw-main
|
|
34
|
+
ip route add 10.226.30.0/24 via "$FW_MAIN" # secure via fw-main
|
|
35
35
|
# Control plane. Only reachable when a secure-mgmt network exists — that is,
|
|
36
36
|
# when celilo-mgr lives there itself, or when a module declaring
|
|
37
37
|
# `zone: secure-mgmt` needs a system to land on (#436). Tolerated silently
|
|
38
38
|
# otherwise so the default topology is unchanged.
|
|
39
|
-
ip route add 10.
|
|
39
|
+
ip route add 10.226.120.0/24 via "$FW_MAIN" 2>/dev/null || true
|
|
40
40
|
|
|
41
41
|
# DNS — Technitium internal resolver (split-horizon) with public fallback.
|
|
42
|
-
# Technitium at
|
|
43
|
-
# resolving www/auth to Caddy's DMZ IP (10.
|
|
42
|
+
# Technitium at 10.226.1.10 is authoritative for iamtheinternet.org internally,
|
|
43
|
+
# resolving www/auth to Caddy's DMZ IP (10.226.10.10) rather than the public WAN IP.
|
|
44
44
|
# Fall back to the simulated public resolver if Technitium is not yet up.
|
|
45
|
-
printf 'nameserver %s\nnameserver 100.100.0.1\n' "${INTERNAL_RESOLVER:-
|
|
45
|
+
printf 'nameserver %s\nnameserver 100.100.0.1\n' "${INTERNAL_RESOLVER:-10.226.1.10}" > /etc/resolv.conf
|
|
46
46
|
|
|
47
47
|
# Fetch Pebble's runtime ACME root CA (Pebble generates a fresh root at each
|
|
48
48
|
# startup, unlike the static /config/pebble/pebble-ca.crt baked in at build
|
|
@@ -28,9 +28,9 @@ ip route add default via "${GATEWAY}"
|
|
|
28
28
|
# A real consumer vantage NEVER has these — that is the whole point. Present only so a
|
|
29
29
|
# `management`-style all-VLAN box can be modelled deliberately.
|
|
30
30
|
if [ "${INTERZONE}" = "1" ]; then
|
|
31
|
-
ip route add 10.
|
|
32
|
-
ip route add 10.
|
|
33
|
-
ip route add 10.
|
|
31
|
+
ip route add 10.226.10.0/24 via 10.226.1.254 2>/dev/null || true # dmz via fw-main
|
|
32
|
+
ip route add 10.226.20.0/24 via 10.226.1.254 2>/dev/null || true # app via fw-main
|
|
33
|
+
ip route add 10.226.30.0/24 via 10.226.1.254 2>/dev/null || true # secure via fw-main
|
|
34
34
|
fi
|
|
35
35
|
|
|
36
36
|
# --- Resolver ---
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
|
-
# CADDY_IP: IP of the caddy container in the DMZ (default 10.
|
|
4
|
+
# CADDY_IP: IP of the caddy container in the DMZ (default 10.226.10.10).
|
|
5
5
|
# All test-domain queries resolve to this IP so management can reach
|
|
6
6
|
# caddy directly without hairpin NAT through the external interface.
|
|
7
|
-
CADDY_IP="${CADDY_IP:-10.
|
|
7
|
+
CADDY_IP="${CADDY_IP:-10.226.10.10}"
|
|
8
8
|
|
|
9
9
|
# DOMAINS: comma-separated list of e2e test domains that should be
|
|
10
10
|
# split-horizon redirected to caddy. Default covers the canonical
|
|
@@ -15,7 +15,7 @@ DOMAINS="${DOMAINS:-iamtheinternet.org,example.net}"
|
|
|
15
15
|
|
|
16
16
|
# Routing — reach comcast-resolver (100.100.0.1) via fw-main or fw-isp.
|
|
17
17
|
# DEFAULT_GATEWAY is set by docker-compose from the topology config.
|
|
18
|
-
GATEWAY="${DEFAULT_GATEWAY:-
|
|
18
|
+
GATEWAY="${DEFAULT_GATEWAY:-10.226.1.254}"
|
|
19
19
|
ip route del default 2>/dev/null || true
|
|
20
20
|
ip route add default via "$GATEWAY"
|
|
21
21
|
|
|
@@ -61,8 +61,8 @@ $(printf "${SPLIT_HORIZON_BLOCK}")
|
|
|
61
61
|
# sit on the `internal` network — celilo-mgr reaches a notification
|
|
62
62
|
# transport locally; a transport needing public ingress could not tell
|
|
63
63
|
# you the ingress was broken.
|
|
64
|
-
local-data: "signal-cli.lab. IN A
|
|
65
|
-
local-data: "signal-sim.lab. IN A
|
|
64
|
+
local-data: "signal-cli.lab. IN A 10.226.1.90"
|
|
65
|
+
local-data: "signal-sim.lab. IN A 10.226.1.91"
|
|
66
66
|
# signal-cli release host — the sim internet's stand-in for GitHub
|
|
67
67
|
# releases, so the signal module's deploy-time download resolves inside
|
|
68
68
|
# the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/bin/bash
|
|
2
2
|
# Generic startup for target machines (dmz/app/secure).
|
|
3
|
-
# The default gateway is passed as $1 (e.g., 10.
|
|
3
|
+
# The default gateway is passed as $1 (e.g., 10.226.10.1 for dmz).
|
|
4
4
|
set -e
|
|
5
5
|
|
|
6
6
|
GATEWAY="${1:?Usage: target-routes.sh <gateway-ip>}"
|
|
@@ -63,21 +63,23 @@ echo "nameserver 100.100.0.1" > /etc/resolv.conf
|
|
|
63
63
|
done
|
|
64
64
|
) &
|
|
65
65
|
|
|
66
|
-
#
|
|
67
|
-
#
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
#
|
|
71
|
-
#
|
|
72
|
-
#
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
66
|
+
# NOTHING BELOW MAY DEPEND ON EGRESS. Readiness is gated on this script
|
|
67
|
+
# (`systemctl is-active target-setup`, 60s), and this script runs the moment the
|
|
68
|
+
# container does — BEFORE fw-ext has installed its NAT. Until it does, a packet
|
|
69
|
+
# for the real internet is not refused, it is blackholed, so anything fetched
|
|
70
|
+
# here pays a full connect timeout rather than failing fast.
|
|
71
|
+
#
|
|
72
|
+
# This used to pre-warm the apt cache, whose sources include dl.cloudsmith.io:
|
|
73
|
+
# four IPs, each timing out, 64s against a 60s budget. Measured both ways on a
|
|
74
|
+
# live stack — from a converged target that same fetch is 302 in 0.19s; with
|
|
75
|
+
# fw-ext's NAT flushed it hangs past 70s. So the failure was never a slow CDN or
|
|
76
|
+
# a broken simulator, which is exactly what "Timeout waiting for caddy
|
|
77
|
+
# target-setup" sent everyone looking for, twice in one day.
|
|
78
|
+
#
|
|
79
|
+
# The apt lists it was warming are now populated at IMAGE BUILD time (see
|
|
80
|
+
# docker/Dockerfile.target-*): Ansible still gets complete indexes on first
|
|
81
|
+
# connect, and readiness no longer races the firewall. Egress itself is
|
|
82
|
+
# untouched — the topology still NATs out, and pebble/apt still use it at deploy
|
|
83
|
+
# time, by which point the firewall has long converged. See #560.
|
|
82
84
|
|
|
83
85
|
echo "Target machine ready: $(hostname)"
|
package/config/socks/startup.sh
CHANGED
|
@@ -10,7 +10,7 @@ VANTAGE="${VANTAGE:-isp-external}"
|
|
|
10
10
|
case "$VANTAGE" in
|
|
11
11
|
isp-external)
|
|
12
12
|
# Residential-user view. Default route via fw-ext (.101) which DNATs
|
|
13
|
-
# public-internet traffic into the dmz (caddy at 10.
|
|
13
|
+
# public-internet traffic into the dmz (caddy at 10.226.10.10). DNS via
|
|
14
14
|
# comcast-resolver (.1) which walks the root → TLD → namecheap chain.
|
|
15
15
|
GATEWAY="100.100.0.101"
|
|
16
16
|
NAMESERVER="100.100.0.1"
|
|
@@ -19,8 +19,8 @@ case "$VANTAGE" in
|
|
|
19
19
|
# Operator-on-LAN view. Default route via fw-isp (.1); split-horizon
|
|
20
20
|
# resolver at .10 returns the firewall's same-subnet NAT IP for the
|
|
21
21
|
# test domain, which DNATs to caddy.
|
|
22
|
-
GATEWAY="
|
|
23
|
-
NAMESERVER="
|
|
22
|
+
GATEWAY="10.226.1.1"
|
|
23
|
+
NAMESERVER="10.226.1.10"
|
|
24
24
|
;;
|
|
25
25
|
*)
|
|
26
26
|
echo "Unknown VANTAGE: $VANTAGE (expected isp-external or internal)" >&2
|
|
@@ -87,7 +87,21 @@ RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
|
|
|
87
87
|
gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
88
88
|
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
|
|
89
89
|
tee /etc/apt/sources.list.d/caddy-stable.list
|
|
90
|
-
|
|
90
|
+
# Once caddy is installed the cloudsmith source has no job left — but the file
|
|
91
|
+
# must STAY: the Ansible role skips re-adding it on `creates:`, so deleting it
|
|
92
|
+
# only moves the curl into the deploy. Neutered to a comment instead, so no
|
|
93
|
+
# `apt-get update` in the sim waits on a repo whose only package is already
|
|
94
|
+
# installed here.
|
|
95
|
+
#
|
|
96
|
+
# The lists are deliberately NOT cleaned afterwards. target-setup used to run
|
|
97
|
+
# `apt-get update` at boot to guarantee Ansible found complete indexes, paying
|
|
98
|
+
# an external round-trip inside the 60s readiness budget for it; baking the
|
|
99
|
+
# indexes here buys the same guarantee off that path. A source that fails to
|
|
100
|
+
# refresh at deploy time keeps this copy. See #560.
|
|
101
|
+
RUN apt-get update && apt-get install -y caddy \
|
|
102
|
+
&& printf '# neutered at image build: caddy is pre-installed (see #560)\n' \
|
|
103
|
+
> /etc/apt/sources.list.d/caddy-stable.list \
|
|
104
|
+
&& apt-get update
|
|
91
105
|
|
|
92
106
|
STOPSIGNAL SIGRTMIN+3
|
|
93
107
|
ENTRYPOINT ["/sbin/init"]
|
|
@@ -97,5 +97,11 @@ COPY config/routing/docker-image-preload.service /etc/systemd/system/docker-imag
|
|
|
97
97
|
RUN systemctl enable docker-image-preload
|
|
98
98
|
|
|
99
99
|
|
|
100
|
+
# Bake complete apt indexes into the image. target-setup used to run
|
|
101
|
+
# `apt-get update` at boot for this, which put a third party's latency inside
|
|
102
|
+
# the 60s readiness budget (#560). Doing it here gives Ansible the same
|
|
103
|
+
# guarantee — populated lists on first connect — off the readiness path.
|
|
104
|
+
RUN apt-get update
|
|
105
|
+
|
|
100
106
|
STOPSIGNAL SIGRTMIN+3
|
|
101
107
|
ENTRYPOINT ["/sbin/init"]
|
|
@@ -94,5 +94,11 @@ RUN printf '[Unit]\nDescription=Technitium DNS Server\nAfter=network.target\n\n[
|
|
|
94
94
|
> /etc/systemd/system/dns.service && \
|
|
95
95
|
systemctl enable dns
|
|
96
96
|
|
|
97
|
+
# Bake complete apt indexes into the image. target-setup used to run
|
|
98
|
+
# `apt-get update` at boot for this, which put a third party's latency inside
|
|
99
|
+
# the 60s readiness budget (#560). Doing it here gives Ansible the same
|
|
100
|
+
# guarantee — populated lists on first connect — off the readiness path.
|
|
101
|
+
RUN apt-get update
|
|
102
|
+
|
|
97
103
|
STOPSIGNAL SIGRTMIN+3
|
|
98
104
|
ENTRYPOINT ["/sbin/init"]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
4
|
"description": "E2E test infrastructure for Celilo-deployed applications. Provides a simulated internet with DNS hierarchy, ACME server, firewalls, and target machines in Docker.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
40
|
"@celilo/cli-display": "^0.1.9",
|
|
41
|
-
"@celilo/event-bus": "^0.1.
|
|
41
|
+
"@celilo/event-bus": "^0.1.10",
|
|
42
42
|
"yaml": "^2.8.0",
|
|
43
43
|
"zod": "^3.24.1"
|
|
44
44
|
},
|
|
@@ -68,7 +68,7 @@ const tlsOptions = (() => {
|
|
|
68
68
|
|
|
69
69
|
// Bind to internal interface only (like a real router's management UI)
|
|
70
70
|
// The external interface should only handle port-forwarded traffic via iptables
|
|
71
|
-
const BIND_HOST = process.env.ROUTER_BIND_HOST || '
|
|
71
|
+
const BIND_HOST = process.env.ROUTER_BIND_HOST || '10.226.1.1';
|
|
72
72
|
|
|
73
73
|
const server = Bun.serve({
|
|
74
74
|
port: PORT,
|
|
@@ -17,11 +17,11 @@ let nextIndex = 1;
|
|
|
17
17
|
const dhcpPool: Record<string, string> = {
|
|
18
18
|
Alias: 'cpe-Pool-1',
|
|
19
19
|
Enable: 'true',
|
|
20
|
-
MinAddress: '
|
|
21
|
-
MaxAddress: '
|
|
20
|
+
MinAddress: '10.226.1.2',
|
|
21
|
+
MaxAddress: '10.226.1.150',
|
|
22
22
|
SubnetMask: '255.255.255.0',
|
|
23
|
-
IPRouters: '
|
|
24
|
-
DNSServers: '
|
|
23
|
+
IPRouters: '10.226.1.1',
|
|
24
|
+
DNSServers: '10.226.1.1',
|
|
25
25
|
DomainName: '',
|
|
26
26
|
LeaseTime: '3600',
|
|
27
27
|
Status: 'Enabled',
|
|
@@ -121,7 +121,7 @@ export function setDhcpPool(params: Record<string, string>): CgiObject[] {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
function rewriteDnsmasqConfig(): void {
|
|
124
|
-
const dnsServers = dhcpPool.DNSServers || '
|
|
124
|
+
const dnsServers = dhcpPool.DNSServers || '10.226.1.1';
|
|
125
125
|
const domainName = dhcpPool.DomainName || '';
|
|
126
126
|
|
|
127
127
|
const lines = [
|
|
@@ -129,8 +129,8 @@ function rewriteDnsmasqConfig(): void {
|
|
|
129
129
|
'port=0',
|
|
130
130
|
'interface=eth0',
|
|
131
131
|
'bind-interfaces',
|
|
132
|
-
'dhcp-range=
|
|
133
|
-
'dhcp-option=option:router,
|
|
132
|
+
'dhcp-range=10.226.1.200,10.226.1.220,255.255.255.0,60s',
|
|
133
|
+
'dhcp-option=option:router,10.226.1.1',
|
|
134
134
|
`dhcp-option=option:dns-server,${dnsServers}`,
|
|
135
135
|
'dhcp-lease-max=20',
|
|
136
136
|
'log-dhcp',
|