feat: Phase 3 — hosting rendered maps under their own address - #4
Closed
TheMeinerLP wants to merge 12 commits into
Closed
feat: Phase 3 — hosting rendered maps under their own address#4TheMeinerLP wants to merge 12 commits into
TheMeinerLP wants to merge 12 commits into
Conversation
…uilder Ports Phase 3's hosting surface onto the api/ pattern established by BlueMapMap and WorldSource: BlueMapHosting is a namespaced custom resource fronting several already-rendered maps behind one hostname/ingress. Extends BlueMapConfigBuilder (deliberately unwired since Phase 2a) with buildForHosting(), which emits a webserver.conf plus one maps/<id>.conf and one storages/<id>.conf per map, since different maps can live in different buckets. Credentials stay out of every generated file, matching the render-case contract. Verified webserver.conf's real format against BlueMap 5.23 (apus/runner:dev's bundled CLI, run with no action flag against an empty config folder): it has no bind-address/ip key at all, so no such key is emitted -- the file instead documents in a comment that the webserver listens on all interfaces unconditionally.
Turns a BlueMapHosting into the Deployment, Service, Ingress and (when TLS is enabled) cert-manager Certificate that make an already-rendered map reachable on the web. Pure builder, no Kubernetes client, following RenderJobBuilder's pattern: owner references on every resource, shared Labels, S3 credentials via secretKeyRef, and readiness/liveness probes so the Service never routes to a pod still loading its maps from S3. Adds a lean client-side Certificate model for cert-manager.io/v1, kept outside the operator.api package so the CRD generator's package filter never picks it up.
Task reports are working material, not project documentation — every other phase keeps them under the gitignored .superpowers/ directory. This one landed in docs/ because the agent's worktree did not carry that directory.
Runs the same BlueMap CLI as runner/, in webserver mode (-gw) instead of render mode. Map/storage config comes from a mounted, read-only ConfigMap (as produced by BlueMapConfigBuilder.buildForHosting) rather than env vars, since a multi-map webserver config doesn't fit that contract. The entrypoint copies the read-only config into a writable directory and appends S3 credentials to each storages/*.conf file there, via a file rather than argv, so they never appear in the process command line. Verified against a real MinIO seeded by runner/ with a rendered map: GET /settings.json (200, lists configured maps) is the readiness path for Task 4's probes; GET /maps/<id>/tiles/... serves real rendered tile data read live from S3.
…olation checks Reconciles a BlueMapHosting into a ConfigMap (BlueMapConfigBuilder.buildForHosting), Deployment, Service, Ingress, and optional cert-manager Certificate, and registers it in ApusOperator. Closes two multi-tenancy findings from the prior review: the hostname is now checked against the owning Tenant's spec.hosting.allowedDomains (new TenantSpec field, literal or single-level wildcard match; an empty list means no hosting rather than unrestricted hosting), and referenced BlueMapMaps are resolved only inside the hosting's own namespace, refusing with a condition instead of a Deployment when either check fails or a referenced map has no bound bucket yet. Also: wires OperatorConfig.hostingImage (APUS_HOSTING_IMAGE) through HostingResourceBuilder.deployment(), corrects PROBE_PATH to /settings.json per the task 2 image's verified behaviour, stamps a config-checksum annotation on the pod template so a changed map list restarts the webserver pods, and fixes Certificate.CertificateStatus (an empty class Jackson's FAIL_ON_EMPTY_BEANS rejected on an actual client write, only reachable once a real reconciler -- rather than the pure HostingResourceBuilder -- sent one to a client).
Adds BlueMapHostingIntegrationTest, running against k3s via Testcontainers alongside the existing OperatorIntegrationTest, wired into the operator module's dedicated integrationTest Gradle task (not part of build/check). It proves the generated bluemaphostings CRD registers on a real API server, that a full reconcile produces a ConfigMap/Deployment/Service/Ingress with the expected properties, that both the hostname-vs-tenant-allowedDomains and map-must-exist-in-namespace checks still hold when resolved against a real Tenant/namespace-label lookup, and that client.supports(Certificate.class) genuinely returns false without cert-manager installed -- a branch the Fabric8 mock server can never exercise since it always answers true. Along the way this surfaced a real bug the mock server could not catch: BlueMapConfigBuilder.buildForHosting's logical file paths (e.g. "maps/survival-overworld.conf") were written directly as ConfigMap data keys, which a real API server rejects outright since a data key may not contain '/'. Fixed by sanitising each key (HostingResourceBuilder#configMapKey) and restoring the original nested path via the ConfigMap volume's per-item `path`, which -- unlike the data key -- Kubernetes does allow to contain '/'. Also factors the CRD apply/await helpers used by both real-cluster test classes into a shared net.onelitefeather.apus.operator.testsupport.K3sCrdSupport instead of duplicating them.
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 35899483 | Triggered | Generic High Entropy Secret | 411fe30 | operator/src/test/java/net/onelitefeather/apus/operator/hosting/HostingResourceBuilderTest.java | View secret |
| 35899484 | Triggered | Generic High Entropy Secret | 4fe542b | hosting/entrypoint.sh | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- 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
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 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.
…onfig The strings the secret scanner flagged were not only in old commits: the phase 1 plan carried them in a code sample, and .gitguardian.yaml listed them in plaintext, which made the exemption file a finding of its own. The plan sample now shows a placeholder, and the exemptions are SHA256 digests.
Contributor
Author
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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
BlueMapHostingresourceproduces 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
BlueMapHostingCRDBlueMapConfigBuilder.buildForHostingwebserver.conf— the case the env-var contract cannot coverhostingimageHostingResourceBuilderBlueMapHostingReconcilerBlueMapConfigBuilderwas 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.allowedDomainswas 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
/. The generated config used logical keys likemaps/survival-overworld.conf; the mock server accepted them, a real API server rejectsthem 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 brokenevery hosting in production.
Certificate.CertificateStatuswas a genuinely empty class, which crashes Jackson onany real client write. Invisible while only the pure builder was under test.
-walone serves 404 for everything until-ghas run once — found by actuallyrunning 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-lesscluster 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
hosting pod serves real tiles over HTTP via a direct call.
BlueMapMap.status.bucket, as in the existing operator integration test.the background security review.