From f78e6747b4e2b19a4d9a1b0e9a3f7d03e1920a2b Mon Sep 17 00:00:00 2001 From: Robot Date: Thu, 18 Jun 2026 11:23:00 -0500 Subject: [PATCH] deploy(apple-mdm): route scep to noc1 ca Adds the GX10 /scep route to the noc1 Apple MDM SCEP CA without exposing NanoHUB APIs. --- apps-gx10/fc-apple-mdm/README.md | 24 +++++++---- apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml | 42 +++++++++++++++++++ .../Gx10AppleMdmNanohubTests.cs | 35 ++++++++++++++-- 3 files changed, 91 insertions(+), 10 deletions(-) diff --git a/apps-gx10/fc-apple-mdm/README.md b/apps-gx10/fc-apple-mdm/README.md index f02baa9..746203d 100644 --- a/apps-gx10/fc-apple-mdm/README.md +++ b/apps-gx10/fc-apple-mdm/README.md @@ -13,6 +13,9 @@ traffic at `https://mdm.iamworkin.lan`. - Required secret: `Secret/fc-apple-mdm-runtime`, key `NANOHUB_API_KEY` - Optional later bridge secret: `NANOHUB_WEBHOOK_URL` - Required CA mount: `ConfigMap/fc-apple-mdm-root-ca`, key `root_ca.crt` +- SCEP backend: noc1 systemd service `step-ca-apple-mdm-scep`, forwarded through + selectorless `Service/fc-apple-mdm-scep` and `EndpointSlice/fc-apple-mdm-scep-noc1` + to `10.0.56.10:9080` NanoHUB API authentication is HTTP Basic with username `nanohub` and password from `NANOHUB_API_KEY`. @@ -24,16 +27,21 @@ The Traefik route intentionally exposes only: - `/version` - `/mdm` - `/checkin` +- `/scep` NanoHUB APIs under `/api/v1/*` stay cluster-internal for MDM-N1. The DeviceManagement bridge can use the ClusterIP service directly once its NanoHUB client lane lands. -SCEP is intentionally not exposed here yet. NanoHUB/NanoMDM expects an external -SCEP service; the next runtime lane should either add a dedicated SCEP route -such as `https://mdm.iamworkin.lan/scep/...` backed by an Apple-MDM-specific CA, -or set `APPLE_MDM_SCEP_URL` in the DeviceManagement runtime secret to another -live SCEP endpoint. Do not point the profile at a placeholder URL. +SCEP is backed by the dedicated Apple-MDM-specific RSA step-ca hierarchy on +noc1, not by the IAmWorkin ACME CA. The live profile URL is: + +```text +https://mdm.iamworkin.lan/scep/apple-mdm-scep +``` + +Do not point `APPLE_MDM_SCEP_URL` at a placeholder URL or at the ECDSA +IAmWorkin ACME CA; Smallstep SCEP requires an RSA intermediate/decrypter path. ## Deployment Notes @@ -45,7 +53,9 @@ live SCEP endpoint. Do not point the profile at a placeholder URL. 3. Ensure `mdm.iamworkin.lan` resolves to the GX10 Traefik VIP `10.0.57.202` before cert-manager requests `Certificate/fc-apple-mdm-tls`. 4. Prove `https://mdm.iamworkin.lan/version` after ArgoCD converges. +5. Prove SCEP CA publication with + `curl -sk -o /dev/null -w '%{http_code} %{size_download}\n' 'https://mdm.iamworkin.lan/scep/apple-mdm-scep?operation=GetCACert'`. This lane does not create an APNs MDM push certificate, enrollment profile, -SCEP/device identity service, managed Wi-Fi payload, managed app install, or -supervised iPad enrollment. Those remain MDM-N2 through MDM-N8. +managed Wi-Fi payload, managed app install, or supervised iPad enrollment. Those +remain MDM-N2 through MDM-N8. diff --git a/apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml b/apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml index db5b6e0..8511ecb 100644 --- a/apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml +++ b/apps-gx10/fc-apple-mdm/fc-apple-mdm.yaml @@ -192,6 +192,43 @@ spec: targetPort: 9004 protocol: TCP --- +apiVersion: v1 +kind: Service +metadata: + name: fc-apple-mdm-scep + namespace: fc-apple-mdm + labels: + app: fc-apple-mdm-scep + app.kubernetes.io/name: fc-apple-mdm-scep + app.kubernetes.io/part-of: flowercore +spec: + type: ClusterIP + ports: + - name: http + port: 80 + targetPort: 9080 + protocol: TCP +--- +apiVersion: discovery.k8s.io/v1 +kind: EndpointSlice +metadata: + name: fc-apple-mdm-scep-noc1 + namespace: fc-apple-mdm + labels: + kubernetes.io/service-name: fc-apple-mdm-scep + app.kubernetes.io/name: fc-apple-mdm-scep + app.kubernetes.io/part-of: flowercore +addressType: IPv4 +endpoints: + - addresses: + - 10.0.56.10 + conditions: + ready: true +ports: + - name: http + port: 9080 + protocol: TCP +--- apiVersion: cert-manager.io/v1 kind: Certificate metadata: @@ -218,6 +255,11 @@ spec: entryPoints: - websecure routes: + - match: Host(`mdm.iamworkin.lan`) && PathPrefix(`/scep`) + kind: Rule + services: + - name: fc-apple-mdm-scep + port: 80 - match: Host(`mdm.iamworkin.lan`) && (PathPrefix(`/mdm`) || PathPrefix(`/checkin`) || PathPrefix(`/version`)) kind: Rule services: diff --git a/tests/bluejay-infra-lint/Gx10AppleMdmNanohubTests.cs b/tests/bluejay-infra-lint/Gx10AppleMdmNanohubTests.cs index 85db88b..bbb0662 100644 --- a/tests/bluejay-infra-lint/Gx10AppleMdmNanohubTests.cs +++ b/tests/bluejay-infra-lint/Gx10AppleMdmNanohubTests.cs @@ -17,6 +17,8 @@ public sealed class Gx10AppleMdmNanohubTests Documents.Should().Contain(document => Is(document, "Namespace", "fc-apple-mdm")); Documents.Should().Contain(document => Is(document, "ConfigMap", "fc-apple-mdm-root-ca")); Documents.Should().Contain(document => Is(document, "Service", "fc-apple-mdm")); + Documents.Should().Contain(document => Is(document, "Service", "fc-apple-mdm-scep")); + Documents.Should().Contain(document => Is(document, "EndpointSlice", "fc-apple-mdm-scep-noc1")); Documents.Should().Contain(document => Is(document, "NetworkPolicy", "fc-apple-mdm-netpol")); Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "Secret"); Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "OnePasswordItem"); @@ -66,16 +68,35 @@ public sealed class Gx10AppleMdmNanohubTests } [Fact] - public void Manifest_ExposesOnlyMdmCheckinAndVersionPaths() + public void Manifest_ExposesOnlyMdmCheckinVersionAndScepPaths() { var certificate = Single("Certificate", "fc-apple-mdm-tls"); certificate.Scalar("spec", "issuerRef", "name").Should().Be("step-ca-acme"); certificate.Scalar("spec", "issuerRef", "kind").Should().Be("ClusterIssuer"); certificate.ScalarSequence("spec", "dnsNames").Should().ContainSingle("mdm.iamworkin.lan"); + var scepService = Single("Service", "fc-apple-mdm-scep"); + scepService.Scalar("spec", "type").Should().Be("ClusterIP"); + var scepServicePort = scepService.MappingSequence("spec", "ports").Should().ContainSingle().Subject; + scepServicePort.Scalar("name").Should().Be("http"); + scepServicePort.Scalar("port").Should().Be("80"); + scepServicePort.Scalar("targetPort").Should().Be("9080"); + + var scepEndpointSlice = Single("EndpointSlice", "fc-apple-mdm-scep-noc1"); + scepEndpointSlice.Scalar("addressType").Should().Be("IPv4"); + scepEndpointSlice.Scalar("metadata", "labels", "kubernetes.io/service-name").Should().Be("fc-apple-mdm-scep"); + var scepEndpoint = scepEndpointSlice.MappingSequence("endpoints").Should().ContainSingle().Subject; + scepEndpoint.ScalarSequence("addresses").Should().ContainSingle("10.0.56.10"); + var scepEndpointPort = scepEndpointSlice.MappingSequence("ports").Should().ContainSingle().Subject; + scepEndpointPort.Scalar("name").Should().Be("http"); + scepEndpointPort.Scalar("port").Should().Be("9080"); + var ingress = Single("IngressRoute", "fc-apple-mdm"); - var route = ingress.MappingSequence("spec", "routes").Should().ContainSingle().Subject; - var match = route.Scalar("match"); + var routes = ingress.MappingSequence("spec", "routes"); + routes.Should().HaveCount(2); + var scepRoute = routes.Single(route => route.Scalar("match")?.Contains("PathPrefix(`/scep`)") == true); + var nanohubRoute = routes.Single(route => route.Scalar("match")?.Contains("PathPrefix(`/mdm`)") == true); + var match = nanohubRoute.Scalar("match"); match.Should().Contain("Host(`mdm.iamworkin.lan`)"); match.Should().Contain("PathPrefix(`/mdm`)"); @@ -83,6 +104,12 @@ public sealed class Gx10AppleMdmNanohubTests match.Should().Contain("PathPrefix(`/version`)"); match.Should().NotContain("/api/v1"); match.Should().NotContain("PathPrefix(`/api`)"); + + scepRoute.Scalar("match").Should().Contain("Host(`mdm.iamworkin.lan`)"); + scepRoute.Scalar("match").Should().Contain("PathPrefix(`/scep`)"); + var scepRouteService = scepRoute.MappingSequence("services").Should().ContainSingle().Subject; + scepRouteService.Scalar("name").Should().Be("fc-apple-mdm-scep"); + scepRouteService.Scalar("port").Should().Be("80"); } [Fact] @@ -94,6 +121,8 @@ public sealed class Gx10AppleMdmNanohubTests readme.Should().Contain("Secret/fc-apple-mdm-runtime"); readme.Should().Contain("imagePullPolicy: Never"); readme.Should().Contain("10.0.57.202"); + readme.Should().Contain("https://mdm.iamworkin.lan/scep/apple-mdm-scep"); + readme.Should().Contain("Smallstep SCEP requires an RSA intermediate"); readme.Should().Contain("does not create an APNs MDM push certificate"); readme.Should().Contain("managed Wi-Fi payload"); }