Skip to content

Commit

Permalink
Add option to forward the Common Name (as username) of a client certi… (
Browse files Browse the repository at this point in the history
#2183)

* Add option to forward the Common Name (as username) of a client certificate in an X-Forward header (WebSockets only)

* Fix XFF header name format

* rebar3 fmt
  • Loading branch information
ioolkos committed Aug 30, 2023
1 parent ea95cb9 commit 0361b6e
Show file tree
Hide file tree
Showing 7 changed files with 89 additions and 4 deletions.
13 changes: 13 additions & 0 deletions apps/vmq_server/priv/vmq_server.schema
Original file line number Diff line number Diff line change
Expand Up @@ -947,6 +947,19 @@
hidden
]}.

{mapping, "listener.ws.$name.proxy_xff_use_cn_as_username", "vmq_server.listeners", [
{default, off},
{datatype, flag},
hidden
]}.

{mapping, "listener.ws.$name.proxy_xff_cn_header", "vmq_server.listeners", [
{default, "x-ssl-client-cn"},
{datatype, string},
hidden
]}.


%% @doc If 'listener.tcp.proxy_protocol' is enabled, you may enable
%% 'listener.tcp.proxy_protocol_use_cn_as_username' to use the CN
%% (sni_hostname) passed via the PROXY protocol instead of the MQTT
Expand Down
16 changes: 16 additions & 0 deletions apps/vmq_server/src/vmq_listener_cli.erl
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,22 @@ vmq_listener_start_cmd() ->
(_) -> false
end}
]},
{proxy_xff_trusted_intermediate, [{longname, "proxy_xff_trusted_intermediate"}]},
{proxy_xff_support, [
{longname, "proxy_xff_support"},
{typecast, fun
("true") -> true;
(_) -> false
end}
]},
{proxy_xff_use_cn_as_username, [
{longname, "proxy_xff_use_cn_as_username"},
{typecast, fun
("true") -> true;
(_) -> false
end}
]},
{proxy_xff_cn_header, [{longname, "proxy_xff_cn_header"}]},
{allowed_protocol_versions, [
{longname, "allowed_protocol_versions"},
{typecast, fun(ProtoVers) ->
Expand Down
2 changes: 1 addition & 1 deletion apps/vmq_server/src/vmq_proxy_xff.erl
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ check_trusted_list(LastProxy, TrustedList) ->
true ->
true;
_ ->
lager:error("XFF proxy not in trused list!"),
lager:error("XFF proxy not in trusted list!"),
false
end.

Expand Down
5 changes: 4 additions & 1 deletion apps/vmq_server/src/vmq_ranch_config.erl
Original file line number Diff line number Diff line change
Expand Up @@ -352,7 +352,10 @@ default_session_opts(Opts) ->
{_, V2} ->
[
{xff_proxy, proplists:get_value(proxy_xff_support, Opts, false)},
{proxy_xff_trusted_intermediate, V2}
{proxy_xff_trusted_intermediate, V2},
{xff_cn_header, proplists:get_value(proxy_xff_cn_header, Opts, "")},
{xff_use_cn_as_username,
proplists:get_value(proxy_xff_use_cn_as_username, Opts, false)}
| MaybeProxyDefaults
]
end,
Expand Down
10 changes: 10 additions & 0 deletions apps/vmq_server/src/vmq_schema.erl
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ translate_listeners(Conf) ->
{WSIPs, WSProxyXFFTrusted} = lists:unzip(
extract("listener.ws", "proxy_xff_trusted_intermediate", StrVal, Conf)
),
{WSIPs, WSProxyXFFCN} = lists:unzip(
extract("listener.ws", "proxy_xff_use_cn_as_username", BoolVal, Conf)
),
{WSIPs, WSProxyXFF_CN_HEADER} = lists:unzip(
extract("listener.ws", "proxy_xff_cn_header", StrVal, Conf)
),
{HTTPIPs, HTTPProxyProto} = lists:unzip(
extract("listener.http", "proxy_protocol", BoolVal, Conf)
),
Expand Down Expand Up @@ -369,6 +375,8 @@ translate_listeners(Conf) ->
WSProxyProto,
WSProxyXFF,
WSProxyXFFTrusted,
WSProxyXFFCN,
WSProxyXFF_CN_HEADER,
WSAllowedProto
])
),
Expand Down Expand Up @@ -561,6 +569,8 @@ extract(Prefix, Suffix, Val, Conf) ->
"proxy_protocol",
"proxy_xff_support",
"proxy_xff_trusted_intermediate",
"proxy_xff_use_cn_as_username",
"proxy_xff_cn_header",
"allow_anonymous_override"
],

Expand Down
20 changes: 19 additions & 1 deletion apps/vmq_server/src/vmq_websocket.erl
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,21 @@ init(Req, Opts) ->
_ ->
case proplists:get_value(proxy_protocol_use_cn_as_username, Opts, false) of
false ->
FsmMod:init(Peer, Opts);
% No proxy protocol but we still might have a x-forwarded CN
RequireXFFCN = proplists:get_value(
xff_use_cn_as_username, Opts, false
),
case RequireXFFCN of
false ->
FsmMod:init(Peer, Opts);
true ->
CNHeaderName = proplists:get_value(
xff_cn_header, Opts, <<"x-ssl-client-cn">>
),
HN = ensure_binary(CNHeaderName),
XFFCN = cowboy_req:header(HN, Req),
FsmMod:init(Peer, [{preauth, XFFCN} | Opts])
end;
true ->
case ProxyInfo0 of
error ->
Expand Down Expand Up @@ -257,3 +271,7 @@ select_protocol([Want | Rest], Have) ->

add_socket(Socket, State) ->
State#state{socket = Socket}.

ensure_binary(L) when is_list(L) -> list_to_binary(L);
ensure_binary(L) when is_binary(L) -> L;
ensure_binary(undefined) -> undefined.
27 changes: 26 additions & 1 deletion apps/vmq_server/test/vmq_connect_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ init_per_group(mqttws, Config) ->
init_per_group(mqttwsp, Config) ->
Config1 = [{type, ws},{port, 1891}, {address, "127.0.0.1"}, {proxy_protocol, true}|Config],
start_listener(Config1);
init_per_group(mqttwsx, Config) ->
Config1 = [{type, wsx},{port, 1892}, {address, "127.0.0.1"}, {xff, true}|Config],
start_listener(Config1);
init_per_group(mqttv4, Config) ->
Config1 = [{type, tcp},{port, 1888}, {address, "127.0.0.1"}|Config],
[{protover, 4}|start_listener(Config1)];
Expand Down Expand Up @@ -51,6 +54,7 @@ all() ->
{group, mqtts},
{group, mqttws},
{group, mqttwsp}, % ws with proxy protocol
{group, mqttwsx}, % ws with xff headers
{group, mqttv4},
{group, mqttv5}
].
Expand All @@ -76,6 +80,9 @@ groups() ->
{mqttws, [], [ws_protocols_list_test, ws_no_known_protocols_test] ++ Tests},
{mqttwsp, [], [ws_proxy_protocol_v1_test, ws_proxy_protocol_v2_test,
ws_proxy_protocol_localcommand_v1_test, ws_proxy_protocol_localcommand_v2_test]},
{mqttwsx, [], [ws_xff_peer_test]},
%ws_xff_trusted_intermediate_ok_test, ws_xff_trusted_intermediate_fail_test,
%ws_xff_cn_as_username_test]},
{mqttv5, [auth_on_register_change_username_test, uname_anon_username_test_m5]}
].

Expand Down Expand Up @@ -294,6 +301,15 @@ ws_proxy_protocol_localcommand_v2_test(Config) ->
{ok, Socket} = packet:do_client_connect(Connect, Connack, [{proxy_info, ProxyInfo}|ConnOpts]),
ok = close(Socket, Config).

ws_xff_peer_test(Config) ->
% ct:pal("Config ~p~n", [Config]),
Connect = packet:gen_connect("ws_xff_peer_test", [{keepalive,10}]),
Connack = packet:gen_connack(5),
WSOpt = {conn_opts, [{ws_protocols, ["mqtt"]}]},
ConnOpts = [WSOpt | conn_opts(Config)],
{ok, Socket} = packet:do_client_connect(Connect, Connack, ConnOpts),
ok = close(Socket, Config).

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Hooks
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Expand Down Expand Up @@ -339,7 +355,9 @@ transport(Config) ->
{type, ws} ->
gen_tcp;
{type, wss} ->
ssl
ssl;
{type, wsx} ->
gen_tcp
end.

conn_opts(Config) ->
Expand All @@ -357,6 +375,8 @@ conn_opts(Config) ->
{cacerts, load_cacerts()}
]}];
ws ->
[{transport, vmq_ws_transport}, {conn_opts, []}];
wsx ->
[{transport, vmq_ws_transport}, {conn_opts, []}]
end,
[{port, Port},{hostname, Address},
Expand Down Expand Up @@ -400,6 +420,11 @@ start_listener(Config) ->
[];
ws ->
[{websocket,true}];
wsx -> [{websocket, true},
{proxy_xff_support, true},
{proxy_xff_trusted_intermediate, "127.0.0.1"},
{proxy_xff_use_cn_as_username, true},
{proxy_xff_cn_header, "x-ssl-client-cn"}];
wss -> [{ssl, true},
{nr_of_acceptors, 5},
{cafile, ssl_path("all-ca.crt")},
Expand Down

0 comments on commit 0361b6e

Please sign in to comment.