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` - Required secret: `Secret/fc-apple-mdm-runtime`, key `NANOHUB_API_KEY`
- Optional later bridge secret: `NANOHUB_WEBHOOK_URL` - Optional later bridge secret: `NANOHUB_WEBHOOK_URL`
- Required CA mount: `ConfigMap/fc-apple-mdm-root-ca`, key `root_ca.crt` - 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 NanoHUB API authentication is HTTP Basic with username `nanohub` and password
from `NANOHUB_API_KEY`. from `NANOHUB_API_KEY`.
@@ -24,16 +27,21 @@ The Traefik route intentionally exposes only:
- `/version` - `/version`
- `/mdm` - `/mdm`
- `/checkin` - `/checkin`
- `/scep`
NanoHUB APIs under `/api/v1/*` stay cluster-internal for MDM-N1. The NanoHUB APIs under `/api/v1/*` stay cluster-internal for MDM-N1. The
DeviceManagement bridge can use the ClusterIP service directly once its NanoHUB DeviceManagement bridge can use the ClusterIP service directly once its NanoHUB
client lane lands. client lane lands.
SCEP is intentionally not exposed here yet. NanoHUB/NanoMDM expects an external SCEP is backed by the dedicated Apple-MDM-specific RSA step-ca hierarchy on
SCEP service; the next runtime lane should either add a dedicated SCEP route noc1, not by the IAmWorkin ACME CA. The live profile URL is:
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 ```text
live SCEP endpoint. Do not point the profile at a placeholder URL. 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 ## 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` 3. Ensure `mdm.iamworkin.lan` resolves to the GX10 Traefik VIP `10.0.57.202`
before cert-manager requests `Certificate/fc-apple-mdm-tls`. before cert-manager requests `Certificate/fc-apple-mdm-tls`.
4. Prove `https://mdm.iamworkin.lan/version` after ArgoCD converges. 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, This lane does not create an APNs MDM push certificate, enrollment profile,
SCEP/device identity service, managed Wi-Fi payload, managed app install, or managed Wi-Fi payload, managed app install, or supervised iPad enrollment. Those
supervised iPad enrollment. Those remain MDM-N2 through MDM-N8. remain MDM-N2 through MDM-N8.

View File

@@ -192,6 +192,43 @@ spec:
targetPort: 9004 targetPort: 9004
protocol: TCP 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 apiVersion: cert-manager.io/v1
kind: Certificate kind: Certificate
metadata: metadata:
@@ -218,6 +255,11 @@ spec:
entryPoints: entryPoints:
- websecure - websecure
routes: 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`)) - match: Host(`mdm.iamworkin.lan`) && (PathPrefix(`/mdm`) || PathPrefix(`/checkin`) || PathPrefix(`/version`))
kind: Rule kind: Rule
services: 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, "Namespace", "fc-apple-mdm"));
Documents.Should().Contain(document => Is(document, "ConfigMap", "fc-apple-mdm-root-ca")); 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"));
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().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) == "Secret");
Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "OnePasswordItem"); Documents.Should().NotContain(document => (document.Scalar("kind") ?? string.Empty) == "OnePasswordItem");
@@ -66,16 +68,35 @@ public sealed class Gx10AppleMdmNanohubTests
} }
[Fact] [Fact]
public void Manifest_ExposesOnlyMdmCheckinAndVersionPaths() public void Manifest_ExposesOnlyMdmCheckinVersionAndScepPaths()
{ {
var certificate = Single("Certificate", "fc-apple-mdm-tls"); var certificate = Single("Certificate", "fc-apple-mdm-tls");
certificate.Scalar("spec", "issuerRef", "name").Should().Be("step-ca-acme"); certificate.Scalar("spec", "issuerRef", "name").Should().Be("step-ca-acme");
certificate.Scalar("spec", "issuerRef", "kind").Should().Be("ClusterIssuer"); certificate.Scalar("spec", "issuerRef", "kind").Should().Be("ClusterIssuer");
certificate.ScalarSequence("spec", "dnsNames").Should().ContainSingle("mdm.iamworkin.lan"); 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 ingress = Single("IngressRoute", "fc-apple-mdm");
var route = ingress.MappingSequence("spec", "routes").Should().ContainSingle().Subject; var routes = ingress.MappingSequence("spec", "routes");
var match = route.Scalar("match"); 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("Host(`mdm.iamworkin.lan`)");
match.Should().Contain("PathPrefix(`/mdm`)"); match.Should().Contain("PathPrefix(`/mdm`)");
@@ -83,6 +104,12 @@ public sealed class Gx10AppleMdmNanohubTests
match.Should().Contain("PathPrefix(`/version`)"); match.Should().Contain("PathPrefix(`/version`)");
match.Should().NotContain("/api/v1"); match.Should().NotContain("/api/v1");
match.Should().NotContain("PathPrefix(`/api`)"); 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] [Fact]
@@ -94,6 +121,8 @@ public sealed class Gx10AppleMdmNanohubTests
readme.Should().Contain("Secret/fc-apple-mdm-runtime"); readme.Should().Contain("Secret/fc-apple-mdm-runtime");
readme.Should().Contain("imagePullPolicy: Never"); readme.Should().Contain("imagePullPolicy: Never");
readme.Should().Contain("10.0.57.202"); 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("does not create an APNs MDM push certificate");
readme.Should().Contain("managed Wi-Fi payload"); readme.Should().Contain("managed Wi-Fi payload");
} }