From 9ec99fef4ae04cfcc5643177c11a38da41650ffb Mon Sep 17 00:00:00 2001 From: zmstone Date: Tue, 19 Mar 2024 21:51:21 +0100 Subject: [PATCH] feat: allow client_attr used in authz rules --- apps/emqx/include/emqx_placeholder.hrl | 1 + .../src/emqx_authn/emqx_authn_utils.erl | 1 + .../src/emqx_authz/emqx_authz_rule.erl | 4 ++- .../test/emqx_authz/emqx_authz_file_SUITE.erl | 24 ++++++++++++++++ apps/emqx_auth_http/src/emqx_authz_http.erl | 3 +- .../src/emqx_auth_mongodb.app.src | 2 +- .../src/emqx_authz_mongodb.erl | 3 +- .../src/emqx_auth_mysql.app.src | 2 +- apps/emqx_auth_mysql/src/emqx_authz_mysql.erl | 3 +- .../src/emqx_auth_postgresql.app.src | 2 +- .../src/emqx_authz_postgresql.erl | 3 +- .../src/emqx_auth_redis.app.src | 2 +- apps/emqx_auth_redis/src/emqx_authz_redis.erl | 3 +- apps/emqx_utils/src/emqx_template.erl | 28 +++++++++++++++++-- apps/emqx_utils/test/emqx_template_SUITE.erl | 12 ++++++++ 15 files changed, 81 insertions(+), 12 deletions(-) diff --git a/apps/emqx/include/emqx_placeholder.hrl b/apps/emqx/include/emqx_placeholder.hrl index 8e886fe68..e2711be69 100644 --- a/apps/emqx/include/emqx_placeholder.hrl +++ b/apps/emqx/include/emqx_placeholder.hrl @@ -36,6 +36,7 @@ -define(VAR_CLIENTID, "clientid"). -define(VAR_USERNAME, "username"). -define(VAR_TOPIC, "topic"). +-define(VAR_NS_CLIENT_ATTRS, {var_namespace, "client_attrs"}). -define(PH_PASSWORD, ?PH(?VAR_PASSWORD)). -define(PH_CLIENTID, ?PH(?VAR_CLIENTID)). -define(PH_FROM_CLIENTID, ?PH("from_clientid")). diff --git a/apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl b/apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl index 80da32b0b..fb6bf98fa 100644 --- a/apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl +++ b/apps/emqx_auth/src/emqx_authn/emqx_authn_utils.erl @@ -46,6 +46,7 @@ default_headers_no_content_type/0 ]). +%% VAR_NS_CLIENT_ATTRS is not added to this list because client_attrs is to be initialized from authn result -define(ALLOWED_VARS, [ ?VAR_USERNAME, ?VAR_CLIENTID, diff --git a/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl b/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl index 9ecfa0f64..68ee05b52 100644 --- a/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl +++ b/apps/emqx_auth/src/emqx_authz/emqx_authz_rule.erl @@ -223,7 +223,9 @@ compile_topic(<<"eq ", Topic/binary>>) -> compile_topic({eq, Topic}) -> {eq, emqx_topic:words(bin(Topic))}; compile_topic(Topic) -> - Template = emqx_authz_utils:parse_str(Topic, [?VAR_USERNAME, ?VAR_CLIENTID]), + Template = emqx_authz_utils:parse_str(Topic, [ + ?VAR_USERNAME, ?VAR_CLIENTID, ?VAR_NS_CLIENT_ATTRS + ]), case emqx_template:is_const(Template) of true -> emqx_topic:words(bin(Topic)); false -> {pattern, Template} diff --git a/apps/emqx_auth/test/emqx_authz/emqx_authz_file_SUITE.erl b/apps/emqx_auth/test/emqx_authz/emqx_authz_file_SUITE.erl index 3ed7c1f83..e51f6dd37 100644 --- a/apps/emqx_auth/test/emqx_authz/emqx_authz_file_SUITE.erl +++ b/apps/emqx_auth/test/emqx_authz/emqx_authz_file_SUITE.erl @@ -74,6 +74,30 @@ t_ok(_Config) -> emqx_access_control:authorize(ClientInfo, ?AUTHZ_SUBSCRIBE, <<"t">>) ). +t_client_attrs(_Config) -> + ClientInfo0 = emqx_authz_test_lib:base_client_info(), + ClientInfo = ClientInfo0#{client_attrs => #{<<"device_id">> => <<"id1">>}}, + + ok = setup_config(?RAW_SOURCE#{ + <<"rules">> => <<"{allow, all, all, [\"t/${client_attrs.device_id}/#\"]}.">> + }), + + ?assertEqual( + allow, + emqx_access_control:authorize(ClientInfo, ?AUTHZ_PUBLISH, <<"t/id1/1">>) + ), + + ?assertEqual( + allow, + emqx_access_control:authorize(ClientInfo, ?AUTHZ_SUBSCRIBE, <<"t/id1/#">>) + ), + + ?assertEqual( + deny, + emqx_access_control:authorize(ClientInfo, ?AUTHZ_SUBSCRIBE, <<"t/id2/#">>) + ), + ok. + t_rich_actions(_Config) -> ClientInfo = emqx_authz_test_lib:base_client_info(), diff --git a/apps/emqx_auth_http/src/emqx_authz_http.erl b/apps/emqx_auth_http/src/emqx_authz_http.erl index 721307099..ffc339393 100644 --- a/apps/emqx_auth_http/src/emqx_authz_http.erl +++ b/apps/emqx_auth_http/src/emqx_authz_http.erl @@ -47,7 +47,8 @@ ?VAR_TOPIC, ?VAR_ACTION, ?VAR_CERT_SUBJECT, - ?VAR_CERT_CN_NAME + ?VAR_CERT_CN_NAME, + ?VAR_NS_CLIENT_ATTRS ]). -define(ALLOWED_VARS_RICH_ACTIONS, [ diff --git a/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src index 8970329fe..df3cc1268 100644 --- a/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src +++ b/apps/emqx_auth_mongodb/src/emqx_auth_mongodb.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_auth_mongodb, [ {description, "EMQX MongoDB Authentication and Authorization"}, - {vsn, "0.1.1"}, + {vsn, "0.2.0"}, {registered, []}, {mod, {emqx_auth_mongodb_app, []}}, {applications, [ diff --git a/apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl b/apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl index 8f185c3bd..0bab6ef90 100644 --- a/apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl +++ b/apps/emqx_auth_mongodb/src/emqx_authz_mongodb.erl @@ -40,7 +40,8 @@ ?VAR_CLIENTID, ?VAR_PEERHOST, ?VAR_CERT_CN_NAME, - ?VAR_CERT_SUBJECT + ?VAR_CERT_SUBJECT, + ?VAR_NS_CLIENT_ATTRS ]). description() -> diff --git a/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src b/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src index 24fdd2648..29bf97ac8 100644 --- a/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src +++ b/apps/emqx_auth_mysql/src/emqx_auth_mysql.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_auth_mysql, [ {description, "EMQX MySQL Authentication and Authorization"}, - {vsn, "0.1.2"}, + {vsn, "0.2.0"}, {registered, []}, {mod, {emqx_auth_mysql_app, []}}, {applications, [ diff --git a/apps/emqx_auth_mysql/src/emqx_authz_mysql.erl b/apps/emqx_auth_mysql/src/emqx_authz_mysql.erl index 6356f0017..59ed878ab 100644 --- a/apps/emqx_auth_mysql/src/emqx_authz_mysql.erl +++ b/apps/emqx_auth_mysql/src/emqx_authz_mysql.erl @@ -42,7 +42,8 @@ ?VAR_CLIENTID, ?VAR_PEERHOST, ?VAR_CERT_CN_NAME, - ?VAR_CERT_SUBJECT + ?VAR_CERT_SUBJECT, + ?VAR_NS_CLIENT_ATTRS ]). description() -> diff --git a/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src index bae3da0cb..3978f7dbc 100644 --- a/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src +++ b/apps/emqx_auth_postgresql/src/emqx_auth_postgresql.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_auth_postgresql, [ {description, "EMQX PostgreSQL Authentication and Authorization"}, - {vsn, "0.1.1"}, + {vsn, "0.2.0"}, {registered, []}, {mod, {emqx_auth_postgresql_app, []}}, {applications, [ diff --git a/apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl b/apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl index f53307d24..a77f0a424 100644 --- a/apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl +++ b/apps/emqx_auth_postgresql/src/emqx_authz_postgresql.erl @@ -42,7 +42,8 @@ ?VAR_CLIENTID, ?VAR_PEERHOST, ?VAR_CERT_CN_NAME, - ?VAR_CERT_SUBJECT + ?VAR_CERT_SUBJECT, + ?VAR_NS_CLIENT_ATTRS ]). description() -> diff --git a/apps/emqx_auth_redis/src/emqx_auth_redis.app.src b/apps/emqx_auth_redis/src/emqx_auth_redis.app.src index b5669e706..74168495b 100644 --- a/apps/emqx_auth_redis/src/emqx_auth_redis.app.src +++ b/apps/emqx_auth_redis/src/emqx_auth_redis.app.src @@ -1,7 +1,7 @@ %% -*- mode: erlang -*- {application, emqx_auth_redis, [ {description, "EMQX Redis Authentication and Authorization"}, - {vsn, "0.1.2"}, + {vsn, "0.2.0"}, {registered, []}, {mod, {emqx_auth_redis_app, []}}, {applications, [ diff --git a/apps/emqx_auth_redis/src/emqx_authz_redis.erl b/apps/emqx_auth_redis/src/emqx_authz_redis.erl index a8f52da1b..a7f88f7c6 100644 --- a/apps/emqx_auth_redis/src/emqx_authz_redis.erl +++ b/apps/emqx_auth_redis/src/emqx_authz_redis.erl @@ -40,7 +40,8 @@ ?VAR_CERT_SUBJECT, ?VAR_PEERHOST, ?VAR_CLIENTID, - ?VAR_USERNAME + ?VAR_USERNAME, + ?VAR_NS_CLIENT_ATTRS ]). description() -> diff --git a/apps/emqx_utils/src/emqx_template.erl b/apps/emqx_utils/src/emqx_template.erl index 9a2ac1b8f..21c0e1d4f 100644 --- a/apps/emqx_utils/src/emqx_template.erl +++ b/apps/emqx_utils/src/emqx_template.erl @@ -145,18 +145,42 @@ parse_accessor(Var) -> %% @doc Validate a template against a set of allowed variables. %% If the given template contains any variable not in the allowed set, an error %% is returned. --spec validate([varname()], t()) -> +-spec validate([varname() | {var_namespace, varname()}], t()) -> ok | {error, [_Error :: {varname(), disallowed}]}. validate(Allowed, Template) -> {_, Errors} = render(Template, #{}), {Used, _} = lists:unzip(Errors), - case lists:usort(Used) -- Allowed of + case find_disallowed(lists:usort(Used), Allowed) of [] -> ok; Disallowed -> {error, [{Var, disallowed} || Var <- Disallowed]} end. +find_disallowed([], _Allowed) -> + []; +find_disallowed([Var | Rest], Allowed) -> + case is_allowed(Var, Allowed) of + true -> + find_disallowed(Rest, Allowed); + false -> + [Var | find_disallowed(Rest, Allowed)] + end. + +is_allowed(_Var, []) -> + false; +is_allowed(Var, [{var_namespace, VarPrefix} | Allowed]) -> + case lists:prefix(VarPrefix ++ ".", Var) of + true -> + true; + false -> + is_allowed(Var, Allowed) + end; +is_allowed(Var, [Var | _Allowed]) -> + true; +is_allowed(Var, [_ | Allowed]) -> + is_allowed(Var, Allowed). + %% @doc Check if a template is constant with respect to rendering, i.e. does not %% contain any placeholders. -spec is_const(t()) -> diff --git a/apps/emqx_utils/test/emqx_template_SUITE.erl b/apps/emqx_utils/test/emqx_template_SUITE.erl index 46b42e5db..0a3273170 100644 --- a/apps/emqx_utils/test/emqx_template_SUITE.erl +++ b/apps/emqx_utils/test/emqx_template_SUITE.erl @@ -337,6 +337,18 @@ t_unparse_tmpl_deep(_) -> Template = emqx_template:parse_deep(Term), ?assertEqual(Term, emqx_template:unparse(Template)). +t_allow_var_by_namespace(_) -> + Context = #{d => #{d1 => <<"hi">>}}, + Template = emqx_template:parse(<<"d.d1:${d.d1}">>), + ?assertEqual( + ok, + emqx_template:validate([{var_namespace, "d"}], Template) + ), + ?assertEqual( + {<<"d.d1:hi">>, []}, + render_string(Template, Context) + ). + %% render_string(Template, Context) ->