Skip to content

Commit 2fc8d2b

Browse files
Propagate all credentials to http backend
1 parent f39570c commit 2fc8d2b

21 files changed

+352
-93
lines changed

deps/rabbitmq_auth_backend_http/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@ against the URIs listed in the configuration file. It will add query string
8484
* `username`: the name of the user
8585
* `password`: the password provided (may be missing if e.g. rabbitmq-auth-mechanism-ssl is used)
8686

87+
Note: This plugin may include additional http request parameters in addition to the ones listed above.
88+
For instance, if the user accessed RabbitMQ via the MQTT protocol, it is expected `client_id` and `vhost` request parameters too.
89+
8790
### vhost_path
8891

8992
* `username`: the name of the user

deps/rabbitmq_auth_backend_http/src/rabbit_auth_backend_http.erl

Lines changed: 27 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -34,33 +34,45 @@ description() ->
3434

3535
user_login_authentication(Username, AuthProps) ->
3636

37-
case http_req(p(user_path), q([{username, Username}|extractPassword(AuthProps)])) of
37+
case http_req(p(user_path), q([{username, Username}]++extractOtherCredentials(AuthProps))) of
3838
{error, _} = E -> E;
3939
"deny" -> {refused, "Denied by the backing HTTP service", []};
4040
"allow" ++ Rest -> Tags = [rabbit_data_coercion:to_atom(T) ||
4141
T <- string:tokens(Rest, " ")],
4242

4343
{ok, #auth_user{username = Username,
4444
tags = Tags,
45-
impl = fun() -> proplists:get_value(password, AuthProps, none) end}};
45+
impl = fun() -> proplists:delete(username, AuthProps) end}};
4646
Other -> {error, {bad_response, Other}}
4747
end.
4848

49-
%% Credentials (i.e. password) maybe directly in the password attribute in AuthProps
49+
%% Credentials (e.g. password) maybe directly in the password attribute in AuthProps
5050
%% or as a Function with the attribute rabbit_auth_backend_http if the user was already authenticated with http backend
5151
%% or as a Function with the attribute rabbit_auth_backend_cache if the user was already authenticated via cache backend
52-
extractPassword(AuthProps) ->
53-
case proplists:get_value(password, AuthProps, none) of
54-
none ->
55-
case proplists:get_value(rabbit_auth_backend_http, AuthProps, none) of
56-
none -> case proplists:get_value(rabbit_auth_backend_cache, AuthProps, none) of
57-
none -> [];
58-
PasswordFun -> [{password, PasswordFun()}]
59-
end;
60-
PasswordFun -> [{password, PasswordFun()}]
61-
end;
62-
Password -> [{password, Password}]
63-
end.
52+
resolveUsingPersistedCredentials(AuthProps) ->
53+
case proplists:get_value(rabbit_auth_backend_http, AuthProps, none) of
54+
none -> case proplists:get_value(rabbit_auth_backend_cache, AuthProps, none) of
55+
none -> AuthProps;
56+
CacheAuthPropsFun -> AuthProps ++ CacheAuthPropsFun()
57+
end;
58+
HttpAuthPropsFun -> AuthProps ++ HttpAuthPropsFun()
59+
end.
60+
61+
62+
%% Some protocols may add additional credentials into the AuthProps that should be propagated to
63+
%% the external authentication backends
64+
%% This function excludes any attribute that starts with rabbit_auth_backend_
65+
is_internal_property(rabbit_auth_backend_http) -> true;
66+
is_internal_property(rabbit_auth_backend_cache) -> true;
67+
is_internal_property(_Other) -> false.
68+
69+
extractOtherCredentials(AuthProps) ->
70+
PublicAuthProps = [{K,V} || {K,V} <-AuthProps, not is_internal_property(K)],
71+
case PublicAuthProps of
72+
[] -> resolveUsingPersistedCredentials(AuthProps);
73+
_ -> PublicAuthProps
74+
end.
75+
6476

6577
user_login_authorization(Username, AuthProps) ->
6678
case user_login_authentication(Username, AuthProps) of

deps/rabbitmq_auth_backend_http/test/auth_SUITE.erl

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,33 +20,86 @@
2020
{vhost_path, "http://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/vhost"},
2121
{resource_path, "http://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/resource"},
2222
{topic_path, "http://localhost:" ++ integer_to_list(?AUTH_PORT) ++ "/auth/topic"}]).
23-
-define(ALLOWED_USER, #{username => <<"Ala">>,
23+
-define(ALLOWED_USER, #{username => <<"Ala1">>,
2424
password => <<"Kocur">>,
25+
expected_credentials => [username, password],
2526
tags => [policymaker, monitoring]}).
26-
-define(DENIED_USER, #{username => <<"Alice">>, password => <<"Cat">>}).
27+
-define(ALLOWED_USER_WITH_EXTRA_CREDENTIALS, #{username => <<"Ala2">>,
28+
password => <<"Kocur">>,
29+
client_id => <<"some_id">>,
30+
expected_credentials => [username, password, client_id],
31+
tags => [policymaker, monitoring]}).
32+
-define(DENIED_USER, #{username => <<"Alice">>,
33+
password => <<"Cat">>
34+
}).
2735

28-
all() -> [grants_access_to_user, denies_access_to_user].
36+
all() -> [grants_access_to_user,
37+
denies_access_to_user,
38+
grants_access_to_user_passing_additional_required_authprops,
39+
grants_access_to_user_skipping_internal_authprops,
40+
grants_access_to_user_with_credentials_in_rabbit_auth_backend_http,
41+
grants_access_to_user_with_credentials_in_rabbit_auth_backend_cache].
2942

3043
init_per_suite(Config) ->
3144
configure_http_auth_backend(),
32-
#{username := Username, password := Password, tags := Tags} = ?ALLOWED_USER,
33-
start_http_auth_server(?AUTH_PORT, ?USER_PATH, #{Username => {Password, Tags}}),
34-
[{allowed_user, ?ALLOWED_USER}, {denied_user, ?DENIED_USER} | Config].
45+
{User1, Tuple1} = extractUserTuple(?ALLOWED_USER),
46+
{User2, Tuple2} = extractUserTuple(?ALLOWED_USER_WITH_EXTRA_CREDENTIALS),
47+
start_http_auth_server(?AUTH_PORT, ?USER_PATH, #{User1 => Tuple1, User2 => Tuple2}),
48+
[{allowed_user, ?ALLOWED_USER},
49+
{allowed_user_with_extra_credentials, ?ALLOWED_USER_WITH_EXTRA_CREDENTIALS},
50+
{denied_user, ?DENIED_USER} | Config].
51+
extractUserTuple(User) ->
52+
#{username := Username, password := Password, tags := Tags, expected_credentials := ExpectedCredentials} = User,
53+
{Username, {Password, Tags, ExpectedCredentials}}.
3554

3655
end_per_suite(_Config) ->
3756
stop_http_auth_server().
3857

3958
grants_access_to_user(Config) ->
4059
#{username := U, password := P, tags := T} = ?config(allowed_user, Config),
41-
{ok, User} = rabbit_auth_backend_http:user_login_authentication(U, [{password, P}]),
42-
?assertMatch({U, T, P},
60+
AuthProps = [{password, P}],
61+
{ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps),
62+
63+
?assertMatch({U, T, AuthProps},
4364
{User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}).
4465

4566
denies_access_to_user(Config) ->
4667
#{username := U, password := P} = ?config(denied_user, Config),
4768
?assertMatch({refused, "Denied by the backing HTTP service", []},
4869
rabbit_auth_backend_http:user_login_authentication(U, [{password, P}])).
4970

71+
72+
grants_access_to_user_passing_additional_required_authprops(Config) ->
73+
#{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config),
74+
AuthProps = [{password, P}, {client_id, ClientId}],
75+
{ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps),
76+
?assertMatch({U, T, AuthProps},
77+
{User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}).
78+
79+
grants_access_to_user_skipping_internal_authprops(Config) ->
80+
#{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config),
81+
AuthProps = [{password, P}, {client_id, ClientId}, {rabbit_any_internal_property, <<"some value">>}],
82+
{ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps),
83+
84+
?assertMatch({U, T, AuthProps},
85+
{User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}).
86+
87+
grants_access_to_user_with_credentials_in_rabbit_auth_backend_http(Config) ->
88+
#{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config),
89+
AuthProps = [{rabbit_auth_backend_http, fun() -> [{password, P}, {client_id, ClientId}] end}],
90+
{ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps),
91+
92+
?assertMatch({U, T, AuthProps},
93+
{User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}).
94+
95+
grants_access_to_user_with_credentials_in_rabbit_auth_backend_cache(Config) ->
96+
#{username := U, password := P, tags := T, client_id := ClientId} = ?config(allowed_user_with_extra_credentials, Config),
97+
AuthProps = [{rabbit_auth_backend_cache, fun() -> [{password, P}, {client_id, ClientId}] end}],
98+
{ok, User} = rabbit_auth_backend_http:user_login_authentication(U, AuthProps),
99+
100+
?assertMatch({U, T, AuthProps},
101+
{User#auth_user.username, User#auth_user.tags, (User#auth_user.impl)()}).
102+
50103
%%% HELPERS
51104

52105
configure_http_auth_backend() ->

deps/rabbitmq_auth_backend_http/test/auth_http_mock.erl

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,21 +6,25 @@
66

77
init(Req = #{method := <<"GET">>}, Users) ->
88
QsVals = cowboy_req:parse_qs(Req),
9-
Reply = authenticate(proplists:get_value(<<"username">>, QsVals),
10-
proplists:get_value(<<"password">>, QsVals),
11-
Users),
9+
Reply = authenticate(QsVals, Users),
1210
Req2 = cowboy_req:reply(200, #{<<"content-type">> => <<"text/plain">>}, Reply, Req),
1311
{ok, Req2, Users}.
1412

1513
%%% HELPERS
1614

17-
authenticate(Username, Password, Users) ->
15+
authenticate(QsVals, Users) ->
16+
Username = proplists:get_value(<<"username">>, QsVals),
17+
Password = proplists:get_value(<<"password">>, QsVals),
1818
case maps:get(Username, Users, undefined) of
19-
{MatchingPassword, Tags} when Password =:= MatchingPassword ->
20-
StringTags = lists:map(fun(T) -> io_lib:format("~ts", [T]) end, Tags),
21-
<<"allow ", (list_to_binary(string:join(StringTags, " ")))/binary>>;
22-
{_OtherPassword, _} ->
19+
{MatchingPassword, Tags, ExpectedCredentials} when Password =:= MatchingPassword ->
20+
case lists:all(fun(C) -> proplists:is_defined(list_to_binary(rabbit_data_coercion:to_list(C)),QsVals) end, ExpectedCredentials) of
21+
true -> StringTags = lists:map(fun(T) -> io_lib:format("~ts", [T]) end, Tags),
22+
<<"allow ", (list_to_binary(string:join(StringTags, " ")))/binary>>;
23+
false -> ct:log("Missing required attributes. Expected ~p, Found: ~p", [ExpectedCredentials, QsVals]),
24+
<<"deny">>
25+
end;
26+
{_OtherPassword, _, _} ->
2327
<<"deny">>;
2428
undefined ->
2529
<<"deny">>
26-
end.
30+
end.

deps/rabbitmq_management/selenium/bin/suite_template

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -429,9 +429,11 @@ start_fakeproxy() {
429429
init_mock-auth-backend-http() {
430430
AUTH_BACKEND_HTTP_BASEURL=${AUTH_BACKEND_HTTP_BASEURL:-http://localhost:8888}
431431
AUTH_BACKEND_HTTP_DIR=${TEST_CASES_DIR}/mock-auth-backend-http
432+
AUTH_BACKEND_HTTP_EXPECTATIONS=defaultExpectations.json
432433

433434
print "> AUTH_BACKEND_HTTP_BASEURL: ${AUTH_BACKEND_HTTP_BASEURL}"
434435
print "> AUTH_BACKEND_HTTP_DIR: ${AUTH_BACKEND_HTTP_DIR}"
436+
print "> AUTH_BACKEND_HTTP_EXPECTATIONS: ${AUTH_BACKEND_HTTP_EXPECTATIONS}"
435437

436438
}
437439
start_mock-auth-backend-http() {
@@ -440,16 +442,18 @@ start_mock-auth-backend-http() {
440442
init_mock-auth-backend-http
441443
kill_container_if_exist mock-auth-backend-http
442444

445+
# --env MOCKSERVER_INITIALIZATION_JSON_PATH="/config/$AUTH_BACKEND_HTTP_EXPECTATIONS" \
446+
443447
docker run \
444448
--detach \
445449
--name mock-auth-backend-http \
446450
--net ${DOCKER_NETWORK} \
447451
--publish 8888:1080 \
448-
--env MOCKSERVER_INITIALIZATION_JSON_PATH="/config/expectationInitialiser.json" \
449452
-v ${AUTH_BACKEND_HTTP_DIR}:/config \
450453
mockserver/mockserver
451454

452-
wait_for_url $AUTH_BACKEND_HTTP_BASEURL/ready
455+
#wait_for_url $AUTH_BACKEND_HTTP_BASEURL/ready
456+
wait_for_message mock-auth-backend-http "started on port"
453457
end "mock-auth-backend-http is ready"
454458
}
455459

deps/rabbitmq_management/selenium/full-suite-authnz

Lines changed: 0 additions & 8 deletions
This file was deleted.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
authnz-messaging/auth-cache-http-backends.sh
2+
authnz-messaging/auth-cache-ldap-backends.sh
3+
authnz-messaging/auth-http-backend.sh
4+
authnz-messaging/auth-http-internal-backends-with-internal.sh
5+
authnz-messaging/auth-http-internal-backends.sh
6+
authnz-messaging/auth-internal-backend.sh
7+
authnz-messaging/auth-internal-http-backends.sh
8+
authnz-messaging/auth-ldap-backend.sh
9+
authnz-messaging/auth-http-backend.sh

deps/rabbitmq_management/selenium/package.json

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,10 @@
44
"description": "",
55
"main": "index.js",
66
"scripts": {
7-
"test": "mocha --recursive --trace-warnings --timeout 40000",
87
"fakeportal": "node fakeportal/app.js",
98
"fakeproxy": "node fakeportal/proxy.js",
10-
"amqp10_roundtriptest" : "export $(cat $ENV_FILE | xargs)&& ./run-amqp10-roundtriptest"
9+
"amqp10_roundtriptest": "eval $(cat $ENV_FILE ) &&./run-amqp10-roundtriptest",
10+
"test": " eval $(cat $ENV_FILE ) && mocha --recursive --trace-warnings --timeout 40000"
1111
},
1212
"keywords": [],
1313
"author": "",
@@ -18,6 +18,7 @@
1818
"express": "^4.18.2",
1919
"geckodriver": "^3.0.2",
2020
"http-proxy": "^1.18.1",
21+
"mqtt": "^5.3.3",
2122
"path": "^0.12.7",
2223
"proxy": "^1.0.2",
2324
"selenium-webdriver": "^4.4.0",
Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const assert = require('assert')
22
const { getURLForProtocol } = require('../utils')
3+
const { reset, expectUser, expectVhost, expectResource, allow, verifyAll } = require('../mock_http_backend')
34
const {execSync} = require('child_process')
45

56
const profiles = process.env.PROFILES || ""
@@ -10,17 +11,28 @@ for (const element of profiles.split(" ")) {
1011
}
1112
}
1213

13-
describe('Having the following auth_backends enabled: ' + backends, function () {
14+
describe('Having AMQP 1.0 protocol enabled and the following auth_backends: ' + backends, function () {
15+
let expectations = []
1416

1517
before(function () {
16-
18+
if ( backends.includes("http") ) {
19+
reset()
20+
expectations.push(expectUser({ "username": "httpuser", "password": "httppassword" }, "allow"))
21+
expectations.push(expectVhost({ "username": "httpuser", "vhost": "/"}, "allow"))
22+
expectations.push(expectResource({ "username": "httpuser", "vhost": "/", "resource": "queue", "name": "my-queue", "permission":"configure", "tags":""}, "allow"))
23+
expectations.push(expectResource({ "username": "httpuser", "vhost": "/", "resource": "queue", "name": "my-queue", "permission":"read", "tags":""}, "allow"))
24+
expectations.push(expectResource({ "username": "httpuser", "vhost": "/", "resource": "exchange", "name": "amq.default", "permission":"write", "tags":""}, "allow"))
25+
}
1726
})
1827

1928
it('can open an AMQP 1.0 connection', function () {
2029
execSync("npm run amqp10_roundtriptest")
30+
2131
})
2232

2333
after(function () {
24-
34+
if ( backends.includes("http") ) {
35+
verifyAll(expectations)
36+
}
2537
})
2638
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[
2+
{
3+
"httpRequest": {
4+
"path": "/ready"
5+
},
6+
"httpResponse": {
7+
"body": "ok"
8+
}
9+
}
10+
]

deps/rabbitmq_management/selenium/test/authnz-msg-protocols/mock-auth-backend-http/expectationInitialiser.json

Lines changed: 0 additions & 46 deletions
This file was deleted.

0 commit comments

Comments
 (0)