@celilo/e2e 0.17.1 → 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.
- package/bin/e2e-status +1 -1
- package/config/namecheap/ddns-tls.crt +21 -0
- package/config/namecheap/ddns-tls.key +28 -0
- package/config/namecheap/namecheap-ca.crt +20 -0
- package/config/routing/fw-isp-routes.sh +13 -3
- package/config/routing/management-routes.sh +0 -3
- package/docker/Dockerfile.management +12 -0
- package/docker/Dockerfile.namecheap-dns +6 -0
- package/package.json +2 -2
- package/simulators/namecheap-ddns/server.ts +22 -1
- package/src/container-manager.ts +13 -2
- package/src/docker-compose-generator.ts +41 -4
- package/src/network-builder.ts +17 -0
- package/src/simulator-ips.ts +1 -1
- package/src/types.ts +26 -1
package/bin/e2e-status
CHANGED
|
@@ -32,4 +32,4 @@ done
|
|
|
32
32
|
echo ""
|
|
33
33
|
echo "=== DDNS Simulator ==="
|
|
34
34
|
echo -n " Status: "
|
|
35
|
-
docker compose -p "$PROJECT_NAME" exec -T namecheap-dns curl -s
|
|
35
|
+
docker compose -p "$PROJECT_NAME" exec -T namecheap-dns curl -s --cacert /etc/ssl/namecheap-sim/namecheap-ca.crt https://dynamicdns.park-your-domain.com/status --resolve dynamicdns.park-your-domain.com:443:127.0.0.1 2>/dev/null || echo "(not running)"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDfTCCAmWgAwIBAgIUdjRGJDcAnXSrb8e8pgTGItaituQwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwJjEkMCIGA1UEAwwbY2VsaWxvIGUyZSBOYW1lY2hlYXAgc2ltIENBMCAXDTI2
|
|
4
|
+
MDgyMTA2NDEyNFoYDzIxMjYwNzI4MDY0MTI0WjAqMSgwJgYDVQQDDB9keW5hbWlj
|
|
5
|
+
ZG5zLnBhcmsteW91ci1kb21haW4uY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
|
|
6
|
+
MIIBCgKCAQEAqWyqjNCSLxfJLhgwJBT78SzmLb9sRdHCrXYrxeqBF3dHR3aM5/oj
|
|
7
|
+
CuiwT1l8P87cCauTbfHXSpOKRa0HDaQ5ArCQgBOxcK0Q4FzeUKRUtfbA9QR+b7ym
|
|
8
|
+
FQDSV7nYG8oOFnQybr/uKpfRaficzGQQJZDZ3mEXC24J5LLc0PYc6kgASZMy8QY6
|
|
9
|
+
PkCVdzX8viu0J3jGL780W/7AkNuLyzBmBs9SOyWy8vETY4MsRgm0Umv/Rh0MD33P
|
|
10
|
+
n/D5EFYb8bbKAWnEXbpY7p7N7o2Er1IrNq1SD4TCkmR9VLeNm+pdYt6RKBaFoAYd
|
|
11
|
+
Jbhk5YjnefqvlCPYyW2jyHOkh2I6up/P4QIDAQABo4GcMIGZMCoGA1UdEQQjMCGC
|
|
12
|
+
H2R5bmFtaWNkbnMucGFyay15b3VyLWRvbWFpbi5jb20wCQYDVR0TBAIwADALBgNV
|
|
13
|
+
HQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwHQYDVR0OBBYEFMpDRmoxUAwc
|
|
14
|
+
WJg/0WKwllOse/y7MB8GA1UdIwQYMBaAFNLIv6wf7Q7vOHzctgjyYKGapXkwMA0G
|
|
15
|
+
CSqGSIb3DQEBCwUAA4IBAQAgp2bS+l/5mcoHVTlpt7pKbSx/LNzKAoatbchIQuoI
|
|
16
|
+
uaEy77r6XhLC1BAcqQRIQ38zBDHSQHcR6bI4nSY24HnczZHxM9r6LoJ8MKvXuuH+
|
|
17
|
+
rllrJb3qqvQECO9y4N3bvoK2RT6EjkwqNRsuvn0qtfSOkY2StJrMBdIlFjlAO0FZ
|
|
18
|
+
Bu1+ZBYeqRP+AoTidjzSuU7JgGhGEHLdKJnoBlo4o9FbHgGBw8wgQiUUnWUfcHhw
|
|
19
|
+
Uy+6f2RvK8jrPh2baWnqxpLRBBZdfmN4yu7JKs88uMBpRA9lD29essb/Q9MP8F/H
|
|
20
|
+
2N0zizK46OSkimSMKACbI5U/f2E7JCuQukZV6VotrchC
|
|
21
|
+
-----END CERTIFICATE-----
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
-----BEGIN PRIVATE KEY-----
|
|
2
|
+
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCpbKqM0JIvF8ku
|
|
3
|
+
GDAkFPvxLOYtv2xF0cKtdivF6oEXd0dHdozn+iMK6LBPWXw/ztwJq5Nt8ddKk4pF
|
|
4
|
+
rQcNpDkCsJCAE7FwrRDgXN5QpFS19sD1BH5vvKYVANJXudgbyg4WdDJuv+4ql9Fp
|
|
5
|
+
+JzMZBAlkNneYRcLbgnkstzQ9hzqSABJkzLxBjo+QJV3Nfy+K7QneMYvvzRb/sCQ
|
|
6
|
+
24vLMGYGz1I7JbLy8RNjgyxGCbRSa/9GHQwPfc+f8PkQVhvxtsoBacRduljuns3u
|
|
7
|
+
jYSvUis2rVIPhMKSZH1Ut42b6l1i3pEoFoWgBh0luGTliOd5+q+UI9jJbaPIc6SH
|
|
8
|
+
Yjq6n8/hAgMBAAECggEAMUj3qgPoaoOgVeTaaDykd6mIgglb3UmiOKSWIjpP+DwO
|
|
9
|
+
A6dkYP0eYYeD9mlBICy0TKnZNtPxvXOC9zbYxOqfXP1w45kW9oCZ1rKdR1cuf0p7
|
|
10
|
+
Iit+/lEh/R0ikztwNtFF3NlLbHUvurs0Q3/7WOfI3Kyw/CRBOGV3wkwXuT/G9I2p
|
|
11
|
+
PzPc5eB0XThMXw5TfpcQfRedCOTjGelVk/fDF7cqRuK/g9f77XTfZ76ftLFBREvv
|
|
12
|
+
/7v8rJMoljL08fpU30r1xAFRNQHLfkUVLQxB1l3g9BJ91VieSUHgWljdS4hGDvf/
|
|
13
|
+
djfSIzq4bpkkoYVt8jGOA9+7peahVhWzCEdjVFTlKwKBgQDSi4v+1SCllgqoLSRe
|
|
14
|
+
FvFlWEwMeJWGN2qqxrXfWURgK8ODMgDxYjvwtFr5kO+JfWFrsT5DLYduWOyUkU/O
|
|
15
|
+
b8Qw3Sd2MAgU2cO/ejf/xDIgyIVao+Mg+x2pAADm6NFknUiBHOvZ0MbLZHUpa7uD
|
|
16
|
+
Z0fFXDqhKyIAGC0DvKqD8qp26wKBgQDOAHSZ1kfic074FmUU136I82pwdViVrEHA
|
|
17
|
+
OMNmNaqnrU9HkKqrwIqHrOTdEMNQnkB+OMmJNK6H4N54PILze+TR+EYu6d1BiTWK
|
|
18
|
+
3TAyhaA/eNGa3NuVyg8/wLFPxHj7QLUpv7JsqBKg45/GlPyXU5HtDX0Bsi3COlfX
|
|
19
|
+
YlQi70y5YwKBgHFnwMxON/t4ydy0E/aqHPQdjzjYapGUn9vElIntJys152VvNZrO
|
|
20
|
+
8sJAIxBxxNQluKetHIBKsbv6IdQUQQxMnzxCotLCJFg2w30Lc8ZMA0QtPccIp3pI
|
|
21
|
+
SD2TRukZVk//d+IzlcYI8krznTfmU2X/52SivOG6veGXJAfzQ1U3DCJpAoGAde4Q
|
|
22
|
+
0li3gk3csW2wpEPRbnlbP9INynF4mqBkTEkAz0kKD8Aj1Hs/UviJdHy6M5bE+P1t
|
|
23
|
+
cpy0JZl4AoExvk346rMlCopbXb885ahGhUW2PV/fLratBo1MOYUIfM/PKpcanzHc
|
|
24
|
+
5RM6xtxIhZiZGdB/w6NrIuNsCrr3upfR7sXruF0CgYEAskixy85n2KZzr8kEz3Vb
|
|
25
|
+
ji7zwB9Nl5qSYThc73dmaDCeLGeStfvpjg6TZNHtB9IXzpCo11D1jN04uHigMhAt
|
|
26
|
+
N5hvFmI5eLGLL6EEpMusms9EVPkclybMnS/ykjOX4YNxwTn5fi28ITN36VSulFN/
|
|
27
|
+
OWZYLOMmP6gMqfNJ7ktAA5o=
|
|
28
|
+
-----END PRIVATE KEY-----
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-----BEGIN CERTIFICATE-----
|
|
2
|
+
MIIDLzCCAhegAwIBAgIUeuo47ZC6Ka3Zfq7a36MZPnASRQwwDQYJKoZIhvcNAQEL
|
|
3
|
+
BQAwJjEkMCIGA1UEAwwbY2VsaWxvIGUyZSBOYW1lY2hlYXAgc2ltIENBMCAXDTI2
|
|
4
|
+
MDgyMTA2NDEyNFoYDzIxMjYwNzI4MDY0MTI0WjAmMSQwIgYDVQQDDBtjZWxpbG8g
|
|
5
|
+
ZTJlIE5hbWVjaGVhcCBzaW0gQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEK
|
|
6
|
+
AoIBAQC2W2bH5m1J6SAkRTT7vspz22V9mWlv1TzWOqVuuRBFQ4IrBlSKrCrgOdi6
|
|
7
|
+
/IAu9rkY2DJsgt3ts4L4IhJnA3VRGU+UltdAwPuo5eOHGq9aLPwaa/Rb7q4Act+a
|
|
8
|
+
53wVp3eLDAEvY4fk+dHUay87Ar2sXC+8WgwVZwUGoOpRE7/IWab9Q98a3hDYMQ1h
|
|
9
|
+
B71z2Ecz1g1z1w/zBqjiR4IMwgVV2VZ9yCmXBSad2mVCPf/51rWuY9ooZw73MbRc
|
|
10
|
+
PByejx0Fa8jigfFszG/oE2i7alO3ISmJzODgX8AuqqO3yVokKGTUY1DsJxas1U27
|
|
11
|
+
vhlHtYfVEhnUXgWyZphWvr38DslLAgMBAAGjUzBRMB0GA1UdDgQWBBTSyL+sH+0O
|
|
12
|
+
7zh83LYI8mChmqV5MDAfBgNVHSMEGDAWgBTSyL+sH+0O7zh83LYI8mChmqV5MDAP
|
|
13
|
+
BgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQA4BF303aKFEXpTHrPY
|
|
14
|
+
L3zopaL6EYTL6fzU8dhB5ZwCXfCJUa5Rx860kIFSjSaUd3rCfJ/70MTe9SQUGKVF
|
|
15
|
+
dt659/PA8olQtZQJsNOSjduQxAgwGe3T8V3G/qDogGDTAJKWGMQCWYSEwBjr30pQ
|
|
16
|
+
keyX1ahBzWwSg3FmdEOt35SfhvFQ4S/TAoEM33QARq+jJfMBupPNSIETgcr+Rimn
|
|
17
|
+
YWbshg1zfXuQnju9qV3R1bEST7ZSH+CjXhPu6CCbu/jTT3lPRIB04w2Yw5IAi1Kh
|
|
18
|
+
SzCF3TlQEUXpkPhlYpglhW0q3wgfp7QmnquNLHo6E8I+PyBjkSkg+Xq2cw14bE0i
|
|
19
|
+
MWHp
|
|
20
|
+
-----END CERTIFICATE-----
|
|
@@ -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
|
-
|
|
29
|
-
|
|
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
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
set -e
|
|
3
3
|
|
|
4
4
|
# System-wide environment for all processes (hooks, ansible, etc.)
|
|
5
|
-
echo 'DDNS_ENDPOINT=http://100.64.0.55:8080/update' >> /etc/environment
|
|
6
|
-
export DDNS_ENDPOINT="http://100.64.0.55:8080/update"
|
|
7
5
|
|
|
8
6
|
# Generate SSH keys if needed
|
|
9
7
|
/config/ssh/generate-keys.sh /ssh-keys
|
|
@@ -105,7 +103,6 @@ cat > /root/.zshrc << ZSHRC
|
|
|
105
103
|
export PATH="/root/.bun/bin:/usr/local/bin:\$PATH"
|
|
106
104
|
|
|
107
105
|
# Simulated network overrides
|
|
108
|
-
export DDNS_ENDPOINT="http://100.64.0.55:8080/update"
|
|
109
106
|
|
|
110
107
|
# History
|
|
111
108
|
HISTFILE=/root/.zsh_history
|
|
@@ -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 -------------------------------
|
|
@@ -96,6 +102,12 @@ COPY config/pebble/pebble-ca.crt /usr/local/share/ca-certificates/pebble-ca.crt
|
|
|
96
102
|
# external_web's authoritative post-publish check HTTPS-GETs the route from
|
|
97
103
|
# here, which is why mgmt in particular must trust it.
|
|
98
104
|
COPY config/cpanel-host/cpanel-host-ca.crt /usr/local/share/ca-certificates/cpanel-host-ca.crt
|
|
105
|
+
# Trust the Namecheap DDNS sim's CA. Its cert is likewise PRE-INSTALLED rather
|
|
106
|
+
# than Pebble-issued: celilo does not own a third-party API's TLS. This is what
|
|
107
|
+
# lets namecheap's hooks call the REAL endpoint URL over real TLS, instead of
|
|
108
|
+
# being redirected at an http://ip:port by an environment variable that an
|
|
109
|
+
# allow-listed hook environment no longer carries.
|
|
110
|
+
COPY config/namecheap/namecheap-ca.crt /usr/local/share/ca-certificates/namecheap-ca.crt
|
|
99
111
|
RUN update-ca-certificates
|
|
100
112
|
|
|
101
113
|
# Tell bun (and Node-compat code) to merge the system CA bundle with
|
|
@@ -14,6 +14,12 @@ RUN apt-get update && apt-get install -y \
|
|
|
14
14
|
RUN curl -fsSL https://bun.sh/install | bash
|
|
15
15
|
ENV PATH="/root/.bun/bin:$PATH"
|
|
16
16
|
|
|
17
|
+
# The endpoint's own certificate, PRE-INSTALLED the way a real third-party
|
|
18
|
+
# service's is (same pattern as the cPanel host sim). The CA rides along so the
|
|
19
|
+
# container can probe its own HTTPS endpoint without disabling verification.
|
|
20
|
+
COPY config/namecheap/ddns-tls.crt config/namecheap/ddns-tls.key config/namecheap/namecheap-ca.crt /etc/ssl/namecheap-sim/
|
|
21
|
+
RUN chmod 600 /etc/ssl/namecheap-sim/ddns-tls.key
|
|
22
|
+
|
|
17
23
|
COPY simulators/namecheap-ddns/ /simulator/
|
|
18
24
|
COPY config/routing/dns-routes.sh /dns-routes.sh
|
|
19
25
|
RUN chmod +x /dns-routes.sh
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/e2e",
|
|
3
|
-
"version": "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": "^
|
|
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",
|
|
@@ -46,7 +46,24 @@ const PASSWORD_MAP: Record<string, string> = (() => {
|
|
|
46
46
|
return {};
|
|
47
47
|
})();
|
|
48
48
|
const FALLBACK_PASSWORD = process.env.DDNS_PASSWORD || 'test123';
|
|
49
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The real Namecheap DDNS endpoint is `https://dynamicdns.park-your-domain.com`,
|
|
51
|
+
* so that is what this serves: port 443, with a certificate for that name.
|
|
52
|
+
*
|
|
53
|
+
* It used to listen on plain HTTP at 8080 and the rig pointed the module at
|
|
54
|
+
* `http://100.64.0.55:8080/update` through a `DDNS_ENDPOINT` environment
|
|
55
|
+
* variable. That bypassed both the DNS chain the rig had already built (the
|
|
56
|
+
* `park-your-domain.com` zone has always been there) and TLS, and it stopped
|
|
57
|
+
* working when hooks became subprocesses with an allow-listed environment
|
|
58
|
+
* (openspec/changes/hook-process-boundary, design D5).
|
|
59
|
+
*
|
|
60
|
+
* The certificate is PRE-INSTALLED rather than issued by Pebble, the same way
|
|
61
|
+
* the cPanel host simulator's is, and for the same reason: celilo does not own
|
|
62
|
+
* this service's TLS. A real third-party API has a certificate from a real CA
|
|
63
|
+
* long before celilo talks to it, and there is no ACME exchange to model.
|
|
64
|
+
*/
|
|
65
|
+
const PORT = Number(process.env.DDNS_PORT) || 443;
|
|
66
|
+
const TLS_DIR = process.env.DDNS_TLS_DIR || '/etc/ssl/namecheap-sim';
|
|
50
67
|
|
|
51
68
|
function expectedPasswordFor(domain: string): string {
|
|
52
69
|
return PASSWORD_MAP[domain] ?? FALLBACK_PASSWORD;
|
|
@@ -107,6 +124,10 @@ function normalizeIp(address: string): string {
|
|
|
107
124
|
|
|
108
125
|
const server = Bun.serve({
|
|
109
126
|
port: PORT,
|
|
127
|
+
tls: {
|
|
128
|
+
cert: Bun.file(`${TLS_DIR}/ddns-tls.crt`),
|
|
129
|
+
key: Bun.file(`${TLS_DIR}/ddns-tls.key`),
|
|
130
|
+
},
|
|
110
131
|
fetch(req, server) {
|
|
111
132
|
const url = new URL(req.url);
|
|
112
133
|
|
package/src/container-manager.ts
CHANGED
|
@@ -813,6 +813,11 @@ function buildNetworkHandle(
|
|
|
813
813
|
await handle.celilo(`module config set iptables firewall_ip ${firewallIp}`);
|
|
814
814
|
await handle.celilo(`module config set iptables nat_ip ${natIp}`);
|
|
815
815
|
await handle.celilo(`module config set iptables zones '${JSON.stringify(declaredZones)}'`);
|
|
816
|
+
// Caller-supplied settings last, so a suite can turn on an opt-in policy
|
|
817
|
+
// the way an operator would rather than assert against the default.
|
|
818
|
+
for (const [key, value] of Object.entries(opts.config ?? {})) {
|
|
819
|
+
await handle.celilo(`module config set iptables ${key} ${value}`);
|
|
820
|
+
}
|
|
816
821
|
// `check: false` returns the failed deploy instead of throwing, so a test
|
|
817
822
|
// can assert on WHAT the refusal said. Interface classification refuses
|
|
818
823
|
// by design (D12 onboarding), and a refusal is only useful if it names
|
|
@@ -1259,8 +1264,14 @@ export async function startNetwork(config: NetworkConfig): Promise<NetworkHandle
|
|
|
1259
1264
|
);
|
|
1260
1265
|
}
|
|
1261
1266
|
|
|
1262
|
-
// Wait for DHCP client lease
|
|
1263
|
-
|
|
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) {
|
|
1264
1275
|
console.log('[progress:start] waiting for DHCP client lease | DHCP lease acquired');
|
|
1265
1276
|
// Self-diagnosing via waitFor's onTimeout (e2e-confidence #255): a DHCP-lease
|
|
1266
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: {
|
|
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
|
-
|
|
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
|
|
@@ -595,7 +634,6 @@ export function generateTestComposeYaml(config: NetworkConfig, celiloRoot?: stri
|
|
|
595
634
|
...(config.managementVolumes ?? []),
|
|
596
635
|
],
|
|
597
636
|
environment: {
|
|
598
|
-
DDNS_ENDPOINT: `http://${SIMULATOR_IPS.NAMECHEAP_DNS}:8080/update`,
|
|
599
637
|
// On its own control-plane network the only router in reach is fw-main's
|
|
600
638
|
// leg there — both for the default route and for the segmented zones.
|
|
601
639
|
DEFAULT_GATEWAY: zone === 'secure-mgmt' ? SECURE_MGMT_GATEWAY : managementDefaultGw,
|
|
@@ -821,7 +859,6 @@ export function generateComposeYaml(config: NetworkConfig, celiloRoot = '..'): s
|
|
|
821
859
|
...(config.managementVolumes ?? []),
|
|
822
860
|
],
|
|
823
861
|
environment: {
|
|
824
|
-
DDNS_ENDPOINT: `http://${SIMULATOR_IPS.NAMECHEAP_DNS}:8080/update`,
|
|
825
862
|
DEFAULT_GATEWAY: managementDefaultGw,
|
|
826
863
|
CELILO_REGISTRY_URL: 'http://e2e-registry.lab',
|
|
827
864
|
},
|
package/src/network-builder.ts
CHANGED
|
@@ -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/simulator-ips.ts
CHANGED
|
@@ -23,7 +23,7 @@ export const SIMULATOR_IPS = {
|
|
|
23
23
|
ROOT_DNS: '100.64.0.53',
|
|
24
24
|
/** Authoritative TLD DNS for `com.`, `org.`, `net.`. */
|
|
25
25
|
TLD_DNS: '100.64.0.54',
|
|
26
|
-
/** Authoritative DNS for the customer domains; also serves the DDNS update endpoint at
|
|
26
|
+
/** Authoritative DNS for the customer domains; also serves the DDNS update endpoint at https://dynamicdns.park-your-domain.com. */
|
|
27
27
|
NAMECHEAP_DNS: '100.64.0.55',
|
|
28
28
|
/** Internal e2e module registry (replaces production celilo.computer in tests). */
|
|
29
29
|
REGISTRY: '100.64.0.56',
|
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 */
|
|
@@ -449,7 +462,19 @@ export interface NetworkHandle {
|
|
|
449
462
|
*
|
|
450
463
|
* @param opts.natIp - NAT IP for port-forwarded traffic (default 10.226.1.253)
|
|
451
464
|
*/
|
|
452
|
-
|
|
465
|
+
/**
|
|
466
|
+
* @param opts.config - Extra `module config set iptables <key> <value>` pairs,
|
|
467
|
+
* applied after the topology-derived ones and before the deploy. For
|
|
468
|
+
* settings whose whole point is that they are OPT-IN: the harness deploys a
|
|
469
|
+
* DEFAULT firewall, so a suite testing an opt-in policy has to turn it on
|
|
470
|
+
* the way an operator would, and asserting on a default deploy would only
|
|
471
|
+
* ever measure the default.
|
|
472
|
+
*/
|
|
473
|
+
deployFirewall(opts?: {
|
|
474
|
+
natIp?: string;
|
|
475
|
+
check?: boolean;
|
|
476
|
+
config?: Record<string, string>;
|
|
477
|
+
}): Promise<ExecResult>;
|
|
453
478
|
|
|
454
479
|
/**
|
|
455
480
|
* Deploy the `greenwave` module against the ISP router (`fw-isp`) — the box
|