Prechádzať zdrojové kódy

test: refactor kerberos test suite

after emqtt 1.13.0, the support for kerberos auth is more complete
zmstone 1 rok pred
rodič
commit
73962649e7

+ 0 - 15
apps/emqx_auth_http/test/emqx_authn_scram_restapi_test_server.erl

@@ -98,18 +98,3 @@ default_handler(Req0, State) ->
         Req0
     ),
     {ok, Req, State}.
-
-make_user_info(Password, Algorithm, IterationCount) ->
-    {StoredKey, ServerKey, Salt} = esasl_scram:generate_authentication_info(
-        Password,
-        #{
-            algorithm => Algorithm,
-            iteration_count => IterationCount
-        }
-    ),
-    #{
-        stored_key => StoredKey,
-        server_key => ServerKey,
-        salt => Salt,
-        is_superuser => false
-    }.

+ 0 - 6
apps/emqx_auth_jwt/test/emqx_authn_jwt_SUITE.erl

@@ -417,12 +417,6 @@ t_jwks_custom_headers(_Config) ->
     on_exit(fun() -> ok = emqx_authn_http_test_server:stop() end),
     ok = emqx_authn_http_test_server:set_handler(jwks_handler_spy()),
 
-    PrivateKey = test_rsa_key(private),
-    Payload = #{
-        <<"username">> => <<"myuser">>,
-        <<"foo">> => <<"myuser">>,
-        <<"exp">> => erlang:system_time(second) + 10
-    },
     Endpoint = iolist_to_binary("https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH),
     Config0 = #{
         <<"mechanism">> => <<"jwt">>,

+ 22 - 111
apps/emqx_auth_kerberos/test/emqx_authn_kerberos_SUITE.erl

@@ -22,7 +22,6 @@
 -define(SVR_PRINCIPAL, <<"mqtt/erlang.emqx.net@KDC.EMQX.NET">>).
 -define(SVR_KEYTAB_FILE, <<"/var/lib/secret/erlang.keytab">>).
 
--define(CLI_NAME, "krb_authn_cli").
 -define(CLI_PRINCIPAL, <<"krb_authn_cli@KDC.EMQX.NET">>).
 -define(CLI_KEYTAB_FILE, <<"/var/lib/secret/krb_authn_cli.keytab">>).
 
@@ -33,6 +32,7 @@ all() ->
     emqx_common_test_helpers:all(?MODULE).
 
 init_per_suite(Config) ->
+    ok = sasl_auth:kinit(?CLI_KEYTAB_FILE, ?CLI_PRINCIPAL),
     Apps = emqx_cth_suite:start([emqx, emqx_conf, emqx_auth, emqx_auth_kerberos], #{
         work_dir => ?config(priv_dir, Config)
     }),
@@ -84,82 +84,64 @@ t_create_invalid(_Config) ->
     ).
 
 t_authenticate(_Config) ->
-    init_auth(),
-
-    {ok, Handler, CT1} = setup_cli(),
+    _ = init_auth(),
+    Args = emqx_authn_kerberos_client:auth_args(?CLI_KEYTAB_FILE, ?CLI_PRINCIPAL),
     {ok, C} = emqtt:start_link(
         #{
             host => ?HOST,
             port => ?PORT,
             proto_ver => v5,
-            properties =>
-                #{
-                    'Authentication-Method' => ?AUTHN_METHOD,
-                    'Authentication-Data' => CT1
-                },
             custom_auth_callbacks =>
                 #{
-                    init => auth_init(Handler),
-                    handle_auth => fun auth_handle/3
+                    init => {fun emqx_authn_kerberos_client:auth_init/1, Args},
+                    handle_auth => fun emqx_authn_kerberos_client:auth_handle/3
                 }
         }
     ),
     ?assertMatch({ok, _}, emqtt:connect(C)),
-    stop_cli(Handler),
     ok.
 
-t_authenticate_bad_props(_Config) ->
-    erlang:process_flag(trap_exit, true),
-    init_auth(),
-
-    {ok, Handler, CT1} = setup_cli(),
+t_authenticate_bad_method(_Config) ->
+    _ = init_auth(),
+    %% The method is GSSAPI-KERBEROS, sending just "GSSAPI" will fail
+    Method = <<"GSSAPI">>,
+    Args = emqx_authn_kerberos_client:auth_args(?CLI_KEYTAB_FILE, ?CLI_PRINCIPAL, Method),
     {ok, C} = emqtt:start_link(
         #{
             host => ?HOST,
             port => ?PORT,
             proto_ver => v5,
-            properties =>
-                #{
-                    'Authentication-Method' => <<"SCRAM-SHA-512">>,
-                    'Authentication-Data' => CT1
-                },
             custom_auth_callbacks =>
                 #{
-                    init => auth_init(Handler),
-                    handle_auth => fun auth_handle/3
+                    init => {fun emqx_authn_kerberos_client:auth_init/1, Args},
+                    handle_auth => fun emqx_authn_kerberos_client:auth_handle/3
                 }
         }
     ),
-
+    unlink(C),
     ?assertMatch({error, {not_authorized, _}}, emqtt:connect(C)),
-    stop_cli(Handler),
     ok.
 
 t_authenticate_bad_token(_Config) ->
-    erlang:process_flag(trap_exit, true),
-    init_auth(),
-
-    {ok, Handler, CT1} = setup_cli(),
+    _ = init_auth(),
+    %% Malform the first client token to test auth failure.
+    Args = emqx_authn_kerberos_client:auth_args(
+        ?CLI_KEYTAB_FILE, ?CLI_PRINCIPAL, ?AUTHN_METHOD, <<"badtoken">>
+    ),
     {ok, C} = emqtt:start_link(
         #{
             host => ?HOST,
             port => ?PORT,
             proto_ver => v5,
-            properties =>
-                #{
-                    'Authentication-Method' => <<"SCRAM-SHA-512">>,
-                    'Authentication-Data' => <<CT1/binary, "invalid">>
-                },
             custom_auth_callbacks =>
                 #{
-                    init => auth_init(Handler),
-                    handle_auth => fun auth_handle/3
+                    init => {fun emqx_authn_kerberos_client:auth_init/1, Args},
+                    handle_auth => fun emqx_authn_kerberos_client:auth_handle/3
                 }
         }
     ),
-
+    unlink(C),
     ?assertMatch({error, {not_authorized, _}}, emqtt:connect(C)),
-    stop_cli(Handler),
     ok.
 
 t_destroy(_) ->
@@ -170,21 +152,18 @@ t_destroy(_) ->
         ?GLOBAL
     ),
 
-    {ok, Handler, CT1} = setup_cli(),
-
     ?assertMatch(
         ignore,
         emqx_authn_mongodb:authenticate(
             #{
                 auth_method => ?AUTHN_METHOD,
-                auth_data => CT1,
+                auth_data => <<"anydata">>,
                 auth_cache => undefined
             },
             State
         )
     ),
 
-    stop_cli(Handler),
     ok.
 
 %%------------------------------------------------------------------------------
@@ -210,71 +189,3 @@ init_auth() ->
     {ok, [#{state := State}]} = emqx_authn_chains:list_authenticators(?GLOBAL),
 
     State.
-
-%%------------------------------------------------------------------------------
-%% Custom auth
-
-auth_init(Handler) ->
-    fun() -> #{handler => Handler, step => 1} end.
-
-auth_handle(AuthState, Reason, Props) ->
-    ct:pal(">>> auth packet received:\n  rc: ~p\n  props:\n  ~p", [Reason, Props]),
-    do_auth_handle(AuthState, Reason, Props).
-
-do_auth_handle(
-    #{handler := Handler, step := Step} = AuthState0,
-    continue_authentication,
-    #{
-        'Authentication-Method' := ?AUTHN_METHOD,
-        'Authentication-Data' := ST
-    }
-) when Step =< 3 ->
-    {ok, CT} = call_cli_agent(Handler, {step, ST}),
-    AuthState = AuthState0#{step := Step + 1},
-    OutProps = #{
-        'Authentication-Method' => ?AUTHN_METHOD,
-        'Authentication-Data' => CT
-    },
-    {continue, {?RC_CONTINUE_AUTHENTICATION, OutProps}, AuthState};
-do_auth_handle(_AuthState, _Reason, _Props) ->
-    {stop, protocol_error}.
-
-%%------------------------------------------------------------------------------
-%% Client Agent
-
-setup_cli() ->
-    Pid = erlang:spawn(fun() -> cli_agent_loop(#{}) end),
-    {ok, CT1} = call_cli_agent(Pid, setup),
-    {ok, Pid, CT1}.
-
-call_cli_agent(Pid, Msg) ->
-    Ref = erlang:make_ref(),
-    erlang:send(Pid, {call, self(), Ref, Msg}),
-    receive
-        {Ref, Data} ->
-            {ok, Data}
-    after 3000 ->
-        error("client agent timeout")
-    end.
-
-stop_cli(Pid) ->
-    erlang:send(Pid, stop).
-
-cli_agent_loop(State) ->
-    receive
-        stop ->
-            ok;
-        {call, From, Ref, Msg} ->
-            {ok, Reply, State2} = cli_agent_handler(Msg, State),
-            erlang:send(From, {Ref, Reply}),
-            cli_agent_loop(State2)
-    end.
-
-cli_agent_handler(setup, State) ->
-    ok = sasl_auth:kinit(?CLI_KEYTAB_FILE, ?CLI_PRINCIPAL),
-    {ok, Client} = sasl_auth:client_new(?SERVICE, ?SVR_HOST, ?CLI_PRINCIPAL, ?CLI_NAME),
-    {ok, {sasl_continue, CT1}} = sasl_auth:client_start(Client),
-    {ok, CT1, State#{client => Client}};
-cli_agent_handler({step, ST}, #{client := Client} = State) ->
-    {ok, {_, CT}} = sasl_auth:client_step(Client, ST),
-    {ok, CT, State}.

+ 112 - 0
apps/emqx_auth_kerberos/test/emqx_authn_kerberos_client.erl

@@ -0,0 +1,112 @@
+%%--------------------------------------------------------------------
+%% Copyright (c) 2024 EMQ Technologies Co., Ltd. All Rights Reserved.
+%%--------------------------------------------------------------------
+
+-module(emqx_authn_kerberos_client).
+
+-export([
+    auth_args/2,
+    auth_args/3,
+    auth_args/4,
+    auth_init/1,
+    auth_handle/3
+]).
+
+-include_lib("emqx/include/emqx_mqtt.hrl").
+
+%% This must match the server principal
+%% For this test, the server principal is "mqtt/erlang.emqx.net@KDC.EMQX.NET"
+server_fqdn() -> <<"erlang.emqx.net">>.
+
+realm() -> <<"KDC.EMQX.NET">>.
+
+server_principal() ->
+    bin(["mqtt/", server_fqdn(), "@", realm()]).
+
+auth_args(ClientKeytab, CLientPrincipal) ->
+    auth_args(ClientKeytab, CLientPrincipal, <<"GSSAPI-KERBEROS">>).
+
+auth_args(ClientKeytab, CLientPrincipal, Method) ->
+    auth_args(ClientKeytab, CLientPrincipal, Method, undefined).
+
+auth_args(ClientKeytab, CLientPrincipal, Method, FirstToken) ->
+    [
+        #{
+            client_keytab => ClientKeytab,
+            client_principal => CLientPrincipal,
+            server_fqdn => server_fqdn(),
+            server_principal => server_principal(),
+            method => Method,
+            first_token => FirstToken
+        }
+    ].
+
+auth_init(#{
+    client_keytab := KeytabFile,
+    client_principal := ClientPrincipal,
+    server_fqdn := ServerFQDN,
+    server_principal := ServerPrincipal,
+    method := Method,
+    first_token := FirstToken
+}) ->
+    [ClientName, _Realm] = binary:split(ClientPrincipal, <<"@">>),
+    ok = sasl_auth:kinit(KeytabFile, ClientPrincipal),
+    {ok, ClientHandle} = sasl_auth:client_new(<<"mqtt">>, ServerFQDN, ServerPrincipal, ClientName),
+    {ok, {sasl_continue, FirstClientToken}} = sasl_auth:client_start(ClientHandle),
+    InitialProps =
+        case FirstToken of
+            undefined -> props(FirstClientToken, Method);
+            Token -> props(Token, Method)
+        end,
+    State = #{client_handle => ClientHandle, step => 1},
+    {InitialProps, State}.
+
+auth_handle(
+    #{
+        step := 1,
+        client_handle := ClientHandle
+    } = AuthState,
+    Reason,
+    Props
+) ->
+    ct:pal("step-1: auth packet received:\n  rc: ~p\n  props:\n  ~p", [Reason, Props]),
+    case {Reason, Props} of
+        {continue_authentication, #{'Authentication-Data' := ServerToken}} ->
+            {ok, {sasl_continue, ClientToken}} =
+                sasl_auth:client_step(ClientHandle, ServerToken),
+            OutProps = props(ClientToken),
+            NewState = AuthState#{step := 2},
+            {continue, {?RC_CONTINUE_AUTHENTICATION, OutProps}, NewState};
+        _ ->
+            {stop, protocol_error}
+    end;
+auth_handle(
+    #{
+        step := 2,
+        client_handle := ClientHandle
+    },
+    Reason,
+    Props
+) ->
+    ct:pal("step-2: auth packet received:\n  rc: ~p\n  props:\n  ~p", [Reason, Props]),
+    case {Reason, Props} of
+        {continue_authentication, #{'Authentication-Data' := ServerToken}} ->
+            {ok, {sasl_ok, ClientToken}} =
+                sasl_auth:client_step(ClientHandle, ServerToken),
+            OutProps = props(ClientToken),
+            NewState = #{done => erlang:system_time()},
+            {continue, {?RC_CONTINUE_AUTHENTICATION, OutProps}, NewState};
+        _ ->
+            {stop, protocol_error}
+    end.
+
+props(Data) ->
+    props(Data, <<"GSSAPI-KERBEROS">>).
+
+props(Data, Method) ->
+    #{
+        'Authentication-Method' => Method,
+        'Authentication-Data' => Data
+    }.
+
+bin(X) -> iolist_to_binary(X).