Skip to content

MQTT Broker: authenticate connections from client certificates - #3203

Open
pankalog wants to merge 1 commit into
mtls/2-broker-mtlsfrom
mtls/3-cert-auth
Open

pankalog wants to merge 1 commit into
mtls/2-broker-mtlsfrom
mtls/3-cert-auth

Conversation

@pankalog

@pankalog pankalog commented Aug 17, 2026 •

Copy link
Copy Markdown
Member

Description

Lets a device authenticate with its client certificate instead of a username and password.

Artemis has already validated the chain against the truststore by the time ActiveMQORSecurityManager runs, so nothing is verified here — the job is mapping an accepted certificate onto a Keycloak service user. Identity comes from the first certificate in the chain, the only one the handshake proves the client holds the key for: CN is the service user, OU is the realm, and an explicit connect username wins over the OU when it names an existing realm.

The service user is looked up within that realm (names are only unique within one), trying the auto-provisioning prefix before the bare CN so both provisioned devices and hand-made service users work. Its secret completes the normal token exchange, so authorisation downstream is unchanged.

Connections with no certificate are untouched; one whose CN matches no service user stays anonymous rather than being rejected, which is what lets an unprovisioned device reach the provisioning topic in the next PR.

How to verify

MqttBrokerTest."Mqtt broker mTLS test" covers a certificate belonging to an existing service user, one not signed by the CA, and a signed one with no service user.

Checklist

  • 1. Acceptance criteria of the linked issue(s) are met
  • 2. Tests are written and all tests pass
  • 3. Changes are manually tested by you and the reviewer

Lets a device authenticate with its client certificate instead of a username and password.
ActiveMQORSecurityManager takes the leaf of the chain presented on the connection - the only
certificate the handshake proves the client holds the key for - requires it to carry the
Client Authentication extended key usage, and derives the identity from its subject: CN is
the service user, OU is the realm. An explicit username still wins over the OU when it names
an existing realm, which is how a device whose certificate carries no OU says where it
belongs.

The service user is then looked up within that realm, since service user names are only
unique within one, trying the auto-provisioning prefix before the bare CN so that both
provisioned devices and hand-made service users can present a certificate. Its secret
completes the normal token exchange, so authorisation is unchanged downstream.

A connection presenting no certificate, or one whose CN matches no service user, is left
anonymous rather than rejected, preserving existing behaviour for plain MQTT clients. A
certificate that is not a client certificate at all is refused.

getSubjectOU joins getSubjectCN in ProvisioningUtil since certificate-derived identity is now
needed outside provisioning; both parse the subject as an RFC 2253 LDAP name rather than by
string splitting.
@sonarqubecloud

sonarqubecloud Bot commented Sep 4, 2026

Copy link
Copy Markdown

@pankalog
pankalog marked this pull request as ready for review September 4, 2026 09:29
@pankalog
pankalog requested a review from richturner September 4, 2026 09:47

@richturner richturner left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two concerns with how a certificate is mapped to an identity in ActiveMQORSecurityManager.authenticate:

1. The connect username can override the authenticated OU

if (!TextUtil.isNullOrEmpty(originalUsername)
    && identityProvider.realmExists(originalUsername)) {
  realm = originalUsername;
}

The OU was verified in the handshake, but the username field is unauthenticated client input. A device whose certificate says CN=device1, OU=realmA can connect with username realmB and log in as the service user device1 in realmB, if one exists. I'd only use the username when the certificate has no OU. Better still, don't let the client pick the realm at all (see 2).

2. The realm isn't tied to the CA that issued the certificate

Login trusts any certificate that chains to the acceptor's single, broker-wide truststore, then maps its CN to any service user in the claimed realm. That includes hand-made service users through the bare-CN fallback. So a CA trusted to issue certificates for one realm's devices can authenticate as service users in any realm, including ones that were never provisioned from a certificate. #3204 checks the OU against the config realm, but only during provisioning, not at login.

Suggestions:

  • Scope login the same way provisioning is scoped: find the provisioning config whose CA signed the leaf (getMatchingX509ProvisioningConfig already does this), and take the realm from that config instead of the OU or username. Reject the connection when the two disagree.
  • Only map to PROVISIONING_USER_PREFIX service users, or make the bare-CN fallback something each realm opts into. That way certificate login can't reach accounts that were created for password or secret auth.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants