Skip to content

Commit 94baa7c

Browse files
Merge pull request #11702 from rabbitmq/mqtt-extract-client-id-from-cert
Extract MQTT client_id from client certificate and propagate to authnz backends
2 parents e396dd1 + 606a651 commit 94baa7c

File tree

7 files changed

+212
-35
lines changed

7 files changed

+212
-35
lines changed

deps/rabbit/src/rabbit_ssl.erl

Lines changed: 35 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,15 @@
1010
-include_lib("public_key/include/public_key.hrl").
1111

1212
-export([peer_cert_issuer/1, peer_cert_subject/1, peer_cert_validity/1]).
13-
-export([peer_cert_subject_items/2, peer_cert_auth_name/1]).
13+
-export([peer_cert_subject_items/2, peer_cert_auth_name/1, peer_cert_auth_name/2]).
1414
-export([cipher_suites_erlang/2, cipher_suites_erlang/1,
1515
cipher_suites_openssl/2, cipher_suites_openssl/1,
1616
cipher_suites/1]).
1717
-export([info/2, cert_info/2]).
1818

1919
%%--------------------------------------------------------------------------
2020

21-
-export_type([certificate/0]).
21+
-export_type([certificate/0, ssl_cert_login_type/0]).
2222

2323
% Due to API differences between OTP releases.
2424
-dialyzer(no_missing_calls).
@@ -109,28 +109,51 @@ peer_cert_subject_alternative_names(Cert, Type) ->
109109
peer_cert_validity(Cert) ->
110110
rabbit_cert_info:validity(Cert).
111111

112+
-type ssl_cert_login_type() ::
113+
{subject_alternative_name | subject_alt_name, atom(), integer()} |
114+
{distinguished_name | common_name, undefined, undefined }.
115+
116+
-spec extract_ssl_cert_login_settings() -> none | ssl_cert_login_type().
117+
extract_ssl_cert_login_settings() ->
118+
case application:get_env(rabbit, ssl_cert_login_from) of
119+
{ok, Mode} ->
120+
case Mode of
121+
subject_alternative_name -> extract_san_login_type(Mode);
122+
subject_alt_name -> extract_san_login_type(Mode);
123+
_ -> {Mode, undefined, undefined}
124+
end;
125+
undefined -> none
126+
end.
127+
128+
extract_san_login_type(Mode) ->
129+
{Mode,
130+
application:get_env(rabbit, ssl_cert_login_san_type, dns),
131+
application:get_env(rabbit, ssl_cert_login_san_index, 0)
132+
}.
133+
112134
%% Extract a username from the certificate
113135
-spec peer_cert_auth_name(certificate()) -> binary() | 'not_found' | 'unsafe'.
114136
peer_cert_auth_name(Cert) ->
115-
{ok, Mode} = application:get_env(rabbit, ssl_cert_login_from),
116-
peer_cert_auth_name(Mode, Cert).
137+
case extract_ssl_cert_login_settings() of
138+
none -> 'not_found';
139+
Settings -> peer_cert_auth_name(Settings, Cert)
140+
end.
117141

118-
-spec peer_cert_auth_name(atom(), certificate()) -> binary() | 'not_found' | 'unsafe'.
119-
peer_cert_auth_name(distinguished_name, Cert) ->
142+
-spec peer_cert_auth_name(ssl_cert_login_type(), certificate()) -> binary() | 'not_found' | 'unsafe'.
143+
peer_cert_auth_name({distinguished_name, _, _}, Cert) ->
120144
case auth_config_sane() of
121145
true -> iolist_to_binary(peer_cert_subject(Cert));
122146
false -> unsafe
123147
end;
124148

125-
peer_cert_auth_name(subject_alt_name, Cert) ->
126-
peer_cert_auth_name(subject_alternative_name, Cert);
149+
peer_cert_auth_name({subject_alt_name, Type, Index0}, Cert) ->
150+
peer_cert_auth_name({subject_alternative_name, Type, Index0}, Cert);
127151

128-
peer_cert_auth_name(subject_alternative_name, Cert) ->
152+
peer_cert_auth_name({subject_alternative_name, Type, Index0}, Cert) ->
129153
case auth_config_sane() of
130154
true ->
131-
Type = application:get_env(rabbit, ssl_cert_login_san_type, dns),
132155
%% lists:nth/2 is 1-based
133-
Index = application:get_env(rabbit, ssl_cert_login_san_index, 0) + 1,
156+
Index = Index0 + 1,
134157
OfType = peer_cert_subject_alternative_names(Cert, otp_san_type(Type)),
135158
rabbit_log:debug("Peer certificate SANs of type ~ts: ~tp, index to use with lists:nth/2: ~b", [Type, OfType, Index]),
136159
case length(OfType) of
@@ -152,7 +175,7 @@ peer_cert_auth_name(subject_alternative_name, Cert) ->
152175
false -> unsafe
153176
end;
154177

155-
peer_cert_auth_name(common_name, Cert) ->
178+
peer_cert_auth_name({common_name, _, _}, Cert) ->
156179
%% If there is more than one CN then we join them with "," in a
157180
%% vaguely DN-like way. But this is more just so we do something
158181
%% more intelligent than crashing, if you actually want to escape

deps/rabbitmq_ct_helpers/tools/tls-certs/openssl.cnf.in

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ keyUsage = keyCertSign, cRLSign
4949
[ client_ca_extensions ]
5050
basicConstraints = CA:false
5151
keyUsage = digitalSignature,keyEncipherment
52+
subjectAltName = @client_alt_names
5253

5354
[ server_ca_extensions ]
5455
basicConstraints = CA:false
@@ -59,3 +60,6 @@ subjectAltName = @server_alt_names
5960
[ server_alt_names ]
6061
DNS.1 = @HOSTNAME@
6162
DNS.2 = localhost
63+
64+
[ client_alt_names ]
65+
DNS.1 = rabbit_client_id

deps/rabbitmq_mqtt/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,7 @@ rabbitmq_integration_suite(
135135
"test/rabbit_auth_backend_mqtt_mock.beam",
136136
"test/util.beam",
137137
],
138-
shard_count = 14,
138+
shard_count = 18,
139139
runtime_deps = [
140140
"@emqtt//:erlang_app",
141141
"@meck//:erlang_app",

deps/rabbitmq_mqtt/priv/schema/rabbitmq_mqtt.schema

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,20 @@ end}.
156156
{datatype, {enum, [true, false]}}]}.
157157

158158

159+
{mapping, "mqtt.ssl_cert_client_id_from", "rabbitmq_mqtt.ssl_cert_client_id_from", [
160+
{datatype, {enum, [distinguished_name, subject_alternative_name]}}
161+
]}.
162+
163+
{mapping, "mqtt.ssl_cert_login_san_type", "rabbitmq_mqtt.ssl_cert_login_san_type", [
164+
{datatype, {enum, [dns, ip, email, uri, other_name]}}
165+
]}.
166+
167+
{mapping, "mqtt.ssl_cert_login_san_index", "rabbitmq_mqtt.ssl_cert_login_san_index", [
168+
{datatype, integer}, {validators, ["non_negative_integer"]}
169+
]}.
170+
171+
172+
159173
%% TCP/Socket options (as per the broker configuration).
160174
%%
161175
%% {tcp_listen_options, [{backlog, 128},

deps/rabbitmq_mqtt/src/rabbit_mqtt_processor.erl

Lines changed: 56 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -182,9 +182,9 @@ process_connect(
182182
Result0 =
183183
maybe
184184
ok ?= check_extended_auth(ConnectProps),
185-
{ok, ClientId} ?= ensure_client_id(ClientId0, CleanStart, ProtoVer),
185+
{ok, ClientId1} ?= extract_client_id_from_certificate(ClientId0, Socket),
186+
{ok, ClientId} ?= ensure_client_id(ClientId1, CleanStart, ProtoVer),
186187
{ok, Username1, Password} ?= check_credentials(Username0, Password0, SslLoginName, PeerIp),
187-
188188
{VHostPickedUsing, {VHost, Username2}} = get_vhost(Username1, SslLoginName, Port),
189189
?LOG_DEBUG("MQTT connection ~s picked vhost using ~s", [ConnName0, VHostPickedUsing]),
190190
ok ?= check_vhost_exists(VHost, Username2, PeerIp),
@@ -642,6 +642,26 @@ check_credentials(Username, Password, SslLoginName, PeerIp) ->
642642
{error, ?RC_BAD_USER_NAME_OR_PASSWORD}
643643
end.
644644

645+
%% Extract client_id from the certificate provided it was configured to do so and
646+
%% it is possible to extract it else returns the client_id passed as parameter
647+
-spec extract_client_id_from_certificate(client_id(), rabbit_net:socket()) -> {ok, client_id()} | {error, reason_code()}.
648+
extract_client_id_from_certificate(Client0, Socket) ->
649+
case extract_ssl_cert_client_id_settings() of
650+
none -> {ok, Client0};
651+
SslClientIdSettings ->
652+
case ssl_client_id(Socket, SslClientIdSettings) of
653+
none ->
654+
{ok, Client0};
655+
Client0 ->
656+
{ok, Client0};
657+
Other ->
658+
?LOG_ERROR(
659+
"MQTT login failed: client_id in the certificate (~tp) does not match the client-provided ID (~p)",
660+
[Other, Client0]),
661+
{error, ?RC_CLIENT_IDENTIFIER_NOT_VALID}
662+
end
663+
end.
664+
645665
-spec ensure_client_id(client_id(), boolean(), protocol_version()) ->
646666
{ok, client_id()} | {error, reason_code()}.
647667
ensure_client_id(<<>>, _CleanStart = false, ProtoVer)
@@ -1029,16 +1049,9 @@ check_vhost_alive(VHost) ->
10291049
end.
10301050

10311051
check_user_login(VHost, Username, Password, ClientId, PeerIp, ConnName) ->
1032-
AuthProps = case Password of
1033-
none ->
1034-
%% SSL user name provided.
1035-
%% Authenticating using username only.
1036-
[];
1037-
_ ->
1038-
[{password, Password},
1039-
{vhost, VHost},
1040-
{client_id, ClientId}]
1041-
end,
1052+
AuthProps = [{vhost, VHost},
1053+
{client_id, ClientId},
1054+
{password, Password}],
10421055
case rabbit_access_control:check_user_login(Username, AuthProps) of
10431056
{ok, User = #user{username = Username1}} ->
10441057
notify_auth_result(user_authentication_success, Username1, ConnName),
@@ -2292,6 +2305,37 @@ ssl_login_name(Sock) ->
22922305
nossl -> none
22932306
end.
22942307

2308+
-spec extract_ssl_cert_client_id_settings() -> none | rabbit_ssl:ssl_cert_login_type().
2309+
extract_ssl_cert_client_id_settings() ->
2310+
case application:get_env(?APP_NAME, ssl_cert_client_id_from) of
2311+
{ok, Mode} ->
2312+
case Mode of
2313+
subject_alternative_name -> extract_client_id_san_type(Mode);
2314+
_ -> {Mode, undefined, undefined}
2315+
end;
2316+
undefined -> none
2317+
end.
2318+
2319+
extract_client_id_san_type(Mode) ->
2320+
{Mode,
2321+
application:get_env(?APP_NAME, ssl_cert_client_id_san_type, dns),
2322+
application:get_env(?APP_NAME, ssl_cert_client_id_san_index, 0)
2323+
}.
2324+
2325+
2326+
-spec ssl_client_id(rabbit_net:socket(), rabbit_ssl:ssl_cert_login_type()) ->
2327+
none | binary().
2328+
ssl_client_id(Sock, SslClientIdSettings) ->
2329+
case rabbit_net:peercert(Sock) of
2330+
{ok, C} -> case rabbit_ssl:peer_cert_auth_name(SslClientIdSettings, C) of
2331+
unsafe -> none;
2332+
not_found -> none;
2333+
Name -> Name
2334+
end;
2335+
{error, no_peercert} -> none;
2336+
nossl -> none
2337+
end.
2338+
22952339
-spec proto_integer_to_atom(protocol_version()) -> protocol_version_atom().
22962340
proto_integer_to_atom(3) ->
22972341
?MQTT_PROTO_V3;

0 commit comments

Comments
 (0)