From 1886893fa13c2f9666bd62601014e3c0d68e8190 Mon Sep 17 00:00:00 2001 From: EMQ-YangM Date: Wed, 13 Apr 2022 14:13:24 +0800 Subject: [PATCH 1/8] fix: add zip_compress functions --- apps/emqx_rule_engine/src/emqx_rule_funcs.erl | 15 +++++++++++++++ .../test/emqx_rule_funcs_SUITE.erl | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/apps/emqx_rule_engine/src/emqx_rule_funcs.erl b/apps/emqx_rule_engine/src/emqx_rule_funcs.erl index ae5552159..3c606e310 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_funcs.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_funcs.erl @@ -179,6 +179,11 @@ , unzip/1 ]). +%% compressed Funcs +-export([ zip_compress/1 + , zip_uncompress/1 + ]). + %% Data encode and decode -export([ base64_encode/1 , base64_decode/1 @@ -817,6 +822,16 @@ zip(S) when is_binary(S) -> unzip(S) when is_binary(S) -> zlib:unzip(S). +%%------------------------------------------------------------------------------ +%% zip_compress Funcs +%%------------------------------------------------------------------------------ + +zip_compress(S) when is_binary(S) -> + zlib:compress(S). + +zip_uncompress(S) when is_binary(S) -> + zlib:uncompress(S). + %%------------------------------------------------------------------------------ %% Data encode and decode Funcs %%------------------------------------------------------------------------------ diff --git a/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl index 350a57051..0f82b691d 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_funcs_SUITE.erl @@ -632,6 +632,17 @@ prop_zip_fun() -> ?FORALL(S, binary(), S == apply_func(unzip, [apply_func(zip, [S])])). +%%------------------------------------------------------------------------------ +%% Test cases for zip funcs +%%------------------------------------------------------------------------------ + +t_zip_compress_funcs(_) -> + ?PROPTEST(prop_zip_compress_fun). + +prop_zip_compress_fun() -> + ?FORALL(S, binary(), + S == apply_func(zip_uncompress, [apply_func(zip_compress, [S])])). + %%------------------------------------------------------------------------------ %% Test cases for base64 %%------------------------------------------------------------------------------ From 4893cc2c6c454fb443ef8898e64cf53a93a2e1c9 Mon Sep 17 00:00:00 2001 From: EMQ-YangM Date: Wed, 13 Apr 2022 15:33:35 +0800 Subject: [PATCH 2/8] fix: update CHANGES-4.3.md --- CHANGES-4.3.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGES-4.3.md b/CHANGES-4.3.md index 7eaf2e586..29ea968a8 100644 --- a/CHANGES-4.3.md +++ b/CHANGES-4.3.md @@ -31,6 +31,7 @@ File format: node. It will improves the efficiency of shared messages dispatching in certain scenarios, especially when the emqx-bridge-mqtt plugin is configured as shared subscription. [#7462] +* Add some compression functions to rule-engine: gzip, gunzip, zip, unzip, zip_compress, zip_uncompress ### Bug fixes From f763775588096f7712dc93b5e32d3646b307c340 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Mon, 11 Apr 2022 19:15:14 +0300 Subject: [PATCH 3/8] chore(emqx_auth_jwt): do not use gen_server call for jwt verification --- apps/emqx_auth_jwt/src/emqx_auth_jwt.erl | 5 +- apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl | 7 +-- apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl | 63 +++++++++++--------- 3 files changed, 39 insertions(+), 36 deletions(-) diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl index ba37eac2b..487bc837f 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl @@ -46,8 +46,7 @@ register_metrics() -> %% Authentication callbacks %%-------------------------------------------------------------------- -check(ClientInfo, AuthResult, #{pid := Pid, - from := From, +check(ClientInfo, AuthResult, #{from := From, checklists := Checklists}) -> case maps:find(From, ClientInfo) of error -> @@ -55,7 +54,7 @@ check(ClientInfo, AuthResult, #{pid := Pid, {ok, undefined} -> ok = emqx_metrics:inc(?AUTH_METRICS(ignore)); {ok, Token} -> - case emqx_auth_jwt_svr:verify(Pid, Token) of + case emqx_auth_jwt_svr:verify(Token) of {error, not_found} -> ok = emqx_metrics:inc(?AUTH_METRICS(ignore)); {error, not_token} -> diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl index e501b0af4..b38730af5 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl @@ -33,11 +33,10 @@ start(_Type, _Args) -> {ok, Pid} = start_auth_server(jwks_svr_options()), ok = emqx_auth_jwt:register_metrics(), - AuthEnv0 = auth_env(), - AuthEnv1 = AuthEnv0#{pid => Pid}, + AuthEnv = auth_env(), - _ = emqx:hook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv1]}), - {ok, Sup, AuthEnv1}. + _ = emqx:hook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv]}), + {ok, Sup, AuthEnv}. stop(AuthEnv) -> emqx:unhook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv]}). diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl index f34cde783..5cc98ef9e 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl @@ -26,7 +26,7 @@ %% APIs -export([start_link/1]). --export([verify/2]). +-export([verify/1]). %% gen_server callbacks -export([ init/1 @@ -44,8 +44,9 @@ | {interval, pos_integer()}. -define(INTERVAL, 300000). +-define(TAB, ?MODULE). --record(state, {static, remote, addr, tref, intv}). +-record(state, {addr, tref, intv}). %%-------------------------------------------------------------------- %% APIs @@ -55,13 +56,13 @@ start_link(Options) -> gen_server:start_link(?MODULE, [Options], []). --spec verify(pid(), binary()) +-spec verify(binary()) -> {error, term()} | {ok, Payload :: map()}. -verify(S, JwsCompacted) when is_binary(JwsCompacted) -> +verify(JwsCompacted) when is_binary(JwsCompacted) -> case catch jose_jws:peek(JwsCompacted) of {'EXIT', _} -> {error, not_token}; - _ -> gen_server:call(S, {verify, JwsCompacted}) + _ -> do_verify(JwsCompacted) end. %%-------------------------------------------------------------------- @@ -70,12 +71,13 @@ verify(S, JwsCompacted) when is_binary(JwsCompacted) -> init([Options]) -> ok = jose:json_module(jiffy), + _ = ets:new(?TAB, [set, protected, named_table]), {Static, Remote} = do_init_jwks(Options), + true = ets:insert(?TAB, {static, Static}), + true = ets:insert(?TAB, {remote, Remote}), Intv = proplists:get_value(interval, Options, ?INTERVAL), {ok, reset_timer( #state{ - static = Static, - remote = Remote, addr = proplists:get_value(jwks_addr, Options), intv = Intv})}. @@ -105,9 +107,6 @@ do_init_jwks(Options) -> Remote = K2J(jwks_addr, fun request_jwks/1), {[J ||J <- [OctJwk, PemJwk], J /= undefined], Remote}. -handle_call({verify, JwsCompacted}, _From, State) -> - handle_verify(JwsCompacted, State); - handle_call(_Req, _From, State) -> {reply, ok, State}. @@ -116,7 +115,7 @@ handle_cast(_Msg, State) -> handle_info({timeout, _TRef, refresh}, State = #state{addr = Addr}) -> NState = try - State#state{remote = request_jwks(Addr)} + true = ets:insert(?TAB, {remote, request_jwks(Addr)}) catch _:_ -> State end, @@ -136,24 +135,10 @@ code_change(_OldVsn, State, _Extra) -> %% Internal funcs %%-------------------------------------------------------------------- -handle_verify(JwsCompacted, - State = #state{static = Static, remote = Remote}) -> - try - Jwks = case emqx_json:decode(jose_jws:peek_protected(JwsCompacted), [return_maps]) of - #{<<"kid">> := Kid} when Remote /= undefined -> - [J || J <- Remote, maps:get(<<"kid">>, J#jose_jwk.fields, undefined) =:= Kid]; - _ -> Static - end, - case Jwks of - [] -> {reply, {error, not_found}, State}; - _ -> - {reply, do_verify(JwsCompacted, Jwks), State} - end - catch - Class : Reason : Stk -> - ?LOG(error, "Handle JWK crashed: ~p, ~p, stacktrace: ~p~n", - [Class, Reason, Stk]), - {reply, {error, invalid_signature}, State} +keys(Type) -> + case ets:lookup(?TAB, Type) of + [{_, Keys}] -> Keys; + [] -> [] end. request_jwks(Addr) -> @@ -181,6 +166,26 @@ cancel_timer(State = #state{tref = TRef}) -> _ = erlang:cancel_timer(TRef), State#state{tref = undefined}. +do_verify(JwsCompacted) -> + try + Remote = keys(remote), + Jwks = case emqx_json:decode(jose_jws:peek_protected(JwsCompacted), [return_maps]) of + #{<<"kid">> := Kid} when Remote /= undefined -> + [J || J <- Remote, maps:get(<<"kid">>, J#jose_jwk.fields, undefined) =:= Kid]; + _ -> keys(static) + end, + case Jwks of + [] -> {error, not_found}; + _ -> + do_verify(JwsCompacted, Jwks) + end + catch + Class : Reason : Stk -> + ?LOG(error, "Handle JWK crashed: ~p, ~p, stacktrace: ~p~n", + [Class, Reason, Stk]), + {error, invalid_signature} + end. + do_verify(_JwsCompated, []) -> {error, invalid_signature}; do_verify(JwsCompacted, [Jwk|More]) -> From d8db671abb33babf6a0a026e68a048762ce8ba63 Mon Sep 17 00:00:00 2001 From: Ilya Averyanov Date: Mon, 11 Apr 2022 23:00:31 +0300 Subject: [PATCH 4/8] feat(emqx_auth_jwt): use JWT for ACL checks --- CHANGES-4.3.md | 2 + apps/emqx_auth_jwt/README.md | 32 ++++ apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf | 5 + apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema | 10 + .../emqx_auth_jwt/src/emqx_auth_jwt.appup.src | 22 +-- apps/emqx_auth_jwt/src/emqx_auth_jwt.erl | 40 +++- apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl | 18 +- apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl | 18 +- .../test/emqx_auth_jwt_SUITE.erl | 174 +++++++++++++++--- src/emqx.appup.src | 54 ++++-- src/emqx_access_rule.erl | 3 +- 11 files changed, 301 insertions(+), 77 deletions(-) diff --git a/CHANGES-4.3.md b/CHANGES-4.3.md index 29ea968a8..400b94531 100644 --- a/CHANGES-4.3.md +++ b/CHANGES-4.3.md @@ -32,6 +32,8 @@ File format: scenarios, especially when the emqx-bridge-mqtt plugin is configured as shared subscription. [#7462] * Add some compression functions to rule-engine: gzip, gunzip, zip, unzip, zip_compress, zip_uncompress +* Add support for JWT authorization [#7596] + Now MQTT clients may be authorized with respect to a specific claim containing publish/subscribe topic whitelists. ### Bug fixes diff --git a/apps/emqx_auth_jwt/README.md b/apps/emqx_auth_jwt/README.md index 7af41f490..954171692 100644 --- a/apps/emqx_auth_jwt/README.md +++ b/apps/emqx_auth_jwt/README.md @@ -46,6 +46,11 @@ auth.jwt.verify_claims = off ## - %u: username ## - %c: clientid # auth.jwt.verify_claims.username = %u + +## Name of the claim containg ACL rules +## +## Value: String +#auth.jwt.acl_claim_name = acl ``` Load the Plugin @@ -62,6 +67,33 @@ Example mosquitto_pub -t 'pub' -m 'hello' -i test -u test -P eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYm9iIiwiYWdlIjoyOX0.bIV_ZQ8D5nQi0LT8AVkpM4Pd6wmlbpR9S8nOLJAsA8o ``` +ACL +--- +JWT may contain lists of topics allowed for subscribing/publishing (ACL rules): + +Payload example: +```json +{ + "sub": "emqx", + "name": "John Doe", + "iat": 1516239022, + "exp": 1516239122, + "acl": { + "sub": [ + "a/b", + "c/+", + "%u/%c" + ], + "pub": [ + "a/b", + "c/+", + "%u/%c" + ] + } +} +``` + + Algorithms ---------- diff --git a/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf b/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf index 748902f9f..372b342d1 100644 --- a/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf +++ b/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf @@ -47,3 +47,8 @@ auth.jwt.verify_claims = off ## For example, to verify that the username in the JWT payload is the same ## as the client (MQTT protocol) username #auth.jwt.verify_claims.username = %u + +## Name of the claim containg ACL rules +## +## Value: String +#auth.jwt.acl_claim_name = acl diff --git a/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema b/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema index 3d8de3678..ff676c3ff 100644 --- a/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema +++ b/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema @@ -47,3 +47,13 @@ end, [], cuttlefish_variable:filter_by_prefix("auth.jwt.verify_claims", Conf)) end end}. + +{mapping, "auth.jwt.acl_claim_name", "emqx_auth_jwt.acl_claim_name", [ + {default, "acl"}, + {datatype, string} +]}. + +{translation, "emqx_auth_jwt.acl_claim_name", fun(Conf) -> + list_to_binary(cuttlefish:conf_get("auth.jwt.acl_claim_name", Conf)) +end}. + diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src b/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src index 684b4fa93..9af4560bf 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src @@ -1,15 +1,9 @@ -%% -*-: erlang -*- +%% -*- mode: erlang -*- +%% Unless you know what you are doing, DO NOT edit manually!! {VSN, - [ - {<<"4\\.3\\.[0-1]">>, [ - {load_module, emqx_auth_jwt_svr, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ], - [ - {<<"4\\.3\\.[0-1]">>, [ - {load_module, emqx_auth_jwt_svr, brutal_purge, soft_purge, []} - ]}, - {<<".*">>, []} - ] -}. + [{<<"4\\.3\\.[0-1]">>, + [{restart_application, emqx_auth_jwt}]}, + {<<".*">>,[]}], + [{<<"4\\.3\\.[0-1]">>, + [{restart_application, emqx_auth_jwt}]}, + {<<".*">>,[]}]}. diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl index 487bc837f..46451d1bb 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -22,7 +22,8 @@ -logger_header("[JWT]"). -export([ register_metrics/0 - , check/3 + , check_auth/3 + , check_acl/5 , description/0 ]). @@ -46,8 +47,7 @@ register_metrics() -> %% Authentication callbacks %%-------------------------------------------------------------------- -check(ClientInfo, AuthResult, #{from := From, - checklists := Checklists}) -> +check_auth(ClientInfo, AuthResult, #{from := From, checklists := Checklists}) -> case maps:find(From, ClientInfo) of error -> ok = emqx_metrics:inc(?AUTH_METRICS(ignore)); @@ -67,12 +67,38 @@ check(ClientInfo, AuthResult, #{from := From, end end. +check_acl(ClientInfo = #{jwt_claims := Claims}, + PubSub, + Topic, + _NoMatchAction, + #{acl_claim_name := AclClaimName}) -> + Deadline = erlang:system_time(second), + case Claims of + #{AclClaimName := Acl, <<"exp">> := Exp} + when is_integer(Exp) andalso Exp >= Deadline -> + verify_acl(ClientInfo, Acl, PubSub, Topic); + _ -> ignore + end. + description() -> "Authentication with JWT". %%------------------------------------------------------------------------------ %% Verify Claims %%-------------------------------------------------------------------- +verify_acl(ClientInfo, #{<<"sub">> := SubTopics}, subscribe, Topic) when is_list(SubTopics) -> + verify_acl(ClientInfo, SubTopics, Topic); +verify_acl(ClientInfo, #{<<"pub">> := PubTopics}, publish, Topic) when is_list(PubTopics) -> + verify_acl(ClientInfo, PubTopics, Topic); +verify_acl(_ClientInfo, _Acl, _PubSub, _Topic) -> {stop, deny}. + +verify_acl(_ClientInfo, [], _Topic) -> {stop, deny}; +verify_acl(ClientInfo, [AclTopic | AclTopics], Topic) -> + case match_topic(ClientInfo, AclTopic, Topic) of + true -> {stop, allow}; + false -> verify_acl(ClientInfo, AclTopics, Topic) + end. + verify_claims(Checklists, Claims, ClientInfo) -> case do_verify_claims(feedvar(Checklists, ClientInfo), Claims) of {error, Reason} -> @@ -96,3 +122,9 @@ feedvar(Checklists, #{username := Username, clientid := ClientId}) -> ({K, <<"%c">>}) -> {K, ClientId}; ({K, Expected}) -> {K, Expected} end, Checklists). + +match_topic(ClientInfo, AclTopic, Topic) -> + AclTopicWords = emqx_topic:words(AclTopic), + TopicWords = emqx_topic:words(Topic), + AclTopicRendered = emqx_access_rule:feed_var(ClientInfo, AclTopicWords), + emqx_topic:match(TopicWords, AclTopicRendered). diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl index b38730af5..a1e9fc619 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl @@ -31,15 +31,20 @@ start(_Type, _Args) -> {ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []), - {ok, Pid} = start_auth_server(jwks_svr_options()), + {ok, _} = start_auth_server(jwks_svr_options()), ok = emqx_auth_jwt:register_metrics(), + AuthEnv = auth_env(), + _ = emqx:hook('client.authenticate', {emqx_auth_jwt, check_auth, [AuthEnv]}), + + AclEnv = acl_env(), + _ = emqx:hook('client.check_acl', {emqx_auth_jwt, check_acl, [AclEnv]}), - _ = emqx:hook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv]}), - {ok, Sup, AuthEnv}. + {ok, Sup}. -stop(AuthEnv) -> - emqx:unhook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv]}). +stop(_State) -> + emqx:unhook('client.authenticate', {emqx_auth_jwt, check_auth}), + emqx:unhook('client.check_acl', {emqx_auth_jwt, check_acl}). %%-------------------------------------------------------------------- %% Dummy supervisor @@ -68,6 +73,9 @@ auth_env() -> , checklists => Checklists }. +acl_env() -> + #{acl_claim_name => env(acl_claim_name, <<"acl">>)}. + jwks_svr_options() -> [{K, V} || {K, V} <- [{secret, env(secret, undefined)}, diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl index 5cc98ef9e..05e45cec3 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -73,8 +73,7 @@ init([Options]) -> ok = jose:json_module(jiffy), _ = ets:new(?TAB, [set, protected, named_table]), {Static, Remote} = do_init_jwks(Options), - true = ets:insert(?TAB, {static, Static}), - true = ets:insert(?TAB, {remote, Remote}), + true = ets:insert(?TAB, [{static, Static}, {remote, Remote}]), Intv = proplists:get_value(interval, Options, ?INTERVAL), {ok, reset_timer( #state{ @@ -181,7 +180,7 @@ do_verify(JwsCompacted) -> end catch Class : Reason : Stk -> - ?LOG(error, "Handle JWK crashed: ~p, ~p, stacktrace: ~p~n", + ?LOG(error, "verify JWK crashed: ~p, ~p, stacktrace: ~p~n", [Class, Reason, Stk]), {error, invalid_signature} end. @@ -219,11 +218,12 @@ check_claims(Claims) -> do_check_claim([], Claims) -> Claims; do_check_claim([{K, F}|More], Claims) -> - case maps:take(K, Claims) of - error -> do_check_claim(More, Claims); - {V, NClaims} -> + case Claims of + #{K := V} -> case F(V) of - true -> do_check_claim(More, NClaims); + true -> do_check_claim(More, Claims); _ -> {false, K} - end + end; + _ -> + do_check_claim(More, Claims) end. diff --git a/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl b/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl index d4f562b6f..e28c7348b 100644 --- a/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl +++ b/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,22 +20,13 @@ -compile(nowarn_export_all). -include_lib("emqx/include/emqx.hrl"). +-include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -define(APP, emqx_auth_jwt). -all() -> - [{group, emqx_auth_jwt}]. - -groups() -> - [{emqx_auth_jwt, [sequence], [ t_check_auth - , t_check_claims - , t_check_claims_clientid - , t_check_claims_username - , t_check_claims_kid_in_header - ]} - ]. +all() -> emqx_ct:all(?MODULE). init_per_suite(Config) -> emqx_ct_helpers:start_apps([emqx_auth_jwt], fun set_special_configs/1), @@ -44,6 +35,14 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([emqx_auth_jwt]). +init_per_testcase(TestCase, Config) -> + ?MODULE:TestCase(init, Config), + emqx_ct_helpers:start_apps([emqx_auth_jwt], fun set_special_configs/1), + Config. + +end_per_testcase(_Case, _Config) -> + emqx_ct_helpers:stop_apps([emqx_auth_jwt]). + set_special_configs(emqx) -> application:set_env(emqx, allow_anonymous, false), application:set_env(emqx, acl_nomatch, deny), @@ -78,7 +77,9 @@ sign(Payload, Alg, Key) -> %% Testcases %%------------------------------------------------------------------------------ -t_check_auth(_) -> +t_check_auth(init, _Config) -> + application:unset_env(emqx_auth_jwt, verify_claims). +t_check_auth(_Config) -> Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external}, Jwt = sign([{clientid, <<"client1">>}, {username, <<"plain">>}, @@ -102,10 +103,9 @@ t_check_auth(_) -> ?assertEqual({error, invalid_signature}, Result2), ?assertMatch({error, _}, emqx_access_control:authenticate(Plain#{password => <<"asd">>})). -t_check_claims(_) -> - application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]), - application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt), - +t_check_claims(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). +t_check_claims(_Config) -> Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external}, Jwt = sign([{client_id, <<"client1">>}, {username, <<"plain">>}, @@ -120,9 +120,9 @@ t_check_claims(_) -> ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]), ?assertEqual({error, invalid_signature}, Result2). -t_check_claims_clientid(_) -> - application:set_env(emqx_auth_jwt, verify_claims, [{clientid, <<"%c">>}]), - application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt), +t_check_claims_clientid(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{clientid, <<"%c">>}]). +t_check_claims_clientid(_Config) -> Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external}, Jwt = sign([{clientid, <<"client23">>}, {username, <<"plain">>}, @@ -136,10 +136,9 @@ t_check_claims_clientid(_) -> ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]), ?assertEqual({error, invalid_signature}, Result2). -t_check_claims_username(_) -> - application:set_env(emqx_auth_jwt, verify_claims, [{username, <<"%u">>}]), - application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt), - +t_check_claims_username(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{username, <<"%u">>}]). +t_check_claims_username(_Config) -> Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external}, Jwt = sign([{client_id, <<"client23">>}, {username, <<"plain">>}, @@ -153,8 +152,9 @@ t_check_claims_username(_) -> ct:pal("Auth result for the invalid jwt: ~p~n", [Result3]), ?assertEqual({error, invalid_signature}, Result3). -t_check_claims_kid_in_header(_) -> - application:set_env(emqx_auth_jwt, verify_claims, []), +t_check_claims_kid_in_header(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, []). +t_check_claims_kid_in_header(_Config) -> Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external}, Jwt = sign([{clientid, <<"client23">>}, {username, <<"plain">>}, @@ -164,3 +164,125 @@ t_check_claims_kid_in_header(_) -> Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}), ct:pal("Auth result: ~p~n", [Result0]), ?assertMatch({ok, #{auth_result := success, jwt_claims := _}}, Result0). + +t_check_jwt_acl(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). +t_check_jwt_acl(_Config) -> + Jwt = sign([{client_id, <<"client1">>}, + {username, <<"plain">>}, + {sub, value}, + {acl, [{sub, [<<"a/b">>]}, + {pub, [<<"c/d">>]}]}, + {exp, os:system_time(seconds) + 10}], + <<"HS256">>, + <<"emqxsecret">>), + + {ok, C} = emqtt:start_link( + [{clean_start, true}, + {proto_ver, v5}, + {client_id, <<"client1">>}, + {password, Jwt}]), + {ok, _} = emqtt:connect(C), + + ?assertMatch( + {ok, #{}, [0]}, + emqtt:subscribe(C, <<"a/b">>, 0)), + + ?assertMatch( + ok, + emqtt:publish(C, <<"c/d">>, <<"hi">>, 0)), + + ?assertMatch( + {ok, #{}, [?RC_NOT_AUTHORIZED]}, + emqtt:subscribe(C, <<"c/d">>, 0)), + + ok = emqtt:publish(C, <<"a/b">>, <<"hi">>, 0), + + receive + {publish, #{topic := <<"a/b">>}} -> + ?assert(false, "Publish to `a/b` should not be allowed") + after 100 -> ok + end, + + ok = emqtt:disconnect(C). + +t_check_jwt_acl_no_recs(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). +t_check_jwt_acl_no_recs(_Config) -> + Jwt = sign([{client_id, <<"client1">>}, + {username, <<"plain">>}, + {sub, value}, + {acl, []}, + {exp, os:system_time(seconds) + 10}], + <<"HS256">>, + <<"emqxsecret">>), + + {ok, C} = emqtt:start_link( + [{clean_start, true}, + {proto_ver, v5}, + {client_id, <<"client1">>}, + {password, Jwt}]), + {ok, _} = emqtt:connect(C), + + ?assertMatch( + {ok, #{}, [?RC_NOT_AUTHORIZED]}, + emqtt:subscribe(C, <<"a/b">>, 0)), + + ok = emqtt:disconnect(C). + +t_check_jwt_acl_no_acl_claim(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). +t_check_jwt_acl_no_acl_claim(_Config) -> + Jwt = sign([{client_id, <<"client1">>}, + {username, <<"plain">>}, + {sub, value}, + {exp, os:system_time(seconds) + 10}], + <<"HS256">>, + <<"emqxsecret">>), + + {ok, C} = emqtt:start_link( + [{clean_start, true}, + {proto_ver, v5}, + {client_id, <<"client1">>}, + {password, Jwt}]), + {ok, _} = emqtt:connect(C), + + ?assertMatch( + {ok, #{}, [?RC_NOT_AUTHORIZED]}, + emqtt:subscribe(C, <<"a/b">>, 0)), + + ok = emqtt:disconnect(C). + +t_check_jwt_acl_expire(init, _Config) -> + application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). +t_check_jwt_acl_expire(_Config) -> + Jwt = sign([{client_id, <<"client1">>}, + {username, <<"plain">>}, + {sub, value}, + {acl, [{sub, [<<"a/b">>]}]}, + {exp, os:system_time(seconds) + 1}], + <<"HS256">>, + <<"emqxsecret">>), + + {ok, C} = emqtt:start_link( + [{clean_start, true}, + {proto_ver, v5}, + {client_id, <<"client1">>}, + {password, Jwt}]), + {ok, _} = emqtt:connect(C), + + ?assertMatch( + {ok, #{}, [0]}, + emqtt:subscribe(C, <<"a/b">>, 0)), + + ?assertMatch( + {ok, #{}, [0]}, + emqtt:unsubscribe(C, <<"a/b">>)), + + timer:sleep(2000), + + ?assertMatch( + {ok, #{}, [?RC_NOT_AUTHORIZED]}, + emqtt:subscribe(C, <<"a/b">>, 0)), + + ok = emqtt:disconnect(C). diff --git a/src/emqx.appup.src b/src/emqx.appup.src index 1ef499276..347634ba9 100644 --- a/src/emqx.appup.src +++ b/src/emqx.appup.src @@ -2,7 +2,8 @@ %% Unless you know what you are doing, DO NOT edit manually!! {VSN, [{"4.3.14", - [{load_module,emqx,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_sys,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, @@ -10,7 +11,8 @@ {load_module,emqx_app,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}]}, {"4.3.13", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, @@ -27,7 +29,8 @@ {load_module,emqx_misc,brutal_purge,soft_purge,[]}, {load_module,emqx_connection,brutal_purge,soft_purge,[]}]}, {"4.3.12", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_frame,brutal_purge,soft_purge,[]}, @@ -52,7 +55,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.11", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -79,7 +83,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.10", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -106,7 +111,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.9", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -137,7 +143,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.8", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -168,7 +175,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.7", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -200,7 +208,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.6", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -444,7 +453,8 @@ {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {<<".*">>,[]}], [{"4.3.14", - [{load_module,emqx,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_sys,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, @@ -452,7 +462,8 @@ {load_module,emqx_app,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}]}, {"4.3.13", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, @@ -469,7 +480,8 @@ {load_module,emqx_cm,brutal_purge,soft_purge,[]}, {load_module,emqx_connection,brutal_purge,soft_purge,[]}]}, {"4.3.12", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_frame,brutal_purge,soft_purge,[]}, @@ -493,7 +505,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.11", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -519,7 +532,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.10", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -545,7 +559,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.9", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -575,7 +590,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.8", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -605,7 +621,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.7", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -636,7 +653,8 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.6", - [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, + {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, diff --git a/src/emqx_access_rule.erl b/src/emqx_access_rule.erl index 49831d614..da2a39a22 100644 --- a/src/emqx_access_rule.erl +++ b/src/emqx_access_rule.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2017-2022 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ %% APIs -export([ match/3 , compile/1 + , feed_var/2 ]). -export_type([rule/0]). From ae8f03a1884c5a6189b8b204a0a165059d31e856 Mon Sep 17 00:00:00 2001 From: Shawn <506895667@qq.com> Date: Wed, 13 Apr 2022 23:16:29 +0800 Subject: [PATCH 5/8] fix(rules): deny POST /rules with an existing Id --- .../src/emqx_rule_engine_api.erl | 24 +++++++++++++------ .../test/emqx_rule_engine_SUITE.erl | 8 +++++++ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl b/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl index 6a50c553d..baa47d71d 100644 --- a/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl +++ b/apps/emqx_rule_engine/src/emqx_rule_engine_api.erl @@ -222,19 +222,29 @@ test_rule_sql(Params) -> do_create_rule(Params) -> case parse_rule_params(Params) of {ok, ParsedParams} -> - case emqx_rule_engine:create_rule(ParsedParams) of - {ok, Rule} -> return({ok, record_to_map(Rule)}); - {error, {action_not_found, ActionName}} -> - return({error, 400, ?ERR_NO_ACTION(ActionName)}); - {error, Reason} -> - ?LOG(error, "~p failed: ~0p", [?FUNCTION_NAME, Reason]), - return({error, 400, ?ERR_BADARGS(Reason)}) + case maps:find(id, ParsedParams) of + {ok, RuleId} -> + case emqx_rule_registry:get_rule(RuleId) of + {ok, _} -> return({error, 400, <<"Already Exists">>}); + not_found -> do_create_rule2(ParsedParams) + end; + error -> do_create_rule2(ParsedParams) end; {error, Reason} -> ?LOG(error, "~p failed: ~0p", [?FUNCTION_NAME, Reason]), return({error, 400, ?ERR_BADARGS(Reason)}) end. +do_create_rule2(ParsedParams) -> + case emqx_rule_engine:create_rule(ParsedParams) of + {ok, Rule} -> return({ok, record_to_map(Rule)}); + {error, {action_not_found, ActionName}} -> + return({error, 400, ?ERR_NO_ACTION(ActionName)}); + {error, Reason} -> + ?LOG(error, "~p failed: ~0p", [?FUNCTION_NAME, Reason]), + return({error, 400, ?ERR_BADARGS(Reason)}) + end. + update_rule(#{id := Id}, Params) -> case parse_rule_params(Params, #{id => Id}) of {ok, ParsedParams} -> diff --git a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl index 240438134..20a5345f7 100644 --- a/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl +++ b/apps/emqx_rule_engine/test/emqx_rule_engine_SUITE.erl @@ -428,6 +428,14 @@ t_crud_rule_api(_Config) -> {<<"params">>,[{<<"arg1">>,1}]}]]}, {<<"description">>, <<"debug rule">>}]), RuleID = maps:get(id, Rule), + {ok, #{code := 400, message := <<"Already Exists">>}} = + emqx_rule_engine_api:create_rule(#{}, + [{<<"name">>, <<"debug-rule">>}, + {<<"id">>, RuleID}, + {<<"rawsql">>, <<"select * from \"t/a\"">>}, + {<<"actions">>, [[{<<"name">>,<<"inspect">>}, + {<<"params">>,[{<<"arg1">>,1}]}]]}, + {<<"description">>, <<"debug rule">>}]), %ct:pal("RCreated : ~p", [Rule]), {ok, #{code := 0, data := Rules}} = emqx_rule_engine_api:list_rules(#{}, []), From 47a5bc3f09aa21bdf6acb192c0745506e8d1b742 Mon Sep 17 00:00:00 2001 From: JianBo He Date: Thu, 14 Apr 2022 10:44:06 +0800 Subject: [PATCH 6/8] Revert "Add support for JWT authorization" --- CHANGES-4.3.md | 2 - apps/emqx_auth_jwt/README.md | 32 ---- apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf | 5 - apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema | 10 - .../emqx_auth_jwt/src/emqx_auth_jwt.appup.src | 22 ++- apps/emqx_auth_jwt/src/emqx_auth_jwt.erl | 43 +---- apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl | 21 +-- apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl | 75 ++++---- .../test/emqx_auth_jwt_SUITE.erl | 174 +++--------------- src/emqx.appup.src | 54 ++---- src/emqx_access_rule.erl | 3 +- 11 files changed, 107 insertions(+), 334 deletions(-) diff --git a/CHANGES-4.3.md b/CHANGES-4.3.md index 400b94531..29ea968a8 100644 --- a/CHANGES-4.3.md +++ b/CHANGES-4.3.md @@ -32,8 +32,6 @@ File format: scenarios, especially when the emqx-bridge-mqtt plugin is configured as shared subscription. [#7462] * Add some compression functions to rule-engine: gzip, gunzip, zip, unzip, zip_compress, zip_uncompress -* Add support for JWT authorization [#7596] - Now MQTT clients may be authorized with respect to a specific claim containing publish/subscribe topic whitelists. ### Bug fixes diff --git a/apps/emqx_auth_jwt/README.md b/apps/emqx_auth_jwt/README.md index 954171692..7af41f490 100644 --- a/apps/emqx_auth_jwt/README.md +++ b/apps/emqx_auth_jwt/README.md @@ -46,11 +46,6 @@ auth.jwt.verify_claims = off ## - %u: username ## - %c: clientid # auth.jwt.verify_claims.username = %u - -## Name of the claim containg ACL rules -## -## Value: String -#auth.jwt.acl_claim_name = acl ``` Load the Plugin @@ -67,33 +62,6 @@ Example mosquitto_pub -t 'pub' -m 'hello' -i test -u test -P eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJuYW1lIjoiYm9iIiwiYWdlIjoyOX0.bIV_ZQ8D5nQi0LT8AVkpM4Pd6wmlbpR9S8nOLJAsA8o ``` -ACL ---- -JWT may contain lists of topics allowed for subscribing/publishing (ACL rules): - -Payload example: -```json -{ - "sub": "emqx", - "name": "John Doe", - "iat": 1516239022, - "exp": 1516239122, - "acl": { - "sub": [ - "a/b", - "c/+", - "%u/%c" - ], - "pub": [ - "a/b", - "c/+", - "%u/%c" - ] - } -} -``` - - Algorithms ---------- diff --git a/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf b/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf index 372b342d1..748902f9f 100644 --- a/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf +++ b/apps/emqx_auth_jwt/etc/emqx_auth_jwt.conf @@ -47,8 +47,3 @@ auth.jwt.verify_claims = off ## For example, to verify that the username in the JWT payload is the same ## as the client (MQTT protocol) username #auth.jwt.verify_claims.username = %u - -## Name of the claim containg ACL rules -## -## Value: String -#auth.jwt.acl_claim_name = acl diff --git a/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema b/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema index ff676c3ff..3d8de3678 100644 --- a/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema +++ b/apps/emqx_auth_jwt/priv/emqx_auth_jwt.schema @@ -47,13 +47,3 @@ end, [], cuttlefish_variable:filter_by_prefix("auth.jwt.verify_claims", Conf)) end end}. - -{mapping, "auth.jwt.acl_claim_name", "emqx_auth_jwt.acl_claim_name", [ - {default, "acl"}, - {datatype, string} -]}. - -{translation, "emqx_auth_jwt.acl_claim_name", fun(Conf) -> - list_to_binary(cuttlefish:conf_get("auth.jwt.acl_claim_name", Conf)) -end}. - diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src b/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src index 9af4560bf..684b4fa93 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt.appup.src @@ -1,9 +1,15 @@ -%% -*- mode: erlang -*- -%% Unless you know what you are doing, DO NOT edit manually!! +%% -*-: erlang -*- {VSN, - [{<<"4\\.3\\.[0-1]">>, - [{restart_application, emqx_auth_jwt}]}, - {<<".*">>,[]}], - [{<<"4\\.3\\.[0-1]">>, - [{restart_application, emqx_auth_jwt}]}, - {<<".*">>,[]}]}. + [ + {<<"4\\.3\\.[0-1]">>, [ + {load_module, emqx_auth_jwt_svr, brutal_purge, soft_purge, []} + ]}, + {<<".*">>, []} + ], + [ + {<<"4\\.3\\.[0-1]">>, [ + {load_module, emqx_auth_jwt_svr, brutal_purge, soft_purge, []} + ]}, + {<<".*">>, []} + ] +}. diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl index 46451d1bb..ba37eac2b 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -22,8 +22,7 @@ -logger_header("[JWT]"). -export([ register_metrics/0 - , check_auth/3 - , check_acl/5 + , check/3 , description/0 ]). @@ -47,14 +46,16 @@ register_metrics() -> %% Authentication callbacks %%-------------------------------------------------------------------- -check_auth(ClientInfo, AuthResult, #{from := From, checklists := Checklists}) -> +check(ClientInfo, AuthResult, #{pid := Pid, + from := From, + checklists := Checklists}) -> case maps:find(From, ClientInfo) of error -> ok = emqx_metrics:inc(?AUTH_METRICS(ignore)); {ok, undefined} -> ok = emqx_metrics:inc(?AUTH_METRICS(ignore)); {ok, Token} -> - case emqx_auth_jwt_svr:verify(Token) of + case emqx_auth_jwt_svr:verify(Pid, Token) of {error, not_found} -> ok = emqx_metrics:inc(?AUTH_METRICS(ignore)); {error, not_token} -> @@ -67,38 +68,12 @@ check_auth(ClientInfo, AuthResult, #{from := From, checklists := Checklists}) -> end end. -check_acl(ClientInfo = #{jwt_claims := Claims}, - PubSub, - Topic, - _NoMatchAction, - #{acl_claim_name := AclClaimName}) -> - Deadline = erlang:system_time(second), - case Claims of - #{AclClaimName := Acl, <<"exp">> := Exp} - when is_integer(Exp) andalso Exp >= Deadline -> - verify_acl(ClientInfo, Acl, PubSub, Topic); - _ -> ignore - end. - description() -> "Authentication with JWT". %%------------------------------------------------------------------------------ %% Verify Claims %%-------------------------------------------------------------------- -verify_acl(ClientInfo, #{<<"sub">> := SubTopics}, subscribe, Topic) when is_list(SubTopics) -> - verify_acl(ClientInfo, SubTopics, Topic); -verify_acl(ClientInfo, #{<<"pub">> := PubTopics}, publish, Topic) when is_list(PubTopics) -> - verify_acl(ClientInfo, PubTopics, Topic); -verify_acl(_ClientInfo, _Acl, _PubSub, _Topic) -> {stop, deny}. - -verify_acl(_ClientInfo, [], _Topic) -> {stop, deny}; -verify_acl(ClientInfo, [AclTopic | AclTopics], Topic) -> - case match_topic(ClientInfo, AclTopic, Topic) of - true -> {stop, allow}; - false -> verify_acl(ClientInfo, AclTopics, Topic) - end. - verify_claims(Checklists, Claims, ClientInfo) -> case do_verify_claims(feedvar(Checklists, ClientInfo), Claims) of {error, Reason} -> @@ -122,9 +97,3 @@ feedvar(Checklists, #{username := Username, clientid := ClientId}) -> ({K, <<"%c">>}) -> {K, ClientId}; ({K, Expected}) -> {K, Expected} end, Checklists). - -match_topic(ClientInfo, AclTopic, Topic) -> - AclTopicWords = emqx_topic:words(AclTopic), - TopicWords = emqx_topic:words(Topic), - AclTopicRendered = emqx_access_rule:feed_var(ClientInfo, AclTopicWords), - emqx_topic:match(TopicWords, AclTopicRendered). diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl index a1e9fc619..e501b0af4 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_app.erl @@ -31,20 +31,16 @@ start(_Type, _Args) -> {ok, Sup} = supervisor:start_link({local, ?MODULE}, ?MODULE, []), - {ok, _} = start_auth_server(jwks_svr_options()), + {ok, Pid} = start_auth_server(jwks_svr_options()), ok = emqx_auth_jwt:register_metrics(), + AuthEnv0 = auth_env(), + AuthEnv1 = AuthEnv0#{pid => Pid}, - AuthEnv = auth_env(), - _ = emqx:hook('client.authenticate', {emqx_auth_jwt, check_auth, [AuthEnv]}), - - AclEnv = acl_env(), - _ = emqx:hook('client.check_acl', {emqx_auth_jwt, check_acl, [AclEnv]}), + _ = emqx:hook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv1]}), + {ok, Sup, AuthEnv1}. - {ok, Sup}. - -stop(_State) -> - emqx:unhook('client.authenticate', {emqx_auth_jwt, check_auth}), - emqx:unhook('client.check_acl', {emqx_auth_jwt, check_acl}). +stop(AuthEnv) -> + emqx:unhook('client.authenticate', {emqx_auth_jwt, check, [AuthEnv]}). %%-------------------------------------------------------------------- %% Dummy supervisor @@ -73,9 +69,6 @@ auth_env() -> , checklists => Checklists }. -acl_env() -> - #{acl_claim_name => env(acl_claim_name, <<"acl">>)}. - jwks_svr_options() -> [{K, V} || {K, V} <- [{secret, env(secret, undefined)}, diff --git a/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl b/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl index 05e45cec3..f34cde783 100644 --- a/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl +++ b/apps/emqx_auth_jwt/src/emqx_auth_jwt_svr.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ %% APIs -export([start_link/1]). --export([verify/1]). +-export([verify/2]). %% gen_server callbacks -export([ init/1 @@ -44,9 +44,8 @@ | {interval, pos_integer()}. -define(INTERVAL, 300000). --define(TAB, ?MODULE). --record(state, {addr, tref, intv}). +-record(state, {static, remote, addr, tref, intv}). %%-------------------------------------------------------------------- %% APIs @@ -56,13 +55,13 @@ start_link(Options) -> gen_server:start_link(?MODULE, [Options], []). --spec verify(binary()) +-spec verify(pid(), binary()) -> {error, term()} | {ok, Payload :: map()}. -verify(JwsCompacted) when is_binary(JwsCompacted) -> +verify(S, JwsCompacted) when is_binary(JwsCompacted) -> case catch jose_jws:peek(JwsCompacted) of {'EXIT', _} -> {error, not_token}; - _ -> do_verify(JwsCompacted) + _ -> gen_server:call(S, {verify, JwsCompacted}) end. %%-------------------------------------------------------------------- @@ -71,12 +70,12 @@ verify(JwsCompacted) when is_binary(JwsCompacted) -> init([Options]) -> ok = jose:json_module(jiffy), - _ = ets:new(?TAB, [set, protected, named_table]), {Static, Remote} = do_init_jwks(Options), - true = ets:insert(?TAB, [{static, Static}, {remote, Remote}]), Intv = proplists:get_value(interval, Options, ?INTERVAL), {ok, reset_timer( #state{ + static = Static, + remote = Remote, addr = proplists:get_value(jwks_addr, Options), intv = Intv})}. @@ -106,6 +105,9 @@ do_init_jwks(Options) -> Remote = K2J(jwks_addr, fun request_jwks/1), {[J ||J <- [OctJwk, PemJwk], J /= undefined], Remote}. +handle_call({verify, JwsCompacted}, _From, State) -> + handle_verify(JwsCompacted, State); + handle_call(_Req, _From, State) -> {reply, ok, State}. @@ -114,7 +116,7 @@ handle_cast(_Msg, State) -> handle_info({timeout, _TRef, refresh}, State = #state{addr = Addr}) -> NState = try - true = ets:insert(?TAB, {remote, request_jwks(Addr)}) + State#state{remote = request_jwks(Addr)} catch _:_ -> State end, @@ -134,10 +136,24 @@ code_change(_OldVsn, State, _Extra) -> %% Internal funcs %%-------------------------------------------------------------------- -keys(Type) -> - case ets:lookup(?TAB, Type) of - [{_, Keys}] -> Keys; - [] -> [] +handle_verify(JwsCompacted, + State = #state{static = Static, remote = Remote}) -> + try + Jwks = case emqx_json:decode(jose_jws:peek_protected(JwsCompacted), [return_maps]) of + #{<<"kid">> := Kid} when Remote /= undefined -> + [J || J <- Remote, maps:get(<<"kid">>, J#jose_jwk.fields, undefined) =:= Kid]; + _ -> Static + end, + case Jwks of + [] -> {reply, {error, not_found}, State}; + _ -> + {reply, do_verify(JwsCompacted, Jwks), State} + end + catch + Class : Reason : Stk -> + ?LOG(error, "Handle JWK crashed: ~p, ~p, stacktrace: ~p~n", + [Class, Reason, Stk]), + {reply, {error, invalid_signature}, State} end. request_jwks(Addr) -> @@ -165,26 +181,6 @@ cancel_timer(State = #state{tref = TRef}) -> _ = erlang:cancel_timer(TRef), State#state{tref = undefined}. -do_verify(JwsCompacted) -> - try - Remote = keys(remote), - Jwks = case emqx_json:decode(jose_jws:peek_protected(JwsCompacted), [return_maps]) of - #{<<"kid">> := Kid} when Remote /= undefined -> - [J || J <- Remote, maps:get(<<"kid">>, J#jose_jwk.fields, undefined) =:= Kid]; - _ -> keys(static) - end, - case Jwks of - [] -> {error, not_found}; - _ -> - do_verify(JwsCompacted, Jwks) - end - catch - Class : Reason : Stk -> - ?LOG(error, "verify JWK crashed: ~p, ~p, stacktrace: ~p~n", - [Class, Reason, Stk]), - {error, invalid_signature} - end. - do_verify(_JwsCompated, []) -> {error, invalid_signature}; do_verify(JwsCompacted, [Jwk|More]) -> @@ -218,12 +214,11 @@ check_claims(Claims) -> do_check_claim([], Claims) -> Claims; do_check_claim([{K, F}|More], Claims) -> - case Claims of - #{K := V} -> + case maps:take(K, Claims) of + error -> do_check_claim(More, Claims); + {V, NClaims} -> case F(V) of - true -> do_check_claim(More, Claims); + true -> do_check_claim(More, NClaims); _ -> {false, K} - end; - _ -> - do_check_claim(More, Claims) + end end. diff --git a/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl b/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl index e28c7348b..d4f562b6f 100644 --- a/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl +++ b/apps/emqx_auth_jwt/test/emqx_auth_jwt_SUITE.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2020-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2020-2021 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -20,13 +20,22 @@ -compile(nowarn_export_all). -include_lib("emqx/include/emqx.hrl"). --include_lib("emqx/include/emqx_mqtt.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("common_test/include/ct.hrl"). -define(APP, emqx_auth_jwt). -all() -> emqx_ct:all(?MODULE). +all() -> + [{group, emqx_auth_jwt}]. + +groups() -> + [{emqx_auth_jwt, [sequence], [ t_check_auth + , t_check_claims + , t_check_claims_clientid + , t_check_claims_username + , t_check_claims_kid_in_header + ]} + ]. init_per_suite(Config) -> emqx_ct_helpers:start_apps([emqx_auth_jwt], fun set_special_configs/1), @@ -35,14 +44,6 @@ init_per_suite(Config) -> end_per_suite(_Config) -> emqx_ct_helpers:stop_apps([emqx_auth_jwt]). -init_per_testcase(TestCase, Config) -> - ?MODULE:TestCase(init, Config), - emqx_ct_helpers:start_apps([emqx_auth_jwt], fun set_special_configs/1), - Config. - -end_per_testcase(_Case, _Config) -> - emqx_ct_helpers:stop_apps([emqx_auth_jwt]). - set_special_configs(emqx) -> application:set_env(emqx, allow_anonymous, false), application:set_env(emqx, acl_nomatch, deny), @@ -77,9 +78,7 @@ sign(Payload, Alg, Key) -> %% Testcases %%------------------------------------------------------------------------------ -t_check_auth(init, _Config) -> - application:unset_env(emqx_auth_jwt, verify_claims). -t_check_auth(_Config) -> +t_check_auth(_) -> Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external}, Jwt = sign([{clientid, <<"client1">>}, {username, <<"plain">>}, @@ -103,9 +102,10 @@ t_check_auth(_Config) -> ?assertEqual({error, invalid_signature}, Result2), ?assertMatch({error, _}, emqx_access_control:authenticate(Plain#{password => <<"asd">>})). -t_check_claims(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). -t_check_claims(_Config) -> +t_check_claims(_) -> + application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]), + application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt), + Plain = #{clientid => <<"client1">>, username => <<"plain">>, zone => external}, Jwt = sign([{client_id, <<"client1">>}, {username, <<"plain">>}, @@ -120,9 +120,9 @@ t_check_claims(_Config) -> ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]), ?assertEqual({error, invalid_signature}, Result2). -t_check_claims_clientid(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{clientid, <<"%c">>}]). -t_check_claims_clientid(_Config) -> +t_check_claims_clientid(_) -> + application:set_env(emqx_auth_jwt, verify_claims, [{clientid, <<"%c">>}]), + application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt), Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external}, Jwt = sign([{clientid, <<"client23">>}, {username, <<"plain">>}, @@ -136,9 +136,10 @@ t_check_claims_clientid(_Config) -> ct:pal("Auth result for the invalid jwt: ~p~n", [Result2]), ?assertEqual({error, invalid_signature}, Result2). -t_check_claims_username(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{username, <<"%u">>}]). -t_check_claims_username(_Config) -> +t_check_claims_username(_) -> + application:set_env(emqx_auth_jwt, verify_claims, [{username, <<"%u">>}]), + application:stop(emqx_auth_jwt), application:start(emqx_auth_jwt), + Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external}, Jwt = sign([{client_id, <<"client23">>}, {username, <<"plain">>}, @@ -152,9 +153,8 @@ t_check_claims_username(_Config) -> ct:pal("Auth result for the invalid jwt: ~p~n", [Result3]), ?assertEqual({error, invalid_signature}, Result3). -t_check_claims_kid_in_header(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, []). -t_check_claims_kid_in_header(_Config) -> +t_check_claims_kid_in_header(_) -> + application:set_env(emqx_auth_jwt, verify_claims, []), Plain = #{clientid => <<"client23">>, username => <<"plain">>, zone => external}, Jwt = sign([{clientid, <<"client23">>}, {username, <<"plain">>}, @@ -164,125 +164,3 @@ t_check_claims_kid_in_header(_Config) -> Result0 = emqx_access_control:authenticate(Plain#{password => Jwt}), ct:pal("Auth result: ~p~n", [Result0]), ?assertMatch({ok, #{auth_result := success, jwt_claims := _}}, Result0). - -t_check_jwt_acl(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). -t_check_jwt_acl(_Config) -> - Jwt = sign([{client_id, <<"client1">>}, - {username, <<"plain">>}, - {sub, value}, - {acl, [{sub, [<<"a/b">>]}, - {pub, [<<"c/d">>]}]}, - {exp, os:system_time(seconds) + 10}], - <<"HS256">>, - <<"emqxsecret">>), - - {ok, C} = emqtt:start_link( - [{clean_start, true}, - {proto_ver, v5}, - {client_id, <<"client1">>}, - {password, Jwt}]), - {ok, _} = emqtt:connect(C), - - ?assertMatch( - {ok, #{}, [0]}, - emqtt:subscribe(C, <<"a/b">>, 0)), - - ?assertMatch( - ok, - emqtt:publish(C, <<"c/d">>, <<"hi">>, 0)), - - ?assertMatch( - {ok, #{}, [?RC_NOT_AUTHORIZED]}, - emqtt:subscribe(C, <<"c/d">>, 0)), - - ok = emqtt:publish(C, <<"a/b">>, <<"hi">>, 0), - - receive - {publish, #{topic := <<"a/b">>}} -> - ?assert(false, "Publish to `a/b` should not be allowed") - after 100 -> ok - end, - - ok = emqtt:disconnect(C). - -t_check_jwt_acl_no_recs(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). -t_check_jwt_acl_no_recs(_Config) -> - Jwt = sign([{client_id, <<"client1">>}, - {username, <<"plain">>}, - {sub, value}, - {acl, []}, - {exp, os:system_time(seconds) + 10}], - <<"HS256">>, - <<"emqxsecret">>), - - {ok, C} = emqtt:start_link( - [{clean_start, true}, - {proto_ver, v5}, - {client_id, <<"client1">>}, - {password, Jwt}]), - {ok, _} = emqtt:connect(C), - - ?assertMatch( - {ok, #{}, [?RC_NOT_AUTHORIZED]}, - emqtt:subscribe(C, <<"a/b">>, 0)), - - ok = emqtt:disconnect(C). - -t_check_jwt_acl_no_acl_claim(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). -t_check_jwt_acl_no_acl_claim(_Config) -> - Jwt = sign([{client_id, <<"client1">>}, - {username, <<"plain">>}, - {sub, value}, - {exp, os:system_time(seconds) + 10}], - <<"HS256">>, - <<"emqxsecret">>), - - {ok, C} = emqtt:start_link( - [{clean_start, true}, - {proto_ver, v5}, - {client_id, <<"client1">>}, - {password, Jwt}]), - {ok, _} = emqtt:connect(C), - - ?assertMatch( - {ok, #{}, [?RC_NOT_AUTHORIZED]}, - emqtt:subscribe(C, <<"a/b">>, 0)), - - ok = emqtt:disconnect(C). - -t_check_jwt_acl_expire(init, _Config) -> - application:set_env(emqx_auth_jwt, verify_claims, [{sub, <<"value">>}]). -t_check_jwt_acl_expire(_Config) -> - Jwt = sign([{client_id, <<"client1">>}, - {username, <<"plain">>}, - {sub, value}, - {acl, [{sub, [<<"a/b">>]}]}, - {exp, os:system_time(seconds) + 1}], - <<"HS256">>, - <<"emqxsecret">>), - - {ok, C} = emqtt:start_link( - [{clean_start, true}, - {proto_ver, v5}, - {client_id, <<"client1">>}, - {password, Jwt}]), - {ok, _} = emqtt:connect(C), - - ?assertMatch( - {ok, #{}, [0]}, - emqtt:subscribe(C, <<"a/b">>, 0)), - - ?assertMatch( - {ok, #{}, [0]}, - emqtt:unsubscribe(C, <<"a/b">>)), - - timer:sleep(2000), - - ?assertMatch( - {ok, #{}, [?RC_NOT_AUTHORIZED]}, - emqtt:subscribe(C, <<"a/b">>, 0)), - - ok = emqtt:disconnect(C). diff --git a/src/emqx.appup.src b/src/emqx.appup.src index 347634ba9..1ef499276 100644 --- a/src/emqx.appup.src +++ b/src/emqx.appup.src @@ -2,8 +2,7 @@ %% Unless you know what you are doing, DO NOT edit manually!! {VSN, [{"4.3.14", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx,brutal_purge,soft_purge,[]}, + [{load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_sys,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, @@ -11,8 +10,7 @@ {load_module,emqx_app,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}]}, {"4.3.13", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, @@ -29,8 +27,7 @@ {load_module,emqx_misc,brutal_purge,soft_purge,[]}, {load_module,emqx_connection,brutal_purge,soft_purge,[]}]}, {"4.3.12", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_frame,brutal_purge,soft_purge,[]}, @@ -55,8 +52,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.11", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -83,8 +79,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.10", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -111,8 +106,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.9", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -143,8 +137,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.8", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -175,8 +168,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.7", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -208,8 +200,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.6", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -453,8 +444,7 @@ {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {<<".*">>,[]}], [{"4.3.14", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx,brutal_purge,soft_purge,[]}, + [{load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_sys,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, @@ -462,8 +452,7 @@ {load_module,emqx_app,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}]}, {"4.3.13", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, @@ -480,8 +469,7 @@ {load_module,emqx_cm,brutal_purge,soft_purge,[]}, {load_module,emqx_connection,brutal_purge,soft_purge,[]}]}, {"4.3.12", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_frame,brutal_purge,soft_purge,[]}, @@ -505,8 +493,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.11", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -532,8 +519,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.10", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -559,8 +545,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.9", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -590,8 +575,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.8", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -621,8 +605,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.7", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, @@ -653,8 +636,7 @@ {load_module,emqx_message,brutal_purge,soft_purge,[]}, {load_module,emqx_limiter,brutal_purge,soft_purge,[]}]}, {"4.3.6", - [{load_module,emqx_access_rule,brutal_purge,soft_purge,[]}, - {load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, + [{load_module,emqx_shared_sub,brutal_purge,soft_purge,[]}, {load_module,emqx_hooks,brutal_purge,soft_purge,[]}, {load_module,emqx_plugins,brutal_purge,soft_purge,[]}, {load_module,emqx_pmon,brutal_purge,soft_purge,[]}, diff --git a/src/emqx_access_rule.erl b/src/emqx_access_rule.erl index da2a39a22..49831d614 100644 --- a/src/emqx_access_rule.erl +++ b/src/emqx_access_rule.erl @@ -1,5 +1,5 @@ %%-------------------------------------------------------------------- -%% Copyright (c) 2017-2022 EMQ Technologies Co., Ltd. All Rights Reserved. +%% Copyright (c) 2017-2021 EMQ Technologies Co., Ltd. All Rights Reserved. %% %% Licensed under the Apache License, Version 2.0 (the "License"); %% you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ %% APIs -export([ match/3 , compile/1 - , feed_var/2 ]). -export_type([rule/0]). From 7b14e822a636ca9888c1ad53a6238362e78f761d Mon Sep 17 00:00:00 2001 From: JianBo He Date: Thu, 14 Apr 2022 10:59:29 +0800 Subject: [PATCH 7/8] fix(shared-sub): convert group name to binary --- priv/emqx.schema | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priv/emqx.schema b/priv/emqx.schema index f48386461..4d7b1beda 100644 --- a/priv/emqx.schema +++ b/priv/emqx.schema @@ -2344,7 +2344,7 @@ end}. {translation, "emqx.shared_subscription_strategy_per_group", fun(Conf) -> Conf0 = cuttlefish_variable:filter_by_prefix("broker", Conf), Groups = lists:filtermap(fun({["broker", Group, "shared_subscription_strategy"], Strategy}) -> - {true, {Group, list_to_binary(Strategy)}}; + {true, {list_to_binary(Group), Strategy}}; (_) -> false end, Conf0), From d40a1461390e2fe8dd83391f37067049e743db3f Mon Sep 17 00:00:00 2001 From: JianBo He Date: Thu, 14 Apr 2022 11:54:05 +0800 Subject: [PATCH 8/8] chore: bump vsn --- include/emqx_release.hrl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/emqx_release.hrl b/include/emqx_release.hrl index c5d14e53e..a004356ee 100644 --- a/include/emqx_release.hrl +++ b/include/emqx_release.hrl @@ -29,7 +29,7 @@ -ifndef(EMQX_ENTERPRISE). --define(EMQX_RELEASE, {opensource, "4.3.14-rc.2"}). +-define(EMQX_RELEASE, {opensource, "4.3.14-rc.3"}). -else.