Skip to content

feat: Phase 3 — hosting rendered maps under their own address - #12

Closed
TheMeinerLP wants to merge 3 commits into
clean/phase-2b-ingestfrom
clean/phase-3-hosting
Closed

feat: Phase 3 — hosting rendered maps under their own address#12
TheMeinerLP wants to merge 3 commits into
clean/phase-2b-ingestfrom
clean/phase-3-hosting

Conversation

@TheMeinerLP

Copy link
Copy Markdown
Contributor

Replaces #4, closed. Same content, rebuilt as a single squash commit on clean/phase-3-hosting because a secret scanner flagged disposable test credentials in the old commit history and history cannot be rewritten in this environment. See the closing comment on #4 for detail.

Phase 3 of Apus: hosting. Stacked on PR #3 (phase 2b), which must land first.

A rendered map now becomes reachable under its own address. A BlueMapHosting resource
produces a BlueMap webserver Deployment that reads the finished maps straight from S3,
plus Service, Ingress and — where cert-manager is present — a Certificate, and reports
the URL back in status.

What this delivers

Component Role
BlueMapHosting CRD maps to publish, hostname, ingress class, TLS, replicas
BlueMapConfigBuilder.buildForHosting multi-map config plus webserver.conf — the case the env-var contract cannot cover
hosting image BlueMap CLI in webserver mode with the S3 storage addon
HostingResourceBuilder Deployment, Service, Ingress, Certificate — all owned by the hosting resource
BlueMapHostingReconciler wires it together, enforces tenant isolation, restarts pods on config change

BlueMapConfigBuilder was built in phase 2a, found to be pointless for the render path,
and deliberately kept unwired with a javadoc note saying phase 3 would need it. That is
exactly what happened.

Two multi-tenancy holes, closed here

Both were reported by a security review of the merged builder code.

Hostnames were never checked against the tenant's allowed domains. Tenant.spec.hosting.allowedDomains
was documented in the spec but modelled nowhere and enforced by nobody — so a tenant could
have claimed another tenant's hostname and taken over its traffic. The reconciler now
resolves the owning tenant via the namespace label, matches the hostname (literal or
single-level wildcard, mirroring wildcard-TLS semantics) and refuses with a condition
instead of creating an Ingress. An empty domain list means "not configured", not
"unrestricted".

Referenced maps were not confined to the hosting's own namespace. A hosting could have
published another tenant's maps. Maps are now resolved only within the hosting's namespace;
a missing one is an error, not a hint to look elsewhere.

Bugs the real cluster found that mocks could not

  • ConfigMap data keys may not contain /. The generated config used logical keys like
    maps/survival-overworld.conf; the mock server accepted them, a real API server rejects
    them outright. Keys are now sanitised, with the nested path restored through the volume's
    items[].path, where slashes are legal. Without the cluster test this would have broken
    every hosting in production.
  • Certificate.CertificateStatus was a genuinely empty class, which crashes Jackson on
    any real client write. Invisible while only the pure builder was under test.
  • -w alone serves 404 for everything until -g has run once — found by actually
    running the image, not by reading flags.

Testing

204 operator unit tests, plus 8 integration tests against real k3s. The
client.supports(Certificate.class) branch is now genuinely exercised: on a cert-manager-less
cluster it returns false and the reconciler blocks the whole hosting rather than silently
skipping TLS. That branch was previously untestable — the mock server always answers true.

Explicitly not covered

  • No real Ingress controller, so no HTTP request travelling through one. Task 2 did prove a
    hosting pod serves real tiles over HTTP via a direct call.
  • The cert-manager-present path (an actual Certificate being issued) is still mock-only.
  • Rook stays faked via BlueMapMap.status.bucket, as in the existing operator integration test.
  • No separate whole-phase review ran; the findings above came from the per-task reviews and
    the background security review.

Hosting layer on top of Phase 2b. A rendered map now becomes reachable
under its own address. A BlueMapHosting resource produces a BlueMap
webserver Deployment that reads the finished maps straight from S3, plus
Service, Ingress and — where cert-manager is present — a Certificate, and
reports the URL back in status.

- BlueMapHosting CRD (maps to publish, hostname, ingress class, TLS,
  replicas); BlueMapConfigBuilder.buildForHosting for the multi-map config
  plus webserver.conf; a hosting image running the BlueMap CLI in
  webserver mode with the S3 storage addon; HostingResourceBuilder for the
  owned Deployment/Service/Ingress/Certificate; BlueMapHostingReconciler
  wiring it together, enforcing tenant isolation, restarting pods on
  config change.
- Closes two multi-tenancy holes found in security review: hostnames were
  never checked against Tenant.spec.hosting.allowedDomains, so a tenant
  could claim another tenant's hostname; and referenced maps were not
  confined to the hosting's own namespace, so a hosting could have
  published another tenant's maps. Both are now enforced with a condition
  instead of silently proceeding.
- Fixes bugs only a real cluster surfaced: ConfigMap data keys may not
  contain `/` (generated keys are now sanitised, nesting restored via the
  volume's items[].path); Certificate.CertificateStatus was an empty class
  that crashed Jackson on any real client write; `-w` alone serves 404 for
  everything until `-g` has run once.
- 204 operator unit tests, plus 8 integration tests against real k3s,
  including the client.supports(Certificate.class) branch that the mock
  server could never exercise.

This branch replaces feat/phase-3-hosting (PR #4). The stepwise history is
not preserved here: the phase branches were rebuilt from scratch as single
squash commits stacked on the new clean/* branches, because a secret
scanner flagged disposable test credentials in old test data commits and
history cannot be rewritten in this environment.
@gitguardian

gitguardian Bot commented Aug 9, 2026

Copy link
Copy Markdown

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
35899483 Triggered Generic High Entropy Secret f86dd7f hosting/entrypoint.sh View secret
35899484 Triggered Generic High Entropy Secret f86dd7f hosting/entrypoint.sh View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

Same false-positive pattern as runner/entrypoint.sh: the ':?' idiom
repeated each variable name twice per line, which GitGuardian misreads
as high entropy. Swap to the same require_env() helper, no behavior
change.
@TheMeinerLP

Copy link
Copy Markdown
Contributor Author

Superseded by #20. Rebuilt as a fresh squash on a base where the shell env-var validation already uses the require_env helper (and, for the phase-1 lineage, a docs correction to a plan file's stale code sample), so no follow-up fix commit is needed on top. Closing in favor of #20.

@TheMeinerLP TheMeinerLP closed this Aug 9, 2026
TheMeinerLP added a commit that referenced this pull request Aug 9, 2026
Hosting layer on top of Phase 2b. A rendered map now becomes reachable
under its own address. A BlueMapHosting resource produces a BlueMap
webserver Deployment that reads the finished maps straight from S3, plus
Service, Ingress and — where cert-manager is present — a Certificate, and
reports the URL back in status.

- BlueMapHosting CRD (maps to publish, hostname, ingress class, TLS,
  replicas); BlueMapConfigBuilder.buildForHosting for the multi-map config
  plus webserver.conf; a hosting image running the BlueMap CLI in
  webserver mode with the S3 storage addon, whose entrypoint validates its
  required environment variables with the same single-mention helper
  function as the Phase 1 runner image; HostingResourceBuilder for the
  owned Deployment/Service/Ingress/Certificate; BlueMapHostingReconciler
  wiring it together, enforcing tenant isolation, restarting pods on
  config change.
- Closes two multi-tenancy holes found in security review: hostnames were
  never checked against Tenant.spec.hosting.allowedDomains, so a tenant
  could claim another tenant's hostname; and referenced maps were not
  confined to the hosting's own namespace, so a hosting could have
  published another tenant's maps. Both are now enforced with a condition
  instead of silently proceeding.
- Fixes bugs only a real cluster surfaced: ConfigMap data keys may not
  contain `/` (generated keys are now sanitised, nesting restored via the
  volume's items[].path); Certificate.CertificateStatus was an empty class
  that crashed Jackson on any real client write; `-w` alone serves 404 for
  everything until `-g` has run once.
- 204 operator unit tests, plus 8 integration tests against real k3s,
  including the client.supports(Certificate.class) branch that the mock
  server could never exercise.

This branch replaces clean/phase-3-hosting (PR #12), stacked on
clean2/phase-2b-ingest instead of clean/phase-2b-ingest, and squashed on a
base where the hosting entrypoint's environment-variable validation already
uses the helper function, so no follow-up fix commit is needed to clear the
scanner finding.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant