Merge pull request #13229 from zmstone/0611-auth-with-raw-cert

feat(authn): support `${cert_pem}` placeholder
This commit is contained in:
zmstone 2024-06-11 21:16:01 +02:00 committed by GitHub
commit 51810c6919
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 32 additions and 7 deletions

View File

@ -28,8 +28,10 @@
%% cert %% cert
-define(VAR_CERT_SUBJECT, "cert_subject"). -define(VAR_CERT_SUBJECT, "cert_subject").
-define(VAR_CERT_CN_NAME, "cert_common_name"). -define(VAR_CERT_CN_NAME, "cert_common_name").
-define(VAR_CERT_PEM, "cert_pem").
-define(PH_CERT_SUBJECT, ?PH(?VAR_CERT_SUBJECT)). -define(PH_CERT_SUBJECT, ?PH(?VAR_CERT_SUBJECT)).
-define(PH_CERT_CN_NAME, ?PH(?VAR_CERT_CN_NAME)). -define(PH_CERT_CN_NAME, ?PH(?VAR_CERT_CN_NAME)).
-define(PH_CERT_PEM, ?PH(?VAR_CERT_PEM)).
%% MQTT/Gateway %% MQTT/Gateway
-define(VAR_PASSWORD, "password"). -define(VAR_PASSWORD, "password").

View File

@ -1729,6 +1729,16 @@ count_flapping_event(_ConnPkt, #channel{clientinfo = ClientInfo}) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
%% Authenticate %% Authenticate
%% If peercert exists, add it as `cert_pem` credential field.
maybe_add_cert(Map, #channel{conninfo = ConnInfo}) ->
maybe_add_cert(Map, ConnInfo);
maybe_add_cert(Map, #{peercert := PeerCert}) when is_binary(PeerCert) ->
%% NOTE: it's raw binary at this point,
%% encoding to PEM (base64) is done lazy in emqx_authn_utils:render_var
Map#{cert_pem => PeerCert};
maybe_add_cert(Map, _) ->
Map.
authenticate( authenticate(
?CONNECT_PACKET( ?CONNECT_PACKET(
#mqtt_packet_connect{ #mqtt_packet_connect{
@ -1741,20 +1751,23 @@ authenticate(
auth_cache = AuthCache auth_cache = AuthCache
} = Channel } = Channel
) -> ) ->
%% Auth with CONNECT packet for MQTT v5
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined), AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),
do_authenticate( Credential0 =
ClientInfo#{ ClientInfo#{
auth_method => AuthMethod, auth_method => AuthMethod,
auth_data => AuthData, auth_data => AuthData,
auth_cache => AuthCache auth_cache => AuthCache
}, },
Channel Credential = maybe_add_cert(Credential0, Channel),
); do_authenticate(Credential, Channel);
authenticate( authenticate(
?CONNECT_PACKET(#mqtt_packet_connect{password = Password}), ?CONNECT_PACKET(#mqtt_packet_connect{password = Password}),
#channel{clientinfo = ClientInfo} = Channel #channel{clientinfo = ClientInfo} = Channel
) -> ) ->
do_authenticate(ClientInfo#{password => Password}, Channel); %% Auth with CONNECT packet for MQTT v3
Credential = maybe_add_cert(ClientInfo#{password => Password}, Channel),
do_authenticate(Credential, Channel);
authenticate( authenticate(
?AUTH_PACKET(_, #{'Authentication-Method' := AuthMethod} = Properties), ?AUTH_PACKET(_, #{'Authentication-Method' := AuthMethod} = Properties),
#channel{ #channel{
@ -1763,6 +1776,7 @@ authenticate(
auth_cache = AuthCache auth_cache = AuthCache
} = Channel } = Channel
) -> ) ->
%% Enhanced auth
case emqx_mqtt_props:get('Authentication-Method', ConnProps, undefined) of case emqx_mqtt_props:get('Authentication-Method', ConnProps, undefined) of
AuthMethod -> AuthMethod ->
AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined), AuthData = emqx_mqtt_props:get('Authentication-Data', Properties, undefined),

View File

@ -55,6 +55,7 @@
?VAR_PEERHOST, ?VAR_PEERHOST,
?VAR_CERT_SUBJECT, ?VAR_CERT_SUBJECT,
?VAR_CERT_CN_NAME, ?VAR_CERT_CN_NAME,
?VAR_CERT_PEM,
?VAR_NS_CLIENT_ATTRS ?VAR_NS_CLIENT_ATTRS
]). ]).
@ -357,6 +358,8 @@ render_var(_, undefined) ->
% Any allowed but undefined binding will be replaced with empty string, even when % Any allowed but undefined binding will be replaced with empty string, even when
% rendering SQL values. % rendering SQL values.
<<>>; <<>>;
render_var(?VAR_CERT_PEM, Value) ->
base64:encode(Value);
render_var(?VAR_PEERHOST, Value) -> render_var(?VAR_PEERHOST, Value) ->
inet:ntoa(Value); inet:ntoa(Value);
render_var(_Name, Value) -> render_var(_Name, Value) ->

View File

@ -37,6 +37,7 @@
protocol => mqtt, protocol => mqtt,
cert_subject => <<"cert_subject_data">>, cert_subject => <<"cert_subject_data">>,
cert_common_name => <<"cert_common_name_data">>, cert_common_name => <<"cert_common_name_data">>,
cert_pem => <<"fake_raw_cert_to_be_base64_encoded">>,
client_attrs => #{<<"group">> => <<"g1">>} client_attrs => #{<<"group">> => <<"g1">>}
}). }).
@ -222,7 +223,8 @@ t_no_value_for_placeholder(_Config) ->
{ok, RawBody, Req1} = cowboy_req:read_body(Req0), {ok, RawBody, Req1} = cowboy_req:read_body(Req0),
#{ #{
<<"cert_subject">> := <<"">>, <<"cert_subject">> := <<"">>,
<<"cert_common_name">> := <<"">> <<"cert_common_name">> := <<"">>,
<<"cert_pem">> := <<"">>
} = emqx_utils_json:decode(RawBody, [return_maps]), } = emqx_utils_json:decode(RawBody, [return_maps]),
Req = cowboy_req:reply( Req = cowboy_req:reply(
200, 200,
@ -238,7 +240,8 @@ t_no_value_for_placeholder(_Config) ->
<<"headers">> => #{<<"content-type">> => <<"application/json">>}, <<"headers">> => #{<<"content-type">> => <<"application/json">>},
<<"body">> => #{ <<"body">> => #{
<<"cert_subject">> => ?PH_CERT_SUBJECT, <<"cert_subject">> => ?PH_CERT_SUBJECT,
<<"cert_common_name">> => ?PH_CERT_CN_NAME <<"cert_common_name">> => ?PH_CERT_CN_NAME,
<<"cert_pem">> => ?PH_CERT_PEM
} }
}, },
@ -251,7 +254,7 @@ t_no_value_for_placeholder(_Config) ->
ok = emqx_authn_http_test_server:set_handler(Handler), ok = emqx_authn_http_test_server:set_handler(Handler),
Credentials = maps:without([cert_subject, cert_common_name], ?CREDENTIALS), Credentials = maps:without([cert_subject, cert_common_name, cert_pem], ?CREDENTIALS),
?assertMatch({ok, _}, emqx_access_control:authenticate(Credentials)), ?assertMatch({ok, _}, emqx_access_control:authenticate(Credentials)),
@ -656,8 +659,10 @@ samples() ->
<<"peerhost">> := <<"127.0.0.1">>, <<"peerhost">> := <<"127.0.0.1">>,
<<"cert_subject">> := <<"cert_subject_data">>, <<"cert_subject">> := <<"cert_subject_data">>,
<<"cert_common_name">> := <<"cert_common_name_data">>, <<"cert_common_name">> := <<"cert_common_name_data">>,
<<"cert_pem">> := CertPem,
<<"the_group">> := <<"g1">> <<"the_group">> := <<"g1">>
} = emqx_utils_json:decode(RawBody, [return_maps]), } = emqx_utils_json:decode(RawBody, [return_maps]),
<<"fake_raw_cert_to_be_base64_encoded">> = base64:decode(CertPem),
Req = cowboy_req:reply( Req = cowboy_req:reply(
200, 200,
#{<<"content-type">> => <<"application/json">>}, #{<<"content-type">> => <<"application/json">>},
@ -676,6 +681,7 @@ samples() ->
<<"peerhost">> => ?PH_PEERHOST, <<"peerhost">> => ?PH_PEERHOST,
<<"cert_subject">> => ?PH_CERT_SUBJECT, <<"cert_subject">> => ?PH_CERT_SUBJECT,
<<"cert_common_name">> => ?PH_CERT_CN_NAME, <<"cert_common_name">> => ?PH_CERT_CN_NAME,
<<"cert_pem">> => ?PH_CERT_PEM,
<<"the_group">> => <<"${client_attrs.group}">> <<"the_group">> => <<"${client_attrs.group}">>
} }
}, },