@celilo/e2e 0.9.3 → 0.10.2

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.
Files changed (62) hide show
  1. package/config/cpanel-host/cpanel-host-ca.crt +20 -0
  2. package/config/cpanel-host/docroot/.htaccess +14 -0
  3. package/config/cpanel-host/docroot/cms.html +4 -0
  4. package/config/cpanel-host/site-tls.crt +20 -0
  5. package/config/cpanel-host/site-tls.key +28 -0
  6. package/config/cpanel-host/site.conf +27 -0
  7. package/config/dns/com.zone +7 -0
  8. package/config/dns/knot-namecheap.conf +6 -0
  9. package/config/dns/tangohost.com.zone +14 -0
  10. package/config/resolver/unbound.conf +26 -0
  11. package/config/routing/fw-ext-routes.sh +16 -1
  12. package/config/routing/management-routes.sh +14 -4
  13. package/config/routing/minio-startup.sh +9 -0
  14. package/config/routing/public-sim-entrypoint.sh +24 -0
  15. package/config/routing/resolver-internal-routes.sh +10 -0
  16. package/docker/Dockerfile.apt-repo-sim +8 -0
  17. package/docker/Dockerfile.cpanel-host-sim +75 -0
  18. package/docker/Dockerfile.firewall +12 -0
  19. package/docker/Dockerfile.management +6 -0
  20. package/docker/Dockerfile.minio +7 -0
  21. package/docker/Dockerfile.npm-registry-sim +8 -0
  22. package/docker/Dockerfile.observer +11 -0
  23. package/docker/Dockerfile.registry +8 -0
  24. package/docker/Dockerfile.signal-cli +60 -0
  25. package/docker/Dockerfile.signal-release +44 -0
  26. package/docker/Dockerfile.signal-sim +17 -0
  27. package/docker/Dockerfile.target-machine +9 -0
  28. package/package.json +3 -3
  29. package/registry-server/package.json +17 -0
  30. package/registry-server/src/auth.test.ts +76 -0
  31. package/registry-server/src/auth.ts +105 -0
  32. package/registry-server/src/bootstrap-packaging.test.ts +71 -0
  33. package/registry-server/src/bootstrap.ts +250 -0
  34. package/registry-server/src/index.ts +16 -0
  35. package/registry-server/src/introspection.test.ts +247 -0
  36. package/registry-server/src/introspection.ts +204 -0
  37. package/registry-server/src/landing.ts +90 -0
  38. package/registry-server/src/module-owner-store.test.ts +85 -0
  39. package/registry-server/src/module-owner-store.ts +108 -0
  40. package/registry-server/src/rate-limit.test.ts +62 -0
  41. package/registry-server/src/rate-limit.ts +83 -0
  42. package/registry-server/src/scoped-token-store.test.ts +93 -0
  43. package/registry-server/src/scoped-token-store.ts +110 -0
  44. package/registry-server/src/server.test.ts +771 -0
  45. package/registry-server/src/server.ts +701 -0
  46. package/registry-server/src/storage.test.ts +148 -0
  47. package/registry-server/src/storage.ts +150 -0
  48. package/registry-server/src/validation.test.ts +86 -0
  49. package/registry-server/src/validation.ts +60 -0
  50. package/registry-server/tsconfig.json +15 -0
  51. package/scripts/stage-libsignal.ts +116 -0
  52. package/simulators/signal-cli/server.ts +328 -0
  53. package/src/cli/build.ts +7 -0
  54. package/src/cli/index.ts +6 -0
  55. package/src/container-manager.ts +47 -10
  56. package/src/docker-compose-generator.ts +120 -16
  57. package/src/exit-cleanup.test.ts +56 -0
  58. package/src/index.ts +7 -0
  59. package/src/network-builder.ts +66 -0
  60. package/src/public-sim-routes.test.ts +117 -0
  61. package/src/simulator-ips.ts +9 -0
  62. package/src/types.ts +71 -1
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDMzCCAhugAwIBAgIUNwg2dEjTZomtYfUlMacwvA348c4wDQYJKoZIhvcNAQEL
3
+ BQAwKDEmMCQGA1UEAwwdY2VsaWxvIGUyZSBjUGFuZWwtaG9zdCBzaW0gQ0EwIBcN
4
+ MjYwNzI5MTI1MDM3WhgPMjEyNjA3MDUxMjUwMzdaMCgxJjAkBgNVBAMMHWNlbGls
5
+ byBlMmUgY1BhbmVsLWhvc3Qgc2ltIENBMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A
6
+ MIIBCgKCAQEAqCdcXJ1jCLtUBVTH/Tu9VMhGnVjWy6UL5s99zprxxH09u23UWwAa
7
+ Ya48BIBbS25bv9YinLfWo5BtL7fOtZYNw3LpWSYi6NRAyUldt9mDOQ0jufQRZs5E
8
+ 3HiRRIdHKF5ABQRTM7toMWFW6X7kTSH4ySJqQUtXThf/9C4bca8dDLNwIt+3HMfq
9
+ 9vLP7uobTtNkasNZhmpKePtU5U90igwF04bOKF51nvTBABpuK/MrSOVXFjpabsrl
10
+ LHy7dG0I2J3C//N++qeBpXqEO1O7r9rcGN97FV7s5IM1TF0s/JPnztqt7mgiXYQf
11
+ bFAQ91Wrog2ROSecAxaXG/T2SrpTzVkrKQIDAQABo1MwUTAdBgNVHQ4EFgQUkib0
12
+ DPU9hPj3xiY5hHjHcgq/bNcwHwYDVR0jBBgwFoAUkib0DPU9hPj3xiY5hHjHcgq/
13
+ bNcwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAdbGBHyoCoaAC
14
+ mIM8VMjWBUwC6lPiPsrKXidym6AH1Sz446ZUje+q/h1NSeNx6hRpW92gY8AYylmN
15
+ cnzs6fB2cibToxxIQM1IgdGdjRBHvyJKjDOC9oWrRxuzEqoDtM3IFIO0rWmW8ubF
16
+ ouxakB1EAm9Lg+I7ZfljGjK7lhdPOnUPcciNR4TguDyeNFzUIvI9Ajc4HDgnom41
17
+ 8lJ7kgYFiyVsThKXGalO2RphjGJ9mdF4UzQnukJQZSe+tsFhREsZtUFFAApZy6QF
18
+ XdC33NNtDAOPH/iYL1E+t2E50t1NfC6z7keiBPFzfWVeNGr6dGko7xZlr5YkSCym
19
+ ++fPX7Ivrg==
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,14 @@
1
+ # BEGIN WordPress
2
+ # The stock WordPress front-controller rules, verbatim in shape: any request
3
+ # that is NOT a real file and NOT a real directory is handed to the CMS. A
4
+ # published subfolder (e.g. /nexus/) IS a real directory, so it is served from
5
+ # disk and never reaches the CMS — which is exactly what external_web relies on.
6
+ <IfModule mod_rewrite.c>
7
+ RewriteEngine On
8
+ RewriteBase /
9
+ RewriteRule ^index\.php$ - [L]
10
+ RewriteCond %{REQUEST_FILENAME} !-f
11
+ RewriteCond %{REQUEST_FILENAME} !-d
12
+ RewriteRule . /cms.html [L]
13
+ </IfModule>
14
+ # END WordPress
@@ -0,0 +1,4 @@
1
+ <!doctype html>
2
+ <title>Tango Host CMS</title>
3
+ <h1>CELILO_E2E_CMS_MARKER</h1>
4
+ <p>PHP-free stand-in for the host's own WordPress front controller.</p>
@@ -0,0 +1,20 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIDPzCCAiegAwIBAgIUBUnN9NoJfptkeic1uhSMmvr/KskwDQYJKoZIhvcNAQEL
3
+ BQAwKDEmMCQGA1UEAwwdY2VsaWxvIGUyZSBjUGFuZWwtaG9zdCBzaW0gQ0EwIBcN
4
+ MjYwNzI5MTI1MDM3WhgPMjEyNjA3MDUxMjUwMzdaMBgxFjAUBgNVBAMMDXRhbmdv
5
+ aG9zdC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDXAn3dqIFu
6
+ MjApQp1HL+V3O6rTxgH5Z+0x3PqFegGDD1sPR1FNj9X5WfG7cipZgvQsjVI8xE/b
7
+ 8j9BDLyUdhyIiDiDGnz4goGE45t6n3kN9llaOLHEvPliZSx/XuCseu6LbzwMGJc6
8
+ bdB6PFmKr0FYXZX67KwFyN/s/IXEpaCfJIEjPF7VJDXW+p4XMGkPuIgGki7+8u9p
9
+ ZfhP5rZPPxEyWxjruS72nxfPC97G7LhSh40i5KGAQIYaSYuPuhUr+3xEdF8DVmA1
10
+ x+JtXBkIBfrIfC64qDwVk1QTUn0FhYsBBL8L4eynED/pRaToOWPggZSmie1nYAtE
11
+ pzNgAACOIO13AgMBAAGjbzBtMCsGA1UdEQQkMCKCDXRhbmdvaG9zdC5jb22CEXd3
12
+ dy50YW5nb2hvc3QuY29tMB0GA1UdDgQWBBS7FibNCjP8A4HR4SrlP5taWQfPmjAf
13
+ BgNVHSMEGDAWgBSSJvQM9T2E+PfGJjmEeMdyCr9s1zANBgkqhkiG9w0BAQsFAAOC
14
+ AQEAQPTJ95Lu7RhObp8ONTfbt275GsO7osRqPX8EoWsySu1Vhs7BWvOgs0zlRgo2
15
+ QSAyXuW5ZmHEVvq1OOfsA/HAkZn0H1h6z6Fz7RHlgYLFhkd6IAE8JIg705a9w2Zo
16
+ denA9PV2SFCsEEIa7hQ/BG4YgvBvf7pCOtdHg3WlUyhLMvkENJh2m/x4AATmICqs
17
+ uNCu0HuxpaitaiqtTyXcAy1yRITPoZYfKt66j6MmEaPB/e0Rs3GcQuY2VsTb3YYS
18
+ q5cEjxxOhDZL50d2F4UTwoy9244lBgIO/T1MCMlnLuqMmRDxvF4drTmt2QjShJ6n
19
+ z/x7eUUMGyBbHqBE73ZgG26eEQ==
20
+ -----END CERTIFICATE-----
@@ -0,0 +1,28 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDXAn3dqIFuMjAp
3
+ Qp1HL+V3O6rTxgH5Z+0x3PqFegGDD1sPR1FNj9X5WfG7cipZgvQsjVI8xE/b8j9B
4
+ DLyUdhyIiDiDGnz4goGE45t6n3kN9llaOLHEvPliZSx/XuCseu6LbzwMGJc6bdB6
5
+ PFmKr0FYXZX67KwFyN/s/IXEpaCfJIEjPF7VJDXW+p4XMGkPuIgGki7+8u9pZfhP
6
+ 5rZPPxEyWxjruS72nxfPC97G7LhSh40i5KGAQIYaSYuPuhUr+3xEdF8DVmA1x+Jt
7
+ XBkIBfrIfC64qDwVk1QTUn0FhYsBBL8L4eynED/pRaToOWPggZSmie1nYAtEpzNg
8
+ AACOIO13AgMBAAECggEADjLz7Pd71LMryw3DJThchwtBtMqWxsm+OFTHlpkAEI+r
9
+ C89B50VgdB8kf6SCd3bWeTq7+5dbTsMoujsFGSjTqZCVatMM/bxgoU4GroppjCqA
10
+ rByiYMO6FG1qCt9RJxLXPg4SgRn+orUt12S05Ru9zQAhL3kBw0AIybXDf8EQYnH/
11
+ tbRhx3/YHcaIgRjzd/zTBmfgmiYNGInNO8q2O6UssuHpdFC2YPHkdHhLZNWX5gBz
12
+ 0VHK++ff4ZkpzOOZVlS//UoKMMtwez4dutu+ZX7jGCeXkKEwuNyXmPBRj5vpeGoy
13
+ J/CaGArR+7ut/dOqZv7bEF5bp6dZWXxjdHtaga+OwQKBgQDw2efq+In79MfRgdJt
14
+ duk0wkuGress648qzPczGfLu7LzwY7Q+QUVPgrziUmkEgw/Xvw+TarLi1KaBizYt
15
+ yP2kB3HP7Z4Xufe7M3N/98S9VcOELH8/MT4L5uxhkqeaJ0n4ev3alEih4HKCL4Bs
16
+ NKqdWPds6EE+hOoVsTJLS17JowKBgQDkiH8SJ0WL4zmGknEdI3CB7BbsVlCtR/Wx
17
+ Qw6fsn5Fwian7SfMLJSc3FpZ2E0BM8PJKNLoXU9sxGRgQT0HczGFyAKW9ifh4K/r
18
+ EovG4+9Xh9s56c8AFTs6mMQgL1lhsFgyzUwNxscGOTqp949QAHe7QLY6G5IzYi3q
19
+ /8p0m5jyHQKBgDrYQn16M9xHNfrOCSeNgfTN8734CBl1hAklAtCNanlNhewFsCf1
20
+ soV2n8d78wSTMO+5Uojl7MbWxceox5fwH2hQGVJ/77xER26JZ5LAamUH6zkD1iMO
21
+ WOZ3iRFdz20uZDg9dIBO8l/6gn+kM3A8feLrtaf6JtarirW6EVusEoCZAoGAOoRM
22
+ 7UwK/ce/+hpTRl/Fti7Kq9dqLwSG/82o1/FvbdNJ7KPIFfqwTv4JUIoXJaComECQ
23
+ yJjj9njPLuplgxvW6WeOajM7gRX0VZ/ZFKNWrLYHx2t76oyFLvrg9VDKdT4fR1rU
24
+ B48HdSuAPvc5xgVooxTt1DEtHWUdVtgKchNJdW0CgYBdYIBZr/tafno7kXJAGl2Q
25
+ /I7hNXvtAsw8OMU8mls6DXc2RXxO1J4V67IjBoyqyFoBZD10N5Si7vxgCdIv9Vhc
26
+ b/wQAHC9b2C4qX2i6UlUL60XQmNlGchh/fLL8R9dOE0+rhvkkK+CyF2vRUuGxchE
27
+ JZdQ89koDLYQMtdzeEGnOg==
28
+ -----END PRIVATE KEY-----
@@ -0,0 +1,27 @@
1
+ # The off-fleet host's own vhost. celilo does NOT manage this file — the point
2
+ # of external_web is that the host owns its web server, TLS, and DNS, and
3
+ # celilo only drops files into the document root.
4
+ <VirtualHost *:80>
5
+ ServerName tangohost.com
6
+ ServerAlias www.tangohost.com
7
+ DocumentRoot /home/theglo18/tangohost.com
8
+ <Directory /home/theglo18/tangohost.com>
9
+ Options -Indexes +FollowSymLinks
10
+ AllowOverride All
11
+ Require all granted
12
+ </Directory>
13
+ </VirtualHost>
14
+
15
+ <VirtualHost *:443>
16
+ ServerName tangohost.com
17
+ ServerAlias www.tangohost.com
18
+ DocumentRoot /home/theglo18/tangohost.com
19
+ SSLEngine on
20
+ SSLCertificateFile /etc/ssl/cpanel-sim/site-tls.crt
21
+ SSLCertificateKeyFile /etc/ssl/cpanel-sim/site-tls.key
22
+ <Directory /home/theglo18/tangohost.com>
23
+ Options -Indexes +FollowSymLinks
24
+ AllowOverride All
25
+ Require all granted
26
+ </Directory>
27
+ </VirtualHost>
@@ -9,3 +9,10 @@ com. IN NS tld-dns.lab.
9
9
 
10
10
  park-your-domain IN NS ns1.park-your-domain.com.
11
11
  ns1.park-your-domain.com. IN A 100.64.0.55
12
+
13
+ ; The OFF-FLEET web host (external_web). Unlike every customer name, this one
14
+ ; resolves to the host's OWN public address (100.64.0.63), NOT the customer
15
+ ; firewall — because the host genuinely is a third party out on the internet,
16
+ ; not a service behind the customer's NAT. No RFC1918 leak: .62 is public.
17
+ tangohost IN NS ns1.tangohost.com.
18
+ ns1.tangohost.com. IN A 100.64.0.55
@@ -14,6 +14,12 @@ zone:
14
14
  file: "/config/example.net.zone"
15
15
  storage: "/var/lib/knot"
16
16
 
17
+ # The OFF-FLEET web host's own zone. Served here only because this is the
18
+ # simulator's authoritative nameserver; celilo never writes to it.
19
+ - domain: "tangohost.com"
20
+ file: "/config/tangohost.com.zone"
21
+ storage: "/var/lib/knot"
22
+
17
23
  - domain: "park-your-domain.com"
18
24
  file: "/config/park-your-domain.com.zone"
19
25
  storage: "/var/lib/knot"
@@ -0,0 +1,14 @@
1
+ $ORIGIN tangohost.com.
2
+ $TTL 300
3
+
4
+ @ IN SOA ns1.tangohost.com. admin.tangohost.com. (
5
+ 2026072901 300 60 604800 300
6
+ )
7
+
8
+ ; The OFF-FLEET host's own public DNS — it owns this zone, celilo does not.
9
+ ; Both names point at the host's own public address, NOT the customer
10
+ ; firewall (100.100.0.100): this host lives out on the simulated internet.
11
+ @ IN NS ns1.tangohost.com.
12
+ ns1 IN A 100.64.0.55
13
+ @ IN A 100.64.0.63
14
+ www IN A 100.64.0.63
@@ -23,6 +23,21 @@ server:
23
23
  local-data: "npm-registry.lab. IN A 100.64.0.59"
24
24
  # MinIO S3 simulator — backup/restore target. See SIMULATOR_IPS.MINIO.
25
25
  local-data: "minio.lab. IN A 100.64.0.61"
26
+ # apt-repo simulator. Also needed HERE, not only in the internal resolver:
27
+ # module target hosts resolve through the PUBLIC resolver, and in production
28
+ # apt.celilo.computer is a publicly-resolvable name — so a host fetching a
29
+ # package from it is the faithful case, not a shortcut.
30
+ local-data: "apt.celilo.lab. IN A 100.64.0.60"
31
+ # signal-cli daemon (real, unlinked) and its simulator counterpart. Both
32
+ # sit on the `internal` network — celilo-mgr reaches a notification
33
+ # transport locally; a transport needing public ingress could not tell
34
+ # you the ingress was broken.
35
+ local-data: "signal-cli.lab. IN A 192.168.0.90"
36
+ local-data: "signal-sim.lab. IN A 192.168.0.91"
37
+ # signal-cli release host — the sim internet's stand-in for GitHub
38
+ # releases, so the signal module's deploy-time download resolves inside
39
+ # the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
40
+ local-data: "signal-release.lab. IN A 100.64.0.62"
26
41
  # External website prober simulator — without this override, isitup.org
27
42
  # falls through the "forward-zone: ." below and resolves to the REAL
28
43
  # Namecheap-parked domain on the public internet.
@@ -50,6 +65,17 @@ stub-zone:
50
65
  name: "park-your-domain.com."
51
66
  stub-addr: 100.64.0.55
52
67
 
68
+ # The OFF-FLEET web host's domain (external_web). Like every other simulated
69
+ # domain it needs a stub, or it falls through to the real internet — and
70
+ # tangohost.com is a REAL registered name out there, so the leak resolves to a
71
+ # real address and the SSH bootstrap times out against a stranger's server.
72
+ # namecheap-dns is authoritative for the zone; the zone points at the host's own
73
+ # public address (100.64.0.63), NOT the customer firewall, because the host
74
+ # genuinely is a third party on the internet.
75
+ stub-zone:
76
+ name: "tangohost.com."
77
+ stub-addr: 100.64.0.55
78
+
53
79
  # Everything else → the real internet, via Docker's embedded resolver.
54
80
  #
55
81
  # 127.0.0.11 is Docker's per-container DNS; it relays to whatever the HOST is
@@ -13,7 +13,22 @@ ip route add 10.0.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
 
16
- # NAT/forward between isp-external and internet-external
16
+ # NAT/forward between isp-external and internet-external.
17
+ #
18
+ # The customer's public /24 is ROUTED, never re-NAT'd. An ISP does not NAT a
19
+ # subscriber that already holds a public address — the whole point of
20
+ # 100.100.0.100 is that it IS the customer's address on the internet. fw-ext
21
+ # used to MASQUERADE it anyway, so every packet leaving the fleet reached the
22
+ # public simulators bearing fw-ext's own 100.64.0.1.
23
+ #
24
+ # That is not cosmetic. Namecheap-style DDNS registers the SOURCE address of
25
+ # the update when the caller omits `ip=` (which is now how celilo registers
26
+ # public names — #464/#466), so the double NAT published 100.64.0.1 for every
27
+ # public hostname: an address that DNATs to nothing, breaking ACME validation
28
+ # and any inbound reach. It also silently clobbered seeded apex records.
29
+ # Anything that reasons about "the address the internet sees us as" was
30
+ # measuring the simulator's own artefact.
31
+ iptables -t nat -A POSTROUTING -s 100.100.0.0/24 -j RETURN
17
32
  iptables -t nat -A POSTROUTING -j MASQUERADE
18
33
  iptables -P FORWARD ACCEPT
19
34
 
@@ -22,17 +22,27 @@ chmod 600 /root/.ssh/config
22
22
  # Routing — DEFAULT_GATEWAY comes from docker-compose environment
23
23
  # default: 192.168.0.1 (fw-isp), direct-internet: 192.168.0.254 (fw-main)
24
24
  GATEWAY="${DEFAULT_GATEWAY:-192.168.0.1}"
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 (192.168.0.254). When
27
+ # celilo-mgr lives on its OWN control-plane network (managementZone secure-mgmt),
28
+ # it reaches them via fw-main's leg on THAT network instead.
29
+ FW_MAIN="${FW_MAIN_HOP:-192.168.0.254}"
25
30
  ip route del default 2>/dev/null || true
26
31
  ip route add default via "$GATEWAY"
27
- ip route add 10.0.10.0/24 via 192.168.0.254 # dmz via fw-main
28
- ip route add 10.0.20.0/24 via 192.168.0.254 # app via fw-main
29
- ip route add 10.0.30.0/24 via 192.168.0.254 # secure via fw-main
32
+ ip route add 10.0.10.0/24 via "$FW_MAIN" # dmz via fw-main
33
+ ip route add 10.0.20.0/24 via "$FW_MAIN" # app via fw-main
34
+ ip route add 10.0.30.0/24 via "$FW_MAIN" # secure via fw-main
35
+ # Control plane. Only reachable when a secure-mgmt network exists — that is,
36
+ # when celilo-mgr lives there itself, or when a module declaring
37
+ # `zone: secure-mgmt` needs a system to land on (#436). Tolerated silently
38
+ # otherwise so the default topology is unchanged.
39
+ ip route add 10.0.120.0/24 via "$FW_MAIN" 2>/dev/null || true
30
40
 
31
41
  # DNS — Technitium internal resolver (split-horizon) with public fallback.
32
42
  # Technitium at 192.168.0.10 is authoritative for iamtheinternet.org internally,
33
43
  # resolving www/auth to Caddy's DMZ IP (10.0.10.10) rather than the public WAN IP.
34
44
  # Fall back to the simulated public resolver if Technitium is not yet up.
35
- printf 'nameserver 192.168.0.10\nnameserver 100.100.0.1\n' > /etc/resolv.conf
45
+ printf 'nameserver %s\nnameserver 100.100.0.1\n' "${INTERNAL_RESOLVER:-192.168.0.10}" > /etc/resolv.conf
36
46
 
37
47
  # Fetch Pebble's runtime ACME root CA (Pebble generates a fresh root at each
38
48
  # startup, unlike the static /config/pebble/pebble-ca.crt baked in at build
@@ -7,6 +7,15 @@
7
7
  # `mc mb --ignore-existing`.
8
8
  set -e
9
9
 
10
+ # Route the customer's public prefix via the ISP edge (fw-ext), like every
11
+ # other host on internet-external. Docker's default bridge gateway has no path
12
+ # across networks, so without this a reply to the customer's WAN address is
13
+ # dropped and `backup --storage` hangs. fw-ext is per-test, so this is
14
+ # expected to fail while shared infra runs alone — hence the tolerance.
15
+ # (`busybox ip` because the MinIO image ships no iproute2.)
16
+ busybox ip route del default 2>/dev/null || true
17
+ busybox ip route add default via 100.64.0.1 2>/dev/null || true
18
+
10
19
  # Serve the S3 API on :80 so the management box reaches it over the SAME
11
20
  # proven HTTP egress path as the other public sims (apt-repo, npm-registry):
12
21
  # fw-ext's Squid-bypass exception is scoped to dport 80/443 → 100.64.0.0/24, so
@@ -0,0 +1,24 @@
1
+ #!/bin/sh
2
+ # Entrypoint wrapper for a simulator on `internet-external` — a host out on
3
+ # the simulated public internet.
4
+ #
5
+ # Such a host must reach the customer's public prefix (100.100.0.0/24) through
6
+ # the ISP edge, fw-ext. Docker's default bridge gateway has no path across
7
+ # networks, so without this route a reply to the customer's WAN address is
8
+ # simply dropped and the connection hangs.
9
+ #
10
+ # This used to be invisible: fw-ext MASQUERADEd the customer to its own
11
+ # 100.64.0.1, which is on-link here, so replies never needed routing at all.
12
+ # Once fw-ext stopped re-NATing a subscriber that already holds a public
13
+ # address (the ISP routes it; it does not NAT it), the omission surfaced —
14
+ # every sim that had this route already kept working, and the four that did
15
+ # not started timing out. celilo-website-sim, isitup, pebble, namecheap-dns
16
+ # and the DNS hierarchy have carried it all along; this is the same two lines.
17
+ #
18
+ # fw-ext is per-test, so the route is unreachable while shared infra runs
19
+ # alone. That is harmless — nothing dials the customer before a test brings
20
+ # fw-ext up — hence the tolerant `|| true`.
21
+ ip route del default 2>/dev/null || true
22
+ ip route add default via 100.64.0.1 2>/dev/null || true
23
+
24
+ exec "$@"
@@ -57,6 +57,16 @@ $(printf "${SPLIT_HORIZON_BLOCK}")
57
57
  # MinIO S3 simulator — backup/restore target for the migration e2e.
58
58
  # See SIMULATOR_IPS.MINIO.
59
59
  local-data: "minio.lab. IN A 100.64.0.61"
60
+ # signal-cli daemon (real, unlinked) and its simulator counterpart. Both
61
+ # sit on the `internal` network — celilo-mgr reaches a notification
62
+ # transport locally; a transport needing public ingress could not tell
63
+ # you the ingress was broken.
64
+ local-data: "signal-cli.lab. IN A 192.168.0.90"
65
+ local-data: "signal-sim.lab. IN A 192.168.0.91"
66
+ # signal-cli release host — the sim internet's stand-in for GitHub
67
+ # releases, so the signal module's deploy-time download resolves inside
68
+ # the sealed network. See SIMULATOR_IPS.SIGNAL_RELEASE.
69
+ local-data: "signal-release.lab. IN A 100.64.0.62"
60
70
 
61
71
  forward-zone:
62
72
  name: "."
@@ -49,8 +49,16 @@ RUN set -eux; \
49
49
  -o APT::FTPArchive::Release::Architectures="amd64 arm64" \
50
50
  release dists/stable > dists/stable/Release
51
51
 
52
+ # Route the customer's public prefix via the ISP edge, like every other host
53
+ # on internet-external. See config/routing/public-sim-entrypoint.sh.
54
+ RUN apt-get update \
55
+ && apt-get install -y --no-install-recommends iproute2 \
56
+ && rm -rf /var/lib/apt/lists/*
57
+ COPY config/routing/public-sim-entrypoint.sh /usr/local/bin/public-sim-entrypoint.sh
58
+
52
59
  EXPOSE 80
53
60
 
54
61
  # python3's http.server is enough for apt (GET + range requests over static
55
62
  # files). No special MIME/headers needed.
63
+ ENTRYPOINT ["/bin/sh", "/usr/local/bin/public-sim-entrypoint.sh"]
56
64
  CMD ["python3", "-m", "http.server", "80", "--directory", "/repo"]
@@ -0,0 +1,75 @@
1
+ # An OFF-FLEET web host: a third-party cPanel/SSH shared account that celilo
2
+ # publishes to but does NOT govern. It is a PUBLIC PEER on `internet-external`
3
+ # alongside namecheap-dns and pebble — NOT behind the customer firewall — so
4
+ # public DNS resolving its domain to its own address bends no RFC1918 rule.
5
+ #
6
+ # Faithful to what we verified against the live A2/CloudLinux account:
7
+ # - sshd on 7822, an unprivileged account (no root, no systemd control)
8
+ # - Apache + mod_rewrite serving the stock WordPress .htaccess, so a real
9
+ # directory under the docroot is served BEFORE the CMS front controller
10
+ # - the domain's TLS certificate pre-installed (as AutoSSL would leave it),
11
+ # not obtained via ACME — external_web never owns TLS
12
+ # - ~/.cpanel/userdata/<domain> carrying the authoritative documentroot
13
+ #
14
+ # No systemd: sshd + apache are all this host runs, so a two-process entrypoint
15
+ # is enough and boots in a fraction of the time.
16
+ FROM ubuntu:22.04
17
+
18
+ ENV DEBIAN_FRONTEND=noninteractive
19
+
20
+ COPY config/apt/99retries.conf /etc/apt/apt.conf.d/99retries.conf
21
+
22
+ RUN apt-get update && apt-get install -y \
23
+ openssh-server \
24
+ apache2 \
25
+ ca-certificates \
26
+ iproute2 \
27
+ iputils-ping \
28
+ curl \
29
+ && rm -rf /var/lib/apt/lists/*
30
+
31
+ RUN a2enmod rewrite ssl && a2dissite 000-default
32
+
33
+ # sshd on 7822 with password auth ON — the bootstrap's whole job is to log in
34
+ # with the account password once and install celilo's key, so the account must
35
+ # accept a password until that happens (as a real shared host does).
36
+ RUN mkdir -p /run/sshd && \
37
+ sed -i 's/^#\?Port .*/Port 7822/' /etc/ssh/sshd_config && \
38
+ sed -i 's/^#\?PasswordAuthentication.*/PasswordAuthentication yes/' /etc/ssh/sshd_config && \
39
+ sed -i 's/^#\?PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config && \
40
+ sed -i 's/^#\?PermitRootLogin.*/PermitRootLogin no/' /etc/ssh/sshd_config
41
+
42
+ # The account. Unprivileged and NOT in sudo — celilo cannot run a daemon, bind
43
+ # a port, or touch the firewall here. That inability is what "off-fleet" means.
44
+ RUN useradd -m -s /bin/bash theglo18 && \
45
+ echo 'theglo18:tango-e2e-password' | chpasswd
46
+
47
+ # The host's own site, in the account's home the way cPanel lays an addon
48
+ # domain out, plus the userdata file external_web reads the docroot from.
49
+ COPY config/cpanel-host/docroot/ /home/theglo18/tangohost.com/
50
+ RUN mkdir -p /home/theglo18/.cpanel/userdata && \
51
+ printf 'documentroot: /home/theglo18/tangohost.com\nservername: tangohost.com\nuser: theglo18\n' \
52
+ > /home/theglo18/.cpanel/userdata/tangohost.com && \
53
+ chown -R theglo18:theglo18 /home/theglo18
54
+
55
+ # Apache runs as www-data but must read/serve files the account writes.
56
+ RUN usermod -aG theglo18 www-data && chmod 755 /home/theglo18
57
+
58
+ # Pre-installed TLS for the domain (a committed sim CA + leaf, so the cert is
59
+ # deterministic). mgmt trusts this CA — see Dockerfile.management.
60
+ COPY config/cpanel-host/site-tls.crt config/cpanel-host/site-tls.key /etc/ssl/cpanel-sim/
61
+ COPY config/cpanel-host/site.conf /etc/apache2/sites-available/tangohost.conf
62
+ RUN a2ensite tangohost && chmod 600 /etc/ssl/cpanel-sim/site-tls.key
63
+
64
+ EXPOSE 7822 80 443
65
+
66
+ # apache2 is PID 1; sshd runs beside it. If either dies the container exits,
67
+ # which is what we want from a fixture host. The runtime/lock dirs live under
68
+ # /var/run, which the container wipes at start, so recreate them before apache
69
+ # reads DefaultRuntimeDir.
70
+ # Route the customer's public prefix via the ISP edge, like every other host
71
+ # on internet-external. See config/routing/public-sim-entrypoint.sh.
72
+ COPY config/routing/public-sim-entrypoint.sh /usr/local/bin/public-sim-entrypoint.sh
73
+
74
+ ENTRYPOINT ["/bin/sh", "/usr/local/bin/public-sim-entrypoint.sh"]
75
+ CMD ["/bin/sh", "-c", "mkdir -p /var/run/apache2 /var/lock/apache2 /run/sshd && /usr/sbin/sshd && . /etc/apache2/envvars && exec apache2 -D FOREGROUND"]
@@ -12,8 +12,20 @@ RUN apt-get update && apt-get install -y \
12
12
  dnsutils \
13
13
  procps \
14
14
  python3 \
15
+ wireguard-tools \
16
+ wireguard-go \
15
17
  && rm -rf /var/lib/apt/lists/*
16
18
 
19
+ # Containers have no wireguard kernel module to load, and a test must not depend
20
+ # on whether the Docker host happens to have one. wg-quick falls back to this
21
+ # userspace implementation when `ip link add type wireguard` fails, so the tunnel
22
+ # behaves the same on any host — it just needs /dev/net/tun + NET_ADMIN.
23
+ # NOTE the value: Ubuntu's `wireguard-go` package installs its binary as
24
+ # /usr/bin/wireguard, NOT `wireguard-go`. Naming the binary wrong here fails
25
+ # only on a host WITHOUT the kernel module — i.e. it passes locally and
26
+ # breaks in CI, which is the worst possible way for this to be wrong.
27
+ ENV WG_QUICK_USERSPACE_IMPLEMENTATION=wireguard
28
+
17
29
  RUN mkdir -p /run/sshd && \
18
30
  sed -i 's/#PermitRootLogin.*/PermitRootLogin yes/' /etc/ssh/sshd_config && \
19
31
  sed -i 's/#PubkeyAuthentication.*/PubkeyAuthentication yes/' /etc/ssh/sshd_config
@@ -44,6 +44,12 @@ RUN chsh -s /bin/zsh root
44
44
  COPY config/proxy/squid-ca.crt /usr/local/share/ca-certificates/e2e-proxy-ca.crt
45
45
  # Trust Pebble's test CA (so bun fetch() trusts certs issued by the simulated Let's Encrypt)
46
46
  COPY config/pebble/pebble-ca.crt /usr/local/share/ca-certificates/pebble-ca.crt
47
+ # Trust the OFF-FLEET cPanel host sim's CA. That host's cert is PRE-INSTALLED
48
+ # (as a real shared host's AutoSSL cert is) rather than issued by Pebble —
49
+ # external_web never owns TLS — so its CA is a separate, static trust anchor.
50
+ # external_web's authoritative post-publish check HTTPS-GETs the route from
51
+ # here, which is why mgmt in particular must trust it.
52
+ COPY config/cpanel-host/cpanel-host-ca.crt /usr/local/share/ca-certificates/cpanel-host-ca.crt
47
53
  RUN update-ca-certificates
48
54
 
49
55
  # Tell bun (and Node-compat code) to merge the system CA bundle with
@@ -19,8 +19,15 @@
19
19
 
20
20
  FROM minio/mc:latest AS mc
21
21
 
22
+ # Static busybox purely for its `ip` applet: the MinIO image has no iproute2
23
+ # and no package manager worth invoking, but it still needs the ISP route
24
+ # every host on internet-external carries (see minio-startup.sh).
25
+ FROM busybox:stable-musl AS busybox
26
+
22
27
  FROM minio/minio:latest
23
28
 
29
+ COPY --from=busybox /bin/busybox /usr/local/bin/busybox
30
+
24
31
  # Bring the MinIO client in from the mc image so the entrypoint can create the
25
32
  # bucket once the server is up.
26
33
  COPY --from=mc /usr/bin/mc /usr/bin/mc
@@ -22,5 +22,13 @@ ENV PACKUMENT_DIR=/var/lib/npm \
22
22
  PORT=80 \
23
23
  REGISTRY_URL=http://npm-registry.lab
24
24
 
25
+ # Route the customer's public prefix via the ISP edge, like every other host
26
+ # on internet-external. See config/routing/public-sim-entrypoint.sh.
27
+ RUN apt-get update \
28
+ && apt-get install -y --no-install-recommends iproute2 \
29
+ && rm -rf /var/lib/apt/lists/*
30
+ COPY config/routing/public-sim-entrypoint.sh /usr/local/bin/public-sim-entrypoint.sh
31
+
25
32
  EXPOSE 80
33
+ ENTRYPOINT ["/bin/sh", "/usr/local/bin/public-sim-entrypoint.sh"]
26
34
  CMD ["bun", "run", "src/server.ts"]
@@ -18,8 +18,19 @@ RUN apt-get update && apt-get install -y \
18
18
  iputils-ping \
19
19
  iproute2 \
20
20
  ca-certificates \
21
+ wireguard-tools \
22
+ wireguard-go \
21
23
  && rm -rf /var/lib/apt/lists/*
22
24
 
25
+ # A vantage may need to dial a VPN and probe from INSIDE the tunnel — the only
26
+ # way to assert VPN reach by real signal rather than by reading a rule string.
27
+ # Userspace implementation so the test doesn't depend on the Docker host's kernel.
28
+ # NOTE the value: Ubuntu's `wireguard-go` package installs its binary as
29
+ # /usr/bin/wireguard, NOT `wireguard-go`. Naming the binary wrong here fails
30
+ # only on a host WITHOUT the kernel module — i.e. it passes locally and
31
+ # breaks in CI, which is the worst possible way for this to be wrong.
32
+ ENV WG_QUICK_USERSPACE_IMPLEMENTATION=wireguard
33
+
23
34
  COPY config/routing/observer-setup.sh /startup.sh
24
35
  RUN chmod +x /startup.sh
25
36
 
@@ -30,5 +30,13 @@ ENV DATA_DIR=/var/lib/celilo-registry \
30
30
  # somewhere to put files before the server runs.
31
31
  RUN mkdir -p /uploads
32
32
 
33
+ # Route the customer's public prefix via the ISP edge, like every other host
34
+ # on internet-external. See config/routing/public-sim-entrypoint.sh.
35
+ RUN apt-get update \
36
+ && apt-get install -y --no-install-recommends iproute2 \
37
+ && rm -rf /var/lib/apt/lists/*
38
+ COPY config/routing/public-sim-entrypoint.sh /usr/local/bin/public-sim-entrypoint.sh
39
+
33
40
  EXPOSE 80
41
+ ENTRYPOINT ["/bin/sh", "/usr/local/bin/public-sim-entrypoint.sh"]
34
42
  CMD ["bun", "run", "src/index.ts"]
@@ -0,0 +1,60 @@
1
+ # Real signal-cli, for verifying celilo's client against the actual JSON-RPC
2
+ # interface rather than against a simulator that encodes our assumptions.
3
+ #
4
+ # The account is never linked. That is deliberate and sufficient: signal-cli's
5
+ # HTTP interface is served locally and does not touch Signal's network, so the
6
+ # endpoint paths, envelope shapes, error shapes, SSE receive stream, and the
7
+ # Host/Content-Type guards are all verifiable without an account.
8
+ #
9
+ # What an unlinked daemon CANNOT verify is that a linked account delivers a
10
+ # message to a phone. That needs Signal's real network. DNS is ours in e2e and
11
+ # even the TLS trust store is a swappable classpath resource (`whisper.store`,
12
+ # password "whisper" — see StagingConfig.java), but CDSI/SVR2 use SGX remote
13
+ # attestation whose whole purpose is to make substitution impossible. That
14
+ # limit is real; everything short of it is not.
15
+ #
16
+ # Three facts learned by RUNNING this rather than reading about it:
17
+ # - signal-cli 0.14.6 ships class-file version 69, so it needs a Java 25 JRE.
18
+ # A 21 base dies at startup with UnsupportedClassVersionError.
19
+ # - it needs the native libsignal-client (signal_jni), shipped x86_64 only —
20
+ # as is the "Linux-native" build. Hence the amd64 pin.
21
+ # - extracting the tarball inside the amd64 stage fails under qemu on an
22
+ # arm64 host ("Cannot mkdir: Function not implemented"), so the fetch runs
23
+ # on the builder's own architecture and only the files cross over.
24
+
25
+ ARG SIGNAL_CLI_VERSION=0.14.6
26
+
27
+ FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS fetch
28
+ ARG SIGNAL_CLI_VERSION
29
+ RUN apt-get update \
30
+ && apt-get install -y --no-install-recommends curl ca-certificates \
31
+ && rm -rf /var/lib/apt/lists/*
32
+ # Fetched at BUILD time — the e2e network is sealed at run time.
33
+ RUN curl -fsSL \
34
+ "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz" \
35
+ -o /tmp/signal-cli.tar.gz \
36
+ && mkdir -p /out \
37
+ && tar -xzf /tmp/signal-cli.tar.gz -C /out \
38
+ && mv "/out/signal-cli-${SIGNAL_CLI_VERSION}" /out/signal-cli
39
+ # The published JSON schemas are the authoritative description of the JSON-RPC
40
+ # surface — a test can assert against them rather than against prose.
41
+ RUN curl -fsSL \
42
+ "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}-json-schemas.tar.gz" \
43
+ -o /tmp/schemas.tar.gz \
44
+ && mkdir -p /out/schemas \
45
+ && tar -xzf /tmp/schemas.tar.gz -C /out/schemas
46
+
47
+ FROM --platform=linux/amd64 eclipse-temurin:25-jre
48
+ COPY --from=fetch /out/signal-cli /opt/signal-cli
49
+ COPY --from=fetch /out/schemas /opt/signal-cli-schemas
50
+
51
+ ENV PATH="/opt/signal-cli/bin:${PATH}"
52
+ ENV SIGNAL_CLI_DATA=/var/lib/signal-cli
53
+ RUN mkdir -p "${SIGNAL_CLI_DATA}"
54
+
55
+ EXPOSE 8080
56
+
57
+ # Bound to 0.0.0.0 so the Host-header pin is skipped for in-network callers
58
+ # (HttpServerHandler.isHostAllowed). The container only ever sits on the sealed
59
+ # e2e network.
60
+ CMD ["signal-cli", "--config", "/var/lib/signal-cli", "daemon", "--http", "0.0.0.0:8080"]
@@ -0,0 +1,44 @@
1
+ # The signal-cli release host — the sim internet's stand-in for the GitHub
2
+ # releases the signal module downloads at deploy time.
3
+ #
4
+ # Same reason npm-registry-sim and apt-repo-sim exist: a module that fetches an
5
+ # artifact from the internet must still be deployable inside a sealed network,
6
+ # and the honest way to test that is to SERVE the artifact rather than to skip
7
+ # the download. The module points at this host through its `release_base_url`
8
+ # config, exactly as caddy points at pebble through the ACME directory URL.
9
+ #
10
+ # The tarball is fetched at BUILD time on the builder's own architecture (the
11
+ # e2e network is sealed at run time, and extracting under qemu on an arm64 host
12
+ # fails — see Dockerfile.signal-cli for the full account).
13
+
14
+ ARG SIGNAL_CLI_VERSION=0.14.6
15
+
16
+ FROM --platform=$BUILDPLATFORM debian:bookworm-slim AS fetch
17
+ ARG SIGNAL_CLI_VERSION
18
+ RUN apt-get update \
19
+ && apt-get install -y --no-install-recommends curl ca-certificates \
20
+ && rm -rf /var/lib/apt/lists/*
21
+ # The portable JVM distribution, NOT the "-Linux-native" build: that one is a
22
+ # compiled x86_64 binary and cannot run on aarch64 at all. The JAR plus a JRE
23
+ # plus our own libsignal native works on both architectures.
24
+ #
25
+ # Served at the path BELOW the base URL, not GitHub's full path: the
26
+ # production default (`.../AsamK/signal-cli/releases/download`) already carries
27
+ # that prefix, so the module appends only `/v<version>/<file>`. Mirroring
28
+ # GitHub's full layout here would 404 for exactly that reason.
29
+ RUN mkdir -p "/out/v${SIGNAL_CLI_VERSION}" \
30
+ && curl -fsSL \
31
+ "https://github.com/AsamK/signal-cli/releases/download/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz" \
32
+ -o "/out/v${SIGNAL_CLI_VERSION}/signal-cli-${SIGNAL_CLI_VERSION}.tar.gz"
33
+
34
+ FROM nginx:alpine
35
+ COPY --from=fetch /out /usr/share/nginx/html
36
+
37
+ # Route the customer's public prefix via the ISP edge, like every other host on
38
+ # internet-external. See config/routing/public-sim-entrypoint.sh. (Alpine's
39
+ # busybox already provides `ip`, so nothing to install.)
40
+ COPY config/routing/public-sim-entrypoint.sh /usr/local/bin/public-sim-entrypoint.sh
41
+
42
+ EXPOSE 80
43
+ ENTRYPOINT ["/bin/sh", "/usr/local/bin/public-sim-entrypoint.sh"]
44
+ CMD ["nginx", "-g", "daemon off;"]
@@ -0,0 +1,17 @@
1
+ # The signal-cli SIMULATOR — not the real daemon.
2
+ #
3
+ # Dockerfile.signal-cli runs the actual signal-cli binary and exists to keep
4
+ # our understanding of its JSON-RPC surface honest (the pebble pattern). This
5
+ # image is the fast, drivable stand-in used by tests that need to control what
6
+ # arrives: it can queue an inbound reply, fail a recipient inside a success,
7
+ # and revoke the device link on demand — none of which the real unlinked
8
+ # daemon can be made to do.
9
+ FROM oven/bun:1
10
+
11
+ WORKDIR /app
12
+ COPY simulators/signal-cli/server.ts ./server.ts
13
+
14
+ ENV SIGNAL_RPC_PORT=8080
15
+
16
+ EXPOSE 8080
17
+ CMD ["bun", "run", "server.ts"]