@celilo/e2e 0.18.0 → 0.19.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.
@@ -24,9 +24,19 @@ iptables -P FORWARD ACCEPT
24
24
  # DNS
25
25
  echo "nameserver 203.0.113.1" > /etc/resolv.conf
26
26
 
27
- # Start dnsmasq DHCP server on internal network
28
- dnsmasq --conf-dir=/etc/dnsmasq.d --keep-in-foreground --log-facility=- &
29
- echo "dnsmasq DHCP server started"
27
+ # Start dnsmasq DHCP server on internal network.
28
+ #
29
+ # Skippable, because celilo can serve DHCP itself (`modules/dnsmasq-dhcp`) and
30
+ # two DHCP servers on one broadcast domain race: whichever answers a DISCOVER
31
+ # first wins, and a test asserting which one served a lease would be asserting
32
+ # a coin toss. An operator moving DHCP to celilo turns the router's off, so a
33
+ # suite that tests celilo's DHCP models that by setting ROUTER_DHCP=off.
34
+ if [ "${ROUTER_DHCP:-on}" = "off" ]; then
35
+ echo "dnsmasq DHCP server NOT started (ROUTER_DHCP=off; celilo serves DHCP here)"
36
+ else
37
+ dnsmasq --conf-dir=/etc/dnsmasq.d --keep-in-foreground --log-facility=- &
38
+ echo "dnsmasq DHCP server started"
39
+ fi
30
40
 
31
41
  # Start greenwave simulator
32
42
  if [ -f /simulator/server.ts ]; then
@@ -35,6 +35,12 @@ RUN apt-get update && apt-get install -y \
35
35
  procps \
36
36
  zsh \
37
37
  gnupg \
38
+ # The hook jail (openspec/changes/hook-process-boundary, stage 2) runs every
39
+ # hook under bubblewrap. Without it the management box runs hooks unjailed,
40
+ # which is a valid mode but not the one the e2e suite is there to exercise.
41
+ # The container needs security_opt relaxations to let it build a namespace —
42
+ # see the measured ladder in docker-compose-generator.ts.
43
+ bubblewrap \
38
44
  && rm -rf /var/lib/apt/lists/*
39
45
 
40
46
  # --- Terraform, plus an OFFLINE provider mirror -------------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@celilo/e2e",
3
- "version": "0.18.0",
3
+ "version": "0.19.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",
@@ -37,7 +37,7 @@
37
37
  "README.md"
38
38
  ],
39
39
  "dependencies": {
40
- "@celilo/capabilities": "^3.0.0",
40
+ "@celilo/capabilities": "^3.1.0",
41
41
  "@celilo/cli-display": "^0.2.0",
42
42
  "@celilo/event-bus": "^0.6.0",
43
43
  "@celilo/terraform-fake": "^0.3.1",
@@ -1264,8 +1264,14 @@ export async function startNetwork(config: NetworkConfig): Promise<NetworkHandle
1264
1264
  );
1265
1265
  }
1266
1266
 
1267
- // Wait for DHCP client lease
1268
- if (config.dhcpClient) {
1267
+ // Wait for DHCP client lease.
1268
+ //
1269
+ // Skipped when the router's DHCP is off, because then NOTHING is serving yet:
1270
+ // celilo's own DHCP server is a module, and modules deploy long after the
1271
+ // network is up. Waiting here would time out at 60s on every such suite and
1272
+ // report it as a network-start failure. Those suites drive the client
1273
+ // themselves once their server is deployed.
1274
+ if (config.dhcpClient && config.routerDhcp !== false) {
1269
1275
  console.log('[progress:start] waiting for DHCP client lease | DHCP lease acquired');
1270
1276
  // Self-diagnosing via waitFor's onTimeout (e2e-confidence #255): a DHCP-lease
1271
1277
  // timeout is an intermittent in-suite flake (passes solo). On timeout, dump
@@ -94,6 +94,12 @@ function baseService(opts: {
94
94
  environment?: Record<string, string>;
95
95
  tmpfs?: string[];
96
96
  privileged?: boolean;
97
+ /**
98
+ * Docker `security_opt` entries, e.g. `seccomp=unconfined`. Needed by the
99
+ * management box so the hook jail's bubblewrap can build a namespace inside
100
+ * a container — see the measured ladder at its call site.
101
+ */
102
+ security_opt?: string[];
97
103
  dns?: string[];
98
104
  /** Host devices to expose, e.g. `/dev/net/tun` for a userspace WireGuard tunnel. */
99
105
  devices?: string[];
@@ -215,7 +221,13 @@ function defaultTopologyServices(config: NetworkConfig): Record<string, unknown>
215
221
  volumes: ['ssh-keys:/ssh-keys:ro'],
216
222
  // Which real ISP router this sim stands in for. The protocol is identical
217
223
  // across the two; only the TR-181 vendor extension prefix differs.
218
- environment: { ROUTER_VENDOR_PREFIX: config.routerVendorPrefix ?? 'X_GWS_' },
224
+ environment: {
225
+ ROUTER_VENDOR_PREFIX: config.routerVendorPrefix ?? 'X_GWS_',
226
+ // The simulated router serves DHCP on `internal` by default, which is
227
+ // what an operator's router does. A suite testing celilo's OWN DHCP
228
+ // turns it off, because two servers on one broadcast domain race.
229
+ ROUTER_DHCP: config.routerDhcp === false ? 'off' : 'on',
230
+ },
219
231
  }),
220
232
  };
221
233
  }
@@ -568,7 +580,34 @@ export function generateTestComposeYaml(config: NetworkConfig, celiloRoot?: stri
568
580
  image:
569
581
  variant === 'vanilla' ? 'celilo-e2e/management:vanilla' : 'celilo-e2e/management:latest',
570
582
  networks: { [zone]: { ipv4_address: ip } },
571
- cap_add: ['NET_ADMIN'],
583
+ // SYS_ADMIN is for the hook jail, NET_ADMIN for the routing the box does
584
+ // anyway. See the security_opt block below for why both are here.
585
+ cap_add: ['NET_ADMIN', 'SYS_ADMIN'],
586
+ // The hook jail runs each hook under bubblewrap, which has to build a
587
+ // user namespace and then mount inside it. Docker denies that four
588
+ // separate ways, and every one of these is load-bearing — measured on a
589
+ // colima Ubuntu 24.04.1 VM (kernel 6.8.0-50,
590
+ // kernel.apparmor_restrict_unprivileged_userns=1), by removing one at a
591
+ // time from the working set:
592
+ //
593
+ // drop seccomp=unconfined -> bwrap: pivot_root: Operation not permitted
594
+ // drop apparmor=unconfined -> bwrap: Failed to make / slave: Permission denied
595
+ // drop systempaths=unconfined -> bwrap: Can't mount proc on /newroot/proc
596
+ // drop SYS_ADMIN -> bwrap: setting up uid map: Permission denied
597
+ //
598
+ // Deliberately NOT `privileged: true`, which design D8 measured as
599
+ // working and task 4.1 asked to improve on. It would also grant every
600
+ // capability and every host device; this set grants four named things.
601
+ //
602
+ // `systempaths=unconfined` is the one D8's ladder was missing: it unmasks
603
+ // the /proc paths Docker hides, which is why "all three" of the earlier
604
+ // options still failed on the proc mount.
605
+ //
606
+ // NOTE this makes the container a WEAK place to test the AppArmor
607
+ // profile: apparmor=unconfined means the restriction the profile exists
608
+ // to satisfy is not in force here. That gate is the celilo-builder probe
609
+ // on a real kernel, never this container.
610
+ security_opt: ['seccomp=unconfined', 'apparmor=unconfined', 'systempaths=unconfined'],
572
611
  // ISS-0157: `module import`'s `bun install` hard-links packages from the
573
612
  // bun cache into node_modules, which on overlayfs forces a copy-up + fsync
574
613
  // per file. On a slow/contended builder disk that storm blows past the
@@ -140,6 +140,23 @@ export class NetworkBuilder {
140
140
  return this;
141
141
  }
142
142
 
143
+ /**
144
+ * Stop the simulated ISP router serving DHCP on `internal`.
145
+ *
146
+ * For suites where CELILO serves DHCP (`modules/dnsmasq-dhcp`). Two servers
147
+ * on one broadcast domain race, so without this a lease assertion is a coin
148
+ * toss. It is also what an operator does when moving DHCP to celilo, so the
149
+ * suite models the migration rather than dodging a conflict.
150
+ *
151
+ * NOTE: `withDhcpClient()`'s start-up lease wait expects SOMETHING to answer.
152
+ * Pair this with a suite that deploys celilo's DHCP server, or the client
153
+ * gets no lease and the network never comes up.
154
+ */
155
+ withoutRouterDhcp(): this {
156
+ this.config.routerDhcp = false;
157
+ return this;
158
+ }
159
+
143
160
  /**
144
161
  * Add the Proxmox API simulator on `secure-mgmt`.
145
162
  *
package/src/types.ts CHANGED
@@ -66,6 +66,19 @@ export interface NetworkConfig {
66
66
  * serves both rather than a near-duplicate second one.
67
67
  */
68
68
  routerVendorPrefix?: string;
69
+ /**
70
+ * Whether the simulated ISP router serves DHCP on `internal`. Defaults to
71
+ * true, which is what an operator's router does and what every existing suite
72
+ * relies on.
73
+ *
74
+ * Set false when celilo serves DHCP instead (`modules/dnsmasq-dhcp`). Two
75
+ * DHCP servers on one broadcast domain race — whichever answers a DISCOVER
76
+ * first wins — so a suite asserting WHICH server issued a lease would be
77
+ * asserting a coin toss. Turning the router's off is also what a real
78
+ * operator does when moving DHCP to celilo, so this models the migration
79
+ * rather than papering over a conflict.
80
+ */
81
+ routerDhcp?: boolean;
69
82
  /** Extra volume mounts for the management container (host:container format) */
70
83
  managementVolumes: string[];
71
84
  /** Include a DHCP client container on the internal network */