@celilo/e2e 0.9.3 → 0.10.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/config/cpanel-host/cpanel-host-ca.crt +20 -0
- package/config/cpanel-host/docroot/.htaccess +14 -0
- package/config/cpanel-host/docroot/cms.html +4 -0
- package/config/cpanel-host/site-tls.crt +20 -0
- package/config/cpanel-host/site-tls.key +28 -0
- package/config/cpanel-host/site.conf +27 -0
- package/config/dns/com.zone +7 -0
- package/config/dns/knot-namecheap.conf +6 -0
- package/config/dns/tangohost.com.zone +14 -0
- package/config/resolver/unbound.conf +26 -0
- package/config/routing/management-routes.sh +9 -4
- package/config/routing/resolver-internal-routes.sh +10 -0
- package/docker/Dockerfile.cpanel-host-sim +70 -0
- package/docker/Dockerfile.firewall +12 -0
- package/docker/Dockerfile.management +6 -0
- package/docker/Dockerfile.observer +11 -0
- package/docker/Dockerfile.signal-cli +60 -0
- package/docker/Dockerfile.signal-release +36 -0
- package/docker/Dockerfile.signal-sim +17 -0
- package/docker/Dockerfile.target-machine +9 -0
- package/package.json +3 -3
- package/registry-server/package.json +17 -0
- package/registry-server/src/auth.test.ts +76 -0
- package/registry-server/src/auth.ts +105 -0
- package/registry-server/src/bootstrap-packaging.test.ts +71 -0
- package/registry-server/src/bootstrap.ts +246 -0
- package/registry-server/src/index.ts +16 -0
- package/registry-server/src/introspection.test.ts +247 -0
- package/registry-server/src/introspection.ts +204 -0
- package/registry-server/src/landing.ts +90 -0
- package/registry-server/src/module-owner-store.test.ts +85 -0
- package/registry-server/src/module-owner-store.ts +108 -0
- package/registry-server/src/rate-limit.test.ts +62 -0
- package/registry-server/src/rate-limit.ts +83 -0
- package/registry-server/src/scoped-token-store.test.ts +93 -0
- package/registry-server/src/scoped-token-store.ts +110 -0
- package/registry-server/src/server.test.ts +771 -0
- package/registry-server/src/server.ts +701 -0
- package/registry-server/src/storage.test.ts +148 -0
- package/registry-server/src/storage.ts +150 -0
- package/registry-server/src/validation.test.ts +86 -0
- package/registry-server/src/validation.ts +60 -0
- package/registry-server/tsconfig.json +15 -0
- package/scripts/stage-libsignal.ts +116 -0
- package/simulators/signal-cli/server.ts +272 -0
- package/src/cli/build.ts +7 -0
- package/src/cli/index.ts +6 -0
- package/src/container-manager.ts +5 -1
- package/src/docker-compose-generator.ts +108 -4
- package/src/network-builder.ts +52 -0
- package/src/simulator-ips.ts +9 -0
- package/src/types.ts +34 -0
|
@@ -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,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>
|
package/config/dns/com.zone
CHANGED
|
@@ -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
|
|
@@ -22,17 +22,22 @@ 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
|
|
28
|
-
ip route add 10.0.20.0/24 via
|
|
29
|
-
ip route add 10.0.30.0/24 via
|
|
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
|
|
30
35
|
|
|
31
36
|
# DNS — Technitium internal resolver (split-horizon) with public fallback.
|
|
32
37
|
# Technitium at 192.168.0.10 is authoritative for iamtheinternet.org internally,
|
|
33
38
|
# resolving www/auth to Caddy's DMZ IP (10.0.10.10) rather than the public WAN IP.
|
|
34
39
|
# Fall back to the simulated public resolver if Technitium is not yet up.
|
|
35
|
-
printf 'nameserver
|
|
40
|
+
printf 'nameserver %s\nnameserver 100.100.0.1\n' "${INTERNAL_RESOLVER:-192.168.0.10}" > /etc/resolv.conf
|
|
36
41
|
|
|
37
42
|
# Fetch Pebble's runtime ACME root CA (Pebble generates a fresh root at each
|
|
38
43
|
# startup, unlike the static /config/pebble/pebble-ca.crt baked in at build
|
|
@@ -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: "."
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
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
|
|
@@ -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
|
|
|
@@ -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,36 @@
|
|
|
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
|
+
EXPOSE 80
|
|
@@ -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"]
|
|
@@ -73,7 +73,16 @@ RUN apt-get update && apt-get install -y \
|
|
|
73
73
|
debian-keyring \
|
|
74
74
|
debian-archive-keyring \
|
|
75
75
|
apt-transport-https \
|
|
76
|
+
wireguard-tools \
|
|
77
|
+
wireguard-go \
|
|
76
78
|
&& rm -rf /var/lib/apt/lists/*
|
|
79
|
+
|
|
80
|
+
# A provisioned box may host the `wireguard` module (the VPN does not have to
|
|
81
|
+
# terminate on the firewall). The module's validate_config requires wg to be
|
|
82
|
+
# present and fails loudly when it is not — so the stand-in for an operator's
|
|
83
|
+
# box carries it, exactly as a real one would. Userspace implementation named
|
|
84
|
+
# for the binary Ubuntu actually installs (/usr/bin/wireguard), not the package.
|
|
85
|
+
ENV WG_QUICK_USERSPACE_IMPLEMENTATION=wireguard
|
|
77
86
|
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/gpg.key' | \
|
|
78
87
|
gpg --dearmor -o /usr/share/keyrings/caddy-stable-archive-keyring.gpg
|
|
79
88
|
RUN curl -1sLf 'https://dl.cloudsmith.io/public/caddy/stable/debian.deb.txt' | \
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@celilo/e2e",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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,8 +37,8 @@
|
|
|
37
37
|
"README.md"
|
|
38
38
|
],
|
|
39
39
|
"dependencies": {
|
|
40
|
-
"@celilo/cli-display": "
|
|
41
|
-
"@celilo/event-bus": "
|
|
40
|
+
"@celilo/cli-display": "^0.1.9",
|
|
41
|
+
"@celilo/event-bus": "^0.1.8",
|
|
42
42
|
"yaml": "^2.8.0",
|
|
43
43
|
"zod": "^3.24.1"
|
|
44
44
|
},
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@celilo/registry-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "bun run src/index.ts",
|
|
9
|
+
"dev": "bun --watch src/index.ts",
|
|
10
|
+
"build:binary": "bun build --compile --target=bun-linux-x64 --outfile=dist/celilo-registry-server src/index.ts",
|
|
11
|
+
"test": "bun test"
|
|
12
|
+
},
|
|
13
|
+
"devDependencies": {
|
|
14
|
+
"bun-types": "latest",
|
|
15
|
+
"typescript": "^5"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { describe, expect, test } from 'bun:test';
|
|
2
|
+
import { ADMIN_SCOPE, TokenAuth, hashToken } from './auth';
|
|
3
|
+
|
|
4
|
+
describe('TokenAuth (package-scoped — ISS-0140)', () => {
|
|
5
|
+
test('empty specs → no tokens; nothing authorizes', () => {
|
|
6
|
+
const auth = new TokenAuth([]);
|
|
7
|
+
expect(auth.hasTokens()).toBe(false);
|
|
8
|
+
expect(auth.authorize('anything', 'caddy')).toBe(false);
|
|
9
|
+
expect(auth.isAdmin('anything')).toBe(false);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
test('admin-scoped token publishes ANY package', () => {
|
|
13
|
+
const auth = new TokenAuth([{ token: 'admin-tok', scope: ADMIN_SCOPE }]);
|
|
14
|
+
expect(auth.authorize('admin-tok', 'caddy')).toBe(true);
|
|
15
|
+
expect(auth.authorize('admin-tok', 'lunacycle')).toBe(true);
|
|
16
|
+
expect(auth.isAdmin('admin-tok')).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('package-scoped token publishes ONLY its package', () => {
|
|
20
|
+
const auth = new TokenAuth([{ token: 'luna-tok', scope: 'lunacycle' }]);
|
|
21
|
+
expect(auth.authorize('luna-tok', 'lunacycle')).toBe(true);
|
|
22
|
+
expect(auth.authorize('luna-tok', 'caddy')).toBe(false);
|
|
23
|
+
// A scoped token is NOT an admin token — it cannot mint.
|
|
24
|
+
expect(auth.isAdmin('luna-tok')).toBe(false);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('unknown token never authorizes', () => {
|
|
28
|
+
const auth = new TokenAuth([{ token: 'known', scope: 'lunacycle' }]);
|
|
29
|
+
expect(auth.authorize('unknown', 'lunacycle')).toBe(false);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('tokens are stored hashed, not in cleartext', () => {
|
|
33
|
+
const auth = new TokenAuth([{ token: 'secret-token', scope: ADMIN_SCOPE }]);
|
|
34
|
+
expect(JSON.stringify(auth)).not.toContain('secret-token');
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('tolerates a Bearer prefix and surrounding whitespace', () => {
|
|
38
|
+
const auth = new TokenAuth([{ token: 'clean', scope: ADMIN_SCOPE }]);
|
|
39
|
+
expect(auth.authorize(' clean ', 'caddy')).toBe(true);
|
|
40
|
+
expect(auth.authorize('Bearer clean', 'caddy')).toBe(true);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test('empty header rejected', () => {
|
|
44
|
+
const auth = new TokenAuth([{ token: 'secret', scope: ADMIN_SCOPE }]);
|
|
45
|
+
expect(auth.authorize('', 'caddy')).toBe(false);
|
|
46
|
+
expect(auth.scopeOf('')).toBe(null);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('addHashed / removeHashed manage minted tokens at runtime', () => {
|
|
50
|
+
const auth = new TokenAuth([]);
|
|
51
|
+
const hash = hashToken('minted-tok');
|
|
52
|
+
auth.addHashed(hash, 'lunacycle');
|
|
53
|
+
expect(auth.authorize('minted-tok', 'lunacycle')).toBe(true);
|
|
54
|
+
auth.removeHashed(hash);
|
|
55
|
+
expect(auth.authorize('minted-tok', 'lunacycle')).toBe(false);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
describe('TokenAuth.fromEnv (PUBLISH_TOKENS format)', () => {
|
|
60
|
+
test('bare line → admin scope; "token pkg" line → scoped', () => {
|
|
61
|
+
process.env.PUBLISH_TOKENS = 'admin-tok\nluna-tok lunacycle\n';
|
|
62
|
+
const auth = TokenAuth.fromEnv();
|
|
63
|
+
expect(auth.isAdmin('admin-tok')).toBe(true);
|
|
64
|
+
expect(auth.authorize('luna-tok', 'lunacycle')).toBe(true);
|
|
65
|
+
expect(auth.authorize('luna-tok', 'caddy')).toBe(false);
|
|
66
|
+
process.env.PUBLISH_TOKENS = '';
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test('blank lines ignored', () => {
|
|
70
|
+
process.env.PUBLISH_TOKENS = '\n \nt1\n';
|
|
71
|
+
const auth = TokenAuth.fromEnv();
|
|
72
|
+
expect(auth.hasTokens()).toBe(true);
|
|
73
|
+
expect(auth.isAdmin('t1')).toBe(true);
|
|
74
|
+
process.env.PUBLISH_TOKENS = '';
|
|
75
|
+
});
|
|
76
|
+
});
|