MQTT auth
The celine.mqtt_auth backend that mosquitto-go-auth calls (/user, /acl, /superuser).
Behaviour is described in mqtt-integration.md; these are the parts a
deployment's posture decides.
The posture signal is the platform's (celine.sdk.posture): CELINE_ENV, then ENVIRONMENT;
only dev relaxes — unset, prod, staging, test, local, ci or a typo is hardened.
REQ-0009 — outside dev, startup refuses an MQTT auth service with no token audience or the SDK's local OIDC defaults
create_app runs the posture guard before it loads a policy. In a hardened environment it
raises InsecureConfiguration, listing every violation at once, when:
CELINE_OIDC_AUDIENCEis not set — without it a token issued to any client of the realm is accepted as an MQTT credential;CELINE_OIDC_BASE_URLorCELINE_OIDC_JWKS_URIwas not set and the SDK's local-Keycloak default is in use.
CELINE_OIDC_AUDIENCE is read from the environment and, when set, enforced on every token in
every environment: a token whose aud does not contain it is answered 403. In dev the
audience is optional and the findings above are logged as one warning.
Until 2026-10 the service constructed its OIDC settings with
audience=None, overriding the environment, so no audience was ever checked (NIS2 review finding R24).
REQ-0010 — the service answers no cross-origin request
The service installs no CORS middleware, in any environment: its callers are mosquitto-go-auth's
HTTP backend and health probes, never a browser. A preflight gets no
Access-Control-Allow-Origin, and neither does any other response.
Until 2026-10
devanswered cross-origin requests from any origin, with credentials (NIS2 review finding R31).
REQ-0014 — no MQTT superuser, no group grant, and a service is what its token says it is
The MQTT auth backend under the two-level model (REQ-0011):
/superuseralways answers403, for every token: no scope, group or role makes a client an MQTT superuser. A realmplatform-adminis not one either.- User or service is decided by the token's kind (
celine-sdk'sis_service_account), never by whether the token holds a group. A service account is judged by its scopes; a person's token is a user whatever scopes it carries. - No group grants anything on the broker. The policy input carries no groups at all: no
realm
groups, no organization group, no merge of the two. A group named like a grant (admin,mqtt.admin,<service>.admin,<service>.<resource>.<verb>) in either place is inert. - A platform role grants nothing on the broker either.
platform-adminis not an MQTT grant, and no role reaches the policy throughgroups.
A person's token therefore reaches no topic: the broker's clients are services, each holding
its declared <service>.<resource>.<verb> scopes.
REQ-0017 — a broker token is a token requested for the broker
CELINE_OIDC_AUDIENCE (REQ-0009) is svc-mqtt, and only a token requested with the scope
mqtt carries it:
clients.yamldeclares the scopemqttwithaudience: svc-mqtt;synckeeps one audience mapper (included.custom.audience) on that client scope. The scope grants nothing — topic access is still the token's<service>.<resource>.<verb>and<service>.adminscopes.broker_scope: mqttmakessyncgrant it as an optional scope to every service-account client holding a scope the ACL can grant a topic with and declared by the file that declaresbroker_scope. Nobody lists it by hand, and a browser client never gets it.- A client another file declares does not get it, whatever its scopes: a guest's own
clients (ds's
svc-ds-*, a deployment's organisation clients) hold<service>.<resource>.<verb>scopes for HTTP and never connect to the broker, including when the host's overlay grants them a scope shaped like a topic grant. A guest client that does use the broker is grantedmqttby name, in any file. With one file, nothing changes. - celine-sdk's
MqttBrokerasks its client-credentials provider forscope=mqtt(MqttConfig.token_scope). Tokens a service mints for HTTP calls do not carrysvc-mqtt, so a service that receives another service's token cannot replay it to the broker.
Before 2026-10 the deployed broker required
svc-celine-policies, an audience no client's token carried, so every MQTT client was refused outside dev (NIS2 review finding R24).