deploy(apple-mdm): route scep to noc1 ca

Adds the GX10 /scep route to the noc1 Apple MDM SCEP CA without exposing NanoHUB APIs.
This commit is contained in:
Robot
2026-06-18 11:23:00 -05:00
parent e543018bdc
commit f78e6747b4
3 changed files with 91 additions and 10 deletions

View File

@@ -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.

View File

@@ -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:

View File

@@ -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");
}