Skip to content

Commit 88d9e48

Browse files
Support explicit foward proxy from oauth2 plugin
1 parent d8ca61c commit 88d9e48

File tree

66 files changed

+1175
-202
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+1175
-202
lines changed

.github/workflows/test-authnz.yaml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,8 @@ on:
1010
- 'deps/rabbitmq_auth_**'
1111
- 'deps/rabbitmq_management/src/**'
1212
- 'deps/rabbitmq_management/priv/**'
13-
- 'deps/rabbitmq_management/selenium/**'
13+
- 'selenium/**'
1414
- 'scripts/**'
15-
- .bazelrc
16-
- .bazelversion
17-
- BUILD.*
18-
- '*.bzl'
19-
- '*.bazel'
2015
- .github/workflows/test-authnz.yaml
2116
pull_request:
2217
paths:

.github/workflows/test-management-ui.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@ on:
1212
- 'deps/rabbitmq_management/priv/**'
1313
- 'deps/rabbitmq_web_dispatch/src/**'
1414
- 'scripts/**'
15-
- .bazelrc
16-
- .bazelversion
17-
- BUILD.*
18-
- '*.bzl'
19-
- '*.bazel'
2015
- 'selenium/**'
2116
- .github/workflows/test-management-ui.yaml
2217

deps/oauth2_client/include/types.hrl

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,14 @@
2020
}).
2121
-type openid_configuration() :: #openid_configuration{}.
2222

23+
-record(proxy_options, {
24+
host :: string(),
25+
port :: integer(),
26+
username :: option(string() | binary()),
27+
password :: option(string() | binary())
28+
}).
29+
-type proxy_options() :: #proxy_options{}.
30+
2331
-record(oauth_provider, {
2432
id :: oauth_provider_id(),
2533
issuer :: option(uri_string:uri_string()),
@@ -28,7 +36,8 @@
2836
authorization_endpoint :: option(uri_string:uri_string()),
2937
end_session_endpoint :: option(uri_string:uri_string()),
3038
jwks_uri :: option(uri_string:uri_string()),
31-
ssl_options :: option(list())
39+
ssl_options :: option(list()),
40+
proxy_options :: option(proxy_options())
3241
}).
3342

3443
-type query_list() :: [{unicode:chardata(), unicode:chardata() | true}].

deps/oauth2_client/src/oauth2_client.erl

Lines changed: 166 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,18 @@
77
-module(oauth2_client).
88
-export([get_access_token/2, get_expiration_time/1,
99
refresh_access_token/2,
10+
get_jwks/1,
1011
get_oauth_provider/1, get_oauth_provider/2,
11-
get_openid_configuration/2,
12+
get_openid_configuration/1,
1213
build_openid_discovery_endpoint/3,
1314
merge_openid_configuration/2,
1415
merge_oauth_provider/2,
1516
extract_ssl_options_as_list/1,
16-
format_ssl_options/1, format_oauth_provider/1, format_oauth_provider_id/1
17+
map_proxy_auth_to_httpc_option/1,
18+
map_proxy_to_httpc_option/1,
19+
map_ssl_options_to_httpc_option/1,
20+
map_timeout_to_httpc_option/1,
21+
format_ssl_options/1, format_oauth_provider/1, format_oauth_provider_id/1
1722
]).
1823

1924
-include("oauth2_client.hrl").
@@ -25,29 +30,82 @@ get_access_token(OAuthProvider, Request) ->
2530
rabbit_log:debug("get_access_token using OAuthProvider:~p and client_id:~p",
2631
[OAuthProvider, Request#access_token_request.client_id]),
2732
URL = OAuthProvider#oauth_provider.token_endpoint,
33+
Id = OAuthProvider#oauth_provider.id,
2834
Header = [],
2935
Type = ?CONTENT_URLENCODED,
3036
Body = build_access_token_request_body(Request),
31-
HTTPOptions = get_ssl_options_if_any(OAuthProvider) ++
32-
get_timeout_of_default(Request#access_token_request.timeout),
33-
Options = [],
34-
Response = httpc:request(post, {URL, Header, Type, Body}, HTTPOptions, Options),
37+
HTTPOptions =
38+
map_ssl_options_to_httpc_option(OAuthProvider#oauth_provider.ssl_options) ++
39+
map_timeout_to_httpc_option(Request#access_token_request.timeout),
40+
Response = http_post(Id, URL, Header, Type, Body, HTTPOptions,
41+
OAuthProvider#oauth_provider.proxy_options),
3542
parse_access_token_response(Response).
3643

3744
-spec refresh_access_token(oauth_provider(), refresh_token_request()) ->
3845
{ok, successful_access_token_response()} |
3946
{error, unsuccessful_access_token_response() | any()}.
4047
refresh_access_token(OAuthProvider, Request) ->
48+
Id = OAuthProvider#oauth_provider.id,
4149
URL = OAuthProvider#oauth_provider.token_endpoint,
4250
Header = [],
4351
Type = ?CONTENT_URLENCODED,
4452
Body = build_refresh_token_request_body(Request),
45-
HTTPOptions = get_ssl_options_if_any(OAuthProvider) ++
46-
get_timeout_of_default(Request#refresh_token_request.timeout),
47-
Options = [],
48-
Response = httpc:request(post, {URL, Header, Type, Body}, HTTPOptions, Options),
53+
HTTPOptions =
54+
map_ssl_options_to_httpc_option(OAuthProvider#oauth_provider.ssl_options) ++
55+
map_timeout_to_httpc_option(Request#refresh_token_request.timeout),
56+
Response = http_post(Id, URL, Header, Type, Body, HTTPOptions,
57+
OAuthProvider#oauth_provider.proxy_options),
4958
parse_access_token_response(Response).
5059

60+
ensure_http_client_started(Id, ProxyOptions) ->
61+
Profile = case Id of
62+
root -> root;
63+
_ -> binary_to_atom(Id)
64+
end,
65+
rabbit_log:debug("Starting http client ~p ...", [Profile]),
66+
case inets:start(httpc, [{profile, Profile}]) of
67+
{ok, _} ->
68+
HttpProxyOptions = map_proxy_to_httpc_option(ProxyOptions),
69+
case ensure_http_proxy_options_if_any(Profile, HttpProxyOptions) of
70+
ok -> {ok, Profile};
71+
{error, _} = Error -> Error
72+
end;
73+
{error, {already_started, _}} ->
74+
{ok, Profile};
75+
Error ->
76+
rabbit_log:error("Failed to start httpc client: ~p", [Error]),
77+
Error
78+
end.
79+
ensure_http_proxy_options_if_any(_Profile, []) ->
80+
ok;
81+
ensure_http_proxy_options_if_any(Profile, HttpProxyOptions) ->
82+
case httpc:set_options(HttpProxyOptions, Profile) of
83+
ok ->
84+
rabbit_log:debug("Successfully set_options ~p on http client ~p",
85+
[HttpProxyOptions, Profile]),
86+
ok;
87+
{error, _} = Error -> Error
88+
end.
89+
90+
http_post(Id, URL, Header, Type, Body, HTTPOptions, ProxyOptions) ->
91+
http_request(Id, post, {URL, Header, Type, Body}, HTTPOptions, ProxyOptions).
92+
http_get(Id, URL, HTTPOptions, ProxyOptions) ->
93+
http_request(Id, get, {URL, []}, HTTPOptions, ProxyOptions).
94+
http_request(Id, Method, Payload, HTTPOptions, ProxyOptions) ->
95+
case ensure_http_client_started(Id, ProxyOptions) of
96+
{ok, Profile} ->
97+
case ProxyOptions of
98+
undefined ->
99+
httpc:request(Method, Payload, HTTPOptions, [], Profile);
100+
_ ->
101+
httpc:request(Method, Payload,
102+
HTTPOptions ++ map_proxy_auth_to_httpc_option(ProxyOptions),
103+
[],
104+
Profile)
105+
end;
106+
{error, _} = Error -> Error
107+
end.
108+
51109
append_paths(Path1, Path2) ->
52110
erlang:iolist_to_binary([Path1, Path2]).
53111

@@ -93,14 +151,27 @@ drop_trailing_path_separator(Path) when is_list(Path) ->
93151
_ -> Path
94152
end.
95153

96-
-spec get_openid_configuration(DiscoveryEndpoint :: uri_string:uri_string(),
97-
ssl:tls_option() | []) -> {ok, openid_configuration()} | {error, term()}.
98-
get_openid_configuration(DiscoverEndpoint, TLSOptions) ->
99-
rabbit_log:debug("get_openid_configuration from ~p (~p)", [DiscoverEndpoint,
100-
format_ssl_options(TLSOptions)]),
101-
Options = [],
102-
Response = httpc:request(get, {DiscoverEndpoint, []}, TLSOptions, Options),
154+
-spec get_openid_configuration(oauth_provider()) -> {ok, openid_configuration()} | {error, term()}.
155+
get_openid_configuration(#oauth_provider{id = Id, discovery_endpoint = Endpoint,
156+
ssl_options = SslOptions, proxy_options = ProxyOptions}) ->
157+
rabbit_log:debug("get_openid_configuration from ~p (~p) [~p]", [Endpoint,
158+
format_ssl_options(SslOptions), format_proxy_options(ProxyOptions)]),
159+
HTTPOptions =
160+
map_ssl_options_to_httpc_option(SslOptions) ++
161+
map_timeout_to_httpc_option(?DEFAULT_HTTP_TIMEOUT),
162+
163+
Response = http_get(Id, Endpoint, HTTPOptions, ProxyOptions),
103164
parse_openid_configuration_response(Response).
165+
166+
-spec get_jwks(oauth_provider()) -> {ok, term()} | {error, term()}.
167+
get_jwks(#oauth_provider{id = Id, jwks_uri = JwksUrl,
168+
ssl_options = SslOptions, proxy_options = ProxyOptions}) ->
169+
rabbit_log:debug("get_jwks from ~p (~p) [~p]", [JwksUrl,
170+
format_ssl_options(SslOptions), format_proxy_options(ProxyOptions)]),
171+
HTTPOptions =
172+
map_ssl_options_to_httpc_option(SslOptions) ++
173+
map_timeout_to_httpc_option(?DEFAULT_HTTP_TIMEOUT),
174+
http_get(Id, JwksUrl, HTTPOptions, ProxyOptions).
104175

105176
-spec merge_openid_configuration(openid_configuration(), oauth_provider()) ->
106177
oauth_provider().
@@ -283,7 +354,7 @@ download_oauth_provider(OAuthProvider) ->
283354
undefined -> {error, {missing_oauth_provider_attributes, [issuer]}};
284355
URL ->
285356
rabbit_log:debug("Downloading oauth_provider using ~p ", [URL]),
286-
case get_openid_configuration(URL, get_ssl_options_if_any(OAuthProvider)) of
357+
case get_openid_configuration(OAuthProvider) of
287358
{ok, OpenIdConfiguration} ->
288359
{ok, update_oauth_provider_endpoints_configuration(
289360
merge_openid_configuration(OpenIdConfiguration, OAuthProvider))};
@@ -341,7 +412,6 @@ get_oauth_provider(OAuthProviderId, ListOfRequiredAttributes)
341412
[OAuthProviderId, Error0]),
342413
Error0;
343414
Config ->
344-
rabbit_log:debug("Found oauth_provider configuration ~p", [Config]),
345415
OAuthProvider = map_to_oauth_provider(Config),
346416
rabbit_log:debug("Resolved oauth_provider ~p", [format_oauth_provider(OAuthProvider)]),
347417
case find_missing_attributes(OAuthProvider, ListOfRequiredAttributes) of
@@ -395,8 +465,36 @@ lookup_root_oauth_provider() ->
395465
token_endpoint = get_env(token_endpoint),
396466
authorization_endpoint = get_env(authorization_endpoint),
397467
end_session_endpoint = get_env(end_session_endpoint),
398-
ssl_options = extract_ssl_options_as_list(Map)
468+
ssl_options = extract_ssl_options_as_list(Map),
469+
proxy_options = extract_proxy_options(get_env(proxy, []))
399470
}.
471+
472+
-spec extract_proxy_options(#{atom() => any()}|list()) -> proxy_options() | undefined.
473+
extract_proxy_options(List) when is_list(List) ->
474+
case {proplists:get_value(host, List, undefined),
475+
proplists:get_value(port, List, 0)} of
476+
{undefined, _} -> undefined;
477+
{_, 0} -> undefined;
478+
{H, P} ->
479+
#proxy_options{
480+
host = H,
481+
port = P,
482+
username = proplists:get_value(username, List, undefined),
483+
password = proplists:get_value(password, List, undefined)
484+
}
485+
end;
486+
extract_proxy_options(Map) ->
487+
case {maps:get(host, Map, undefined), maps:get(port, Map, 0)} of
488+
{undefined, _} -> undefined;
489+
{_, 0} -> undefined;
490+
{H, P} ->
491+
#proxy_options{
492+
host = H,
493+
port = P,
494+
username = maps:get(username, Map, undefined),
495+
password = maps:get(password, Map, undefined)
496+
}
497+
end.
400498

401499
-spec extract_ssl_options_as_list(#{atom() => any()}) -> proplists:proplist().
402500
extract_ssl_options_as_list(Map) ->
@@ -522,15 +620,36 @@ append_extra_parameters(Request, QueryList) ->
522620
Params -> Params ++ QueryList
523621
end.
524622

525-
get_ssl_options_if_any(OAuthProvider) ->
526-
case OAuthProvider#oauth_provider.ssl_options of
623+
map_ssl_options_to_httpc_option(SslOptions) ->
624+
case SslOptions of
527625
undefined -> [];
528-
Options -> [{ssl, Options}]
626+
Options -> [{ssl, Options}]
529627
end.
530-
get_timeout_of_default(Timeout) ->
628+
629+
map_timeout_to_httpc_option(Timeout) ->
531630
case Timeout of
532631
undefined -> [{timeout, ?DEFAULT_HTTP_TIMEOUT}];
533-
Timeout -> [{timeout, Timeout}]
632+
Timeout -> [{timeout, Timeout}]
633+
end.
634+
635+
map_proxy_to_httpc_option(ProxyOptions) ->
636+
case ProxyOptions of
637+
undefined -> [];
638+
Proxy -> case {Proxy#proxy_options.host, Proxy#proxy_options.port} of
639+
{_, 0} -> [];
640+
{Host, Port} -> P = {{Host, Port},[]},
641+
[{proxy, P}]
642+
end
643+
end.
644+
645+
map_proxy_auth_to_httpc_option(ProxyOptions) ->
646+
case ProxyOptions of
647+
undefined -> [];
648+
Proxy -> case {Proxy#proxy_options.username, Proxy#proxy_options.password} of
649+
{undefined, _} -> [];
650+
{_, undefined} -> [];
651+
{_, _} = Auth -> [{proxy_auth, Auth}]
652+
end
534653
end.
535654

536655
is_json(?CONTENT_JSON) -> true;
@@ -543,10 +662,8 @@ is_json(_) -> false.
543662
decode_body(_, []) -> [];
544663
decode_body(?CONTENT_JSON, Body) ->
545664
case rabbit_json:try_decode(rabbit_data_coercion:to_binary(Body)) of
546-
{ok, Value} ->
547-
Value;
548-
{error, _} = Error ->
549-
Error
665+
{ok, Value} -> Value;
666+
{error, _} = Error -> Error
550667
end;
551668
decode_body(MimeType, Body) ->
552669
Items = string:split(MimeType, ";"),
@@ -588,14 +705,14 @@ map_to_oauth_provider(PropList) when is_list(PropList) ->
588705
proplists:get_value(jwks_uri, PropList, undefined),
589706
ssl_options =
590707
extract_ssl_options_as_list(maps:from_list(
591-
proplists:get_value(https, PropList, [])))
708+
proplists:get_value(https, PropList, []))),
709+
proxy_options =
710+
extract_proxy_options(proplists:get_value(proxy, PropList, []))
592711
}.
593712
map_to_access_token_response(Code, Reason, Headers, Body) ->
594713
case decode_body(proplists:get_value("content-type", Headers, ?CONTENT_JSON), Body) of
595-
{error, {error, InternalError}} ->
596-
{error, InternalError};
597-
{error, _} = Error ->
598-
Error;
714+
{error, {error, InternalError}} -> {error, InternalError};
715+
{error, _} = Error -> Error;
599716
Value ->
600717
case Code of
601718
200 -> {ok, map_to_successful_access_token_response(Value)};
@@ -626,6 +743,18 @@ format_ssl_options(TlsOptions) ->
626743
proplists:get_value(cacertfile, TlsOptions),
627744
CaCertsCount])).
628745

746+
-spec format_proxy_options(proxy_options()|undefined) -> string().
747+
format_proxy_options(undefined) ->
748+
lists:flatten(io_lib:format("{no proxy}", []));
749+
750+
format_proxy_options(ProxyOptions) ->
751+
lists:flatten(io_lib:format("{host: ~p, port: ~p, username: ~p, " ++
752+
"password: ~p }", [
753+
ProxyOptions#proxy_options.host,
754+
ProxyOptions#proxy_options.port,
755+
ProxyOptions#proxy_options.username,
756+
ProxyOptions#proxy_options.password])).
757+
629758
format_oauth_provider_id(root) -> "<from keyconfig>";
630759
format_oauth_provider_id(Id) -> binary_to_list(Id).
631760

@@ -634,15 +763,16 @@ format_oauth_provider(OAuthProvider) ->
634763
lists:flatten(io_lib:format("{id: ~p, issuer: ~p, discovery_endpoint: ~p, " ++
635764
" token_endpoint: ~p, " ++
636765
"authorization_endpoint: ~p, end_session_endpoint: ~p, " ++
637-
"jwks_uri: ~p, ssl_options: ~p }", [
766+
"jwks_uri: ~p, ssl_options: ~p, proxy_options: ~p}", [
638767
format_oauth_provider_id(OAuthProvider#oauth_provider.id),
639768
OAuthProvider#oauth_provider.issuer,
640769
OAuthProvider#oauth_provider.discovery_endpoint,
641770
OAuthProvider#oauth_provider.token_endpoint,
642771
OAuthProvider#oauth_provider.authorization_endpoint,
643772
OAuthProvider#oauth_provider.end_session_endpoint,
644773
OAuthProvider#oauth_provider.jwks_uri,
645-
format_ssl_options(OAuthProvider#oauth_provider.ssl_options)])).
774+
format_ssl_options(OAuthProvider#oauth_provider.ssl_options),
775+
format_proxy_options(OAuthProvider#oauth_provider.proxy_options)])).
646776

647777
get_env(Par) ->
648778
application:get_env(rabbitmq_auth_backend_oauth2, Par, undefined).

0 commit comments

Comments
 (0)