refactor(client_attr): allow more than one initial extraction

This commit is contained in:
zmstone 2024-04-10 11:24:17 +02:00
parent 2577224bc7
commit da5b01aa46
7 changed files with 100 additions and 60 deletions

View File

@ -269,8 +269,7 @@ init(
}, },
Zone Zone
), ),
AttrExtractionConfig = get_mqtt_conf(Zone, client_attrs_init), ClientInfo = initialize_client_attrs_from_cert(ClientInfo0, Peercert),
ClientInfo = initialize_client_attrs_from_cert(AttrExtractionConfig, ClientInfo0, Peercert),
{NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo), {NClientInfo, NConnInfo} = take_ws_cookie(ClientInfo, ConnInfo),
#channel{ #channel{
conninfo = NConnInfo, conninfo = NConnInfo,
@ -1575,7 +1574,7 @@ enrich_client(ConnPkt, Channel = #channel{clientinfo = ClientInfo}) ->
fun maybe_username_as_clientid/2, fun maybe_username_as_clientid/2,
fun maybe_assign_clientid/2, fun maybe_assign_clientid/2,
%% attr init should happen after clientid and username assign %% attr init should happen after clientid and username assign
fun maybe_set_client_initial_attr/2 fun maybe_set_client_initial_attrs/2
], ],
ConnPkt, ConnPkt,
ClientInfo ClientInfo
@ -1587,7 +1586,17 @@ enrich_client(ConnPkt, Channel = #channel{clientinfo = ClientInfo}) ->
{error, ReasonCode, Channel#channel{clientinfo = NClientInfo}} {error, ReasonCode, Channel#channel{clientinfo = NClientInfo}}
end. end.
initialize_client_attrs_from_cert( initialize_client_attrs_from_cert(#{zone := Zone} = ClientInfo, Peercert) ->
Inits = get_client_attrs_init_config(Zone),
lists:foldl(
fun(Init, Acc) ->
do_initialize_client_attrs_from_cert(Init, Acc, Peercert)
end,
ClientInfo,
Inits
).
do_initialize_client_attrs_from_cert(
#{ #{
extract_from := From, extract_from := From,
extract_regexp := Regexp, extract_regexp := Regexp,
@ -1596,21 +1605,24 @@ initialize_client_attrs_from_cert(
ClientInfo, ClientInfo,
Peercert Peercert
) when From =:= cn orelse From =:= dn -> ) when From =:= cn orelse From =:= dn ->
case extract_client_attr_from_cert(From, Regexp, Peercert) of Attrs0 = maps:get(client_attrs, ClientInfo, #{}),
{ok, Value} -> Attrs =
?SLOG( case extract_client_attr_from_cert(From, Regexp, Peercert) of
debug, {ok, Value} ->
#{ ?SLOG(
msg => "client_attr_init_from_cert", debug,
extracted_as => AttrName, #{
extracted_value => Value msg => "client_attr_init_from_cert",
} extracted_as => AttrName,
), extracted_value => Value
ClientInfo#{client_attrs => #{AttrName => Value}}; }
_ -> ),
ClientInfo#{client_attrs => #{}} Attrs0#{AttrName => Value};
end; _ ->
initialize_client_attrs_from_cert(_, ClientInfo, _Peercert) -> Attrs0
end,
ClientInfo#{client_attrs => Attrs};
do_initialize_client_attrs_from_cert(_, ClientInfo, _Peercert) ->
ClientInfo. ClientInfo.
extract_client_attr_from_cert(cn, Regexp, Peercert) -> extract_client_attr_from_cert(cn, Regexp, Peercert) ->
@ -1668,27 +1680,51 @@ maybe_assign_clientid(#mqtt_packet_connect{clientid = <<>>}, ClientInfo) ->
maybe_assign_clientid(#mqtt_packet_connect{clientid = ClientId}, ClientInfo) -> maybe_assign_clientid(#mqtt_packet_connect{clientid = ClientId}, ClientInfo) ->
{ok, ClientInfo#{clientid => ClientId}}. {ok, ClientInfo#{clientid => ClientId}}.
maybe_set_client_initial_attr(ConnPkt, #{zone := Zone} = ClientInfo0) -> get_client_attrs_init_config(Zone) ->
Config = get_mqtt_conf(Zone, client_attrs_init), case get_mqtt_conf(Zone, client_attrs_init, []) of
ClientInfo = initialize_client_attrs_from_user_property(Config, ConnPkt, ClientInfo0), L when is_list(L) -> L;
Attrs = maps:get(client_attrs, ClientInfo, #{}), M when is_map(M) -> [M]
case extract_attr_from_clientinfo(Config, ClientInfo) of
{ok, Value} ->
#{extract_as := Name} = Config,
?SLOG(
debug,
#{
msg => "client_attr_init_from_clientinfo",
extracted_as => Name,
extracted_value => Value
}
),
{ok, ClientInfo#{client_attrs => Attrs#{Name => Value}}};
_ ->
{ok, ClientInfo}
end. end.
initialize_client_attrs_from_user_property( maybe_set_client_initial_attrs(ConnPkt, #{zone := Zone} = ClientInfo0) ->
Inits = get_client_attrs_init_config(Zone),
ClientInfo = initialize_client_attrs_from_user_property(Inits, ConnPkt, ClientInfo0),
{ok, initialize_client_attrs_from_clientinfo(Inits, ClientInfo)}.
initialize_client_attrs_from_clientinfo(Inits, ClientInfo) ->
lists:foldl(
fun(Init, Acc) ->
Attrs = maps:get(client_attrs, ClientInfo, #{}),
case extract_attr_from_clientinfo(Init, ClientInfo) of
{ok, Value} ->
#{extract_as := Name} = Init,
?SLOG(
debug,
#{
msg => "client_attr_init_from_clientinfo",
extracted_as => Name,
extracted_value => Value
}
),
Acc#{client_attrs => Attrs#{Name => Value}};
_ ->
Acc
end
end,
ClientInfo,
Inits
).
initialize_client_attrs_from_user_property(Inits, ConnPkt, ClientInfo) ->
lists:foldl(
fun(Init, Acc) ->
do_initialize_client_attrs_from_user_property(Init, ConnPkt, Acc)
end,
ClientInfo,
Inits
).
do_initialize_client_attrs_from_user_property(
#{ #{
extract_from := user_property, extract_from := user_property,
extract_as := PropertyKey extract_as := PropertyKey
@ -1696,21 +1732,24 @@ initialize_client_attrs_from_user_property(
ConnPkt, ConnPkt,
ClientInfo ClientInfo
) -> ) ->
case extract_client_attr_from_user_property(ConnPkt, PropertyKey) of Attrs0 = maps:get(client_attrs, ClientInfo, #{}),
{ok, Value} -> Attrs =
?SLOG( case extract_client_attr_from_user_property(ConnPkt, PropertyKey) of
debug, {ok, Value} ->
#{ ?SLOG(
msg => "client_attr_init_from_user_property", debug,
extracted_as => PropertyKey, #{
extracted_value => Value msg => "client_attr_init_from_user_property",
} extracted_as => PropertyKey,
), extracted_value => Value
ClientInfo#{client_attrs => #{PropertyKey => Value}}; }
_ -> ),
ClientInfo Attrs0#{PropertyKey => Value};
end; _ ->
initialize_client_attrs_from_user_property(_, _ConnInfo, ClientInfo) -> Attrs0
end,
ClientInfo#{client_attrs => Attrs};
do_initialize_client_attrs_from_user_property(_, _ConnPkt, ClientInfo) ->
ClientInfo. ClientInfo.
extract_client_attr_from_user_property( extract_client_attr_from_user_property(

View File

@ -3552,9 +3552,9 @@ mqtt_general() ->
)}, )},
{"client_attrs_init", {"client_attrs_init",
sc( sc(
hoconsc:union([disabled, ref("client_attrs_init")]), hoconsc:union([hoconsc:array(ref("client_attrs_init")), ref("client_attrs_init")]),
#{ #{
default => disabled, default => [],
desc => ?DESC("client_attrs_init") desc => ?DESC("client_attrs_init")
} }
)} )}

View File

@ -454,7 +454,7 @@ zone_global_defaults() ->
upgrade_qos => false, upgrade_qos => false,
use_username_as_clientid => false, use_username_as_clientid => false,
wildcard_subscription => true, wildcard_subscription => true,
client_attrs_init => disabled client_attrs_init => []
}, },
overload_protection => overload_protection =>
#{ #{

View File

@ -170,7 +170,7 @@ t_client_attr_as_mountpoint(_Config) ->
?assertMatch([_], emqx_router:match_routes(MatchTopic)), ?assertMatch([_], emqx_router:match_routes(MatchTopic)),
emqtt:stop(Client) emqtt:stop(Client)
end), end),
emqx_config:put_zone_conf(default, [mqtt, client_attrs_init], disabled), emqx_config:put_zone_conf(default, [mqtt, client_attrs_init], []),
ok. ok.
t_current_conns_tcp(_Config) -> t_current_conns_tcp(_Config) ->

View File

@ -572,7 +572,7 @@ t_alias_prefix(_Config) ->
?assertMatch({ok, _, [?RC_NOT_AUTHORIZED]}, emqtt:subscribe(C, SubTopicNotAllowed)), ?assertMatch({ok, _, [?RC_NOT_AUTHORIZED]}, emqtt:subscribe(C, SubTopicNotAllowed)),
unlink(C), unlink(C),
emqtt:stop(C), emqtt:stop(C),
emqx_config:put_zone_conf(default, [mqtt, client_attrs_init], disalbed), emqx_config:put_zone_conf(default, [mqtt, client_attrs_init], []),
ok. ok.
%% client is allowed by ACL to publish to its LWT topic, is connected, %% client is allowed by ACL to publish to its LWT topic, is connected,

View File

@ -7,7 +7,7 @@ an MQTT connection.
### Initialization of `client_attrs` ### Initialization of `client_attrs`
- The `client_attrs` field can be initially populated based on the configuration from one of the - The `client_attrs` fields can be initially populated based on the configuration from one of the
following sources: following sources:
- `cn`: The common name from the TLS client's certificate. - `cn`: The common name from the TLS client's certificate.
- `dn`: The distinguished name from the TLS client's certificate, that is, the certificate "Subject". - `dn`: The distinguished name from the TLS client's certificate, that is, the certificate "Subject".

View File

@ -1575,7 +1575,8 @@ client_attrs_init {
label: "Client Attributes Initialization" label: "Client Attributes Initialization"
desc: """~ desc: """~
Specify how to initialize client attributes. Specify how to initialize client attributes.
One initial client attribute can be initialized as `client_attrs.NAME`, This config accepts one initialization rule, or a list of rules.
Client attributes can be initialized as `client_attrs.NAME`,
where `NAME` is the name of the attribute specified in the config `extract_as`. where `NAME` is the name of the attribute specified in the config `extract_as`.
The initialized client attribute will be stored in the `client_attrs` property with the specified name, The initialized client attribute will be stored in the `client_attrs` property with the specified name,
and can be used as a placeholder in a template for authentication and authorization. and can be used as a placeholder in a template for authentication and authorization.