diff --git a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl index 9295e5c7e..a1fcbe0d6 100644 --- a/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl +++ b/apps/emqx_authn/src/simple_authn/emqx_authn_jwt.erl @@ -114,8 +114,8 @@ certfile(_) -> undefined. keyfile(type) -> string(); keyfile(_) -> undefined. -verify(type) -> boolean(); -verify(default) -> false; +verify(type) -> hoconsc:enum([verify_peer, verify_none]); +verify(default) -> verify_none; verify(_) -> undefined. server_name_indication(type) -> string(); diff --git a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl index 7cf628392..ea16664f4 100644 --- a/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl +++ b/apps/emqx_authn/test/emqx_authn_jwt_SUITE.erl @@ -23,8 +23,6 @@ -include_lib("eunit/include/eunit.hrl"). -include_lib("snabbkaffe/include/snabbkaffe.hrl"). --include("emqx_authn.hrl"). - -define(AUTHN_ID, <<"mechanism:jwt">>). -define(JWKS_PORT, 33333). @@ -156,7 +154,7 @@ t_jwt_authenticator_public_key(_) -> ok. t_jwks_renewal(_Config) -> - {ok, _} = emqx_authn_http_test_server:start_link(?JWKS_PORT, ?JWKS_PATH), + {ok, _} = emqx_authn_http_test_server:start_link(?JWKS_PORT, ?JWKS_PATH, server_ssl_opts()), ok = emqx_authn_http_test_server:set_handler(fun jwks_handler/2), PrivateKey = test_rsa_key(private), @@ -164,45 +162,63 @@ t_jwks_renewal(_Config) -> JWS = generate_jws('public-key', Payload, PrivateKey), Credential = #{username => <<"myuser">>, password => JWS}, + + BadConfig0 = #{mechanism => jwt, + algorithm => 'public-key', + ssl => #{enable => false}, + verify_claims => [], - BadConfig = #{mechanism => jwt, - algorithm => 'public-key', - ssl => #{enable => false}, - verify_claims => [], - - use_jwks => true, - endpoint => "http://127.0.0.1:" ++ integer_to_list(?JWKS_PORT + 1) ++ ?JWKS_PATH, - refresh_interval => 1000 - }, + use_jwks => true, + endpoint => "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT + 1) ++ ?JWKS_PATH, + refresh_interval => 1000 + }, ok = snabbkaffe:start_trace(), {{ok, State0}, _} = ?wait_async_action( - emqx_authn_jwt:create(?AUTHN_ID, BadConfig), + emqx_authn_jwt:create(?AUTHN_ID, BadConfig0), #{?snk_kind := jwks_endpoint_response}, - 1000), + 10000), ok = snabbkaffe:stop(), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State0)), ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)), - GoodConfig = BadConfig#{endpoint => - "http://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH}, + ClientSSLOpts = client_ssl_opts(), + BadClientSSLOpts = ClientSSLOpts#{server_name_indication => "authn-https-unknown-host"}, + + BadConfig1 = BadConfig0#{endpoint => + "https://127.0.0.1:" ++ integer_to_list(?JWKS_PORT) ++ ?JWKS_PATH, + ssl => BadClientSSLOpts}, ok = snabbkaffe:start_trace(), {{ok, State1}, _} = ?wait_async_action( - emqx_authn_jwt:update(GoodConfig, State0), + emqx_authn_jwt:create(?AUTHN_ID, BadConfig1), #{?snk_kind := jwks_endpoint_response}, - 1000), + 10000), ok = snabbkaffe:stop(), - ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State1)), - ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State1)), + ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential, State1)), + ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State0)), - ?assertEqual(ok, emqx_authn_jwt:destroy(State1)), + GoodConfig = BadConfig1#{ssl => ClientSSLOpts}, + + ok = snabbkaffe:start_trace(), + + {{ok, State2}, _} = ?wait_async_action( + emqx_authn_jwt:update(GoodConfig, State1), + #{?snk_kind := jwks_endpoint_response}, + 10000), + + ok = snabbkaffe:stop(), + + ?assertEqual({ok, #{is_superuser => false}}, emqx_authn_jwt:authenticate(Credential, State2)), + ?assertEqual(ignore, emqx_authn_jwt:authenticate(Credential#{password => <<"badpassword">>}, State2)), + + ?assertEqual(ok, emqx_authn_jwt:destroy(State2)), ok = emqx_authn_http_test_server:stop(). %%------------------------------------------------------------------------------ @@ -220,12 +236,17 @@ jwks_handler(Req0, State) -> {ok, Req, State}. test_rsa_key(public) -> - Dir = code:lib_dir(emqx_authn, test), - list_to_binary(filename:join([Dir, "data/public_key.pem"])); + data_file("public_key.pem"); test_rsa_key(private) -> + data_file("private_key.pem"). + +data_file(Name) -> Dir = code:lib_dir(emqx_authn, test), - list_to_binary(filename:join([Dir, "data/private_key.pem"])). + list_to_binary(filename:join([Dir, "data", Name])). + +cert_file(Name) -> + data_file(filename:join(["certs", Name])). generate_jws('hmac-based', Payload, Secret) -> JWK = jose_jwk:from_oct(Secret), @@ -243,3 +264,19 @@ generate_jws('public-key', Payload, PrivateKey) -> Signed = jose_jwt:sign(JWK, Header, Payload), {_, JWS} = jose_jws:compact(Signed), JWS. + +client_ssl_opts() -> + #{keyfile => cert_file("authn-https-client.key"), + certfile => cert_file("authn-https-client.crt"), + cacertfile => cert_file("authn-https-ca.crt"), + enable => true, + verify => verify_peer, + server_name_indication => "authn-https" + }. + +server_ssl_opts() -> + [{keyfile, cert_file("authn-https-server.key")}, + {certfile, cert_file("authn-https-server.crt")}, + {cacertfile, cert_file("authn-https-ca.crt")}, + {verify, verify_none} + ].