Skip to content

Oauth2: Support variable expansion when checking resource access #13941

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 4 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test-authnz.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ jobs:
docker build -t mocha-test --target test .

- name: Run Suites
id: run-suites
id: tests
run: |
IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}')
CONF_DIR_PREFIX="$(mktemp -d)" RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \
Expand All @@ -83,7 +83,7 @@ jobs:
if: always()
uses: actions/[email protected]
env:
SELENIUM_ARTIFACTS: ${{ steps.run-suites.outputs.SELENIUM_ARTIFACTS }}
SELENIUM_ARTIFACTS: ${{ steps.tests.outputs.SELENIUM_ARTIFACTS }}
with:
name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }}
path: |
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/test-management-ui-for-pr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ jobs:
docker build -t mocha-test --target test .

- name: Run short UI suites on a standalone rabbitmq server
id: tests
run: |
IMAGE_TAG=$(find PACKAGES/rabbitmq-server-generic-unix-*.tar.xz | awk -F 'PACKAGES/rabbitmq-server-generic-unix-|.tar.xz' '{print $2}')
CONF_DIR_PREFIX="$(mktemp -d)" RABBITMQ_DOCKER_IMAGE=pivotalrabbitmq/rabbitmq:$IMAGE_TAG \
Expand All @@ -67,7 +68,7 @@ jobs:
if: ${{ failure() && steps.tests.outcome == 'failed' }}
uses: actions/upload-artifact@v4
env:
SELENIUM_ARTIFACTS: ${{ steps.run-suites.outputs.SELENIUM_ARTIFACTS }}
SELENIUM_ARTIFACTS: ${{ steps.tests.outputs.SELENIUM_ARTIFACTS }}
with:
name: test-artifacts-${{ matrix.browser }}-${{ matrix.erlang_version }}
path: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,19 +87,16 @@ user_login_authorization(Username, AuthProps) ->
check_vhost_access(#auth_user{impl = DecodedTokenFun},
VHost, _AuthzData) ->
with_decoded_token(DecodedTokenFun(),
fun(_Token) ->
DecodedToken = DecodedTokenFun(),
Scopes = get_scope(DecodedToken),
ScopeString = rabbit_oauth2_scope:concat_scopes(Scopes, ","),
rabbit_log:debug("Matching virtual host '~ts' against the following scopes: ~ts", [VHost, ScopeString]),
fun(Token) ->
Scopes = get_expanded_scopes(Token, #resource{virtual_host = VHost}),
rabbit_oauth2_scope:vhost_access(VHost, Scopes)
end).

check_resource_access(#auth_user{impl = DecodedTokenFun},
Resource, Permission, _AuthzContext) ->
with_decoded_token(DecodedTokenFun(),
fun(Token) ->
Scopes = get_scope(Token),
Scopes = get_expanded_scopes(Token, Resource),
rabbit_oauth2_scope:resource_access(Resource, Permission, Scopes)
end).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ resource_access(#resource{virtual_host = VHost, name = Name},
end,
get_scope_permissions(Scopes)).

-spec topic_access(rabbit_types:r(atom()), permission(), map(), [binary()]) -> boolean().
topic_access(#resource{virtual_host = VHost, name = ExchangeName},
Permission,
#{routing_key := RoutingKey},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,8 @@ fixture_token() ->

token_with_sub(TokenFixture, Sub) ->
maps:put(<<"sub">>, Sub, TokenFixture).
token_with_claim(TokenFixture, Name, Value) ->
maps:put(Name, Value, TokenFixture).
token_with_scopes(TokenFixture, Scopes) ->
maps:put(<<"scope">>, Scopes, TokenFixture).

Expand Down
30 changes: 29 additions & 1 deletion deps/rabbitmq_auth_backend_oauth2/test/system_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,15 @@ groups() ->
test_successful_connection_with_a_full_permission_token_and_all_defaults,
test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost,
test_successful_connection_with_simple_strings_for_aud_and_scope,
test_successful_connection_with_variable_expansion_on_queue_access,
test_successful_token_refresh,
test_successful_connection_without_verify_aud,
mqtt
]},
{basic_unhappy_path, [], [
test_failed_connection_with_expired_token,
test_failed_connection_with_a_non_token,
test_failed_connection_with_a_token_with_variable_expansion,
test_failed_connection_with_a_token_with_insufficient_vhost_permission,
test_failed_connection_with_a_token_with_insufficient_resource_permission,
more_than_one_resource_server_id_not_allowed_in_one_token,
Expand Down Expand Up @@ -134,7 +136,8 @@ end_per_group(_Group, Config) ->
%%

init_per_testcase(Testcase, Config) when Testcase =:= test_successful_connection_with_a_full_permission_token_and_explicitly_configured_vhost orelse
Testcase =:= test_successful_token_refresh ->
Testcase =:= test_successful_token_refresh orelse
Testcase =:= test_successful_connection_with_variable_expansion_on_queue_access ->
rabbit_ct_broker_helpers:add_vhost(Config, <<"vhost1">>),
rabbit_ct_helpers:testcase_started(Config, Testcase),
Config;
Expand Down Expand Up @@ -420,6 +423,19 @@ test_successful_connection_with_simple_strings_for_aud_and_scope(Config) ->
amqp_channel:call(Ch, #'queue.declare'{exclusive = true}),
close_connection_and_channel(Conn, Ch).

test_successful_connection_with_variable_expansion_on_queue_access(Config) ->
{_Algo, Token} = generate_valid_token(
Config,
<<"rabbitmq.configure:*/{vhost}-{sub}-* rabbitmq.write:*/* rabbitmq.read:*/*">>,
[<<"hare">>, <<"rabbitmq">>],
<<"Bob">>
),
Conn = open_unmanaged_connection(Config, 0, <<"vhost1">>, <<"Bob">>, Token),
{ok, Ch} = amqp_connection:open_channel(Conn),
#'queue.declare_ok'{} =
amqp_channel:call(Ch, #'queue.declare'{queue = <<"vhost1-Bob-1">>, exclusive = true}),
close_connection_and_channel(Conn, Ch).

test_successful_connection_without_verify_aud(Config) ->
{_Algo, Token} = generate_valid_token(
Config,
Expand Down Expand Up @@ -895,6 +911,18 @@ test_failed_connection_with_a_token_with_insufficient_vhost_permission(Config) -
?assertEqual({error, not_allowed},
open_unmanaged_connection(Config, 0, <<"off-limits-vhost">>, <<"username">>, Token)).

test_failed_connection_with_a_token_with_variable_expansion(Config) ->
{_Algo, Token} = generate_valid_token(
Config,
<<"rabbitmq.configure:*/{vhost}-{sub}-* rabbitmq.write:*/* rabbitmq.read:*/*">>,
[<<"hare">>, <<"rabbitmq">>]
),
Conn = open_unmanaged_connection(Config, 0, <<"vhost2">>, <<"username">>, Token),
{ok, Ch} = amqp_connection:open_channel(Conn),
?assertExit({{shutdown, {server_initiated_close, 403, _}}, _},
amqp_channel:call(Ch, #'queue.declare'{queue = <<"vhost1-username-3">>, exclusive = true})),
close_connection(Conn).

test_failed_connection_with_a_token_with_insufficient_resource_permission(Config) ->
{_Algo, Token} = generate_valid_token(Config, [<<"rabbitmq.configure:vhost2/jwt*">>,
<<"rabbitmq.write:vhost2/jwt*">>,
Expand Down
63 changes: 49 additions & 14 deletions deps/rabbitmq_auth_backend_oauth2/test/unit_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ all() ->
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field,
test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field,
test_username_from,
{group, with_rabbitmq_node}
{group, with_rabbitmq_node},
{group, with_resource_server_id}

].
groups() ->
Expand All @@ -62,11 +63,11 @@ groups() ->
},
{with_resource_server_id, [], [
test_successful_access_with_a_token,
test_validate_payload_resource_server_id_mismatch,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field,
test_successful_authorization_without_scopes,
test_successful_authentication_without_scopes,
test_successful_access_with_a_token_that_uses_single_scope_alias_with_var_expansion,
test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_source_field,
test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field,
normalize_token_scope_with_additional_scopes_complex_claims,
Expand Down Expand Up @@ -634,7 +635,7 @@ normalize_token_scope_with_additional_scopes_complex_claims(_) ->
<<"rabbitmq3">> =>
[<<"rabbitmq-resource.write:*/*">>,
<<"rabbitmq-resource-write">>]},
[<<"read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>]
[<<"read:*/*">>]
},
{
"claims are map with list content - empty result",
Expand All @@ -647,7 +648,7 @@ normalize_token_scope_with_additional_scopes_complex_claims(_) ->
"claims are map with binary content",
#{ <<"rabbitmq">> => <<"rabbitmq-resource.read:*/* rabbitmq-resource-read">>,
<<"rabbitmq3">> => <<"rabbitmq-resource.write:*/* rabbitmq-resource-write">>},
[<<"rabbitmq.rabbitmq-resource.read:*/*">>, <<"rabbitmq.rabbitmq-resource-read">>]
[<<"read:*/*">>]
},
{
"claims are map with binary content - empty result",
Expand Down Expand Up @@ -777,6 +778,45 @@ test_successful_access_with_a_token_that_has_tag_scopes(_) ->
{ok, #auth_user{username = Username, tags = [management, policymaker]}} =
user_login_authentication(Username, [{password, Token}]).

test_successful_access_with_a_token_that_uses_single_scope_alias_with_var_expansion(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
set_env(key_config, UaaEnv),
Alias = <<"client-alias-1">>,
set_env(scope_aliases, #{
Alias => [
<<"rabbitmq.configure:{vhost}/q-{sub}/rk-{client_id}**">>
]
}),

VHost = <<"vhost">>,
Username = <<"bob">>,
ClientId = <<"rmq">>,
Token = ?UTIL_MOD:sign_token_hs(?UTIL_MOD:token_with_sub(
?UTIL_MOD:token_with_claim(
?UTIL_MOD:token_with_scope_alias_in_scope_field(Alias), <<"client_id">>, ClientId),
Username), Jwk),

{ok, #auth_user{username = Username} = AuthUser} =
user_login_authentication(Username, [{password, Token}]),

%% vhost access
assert_vhost_access_granted(AuthUser, ClientId),

%% resource access
assert_resource_access_denied(AuthUser, VHost, <<"none">>, read),
assert_resource_access_granted(AuthUser, VHost, <<"q-bob">>, configure),

%% topic access
assert_topic_access_refused(AuthUser, VHost, <<"q-bob">>, configure,
#{routing_key => <<"rk-r2mq/#">>}),
assert_topic_access_granted(AuthUser, VHost, <<"q-bob">>, configure,
#{routing_key => <<"rk-rmq/#">>}),


application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
application:unset_env(rabbitmq_auth_backend_oauth2, key_config).

test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
UaaEnv = [{signing_keys, #{<<"token-key">> => {map, Jwk}}}],
Expand Down Expand Up @@ -813,8 +853,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field(
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),

application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
application:unset_env(rabbitmq_auth_backend_oauth2, key_config).


test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_and_custom_scope_prefix(_) ->
Expand Down Expand Up @@ -855,8 +894,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_scope_field_

application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
application:unset_env(rabbitmq_auth_backend_oauth2, scope_prefix).

test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_field(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
Expand Down Expand Up @@ -901,8 +939,7 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_scope_fi
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),

application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
application:unset_env(rabbitmq_auth_backend_oauth2, key_config).

test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_scope_field(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
Expand Down Expand Up @@ -976,8 +1013,7 @@ test_successful_access_with_a_token_that_uses_single_scope_alias_in_extra_scope_
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),

application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
application:unset_env(rabbitmq_auth_backend_oauth2, key_config).

test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_scope_source_field(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
Expand Down Expand Up @@ -1021,8 +1057,7 @@ test_successful_access_with_a_token_that_uses_multiple_scope_aliases_in_extra_sc
assert_resource_access_denied(AuthUser, VHost, <<"three">>, write),

application:unset_env(rabbitmq_auth_backend_oauth2, scope_aliases),
application:unset_env(rabbitmq_auth_backend_oauth2, key_config),
application:unset_env(rabbitmq_auth_backend_oauth2, resource_server_id).
application:unset_env(rabbitmq_auth_backend_oauth2, key_config).

test_unsuccessful_access_with_a_token_that_uses_missing_scope_alias_in_extra_scope_source_field(_) ->
Jwk = ?UTIL_MOD:fixture_jwk(),
Expand Down
Loading