refactor(auth/jwt): support raw rules from jwt acl claim
This commit is contained in:
parent
da92e62e8c
commit
2be898ca4d
|
@ -51,11 +51,18 @@ parse_rule(
|
||||||
Action = validate_rule_action(ActionType, RuleRaw),
|
Action = validate_rule_action(ActionType, RuleRaw),
|
||||||
{ok, {Permission, Action, Topics}}
|
{ok, {Permission, Action, Topics}}
|
||||||
catch
|
catch
|
||||||
throw:ValidationError ->
|
throw:{Invalid, Which} ->
|
||||||
{error, ValidationError}
|
{error, #{
|
||||||
|
reason => Invalid,
|
||||||
|
value => Which
|
||||||
|
}}
|
||||||
end;
|
end;
|
||||||
parse_rule(RuleRaw) ->
|
parse_rule(RuleRaw) ->
|
||||||
{error, {invalid_rule, RuleRaw}}.
|
{error, #{
|
||||||
|
reason => invalid_rule,
|
||||||
|
value => RuleRaw,
|
||||||
|
explain => "missing 'permission' or 'action' field"
|
||||||
|
}}.
|
||||||
|
|
||||||
-spec format_rule({
|
-spec format_rule({
|
||||||
emqx_authz_rule:permission(),
|
emqx_authz_rule:permission(),
|
||||||
|
@ -88,7 +95,7 @@ validate_rule_topics(#{<<"topic">> := TopicRaw}) when is_binary(TopicRaw) ->
|
||||||
validate_rule_topics(#{<<"topics">> := TopicsRaw}) when is_list(TopicsRaw) ->
|
validate_rule_topics(#{<<"topics">> := TopicsRaw}) when is_list(TopicsRaw) ->
|
||||||
lists:map(fun validate_rule_topic/1, TopicsRaw);
|
lists:map(fun validate_rule_topic/1, TopicsRaw);
|
||||||
validate_rule_topics(RuleRaw) ->
|
validate_rule_topics(RuleRaw) ->
|
||||||
throw({invalid_topics, RuleRaw}).
|
throw({missing_topic_or_topics, RuleRaw}).
|
||||||
|
|
||||||
validate_rule_topic(<<"eq ", TopicRaw/binary>>) ->
|
validate_rule_topic(<<"eq ", TopicRaw/binary>>) ->
|
||||||
{eq, validate_rule_topic(TopicRaw)};
|
{eq, validate_rule_topic(TopicRaw)};
|
||||||
|
@ -98,8 +105,8 @@ validate_rule_permission(<<"allow">>) -> allow;
|
||||||
validate_rule_permission(<<"deny">>) -> deny;
|
validate_rule_permission(<<"deny">>) -> deny;
|
||||||
validate_rule_permission(PermissionRaw) -> throw({invalid_permission, PermissionRaw}).
|
validate_rule_permission(PermissionRaw) -> throw({invalid_permission, PermissionRaw}).
|
||||||
|
|
||||||
validate_rule_action_type(<<"publish">>) -> publish;
|
validate_rule_action_type(P) when P =:= <<"pub">> orelse P =:= <<"publish">> -> publish;
|
||||||
validate_rule_action_type(<<"subscribe">>) -> subscribe;
|
validate_rule_action_type(S) when S =:= <<"sub">> orelse S =:= <<"subscribe">> -> subscribe;
|
||||||
validate_rule_action_type(<<"all">>) -> all;
|
validate_rule_action_type(<<"all">>) -> all;
|
||||||
validate_rule_action_type(ActionRaw) -> throw({invalid_action, ActionRaw}).
|
validate_rule_action_type(ActionRaw) -> throw({invalid_action, ActionRaw}).
|
||||||
|
|
||||||
|
@ -152,7 +159,7 @@ validate_rule_qos_atomic(<<"2">>) -> 2;
|
||||||
validate_rule_qos_atomic(0) -> 0;
|
validate_rule_qos_atomic(0) -> 0;
|
||||||
validate_rule_qos_atomic(1) -> 1;
|
validate_rule_qos_atomic(1) -> 1;
|
||||||
validate_rule_qos_atomic(2) -> 2;
|
validate_rule_qos_atomic(2) -> 2;
|
||||||
validate_rule_qos_atomic(_) -> throw(invalid_qos).
|
validate_rule_qos_atomic(QoS) -> throw({invalid_qos, QoS}).
|
||||||
|
|
||||||
validate_rule_retain(<<"0">>) -> false;
|
validate_rule_retain(<<"0">>) -> false;
|
||||||
validate_rule_retain(<<"1">>) -> true;
|
validate_rule_retain(<<"1">>) -> true;
|
||||||
|
|
|
@ -34,6 +34,10 @@
|
||||||
authorize/4
|
authorize/4
|
||||||
]).
|
]).
|
||||||
|
|
||||||
|
-define(IS_V1(Rules), is_map(Rules)).
|
||||||
|
-define(IS_V2(Rules), is_list(Rules)).
|
||||||
|
|
||||||
|
%% For v1
|
||||||
-define(RULE_NAMES, [
|
-define(RULE_NAMES, [
|
||||||
{[pub, <<"pub">>], publish},
|
{[pub, <<"pub">>], publish},
|
||||||
{[sub, <<"sub">>], subscribe},
|
{[sub, <<"sub">>], subscribe},
|
||||||
|
@ -55,10 +59,46 @@ update(Source) ->
|
||||||
|
|
||||||
destroy(_Source) -> ok.
|
destroy(_Source) -> ok.
|
||||||
|
|
||||||
|
%% @doc Authorize based on cllientinfo enriched with `acl' data.
|
||||||
|
%% e.g. From JWT.
|
||||||
|
%%
|
||||||
|
%% Supproted rules formats are:
|
||||||
|
%%
|
||||||
|
%% v1: (always deny when no match)
|
||||||
|
%%
|
||||||
|
%% #{
|
||||||
|
%% pub => [TopicFilter],
|
||||||
|
%% sub => [TopicFilter],
|
||||||
|
%% all => [TopicFilter]
|
||||||
|
%% }
|
||||||
|
%%
|
||||||
|
%% v2: (rules are checked in sequence, passthrough when no match)
|
||||||
|
%%
|
||||||
|
%% [{
|
||||||
|
%% Permission :: emqx_authz_rule:permission(),
|
||||||
|
%% Action :: emqx_authz_rule:action_condition(),
|
||||||
|
%% Topics :: emqx_authz_rule:topic_condition()
|
||||||
|
%% }]
|
||||||
|
%%
|
||||||
|
%% which is compiled from raw rules like below by emqx_authz_rule_raw
|
||||||
|
%%
|
||||||
|
%% [
|
||||||
|
%% #{
|
||||||
|
%% permission := allow | deny
|
||||||
|
%% action := pub | sub | all
|
||||||
|
%% topic => TopicFilter,
|
||||||
|
%% topics => [TopicFilter] %% when 'topic' is not provided
|
||||||
|
%% qos => 0 | 1 | 2 | [0, 1, 2]
|
||||||
|
%% retain => true | false | all %% only for pub action
|
||||||
|
%% }
|
||||||
|
%% ]
|
||||||
|
%%
|
||||||
authorize(#{acl := Acl} = Client, PubSub, Topic, _Source) ->
|
authorize(#{acl := Acl} = Client, PubSub, Topic, _Source) ->
|
||||||
case check(Acl) of
|
case check(Acl) of
|
||||||
{ok, Rules} when is_map(Rules) ->
|
{ok, Rules} when ?IS_V2(Rules) ->
|
||||||
do_authorize(Client, PubSub, Topic, Rules);
|
authorize_v2(Client, PubSub, Topic, Rules);
|
||||||
|
{ok, Rules} when ?IS_V1(Rules) ->
|
||||||
|
authorize_v1(Client, PubSub, Topic, Rules);
|
||||||
{error, MatchResult} ->
|
{error, MatchResult} ->
|
||||||
MatchResult
|
MatchResult
|
||||||
end;
|
end;
|
||||||
|
@ -69,7 +109,7 @@ authorize(_Client, _PubSub, _Topic, _Source) ->
|
||||||
%% Internal functions
|
%% Internal functions
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
|
|
||||||
check(#{expire := Expire, rules := Rules}) when is_map(Rules) ->
|
check(#{expire := Expire, rules := Rules}) ->
|
||||||
Now = erlang:system_time(second),
|
Now = erlang:system_time(second),
|
||||||
case Expire of
|
case Expire of
|
||||||
N when is_integer(N) andalso N > Now -> {ok, Rules};
|
N when is_integer(N) andalso N > Now -> {ok, Rules};
|
||||||
|
@ -83,13 +123,13 @@ check(#{rules := Rules}) ->
|
||||||
check(#{}) ->
|
check(#{}) ->
|
||||||
{error, nomatch}.
|
{error, nomatch}.
|
||||||
|
|
||||||
do_authorize(Client, PubSub, Topic, AclRules) ->
|
authorize_v1(Client, PubSub, Topic, AclRules) ->
|
||||||
do_authorize(Client, PubSub, Topic, AclRules, ?RULE_NAMES).
|
authorize_v1(Client, PubSub, Topic, AclRules, ?RULE_NAMES).
|
||||||
|
|
||||||
do_authorize(_Client, _PubSub, _Topic, _AclRules, []) ->
|
authorize_v1(_Client, _PubSub, _Topic, _AclRules, []) ->
|
||||||
{matched, deny};
|
{matched, deny};
|
||||||
do_authorize(Client, PubSub, Topic, AclRules, [{Keys, Action} | RuleNames]) ->
|
authorize_v1(Client, PubSub, Topic, AclRules, [{Keys, Action} | RuleNames]) ->
|
||||||
TopicFilters = get_topic_filters(Keys, AclRules, []),
|
TopicFilters = get_topic_filters_v1(Keys, AclRules, []),
|
||||||
case
|
case
|
||||||
emqx_authz_rule:match(
|
emqx_authz_rule:match(
|
||||||
Client,
|
Client,
|
||||||
|
@ -99,13 +139,16 @@ do_authorize(Client, PubSub, Topic, AclRules, [{Keys, Action} | RuleNames]) ->
|
||||||
)
|
)
|
||||||
of
|
of
|
||||||
{matched, Permission} -> {matched, Permission};
|
{matched, Permission} -> {matched, Permission};
|
||||||
nomatch -> do_authorize(Client, PubSub, Topic, AclRules, RuleNames)
|
nomatch -> authorize_v1(Client, PubSub, Topic, AclRules, RuleNames)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
get_topic_filters([], _Rules, Default) ->
|
get_topic_filters_v1([], _Rules, Default) ->
|
||||||
Default;
|
Default;
|
||||||
get_topic_filters([Key | Keys], Rules, Default) ->
|
get_topic_filters_v1([Key | Keys], Rules, Default) ->
|
||||||
case Rules of
|
case Rules of
|
||||||
#{Key := Value} -> Value;
|
#{Key := Value} -> Value;
|
||||||
#{} -> get_topic_filters(Keys, Rules, Default)
|
#{} -> get_topic_filters_v1(Keys, Rules, Default)
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
authorize_v2(Client, PubSub, Topic, Rules) ->
|
||||||
|
emqx_authz_rule:matches(Client, PubSub, Topic, Rules).
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
%% -*- mode: erlang -*-
|
%% -*- mode: erlang -*-
|
||||||
{application, emqx_auth_jwt, [
|
{application, emqx_auth_jwt, [
|
||||||
{description, "EMQX JWT Authentication and Authorization"},
|
{description, "EMQX JWT Authentication and Authorization"},
|
||||||
{vsn, "0.1.1"},
|
{vsn, "0.2.0"},
|
||||||
{registered, []},
|
{registered, []},
|
||||||
{mod, {emqx_auth_jwt_app, []}},
|
{mod, {emqx_auth_jwt_app, []}},
|
||||||
{applications, [
|
{applications, [
|
||||||
|
|
|
@ -219,14 +219,24 @@ verify(undefined, _, _, _) ->
|
||||||
verify(JWT, JWKs, VerifyClaims, AclClaimName) ->
|
verify(JWT, JWKs, VerifyClaims, AclClaimName) ->
|
||||||
case do_verify(JWT, JWKs, VerifyClaims) of
|
case do_verify(JWT, JWKs, VerifyClaims) of
|
||||||
{ok, Extra} ->
|
{ok, Extra} ->
|
||||||
{ok, acl(Extra, AclClaimName)};
|
try
|
||||||
|
{ok, acl(Extra, AclClaimName)}
|
||||||
|
catch
|
||||||
|
throw:{bad_acl_rule, Reason} ->
|
||||||
|
%% it's a invalid token, so ok to log
|
||||||
|
?TRACE_AUTHN_PROVIDER("bad_acl_rule", Reason#{jwt => JWT}),
|
||||||
|
{error, bad_username_or_password}
|
||||||
|
end;
|
||||||
{error, {missing_claim, Claim}} ->
|
{error, {missing_claim, Claim}} ->
|
||||||
|
%% it's a invalid token, so it's ok to log
|
||||||
?TRACE_AUTHN_PROVIDER("missing_jwt_claim", #{jwt => JWT, claim => Claim}),
|
?TRACE_AUTHN_PROVIDER("missing_jwt_claim", #{jwt => JWT, claim => Claim}),
|
||||||
{error, bad_username_or_password};
|
{error, bad_username_or_password};
|
||||||
{error, invalid_signature} ->
|
{error, invalid_signature} ->
|
||||||
|
%% it's a invalid token, so it's ok to log
|
||||||
?TRACE_AUTHN_PROVIDER("invalid_jwt_signature", #{jwks => JWKs, jwt => JWT}),
|
?TRACE_AUTHN_PROVIDER("invalid_jwt_signature", #{jwks => JWKs, jwt => JWT}),
|
||||||
ignore;
|
ignore;
|
||||||
{error, {claims, Claims}} ->
|
{error, {claims, Claims}} ->
|
||||||
|
%% it's a invalid token, so it's ok to log
|
||||||
?TRACE_AUTHN_PROVIDER("invalid_jwt_claims", #{jwt => JWT, claims => Claims}),
|
?TRACE_AUTHN_PROVIDER("invalid_jwt_claims", #{jwt => JWT, claims => Claims}),
|
||||||
{error, bad_username_or_password}
|
{error, bad_username_or_password}
|
||||||
end.
|
end.
|
||||||
|
@ -237,7 +247,8 @@ acl(Claims, AclClaimName) ->
|
||||||
#{AclClaimName := Rules} ->
|
#{AclClaimName := Rules} ->
|
||||||
#{
|
#{
|
||||||
acl => #{
|
acl => #{
|
||||||
rules => Rules,
|
rules => parse_rules(Rules),
|
||||||
|
source_for_logging => jwt,
|
||||||
expire => maps:get(<<"exp">>, Claims, undefined)
|
expire => maps:get(<<"exp">>, Claims, undefined)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -363,3 +374,24 @@ binary_to_number(Bin) ->
|
||||||
_ -> false
|
_ -> false
|
||||||
end
|
end
|
||||||
end.
|
end.
|
||||||
|
|
||||||
|
%% Pars rules which can be in two different formats:
|
||||||
|
%% 1. #{<<"pub">> => [<<"a/b">>, <<"c/d">>], <<"sub">> => [...], <<"all">> => [...]}
|
||||||
|
%% 2. [#{<<"permission">> => <<"allow">>, <<"action">> => <<"publish">>, <<"topic">> => <<"a/b">>}, ...]
|
||||||
|
parse_rules(Rules) when is_map(Rules) ->
|
||||||
|
Rules;
|
||||||
|
parse_rules(Rules) when is_list(Rules) ->
|
||||||
|
lists:map(fun parse_rule/1, Rules).
|
||||||
|
|
||||||
|
parse_rule(Rule) ->
|
||||||
|
case emqx_authz_rule_raw:parse_rule(Rule) of
|
||||||
|
{ok, {Permission, Action, Topics}} ->
|
||||||
|
try
|
||||||
|
emqx_authz_rule:compile({Permission, all, Action, Topics})
|
||||||
|
catch
|
||||||
|
throw:Reason ->
|
||||||
|
throw({bad_acl_rule, Reason})
|
||||||
|
end;
|
||||||
|
{error, Reason} ->
|
||||||
|
throw({bad_acl_rule, Reason})
|
||||||
|
end.
|
||||||
|
|
|
@ -78,7 +78,7 @@ end_per_testcase(_TestCase, _Config) ->
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
||||||
t_topic_rules(_Config) ->
|
t_topic_rules(_Config) ->
|
||||||
Payload = #{
|
JWT = #{
|
||||||
<<"exp">> => erlang:system_time(second) + 60,
|
<<"exp">> => erlang:system_time(second) + 60,
|
||||||
<<"acl">> => #{
|
<<"acl">> => #{
|
||||||
<<"pub">> => [
|
<<"pub">> => [
|
||||||
|
@ -99,7 +99,47 @@ t_topic_rules(_Config) ->
|
||||||
},
|
},
|
||||||
<<"username">> => <<"username">>
|
<<"username">> => <<"username">>
|
||||||
},
|
},
|
||||||
JWT = generate_jws(Payload),
|
test_topic_rules(JWT).
|
||||||
|
|
||||||
|
t_topic_rules_v2(_Config) ->
|
||||||
|
JWT = #{
|
||||||
|
<<"exp">> => erlang:system_time(second) + 60,
|
||||||
|
<<"acl">> => [
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"allow">>,
|
||||||
|
<<"action">> => <<"pub">>,
|
||||||
|
<<"topics">> => [
|
||||||
|
<<"eq testpub1/${username}">>,
|
||||||
|
<<"testpub2/${clientid}">>,
|
||||||
|
<<"testpub3/#">>
|
||||||
|
]
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"allow">>,
|
||||||
|
<<"action">> => <<"sub">>,
|
||||||
|
<<"topics">> =>
|
||||||
|
[
|
||||||
|
<<"eq testsub1/${username}">>,
|
||||||
|
<<"testsub2/${clientid}">>,
|
||||||
|
<<"testsub3/#">>
|
||||||
|
]
|
||||||
|
},
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"allow">>,
|
||||||
|
<<"action">> => <<"all">>,
|
||||||
|
<<"topics">> => [
|
||||||
|
<<"eq testall1/${username}">>,
|
||||||
|
<<"testall2/${clientid}">>,
|
||||||
|
<<"testall3/#">>
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
<<"username">> => <<"username">>
|
||||||
|
},
|
||||||
|
test_topic_rules(JWT).
|
||||||
|
|
||||||
|
test_topic_rules(JWTInput) ->
|
||||||
|
JWT = generate_jws(JWTInput),
|
||||||
|
|
||||||
{ok, C} = emqtt:start_link(
|
{ok, C} = emqtt:start_link(
|
||||||
[
|
[
|
||||||
|
@ -350,6 +390,64 @@ t_check_undefined_expire(_Config) ->
|
||||||
emqx_authz_client_info:authorize(Client, ?AUTHZ_SUBSCRIBE, <<"a/bar">>, undefined)
|
emqx_authz_client_info:authorize(Client, ?AUTHZ_SUBSCRIBE, <<"a/bar">>, undefined)
|
||||||
).
|
).
|
||||||
|
|
||||||
|
t_invalid_rule(_Config) ->
|
||||||
|
emqx_logger:set_log_level(debug),
|
||||||
|
MakeJWT = fun(Acl) ->
|
||||||
|
generate_jws(#{
|
||||||
|
<<"exp">> => erlang:system_time(second) + 60,
|
||||||
|
<<"username">> => <<"username">>,
|
||||||
|
<<"acl">> => Acl
|
||||||
|
})
|
||||||
|
end,
|
||||||
|
InvalidAclList =
|
||||||
|
[
|
||||||
|
%% missing action
|
||||||
|
[#{<<"permission">> => <<"invalid">>}],
|
||||||
|
%% missing topic or topics
|
||||||
|
[#{<<"permission">> => <<"allow">>, <<"action">> => <<"pub">>}],
|
||||||
|
%% invlaid permission, must be allow | deny
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"invalid">>,
|
||||||
|
<<"action">> => <<"pub">>,
|
||||||
|
<<"topic">> => <<"t">>
|
||||||
|
}
|
||||||
|
],
|
||||||
|
%% invalid action, must be pub | sub | all
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"allow">>,
|
||||||
|
<<"action">> => <<"invalid">>,
|
||||||
|
<<"topic">> => <<"t">>
|
||||||
|
}
|
||||||
|
],
|
||||||
|
%% invalid qos
|
||||||
|
[
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"allow">>,
|
||||||
|
<<"action">> => <<"pub">>,
|
||||||
|
<<"topics">> => [<<"t">>],
|
||||||
|
<<"qos">> => 3
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
lists:foreach(
|
||||||
|
fun(InvalidAcl) ->
|
||||||
|
{ok, C} = emqtt:start_link(
|
||||||
|
[
|
||||||
|
{clean_start, true},
|
||||||
|
{proto_ver, v5},
|
||||||
|
{clientid, <<"clientid">>},
|
||||||
|
{username, <<"username">>},
|
||||||
|
{password, MakeJWT(InvalidAcl)}
|
||||||
|
]
|
||||||
|
),
|
||||||
|
unlink(C),
|
||||||
|
?assertMatch({error, {bad_username_or_password, _}}, emqtt:connect(C))
|
||||||
|
end,
|
||||||
|
InvalidAclList
|
||||||
|
).
|
||||||
|
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
%% Helpers
|
%% Helpers
|
||||||
%%------------------------------------------------------------------------------
|
%%------------------------------------------------------------------------------
|
||||||
|
|
|
@ -190,7 +190,7 @@ t_normalize_rules(_Config) ->
|
||||||
|
|
||||||
?assertException(
|
?assertException(
|
||||||
error,
|
error,
|
||||||
{invalid_rule, _},
|
#{reason := invalid_rule},
|
||||||
emqx_authz_mnesia:store_rules(
|
emqx_authz_mnesia:store_rules(
|
||||||
{username, <<"username">>},
|
{username, <<"username">>},
|
||||||
[[<<"allow">>, <<"publish">>, <<"t">>]]
|
[[<<"allow">>, <<"publish">>, <<"t">>]]
|
||||||
|
@ -199,16 +199,22 @@ t_normalize_rules(_Config) ->
|
||||||
|
|
||||||
?assertException(
|
?assertException(
|
||||||
error,
|
error,
|
||||||
{invalid_action, _},
|
#{reason := invalid_action},
|
||||||
emqx_authz_mnesia:store_rules(
|
emqx_authz_mnesia:store_rules(
|
||||||
{username, <<"username">>},
|
{username, <<"username">>},
|
||||||
[#{<<"permission">> => <<"allow">>, <<"action">> => <<"pub">>, <<"topic">> => <<"t">>}]
|
[
|
||||||
|
#{
|
||||||
|
<<"permission">> => <<"allow">>,
|
||||||
|
<<"action">> => <<"badaction">>,
|
||||||
|
<<"topic">> => <<"t">>
|
||||||
|
}
|
||||||
|
]
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
|
|
||||||
?assertException(
|
?assertException(
|
||||||
error,
|
error,
|
||||||
{invalid_permission, _},
|
#{reason := invalid_permission},
|
||||||
emqx_authz_mnesia:store_rules(
|
emqx_authz_mnesia:store_rules(
|
||||||
{username, <<"username">>},
|
{username, <<"username">>},
|
||||||
[
|
[
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
Enhanced JWT ACL Claim Format.
|
||||||
|
|
||||||
|
The JWT ACL claim has been upgraded to support a more versatile format.
|
||||||
|
It now accepts an array structure, which resembles the file-based ACL rules.
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"permission": "allow",
|
||||||
|
"action": "pub",
|
||||||
|
"topic": "${username}/#",
|
||||||
|
"qos": [0,1],
|
||||||
|
"retain": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "allow",
|
||||||
|
"action": "sub",
|
||||||
|
"topic": "eq ${username}/#",
|
||||||
|
"qos": [0,1]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"permission": "deny",
|
||||||
|
"action": "all",
|
||||||
|
"topics": ["#"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
In this new format, when no matching rule is found, the action is not automatically denied.
|
||||||
|
This allows the authorization process to proceed to other configured authorization sources.
|
||||||
|
If no match is found throughout the chain, the final decision defers to the default permission set in `authorization.no_match`.
|
|
@ -1,7 +1,31 @@
|
||||||
emqx_authn_jwt_schema {
|
emqx_authn_jwt_schema {
|
||||||
|
|
||||||
acl_claim_name.desc:
|
acl_claim_name.desc:
|
||||||
"""JWT claim name to use for getting ACL rules."""
|
"""The JWT claim designated for accessing ACL (Access Control List) rules can be specified,
|
||||||
|
such as using the `acl` claim. A typical decoded JWT with this claim might appear as:
|
||||||
|
`{"username": "user1", "acl": ...}`.
|
||||||
|
|
||||||
|
Supported ACL Rule Formats:
|
||||||
|
|
||||||
|
- Object Format:
|
||||||
|
Utilizes action types pub (publish), sub (subscribe), or all (both publish and subscribe).
|
||||||
|
The value is a list of topic filters.
|
||||||
|
Example: `{"pub": ["topic1"], "sub": [], "all": ["${username}/#"]}`.
|
||||||
|
This example signifies that the token owner can publish to topic1 and perform both publish and subscribe
|
||||||
|
actions on topics starting with their username.
|
||||||
|
Note: In this format, if no topic matches, the action is denied, and the authorization process terminates.
|
||||||
|
|
||||||
|
- Array Format (resembles File-Based ACL Rules):
|
||||||
|
Example: `[{"permission": "allow", "action": "all", "topic": "${username}/#"}]`.
|
||||||
|
Additionally, the `pub` or `publish` action rules can be extended with `qos` and `retain` field,
|
||||||
|
and `sub` or `subscribe` action rules can be extended with a `qos` field.
|
||||||
|
Note: Here, if no rule matches, the action is not immediately denied.
|
||||||
|
The process continues to other configured authorization sources,
|
||||||
|
and ultimately falls back to the default permission in config `authorization.no_match`.
|
||||||
|
|
||||||
|
The ACL claim utilizes MQTT topic wildcard matching rules for publishing or subscribing.
|
||||||
|
A special syntax for the 'subscribe' action allows the use of `eq` for an exact match.
|
||||||
|
For instance, `eq t/#` permits or denies subscription to `t/#`, but not to `t/1`."""
|
||||||
|
|
||||||
acl_claim_name.label:
|
acl_claim_name.label:
|
||||||
"""ACL claim name"""
|
"""ACL claim name"""
|
||||||
|
|
Loading…
Reference in New Issue