Gap
test/e2e/ contains zero tests touching remoteControl, wss or mTLS — grepped against main @ 93c47f3. The credentialed runtime push is covered at unit level and, since #326, by a sample plus a manual live-cluster run. Neither runs in CI, so a regression in the chain ships green.
That chain now has real surface worth protecting: #206 (wss/bearer/mTLS), #295 (no HTTP_PROXY), #297 + #318 (handshake classification), #300 (endpoint allow-list), #309 (admission policy), #313 (token requires ca.crt), #322 (token must be a valid header value).
It is buildable — the shape is already verified
Proven end to end on Kubernetes 1.36.3 while writing #326, with the operator running in-cluster:
| Case |
Result |
| no client certificate |
400 — mTLS enforced |
| no bearer |
401 — bearer enforced |
| both |
101, and a well-formed ntn_config_update reached the plaintext backend |
| operator restart |
resumes pushing, fresher epoch |
| bearer broken then repaired |
recovered after 166 s (the ephemeris heartbeat, not a per-cell retry) |
Reusable pieces already in the repo: config/samples/remote-control-tls/gnb-with-tls-sidecar.yaml and docs/remote-control-tls.md.
Proposed test
A new build-tagged suite (mirroring ha_test.go's e2e_ha pattern), running on the existing kind job:
- Fixtures — generate a CA, a server certificate whose SAN covers the Service DNS name, and a client certificate, in
TestMain. No committed key material.
- Backend — a plaintext WebSocket stand-in speaking OCUDU's
ntn_config_update contract: validate the envelope, reply {"cmd":…,"timestamp":…}, record what arrived. Small enough to live in test/e2e/fixtures/ and be built into the kind node, and it makes the assertion possible — the real assertion is what crossed the wire, which a live OCUDU would make harder, not easier.
- Proxy — real
nginx:1.29-alpine with the sample's config, so the sample itself is what is under test rather than a parallel copy that can drift.
- Arms
Two things to decide before starting
- Cost. This is the heaviest E2E in the repo: an image build in CI, cert generation, a multi-container pod. Worth scoping to the arms above and resisting growth.
- NetworkPolicy is NOT coverable here. kind's default CNI does not enforce NetworkPolicy, and neither does this project's dev cluster (Flannel — verified: a deny-all egress policy still let traffic through).
docs/remote-control-tls.md already flags the networkPolicy.gnbPorts interaction as unverified. Either add a policy-capable CNI to the kind job or leave that arm explicitly out — silently omitting it would be worse than either.
Not in scope
Real OCUDU gNB. It needs RF/ZMQ and a core network, and it would test their implementation, not this operator's chain. Upstream idempotency questions are tracked in #207, which now carries a primary-source answer.
Follow-up to #326 and ADR-0010's closing note.
Gap
test/e2e/contains zero tests touchingremoteControl,wssor mTLS — grepped againstmain @ 93c47f3. The credentialed runtime push is covered at unit level and, since #326, by a sample plus a manual live-cluster run. Neither runs in CI, so a regression in the chain ships green.That chain now has real surface worth protecting: #206 (wss/bearer/mTLS), #295 (no HTTP_PROXY), #297 + #318 (handshake classification), #300 (endpoint allow-list), #309 (admission policy), #313 (token requires
ca.crt), #322 (token must be a valid header value).It is buildable — the shape is already verified
Proven end to end on Kubernetes 1.36.3 while writing #326, with the operator running in-cluster:
ntn_config_updatereached the plaintext backendReusable pieces already in the repo:
config/samples/remote-control-tls/gnb-with-tls-sidecar.yamlanddocs/remote-control-tls.md.Proposed test
A new build-tagged suite (mirroring
ha_test.go'se2e_hapattern), running on the existing kind job:TestMain. No committed key material.ntn_config_updatecontract: validate the envelope, reply{"cmd":…,"timestamp":…}, record what arrived. Small enough to live intest/e2e/fixtures/and be built into the kind node, and it makes the assertion possible — the real assertion is what crossed the wire, which a live OCUDU would make harder, not easier.nginx:1.29-alpinewith the sample's config, so the sample itself is what is under test rather than a parallel copy that can drift.mode: mtls+ bearer →EphemerisPushed=True, and the recorded frame is a validntn_config_updatefor the rightplmn/nciwith a future epochRemoteEndpointRejected(this is the fix(ocudu): a refused push handshake self-heals instead of stranding the cell #297 classification, live)tokenwithoutca.crt→ refused before any dial (fix(security): never send a remoteControl.tls bearer token to a publicly-trusted host #313)--remote-control-allowed-endpoint-hosts→RemoteControlEndpointNotAllowed, and the Secret is never read (feat(ntncellconfig): interim confused-deputy endpoint allow-list for credentialed pushes (#251, ADR-0009) #300)Two things to decide before starting
docs/remote-control-tls.mdalready flags thenetworkPolicy.gnbPortsinteraction as unverified. Either add a policy-capable CNI to the kind job or leave that arm explicitly out — silently omitting it would be worse than either.Not in scope
Real OCUDU gNB. It needs RF/ZMQ and a core network, and it would test their implementation, not this operator's chain. Upstream idempotency questions are tracked in #207, which now carries a primary-source answer.
Follow-up to #326 and ADR-0010's closing note.