diff --git a/apps-gx10/fc-devicemgmt/README.md b/apps-gx10/fc-devicemgmt/README.md index ebcfe17..598197c 100644 --- a/apps-gx10/fc-devicemgmt/README.md +++ b/apps-gx10/fc-devicemgmt/README.md @@ -13,6 +13,8 @@ values to clear readiness checks. | Key | Purpose | | --- | --- | +| `DEVICE_MANAGEMENT_OPERATOR_API_KEY` | Required operator API key for authenticated REST/MCP write operations, including Android command queueing. | +| `DEVICE_MANAGEMENT_ADMIN_API_KEY` | Required admin API key for privileged DeviceManagement operations. | | `NANOHUB_API_KEY` | NanoHUB API password for HTTP Basic user `nanohub`. | | `APPLE_MDM_APNS_TOPIC` | MDM APNs topic returned after uploading the Apple MDM push certificate to NanoHUB/NanoMDM. | | `APPLE_MDM_SCEP_URL` | Live SCEP URL included in the enrollment profile. | @@ -27,6 +29,13 @@ Non-secret profile constants stay in GitOps: NanoHUB base URL, MDM server URL, check-in URL, organization/display names, the HTTPS trust anchor certificate, managed Wi-Fi encryption type, auto-join, and MAC-randomization disablement. +DeviceManagement auth is enabled on GX10. The deployment maps +`DEVICE_MANAGEMENT_OPERATOR_API_KEY` to both `Auth__ApiKey` and +`FlowerCore__Auth__ApiKey`; the unprefixed key keeps the MCP API key post-config +path aligned with REST auth. Agent enrollment, heartbeat, inventory, command poll, +and command-result callbacks remain on the unauthenticated agent channel by +application policy; operator write endpoints must use `X-Api-Key`. + ## Readiness Check After changing the runtime secret and letting the pod roll, verify: diff --git a/apps-gx10/fc-devicemgmt/deployment-fc-devicemgmt-web.json b/apps-gx10/fc-devicemgmt/deployment-fc-devicemgmt-web.json index bae6751..47d9987 100644 --- a/apps-gx10/fc-devicemgmt/deployment-fc-devicemgmt-web.json +++ b/apps-gx10/fc-devicemgmt/deployment-fc-devicemgmt-web.json @@ -88,15 +88,55 @@ "name": "FlowerCore__Database__ConnectionStrings__Sqlite", "value": "Data Source=/data/devicemgmt.db" }, - { - "name": "FlowerCore__Database__Password", - "valueFrom": { - "secretKeyRef": { - "key": "DB-Password", - "name": "fc-devicemgmt-runtime" - } - } - }, + { + "name": "FlowerCore__Database__Password", + "valueFrom": { + "secretKeyRef": { + "key": "DB-Password", + "name": "fc-devicemgmt-runtime" + } + } + }, + { + "name": "FlowerCore__Auth__Enabled", + "value": "true" + }, + { + "name": "Auth__ApiKey", + "valueFrom": { + "secretKeyRef": { + "key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY", + "name": "fc-devicemgmt-runtime" + } + } + }, + { + "name": "FlowerCore__Auth__ApiKey", + "valueFrom": { + "secretKeyRef": { + "key": "DEVICE_MANAGEMENT_OPERATOR_API_KEY", + "name": "fc-devicemgmt-runtime" + } + } + }, + { + "name": "Auth__AdminApiKey", + "valueFrom": { + "secretKeyRef": { + "key": "DEVICE_MANAGEMENT_ADMIN_API_KEY", + "name": "fc-devicemgmt-runtime" + } + } + }, + { + "name": "FlowerCore__Auth__AdminApiKey", + "valueFrom": { + "secretKeyRef": { + "key": "DEVICE_MANAGEMENT_ADMIN_API_KEY", + "name": "fc-devicemgmt-runtime" + } + } + }, { "name": "FlowerCore__EventBus__Redis__Configuration", "value": "redis.fc-redis.svc:6379" diff --git a/tests/bluejay-infra-lint/FleetManifestLintTests.cs b/tests/bluejay-infra-lint/FleetManifestLintTests.cs index a4125fa..f652257 100644 --- a/tests/bluejay-infra-lint/FleetManifestLintTests.cs +++ b/tests/bluejay-infra-lint/FleetManifestLintTests.cs @@ -999,6 +999,26 @@ public sealed class FleetManifestLintTests gatewayManifest.Should().Contain("port: 5400"); } + [Fact] + public void Gx10DeviceManagementWriteApis_RequireRuntimeBackedOperatorAuth() + { + var web = Gx10DeploymentContainer("fc-devicemgmt", "deployment-fc-devicemgmt-web.json"); + + JsonEnvValue(web, "FlowerCore__Auth__Enabled").Should().Be("true"); + JsonEnvSecretName(web, "Auth__ApiKey").Should().Be("fc-devicemgmt-runtime"); + JsonEnvSecretKey(web, "Auth__ApiKey").Should().Be("DEVICE_MANAGEMENT_OPERATOR_API_KEY"); + JsonEnvSecretOptional(web, "Auth__ApiKey").Should().BeNull(); + JsonEnvSecretName(web, "FlowerCore__Auth__ApiKey").Should().Be("fc-devicemgmt-runtime"); + JsonEnvSecretKey(web, "FlowerCore__Auth__ApiKey").Should().Be("DEVICE_MANAGEMENT_OPERATOR_API_KEY"); + JsonEnvSecretOptional(web, "FlowerCore__Auth__ApiKey").Should().BeNull(); + JsonEnvSecretName(web, "Auth__AdminApiKey").Should().Be("fc-devicemgmt-runtime"); + JsonEnvSecretKey(web, "Auth__AdminApiKey").Should().Be("DEVICE_MANAGEMENT_ADMIN_API_KEY"); + JsonEnvSecretOptional(web, "Auth__AdminApiKey").Should().BeNull(); + JsonEnvSecretName(web, "FlowerCore__Auth__AdminApiKey").Should().Be("fc-devicemgmt-runtime"); + JsonEnvSecretKey(web, "FlowerCore__Auth__AdminApiKey").Should().Be("DEVICE_MANAGEMENT_ADMIN_API_KEY"); + JsonEnvSecretOptional(web, "FlowerCore__Auth__AdminApiKey").Should().BeNull(); + } + [Fact] public void Gx10PhpTenantRoutes_HaveEdgeControlSubstrate() { @@ -1448,9 +1468,13 @@ public sealed class FleetManifestLintTests private static bool? JsonEnvSecretOptional(JsonElement container, string name) { - return JsonEnvMapping(container, name) is { } env - ? env.GetProperty("valueFrom").GetProperty("secretKeyRef").GetProperty("optional").GetBoolean() - : null; + if (JsonEnvMapping(container, name) is not { } env) + { + return null; + } + + var secretKeyRef = env.GetProperty("valueFrom").GetProperty("secretKeyRef"); + return secretKeyRef.TryGetProperty("optional", out var optional) ? optional.GetBoolean() : null; } private static string? JsonEnvValue(JsonElement container, string name)