Service accounts & API-key TTL ceiling (GY.C3a)
MOD supports a bounded, reviewable inventory of every non-human credential in the installation — the first slice of the strong authentication program. Wording is deliberate: these features support a strong-authentication posture; they do not by themselves make an installation "compliant".
What is a service account here
- API keys —
sk_...credentials mapped to a Keycloak service account (client-credentials flow; short-lived JWTs, 1 hour). Includes app keys (GX.A3), which are machine credentials bound to an app's scope. - Keycloak service-account clients — the OIDC clients MOD creates
for those keys (
api-key-*,mod-app-*), reported from the existing Keycloak admin seam. - MFA-exempt service principals — machine users that never pass the
browser flow (same exemption rule as the MFA surface:
service-account-*,*@service.local, the seeded service users).
The inventory endpoint
GET /v1/platform/compliance/service-accounts
- Platform admin sees the whole platform; a tenant admin sees their own tenant only; everyone else gets 403.
- Each API key row carries id, tenant, owner, description, prefix,
scopes (read-only flag + capability allowlist), created / expires /
last-used timestamps, and flags:
no_expiry,expired,expiry_beyond_ceiling,unused(tunable via?unused_days=N, default 90). The secret and its hash are never returned. - If Keycloak is unreachable, the response is still 200 — a partial
result with a
keycloak_errorfield instead of the client list.
The TTL ceiling
Platform setting auth.api_key_max_ttl_days (default 0 = no
ceiling, preserving the pre-existing behaviour where keys may be
created without an expiry).
When the ceiling is set (> 0):
- creating or rotating an API key without an
expires_at→ 422; - an
expires_atbeyond the ceiling → 422; - the refusal happens before the Keycloak client is created (no orphan clients); app keys are bounded by the ceiling when it is set; while the ceiling is 0 they keep the legacy unbounded behaviour (flagged in the inventory).
Existing keys that were minted before the ceiling is set are not
retroactively expired — the inventory flags them
(no_expiry / expiry_beyond_ceiling) so they can be rotated down.
Conformance check
auth.service_accounts_bounded (supports NIST 800-53 IA-5, CMMC
IA.L2-3.1.3, ISO 27001 A.8.5) reports:
- not-applicable while the ceiling is 0;
- pass when every live API key carries an expiry;
- fail (warn) naming the offending key ids and prefixes (metadata only) when the ceiling is set and a key lacks an expiry.
It runs on the normal conformance schedule and on-demand runs, like the other GY.C12 checks.