Merge pull request #7789 from tigercl/chore/authn-fields

chore: rename certificate in jwt and selector in mongodb, remove pool_size in jwks
This commit is contained in:
zhouzb 2022-04-29 10:14:06 +08:00 committed by GitHub
commit afe526ba67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 120 additions and 125 deletions

View File

@ -21,14 +21,14 @@ emqx_authn_jwt {
}
}
certificate {
public_key {
desc {
en: """The public key used to verify the JWT."""
zh: """用于验证 JWT 的公钥。"""
}
label {
en: """Certificate"""
zh: """证书"""
en: """Public Key"""
zh: """公钥"""
}
}
@ -123,7 +123,7 @@ emqx_authn_jwt {
server_name_indication {
desc {
en: """Server Name Indication (SNI)."""
zh: """服务器名称指示SNI,未指定时将使用 <code>endpoint</code> 作为 SNI。"""
zh: """服务器名称指示SNI。"""
}
label {
en: """Server Name Indication"""
@ -133,8 +133,20 @@ emqx_authn_jwt {
verify_claims {
desc {
en: """The list of claims to verify."""
zh: """The list of claims to verify."""
en: """
A list of custom claims to validate, which is a list of name/value pairs.
Values can use the following placeholders:
- <code>${username}</code>: Will be replaced at runtime with <code>Username</code> used by the client when connecting
- <code>${clientid}</code>: Will be replaced at runtime with <code>Client ID</code> used by the client when connecting
Authentication will verify that the value of claims in the JWT (taken from the Password field) matches what is required in <code>verify_claims</code>.
"""
zh: """
需要验证的自定义声明列表,它是一个名称/值对列表。
值可以使用以下占位符:
- <code>${username}</code>: 将在运行时被替换为客户端连接时使用的用户名
- <code>${clientid}</code>: 将在运行时被替换为客户端连接时使用的客户端标识符
认证时将验证 JWT取自 Password 字段)中 claims 的值是否与 <code>verify_claims</code> 中要求的相匹配。
"""
}
label {
en: """Verify Claims"""
@ -142,17 +154,6 @@ emqx_authn_jwt {
}
}
pool_size {
desc {
en: """JWKS connection count."""
zh: """JWKS 连接数量。"""
}
label {
en: """Pool Size"""
zh: """连接池大小"""
}
}
ssl {
desc {
en: """SSL options."""

View File

@ -31,24 +31,24 @@ emqx_authn_mongodb {
}
}
selector {
filter {
desc {
en: """
Statement that is executed during the authentication process.
Commands can support following wildcards:\n
- `${username}`: substituted with client's username\n
- `${clientid}`: substituted with the clientid
Conditional expression that defines the filter condition in the query.
Filter supports the following placeholders:
- <code>${username}</code>: Will be replaced at runtime with <code>Username</code> used by the client when connecting
- <code>${clientid}</code>: Will be replaced at runtime with <code>Client ID</code> used by the client when connecting
"""
zh: """
认证过程中所使用的查询命令
查询命令支持如下占位符:
- `${username}`: 将在运行时被替换为客户端连接时使用的用户名
- `${clientid}`: 将在运行时被替换为客户端连接时使用的客户端标识符
在查询中定义过滤条件的条件表达式
过滤器支持如下占位符:
- <code>${username}</code>: 将在运行时被替换为客户端连接时使用的用户名
- <code>${clientid}</code>: 将在运行时被替换为客户端连接时使用的客户端标识符
"""
}
label: {
en: """Selector"""
zh: """查询"""
en: """Filter"""
zh: """过滤器"""
}
}

View File

@ -1417,7 +1417,7 @@ authenticator_examples() ->
server => <<"127.0.0.1:27017">>,
database => example,
collection => users,
selector => #{
filter => #{
username => ?PH_USERNAME
},
password_hash_field => <<"password_hash">>,

View File

@ -66,13 +66,12 @@ fields('public-key') ->
{use_jwks, sc(hoconsc:enum([false]), #{required => true, desc => ?DESC(use_jwks)})},
{algorithm,
sc(hoconsc:enum(['public-key']), #{required => true, desc => ?DESC(algorithm)})},
{certificate, fun certificate/1}
{public_key, fun public_key/1}
] ++ common_fields();
fields('jwks') ->
[
{use_jwks, sc(hoconsc:enum([true]), #{required => true, desc => ?DESC(use_jwks)})},
{endpoint, fun endpoint/1},
{pool_size, fun pool_size/1},
{refresh_interval, fun refresh_interval/1},
{ssl, #{
type => hoconsc:union([
@ -125,10 +124,10 @@ secret_base64_encoded(desc) -> ?DESC(?FUNCTION_NAME);
secret_base64_encoded(default) -> false;
secret_base64_encoded(_) -> undefined.
certificate(type) -> string();
certificate(desc) -> ?DESC(?FUNCTION_NAME);
certificate(required) -> ture;
certificate(_) -> undefined.
public_key(type) -> string();
public_key(desc) -> ?DESC(?FUNCTION_NAME);
public_key(required) -> ture;
public_key(_) -> undefined.
endpoint(type) -> string();
endpoint(desc) -> ?DESC(?FUNCTION_NAME);
@ -179,12 +178,6 @@ verify_claims(required) ->
verify_claims(_) ->
undefined.
pool_size(type) -> integer();
pool_size(desc) -> ?DESC(?FUNCTION_NAME);
pool_size(default) -> 8;
pool_size(validator) -> [fun(I) -> I > 0 end];
pool_size(_) -> undefined.
%%------------------------------------------------------------------------------
%% APIs
%%------------------------------------------------------------------------------
@ -294,10 +287,10 @@ create2(#{
create2(#{
use_jwks := false,
algorithm := 'public-key',
certificate := Certificate,
public_key := PublicKey,
verify_claims := VerifyClaims
}) ->
JWK = create_jwk_from_pem_or_file(Certificate),
JWK = create_jwk_from_public_key(PublicKey),
{ok, #{
jwk => JWK,
verify_claims => VerifyClaims
@ -320,15 +313,14 @@ create2(
verify_claims => VerifyClaims
}}.
create_jwk_from_pem_or_file(CertfileOrFilePath) when
is_binary(CertfileOrFilePath);
is_list(CertfileOrFilePath)
create_jwk_from_public_key(PublicKey) when
is_binary(PublicKey); is_list(PublicKey)
->
case filelib:is_file(CertfileOrFilePath) of
case filelib:is_file(PublicKey) of
true ->
jose_jwk:from_pem_file(CertfileOrFilePath);
jose_jwk:from_pem_file(PublicKey);
false ->
jose_jwk:from_pem(iolist_to_binary(CertfileOrFilePath))
jose_jwk:from_pem(iolist_to_binary(PublicKey))
end.
connector_opts(#{ssl := #{enable := Enable} = SSL} = Config) ->

View File

@ -74,7 +74,7 @@ common_fields() ->
{mechanism, emqx_authn_schema:mechanism(password_based)},
{backend, emqx_authn_schema:backend(mongodb)},
{collection, fun collection/1},
{selector, fun selector/1},
{filter, fun filter/1},
{password_hash_field, fun password_hash_field/1},
{salt_field, fun salt_field/1},
{is_superuser_field, fun is_superuser_field/1},
@ -86,11 +86,11 @@ collection(desc) -> ?DESC(?FUNCTION_NAME);
collection(required) -> true;
collection(_) -> undefined.
selector(type) ->
filter(type) ->
map();
selector(desc) ->
filter(desc) ->
?DESC(?FUNCTION_NAME);
selector(_) ->
filter(_) ->
undefined.
password_hash_field(type) -> binary();
@ -122,8 +122,8 @@ refs() ->
create(_AuthenticatorID, Config) ->
create(Config).
create(#{selector := Selector} = Config) ->
SelectorTemplate = emqx_authn_utils:parse_deep(Selector),
create(#{filter := Filter} = Config) ->
FilterTemplate = emqx_authn_utils:parse_deep(Filter),
State = maps:with(
[
collection,
@ -139,7 +139,7 @@ create(#{selector := Selector} = Config) ->
ok = emqx_authn_password_hashing:init(Algorithm),
ResourceId = emqx_authn_utils:make_resource_id(?MODULE),
NState = State#{
selector_template => SelectorTemplate,
filter_template => FilterTemplate,
resource_id => ResourceId
},
case
@ -174,12 +174,12 @@ authenticate(
#{password := Password} = Credential,
#{
collection := Collection,
selector_template := SelectorTemplate,
filter_template := FilterTemplate,
resource_id := ResourceId
} = State
) ->
Selector = emqx_authn_utils:render_deep(SelectorTemplate, Credential),
case emqx_resource:query(ResourceId, {find_one, Collection, Selector, #{}}) of
Filter = emqx_authn_utils:render_deep(FilterTemplate, Credential),
case emqx_resource:query(ResourceId, {find_one, Collection, Filter, #{}}) of
undefined ->
ignore;
{error, Reason} ->
@ -187,7 +187,7 @@ authenticate(
msg => "mongodb_query_failed",
resource => ResourceId,
collection => Collection,
selector => Selector,
filter => Filter,
reason => Reason
}),
ignore;
@ -200,7 +200,7 @@ authenticate(
msg => "cannot_find_password_hash_field",
resource => ResourceId,
collection => Collection,
selector => Selector,
filter => Filter,
password_hash_field => PasswordHashField
}),
ignore;

View File

@ -174,7 +174,7 @@ t_jwt_authenticator_public_key(_) ->
mechanism => jwt,
use_jwks => false,
algorithm => 'public-key',
certificate => PublicKey,
public_key => PublicKey,
verify_claims => []
},
{ok, State} = emqx_authn_jwt:create(?AUTHN_ID, Config),

View File

@ -84,7 +84,7 @@ t_create_invalid(_Config) ->
InvalidConfigs =
[
AuthConfig#{mongo_type => <<"unknown">>},
AuthConfig#{selector => <<"{ \"username\": \"${username}\" }">>},
AuthConfig#{filter => <<"{ \"username\": \"${username}\" }">>},
AuthConfig#{w_mode => <<"unknown">>}
],
@ -177,7 +177,7 @@ t_update(_Config) ->
ok = init_seeds(),
CorrectConfig = raw_mongo_auth_config(),
IncorrectConfig =
CorrectConfig#{selector => #{<<"wrongfield">> => <<"wrongvalue">>}},
CorrectConfig#{filter => #{<<"wrongfield">> => <<"wrongvalue">>}},
{ok, _} = emqx:update_config(
?PATH,
@ -193,7 +193,7 @@ t_update(_Config) ->
}
),
% We update with config with correct selector, provider should update and work properly
% We update with config with correct filter, provider should update and work properly
{ok, _} = emqx:update_config(
?PATH,
{update_authenticator, ?GLOBAL, <<"password_based:mongodb">>, CorrectConfig}
@ -276,7 +276,7 @@ raw_mongo_auth_config() ->
server => mongo_server(),
w_mode => <<"unsafe">>,
selector => #{<<"username">> => <<"${username}">>},
filter => #{<<"username">> => <<"${username}">>},
password_hash_field => <<"password_hash">>,
salt_field => <<"salt">>,
is_superuser_field => <<"is_superuser">>
@ -332,7 +332,7 @@ user_seeds() ->
password => <<"sha256">>
},
config_params => #{
selector => #{<<"username">> => <<"${clientid}">>},
filter => #{<<"username">> => <<"${clientid}">>},
password_hash_algorithm => #{
name => <<"sha256">>,
salt_position => <<"prefix">>
@ -373,7 +373,7 @@ user_seeds() ->
},
config_params => #{
% clientid variable & username credentials
selector => #{<<"username">> => <<"${clientid}">>},
filter => #{<<"username">> => <<"${clientid}">>},
password_hash_algorithm => #{name => <<"bcrypt">>}
},
result => {error, not_authorized}
@ -392,7 +392,7 @@ user_seeds() ->
password => <<"bcrypt">>
},
config_params => #{
selector => #{<<"userid">> => <<"${clientid}">>},
filter => #{<<"userid">> => <<"${clientid}">>},
password_hash_algorithm => #{name => <<"bcrypt">>}
},
result => {error, not_authorized}

View File

@ -189,7 +189,7 @@ raw_mongo_auth_config(SpecificSSLOpts) ->
server => mongo_server(),
w_mode => <<"unsafe">>,
selector => #{<<"username">> => <<"${username}">>},
filter => #{<<"username">> => <<"${username}">>},
password_hash_field => <<"password_hash">>,
salt_field => <<"salt">>,
is_superuser_field => <<"is_superuser">>,

View File

@ -53,7 +53,7 @@ authorization {
# database: mqtt
# ssl: {enable: false}
# collection: mqtt_authz
# selector: { "$or": [ { "username": "${username}" }, { "clientid": "${clientid}" } ] }
# filter: { "$or": [ { "username": "${username}" }, { "clientid": "${clientid}" } ] }
# },
{
type: built_in_database

View File

@ -119,23 +119,24 @@ emqx_authz_api_schema {
}
}
selector {
filter {
desc {
en: """
Statement that is executed during the authorize process.
Commands can support following wildcards:\n
- `${username}`: substituted with client's username\n
- `${clientid}`: substituted with the clientid
Conditional expression that defines the filter condition in the query.
Filter supports the following placeholders:
- <code>${username}</code>: Will be replaced at runtime with <code>Username</code> used by the client when connecting
- <code>${clientid}</code>: Will be replaced at runtime with <code>Client ID</code> used by the client when connecting
"""
zh: """
鉴权过程中所使用的查询命令。
查询命令支持如下占位符:
- `${username}`: 代替客户端的用户名
- `${clientid}`: 代替客户端的客户端标识符"""
在查询中定义过滤条件的条件表达式。
过滤器支持如下占位符:
- <code>${username}</code>: 将在运行时被替换为客户端连接时使用的用户名
- <code>${clientid}</code>: 将在运行时被替换为客户端连接时使用的客户端标识符
"""
}
label {
en: """selector"""
zh: """selector"""
en: """Filter"""
zh: """过滤器"""
}
}

View File

@ -266,23 +266,24 @@ and the new rules will override all rules from the old config file.
}
}
selector {
filter {
desc {
en: """
Statement that is executed during the authorize process.
Commands can support following wildcards:\n
- `${username}`: substituted with client's username\n
- `${clientid}`: substituted with the clientid
Conditional expression that defines the filter condition in the query.
Filter supports the following placeholders:
- <code>${username}</code>: Will be replaced at runtime with <code>Username</code> used by the client when connecting
- <code>${clientid}</code>: Will be replaced at runtime with <code>Client ID</code> used by the client when connecting
"""
zh: """
鉴权过程中所使用的查询命令。
查询命令支持如下占位符:
- `${username}`: 代替客户端的用户名
- `${clientid}`: 代替客户端的客户端标识符"""
在查询中定义过滤条件的条件表达式。
过滤器支持如下占位符:
- <code>${username}</code>: 将在运行时被替换为客户端连接时使用的用户名
- <code>${clientid}</code>: 将在运行时被替换为客户端连接时使用的客户端标识符
"""
}
label {
en: """selector"""
zh: """selector"""
en: """Filter"""
zh: """过滤器"""
}
}

View File

@ -181,7 +181,7 @@ authz_mongo_common_fields() ->
authz_common_fields(mongodb) ++
[
{collection, fun collection/1},
{selector, fun selector/1}
{filter, fun filter/1}
].
collection(type) -> binary();
@ -189,11 +189,11 @@ collection(desc) -> ?DESC(?FUNCTION_NAME);
collection(required) -> true;
collection(_) -> undefined.
selector(type) ->
filter(type) ->
map();
selector(desc) ->
filter(desc) ->
?DESC(?FUNCTION_NAME);
selector(_) ->
filter(_) ->
undefined.
%%------------------------------------------------------------------------------

View File

@ -45,15 +45,15 @@
description() ->
"AuthZ with MongoDB".
init(#{selector := Selector} = Source) ->
init(#{filter := Filter} = Source) ->
case emqx_authz_utils:create_resource(emqx_connector_mongo, Source) of
{error, Reason} ->
error({load_config_error, Reason});
{ok, Id} ->
Source#{
annotations => #{id => Id},
selector_template => emqx_authz_utils:parse_deep(
Selector,
filter_template => emqx_authz_utils:parse_deep(
Filter,
?PLACEHOLDERS
)
}
@ -68,14 +68,14 @@ authorize(
Topic,
#{
collection := Collection,
selector_template := SelectorTemplate,
filter_template := FilterTemplate,
annotations := #{id := ResourceID}
}
) ->
RenderedSelector = emqx_authz_utils:render_deep(SelectorTemplate, Client),
RenderedFilter = emqx_authz_utils:render_deep(FilterTemplate, Client),
Result =
try
emqx_resource:query(ResourceID, {find, Collection, RenderedSelector, #{}})
emqx_resource:query(ResourceID, {find, Collection, RenderedFilter, #{}})
catch
error:Error -> {error, Error}
end,
@ -86,7 +86,7 @@ authorize(
msg => "query_mongo_error",
reason => Reason,
collection => Collection,
selector => RenderedSelector,
filter => RenderedFilter,
resource_id => ResourceID
}),
nomatch;

View File

@ -199,10 +199,10 @@ mongo_common_fields() ->
required => true,
desc => ?DESC(collection)
}},
{selector, #{
{filter, #{
type => map(),
required => true,
desc => ?DESC(selector)
desc => ?DESC(filter)
}}
].

View File

@ -91,7 +91,7 @@ set_special_configs(_App) ->
<<"database">> => <<"mqtt">>,
<<"ssl">> => #{<<"enable">> => false},
<<"collection">> => <<"authz">>,
<<"selector">> => #{<<"a">> => <<"b">>}
<<"filter">> => #{<<"a">> => <<"b">>}
}).
-define(SOURCE3, #{
<<"type">> => <<"mysql">>,

View File

@ -47,7 +47,7 @@
<<"database">> => <<"mqtt">>,
<<"ssl">> => #{<<"enable">> => false},
<<"collection">> => <<"fake">>,
<<"selector">> => #{<<"a">> => <<"b">>}
<<"filter">> => #{<<"a">> => <<"b">>}
}).
-define(SOURCE3, #{
<<"type">> => <<"mysql">>,

View File

@ -85,7 +85,7 @@ t_topic_rules(_Config) ->
ok = emqx_authz_test_lib:test_deny_topic_rules(ClientInfo, fun setup_client_samples/2).
t_complex_selector(_) ->
t_complex_filter(_) ->
%% atom and string values also supported
ClientInfo = #{
clientid => clientid,
@ -111,7 +111,7 @@ t_complex_selector(_) ->
ok = setup_samples(Samples),
ok = setup_config(
#{
<<"selector">> => #{
<<"filter">> => #{
<<"x">> => #{
<<"u">> => <<"${username}">>,
<<"c">> => [#{<<"c">> => <<"${clientid}">>}],
@ -137,7 +137,7 @@ t_mongo_error(_Config) ->
ok = setup_samples([]),
ok = setup_config(
#{<<"selector">> => #{<<"$badoperator">> => <<"$badoperator">>}}
#{<<"filter">> => #{<<"$badoperator">> => <<"$badoperator">>}}
),
ok = emqx_authz_test_lib:test_samples(
@ -165,7 +165,7 @@ t_lookups(_Config) ->
ok = setup_samples([ByClientid]),
ok = setup_config(
#{<<"selector">> => #{<<"clientid">> => <<"${clientid}">>}}
#{<<"filter">> => #{<<"clientid">> => <<"${clientid}">>}}
),
ok = emqx_authz_test_lib:test_samples(
@ -185,7 +185,7 @@ t_lookups(_Config) ->
ok = setup_samples([ByPeerhost]),
ok = setup_config(
#{<<"selector">> => #{<<"peerhost">> => <<"${peerhost}">>}}
#{<<"filter">> => #{<<"peerhost">> => <<"${peerhost}">>}}
),
ok = emqx_authz_test_lib:test_samples(
@ -196,7 +196,7 @@ t_lookups(_Config) ->
]
).
t_bad_selector(_Config) ->
t_bad_filter(_Config) ->
ClientInfo = #{
clientid => <<"clientid">>,
cn => <<"cn">>,
@ -208,7 +208,7 @@ t_bad_selector(_Config) ->
},
ok = setup_config(
#{<<"selector">> => #{<<"$in">> => #{<<"a">> => 1}}}
#{<<"filter">> => #{<<"$in">> => #{<<"a">> => 1}}}
),
ok = emqx_authz_test_lib:test_samples(
@ -251,7 +251,7 @@ setup_client_samples(ClientInfo, Samples) ->
Samples
),
setup_samples(Records),
setup_config(#{<<"selector">> => #{<<"username">> => <<"${username}">>}}).
setup_config(#{<<"filter">> => #{<<"username">> => <<"${username}">>}}).
reset_samples() ->
{true, _} = mc_worker_api:delete(?MONGO_CLIENT, <<"acl">>, #{}),
@ -273,7 +273,7 @@ raw_mongo_authz_config() ->
<<"collection">> => <<"acl">>,
<<"server">> => mongo_server(),
<<"selector">> => #{<<"username">> => <<"${username}">>}
<<"filter">> => #{<<"username">> => <<"${username}">>}
}.
mongo_server() ->

View File

@ -188,11 +188,11 @@ on_stop(InstId, #{poolname := PoolName}) ->
on_query(
InstId,
{Action, Collection, Selector, Projector},
{Action, Collection, Filter, Projector},
AfterQuery,
#{poolname := PoolName} = State
) ->
Request = {Action, Collection, Selector, Projector},
Request = {Action, Collection, Filter, Projector},
?TRACE(
"QUERY",
"mongodb_connector_received",
@ -201,7 +201,7 @@ on_query(
case
ecpool:pick_and_do(
PoolName,
{?MODULE, mongo_query, [Action, Collection, Selector, Projector]},
{?MODULE, mongo_query, [Action, Collection, Filter, Projector]},
no_handover
)
of
@ -297,12 +297,12 @@ connect(Opts) ->
WorkerOptions = proplists:get_value(worker_options, Opts, []),
mongo_api:connect(Type, Hosts, Options, WorkerOptions).
mongo_query(Conn, find, Collection, Selector, Projector) ->
mongo_api:find(Conn, Collection, Selector, Projector);
mongo_query(Conn, find_one, Collection, Selector, Projector) ->
mongo_api:find_one(Conn, Collection, Selector, Projector);
mongo_query(Conn, find, Collection, Filter, Projector) ->
mongo_api:find(Conn, Collection, Filter, Projector);
mongo_query(Conn, find_one, Collection, Filter, Projector) ->
mongo_api:find_one(Conn, Collection, Filter, Projector);
%% Todo xxx
mongo_query(_Conn, _Action, _Collection, _Selector, _Projector) ->
mongo_query(_Conn, _Action, _Collection, _Filter, _Projector) ->
ok.
init_type(#{mongo_type := rs, replica_set_name := ReplicaSetName}) ->