From 80ef5629f3207e037f4653ba4d8f52d72e2ac46f Mon Sep 17 00:00:00 2001 From: David Ansari Date: Tue, 13 Aug 2024 10:19:32 +0200 Subject: [PATCH 1/2] Support SASL mechanism EXTERNAL in Erlang AMQP 1.0 client --- deps/amqp10_client/src/amqp10_client.erl | 4 +- .../src/amqp10_client_connection.erl | 76 +++++++++++-------- deps/rabbit/BUILD.bazel | 1 + deps/rabbit/Makefile | 2 +- deps/rabbit/test/amqp_auth_SUITE.erl | 68 +++++++++++++++-- .../src/rabbit_auth_mechanism_ssl.erl | 4 +- 6 files changed, 110 insertions(+), 45 deletions(-) diff --git a/deps/amqp10_client/src/amqp10_client.erl b/deps/amqp10_client/src/amqp10_client.erl index 68cac2622265..a5de8bc88aa7 100644 --- a/deps/amqp10_client/src/amqp10_client.erl +++ b/deps/amqp10_client/src/amqp10_client.erl @@ -106,9 +106,7 @@ open_connection(ConnectionConfig0) -> notify_when_opened => NotifyWhenOpened, notify_when_closed => NotifyWhenClosed }, - Sasl = maps:get(sasl, ConnectionConfig1), - ConnectionConfig2 = ConnectionConfig1#{sasl => amqp10_client_connection:encrypt_sasl(Sasl)}, - ConnectionConfig = merge_default_tls_options(ConnectionConfig2), + ConnectionConfig = merge_default_tls_options(ConnectionConfig1), amqp10_client_connection:open(ConnectionConfig). %% @doc Closes a connection. diff --git a/deps/amqp10_client/src/amqp10_client_connection.erl b/deps/amqp10_client/src/amqp10_client_connection.erl index 4a9c738eac98..80c75f986a66 100644 --- a/deps/amqp10_client/src/amqp10_client_connection.erl +++ b/deps/amqp10_client/src/amqp10_client_connection.erl @@ -22,9 +22,7 @@ socket_ready/2, protocol_header_received/5, begin_session/1, - heartbeat/1, - encrypt_sasl/1, - decrypt_sasl/1]). + heartbeat/1]). %% gen_statem callbacks -export([init/1, @@ -52,7 +50,8 @@ -type address() :: inet:socket_address() | inet:hostname(). -type encrypted_sasl() :: {plaintext, binary()} | {encrypted, binary()}. --type decrypted_sasl() :: none | anon | {plain, User :: binary(), Pwd :: binary()}. +-type decrypted_sasl() :: none | anon | external | {plain, User :: binary(), Pwd :: binary()}. +-type sasl() :: encrypted_sasl() | decrypted_sasl(). -type connection_config() :: #{container_id => binary(), % AMQP container id @@ -72,9 +71,7 @@ % set to a negative value to allow a sender to "overshoot" the flow % control by this margin transfer_limit_margin => 0 | neg_integer(), - %% These credentials_obfuscation-wrapped values have the type of - %% decrypted_sasl/0 - sasl => encrypted_sasl() | decrypted_sasl(), + sasl => sasl(), properties => amqp10_client_types:properties() }. @@ -92,16 +89,15 @@ }). -export_type([connection_config/0, - amqp10_socket/0, - encrypted_sasl/0, - decrypted_sasl/0]). + amqp10_socket/0]). %% ------------------------------------------------------------------- %% Public API. %% ------------------------------------------------------------------- -spec open(connection_config()) -> supervisor:startchild_ret(). -open(Config) -> +open(Config0) -> + Config = maps:update_with(sasl, fun maybe_encrypt_sasl/1, Config0), %% Start the supervision tree dedicated to that connection. It %% starts at least a connection process (the PID we want to return) %% and a reader process (responsible for opening and reading the @@ -127,17 +123,23 @@ open(Config) -> close(Pid, Reason) -> gen_statem:cast(Pid, {close, Reason}). --spec encrypt_sasl(decrypted_sasl()) -> encrypted_sasl(). -encrypt_sasl(none) -> - credentials_obfuscation:encrypt(none); -encrypt_sasl(DecryptedSasl) -> - credentials_obfuscation:encrypt(term_to_binary(DecryptedSasl)). - --spec decrypt_sasl(encrypted_sasl()) -> decrypted_sasl(). -decrypt_sasl(none) -> - credentials_obfuscation:decrypt(none); -decrypt_sasl(EncryptedSasl) -> - binary_to_term(credentials_obfuscation:decrypt(EncryptedSasl)). +-spec maybe_encrypt_sasl(decrypted_sasl()) -> sasl(). +maybe_encrypt_sasl(Sasl) + when Sasl =:= none orelse + Sasl =:= anon orelse + Sasl =:= external -> + Sasl; +maybe_encrypt_sasl(Plain = {plain, _User, _Passwd}) -> + credentials_obfuscation:encrypt(term_to_binary(Plain)). + +-spec maybe_decrypt_sasl(sasl()) -> decrypted_sasl(). +maybe_decrypt_sasl(Sasl) + when Sasl =:= none orelse + Sasl =:= anon orelse + Sasl =:= external -> + Sasl; +maybe_decrypt_sasl(Encrypted) -> + binary_to_term(credentials_obfuscation:decrypt(Encrypted)). %% ------------------------------------------------------------------- %% Private API. @@ -207,13 +209,11 @@ sasl_hdr_sent({call, From}, begin_session, {keep_state, State1}. sasl_hdr_rcvds(_EvtType, #'v1_0.sasl_mechanisms'{ - sasl_server_mechanisms = {array, symbol, Mechs}}, - State = #state{config = #{sasl := EncryptedSasl}}) -> - DecryptedSasl = decrypt_sasl(EncryptedSasl), - SaslBin = {symbol, decrypted_sasl_to_bin(DecryptedSasl)}, - case lists:any(fun(S) when S =:= SaslBin -> true; - (_) -> false - end, Mechs) of + sasl_server_mechanisms = {array, symbol, AvailableMechs}}, + State = #state{config = #{sasl := Sasl}}) -> + DecryptedSasl = maybe_decrypt_sasl(Sasl), + OurMech = {symbol, decrypted_sasl_to_mechanism(DecryptedSasl)}, + case lists:member(OurMech, AvailableMechs) of true -> ok = send_sasl_init(State, DecryptedSasl), {next_state, sasl_init_sent, State}; @@ -454,6 +454,15 @@ send_close(#state{socket = Socket}, _Reason) -> send_sasl_init(State, anon) -> Frame = #'v1_0.sasl_init'{mechanism = {symbol, <<"ANONYMOUS">>}}, send(Frame, 1, State); +send_sasl_init(State, external) -> + Frame = #'v1_0.sasl_init'{ + mechanism = {symbol, <<"EXTERNAL">>}, + %% "This response is empty when the client is requesting to act + %% as the identity the server associated with its authentication + %% credentials." + %% https://datatracker.ietf.org/doc/html/rfc4422#appendix-A.1 + initial_response = {binary, <<>>}}, + send(Frame, 1, State); send_sasl_init(State, {plain, User, Pass}) -> Response = <<0:8, User/binary, 0:8, Pass/binary>>, Frame = #'v1_0.sasl_init'{mechanism = {symbol, <<"PLAIN">>}, @@ -546,9 +555,12 @@ translate_err(#'v1_0.error'{condition = Cond, description = Desc}) -> amqp10_event(Evt) -> {amqp10_event, {connection, self(), Evt}}. -decrypted_sasl_to_bin({plain, _, _}) -> <<"PLAIN">>; -decrypted_sasl_to_bin(anon) -> <<"ANONYMOUS">>; -decrypted_sasl_to_bin(none) -> <<"ANONYMOUS">>. +decrypted_sasl_to_mechanism(anon) -> + <<"ANONYMOUS">>; +decrypted_sasl_to_mechanism(external) -> + <<"EXTERNAL">>; +decrypted_sasl_to_mechanism({plain, _, _}) -> + <<"PLAIN">>. config_defaults() -> #{sasl => none, diff --git a/deps/rabbit/BUILD.bazel b/deps/rabbit/BUILD.bazel index 6d42d7b9f511..138228227a49 100644 --- a/deps/rabbit/BUILD.bazel +++ b/deps/rabbit/BUILD.bazel @@ -277,6 +277,7 @@ rabbitmq_home( ":test_erlang_app", "//deps/rabbitmq_ct_client_helpers:erlang_app", "//deps/rabbitmq_amqp1_0:erlang_app", + "//deps/rabbitmq_auth_mechanism_ssl:erlang_app", "@inet_tcp_proxy_dist//:erlang_app", "@meck//:erlang_app", ], diff --git a/deps/rabbit/Makefile b/deps/rabbit/Makefile index 92d2b27aa80f..6a9b0422bf91 100644 --- a/deps/rabbit/Makefile +++ b/deps/rabbit/Makefile @@ -134,7 +134,7 @@ LOCAL_DEPS = sasl os_mon inets compiler public_key crypto ssl syntax_tools xmerl BUILD_DEPS = rabbitmq_cli DEPS = ranch rabbit_common amqp10_common rabbitmq_prelaunch ra sysmon_handler stdout_formatter recon redbug observer_cli osiris syslog systemd seshat khepri khepri_mnesia_migration cuttlefish gen_batch_server -TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers meck proper amqp_client rabbitmq_amqp_client rabbitmq_amqp1_0 +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers meck proper amqp_client rabbitmq_amqp_client rabbitmq_amqp1_0 rabbitmq_auth_mechanism_ssl PLT_APPS += mnesia runtime_tools diff --git a/deps/rabbit/test/amqp_auth_SUITE.erl b/deps/rabbit/test/amqp_auth_SUITE.erl index 0ff70bf0c520..9aec79ac7a9f 100644 --- a/deps/rabbit/test/amqp_auth_SUITE.erl +++ b/deps/rabbit/test/amqp_auth_SUITE.erl @@ -87,6 +87,11 @@ groups() -> unbind_queue_target, unbind_from_topic_exchange ] + }, + {sasl_external, [shuffle], + [ + sasl_external_success + ] } ]. @@ -98,15 +103,40 @@ init_per_suite(Config) -> end_per_suite(Config) -> Config. -init_per_group(Group, Config0) -> - PermitV1 = case Group of - address_v1 -> true; - address_v2 -> false - end, +init_per_group(address_v1, Config) -> + init_per_group0(true, Config); +init_per_group(address_v2, Config) -> + init_per_group0(false, Config); +init_per_group(sasl_external, Config0) -> + %% Command `deps/rabbitmq_ct_helpers/tools/tls-certs$ make` + %% will put our hostname as common name in the client cert. + Config1 = rabbit_ct_helpers:merge_app_env( + Config0, + {rabbit, + [ + {permit_deprecated_features, #{amqp_address_v1 => false}}, + {auth_mechanisms, ['EXTERNAL']}, + {ssl_cert_login_from, common_name} + ] + }), + Config = rabbit_ct_helpers:run_setup_steps( + Config1, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()), + {ok, UserString} = inet:gethostname(), + User = unicode:characters_to_binary(UserString), + ok = rabbit_ct_broker_helpers:add_user(Config, User), + Vhost = <<"test vhost">>, + ok = rabbit_ct_broker_helpers:add_vhost(Config, Vhost), + [{test_vhost, Vhost}, + {test_user, User}] ++ Config. + +init_per_group0(PermitV1, Config0) -> Config1 = rabbit_ct_helpers:merge_app_env( - Config0, {rabbit, - [{permit_deprecated_features, - #{amqp_address_v1 => PermitV1}}]}), + Config0, + {rabbit, + [{permit_deprecated_features, #{amqp_address_v1 => PermitV1}}] + }), Config = rabbit_ct_helpers:run_setup_steps( Config1, rabbit_ct_broker_helpers:setup_steps() ++ @@ -1017,6 +1047,28 @@ unbind_from_topic_exchange(Config) -> ok = close_connection_sync(Conn). +sasl_external_success(Config) -> + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp_tls), + Host = ?config(rmq_hostname, Config), + Vhost = ?config(test_vhost, Config), + CACertFile = ?config(rmq_certsdir, Config) ++ "/testca/cacert.pem", + CertFile = ?config(rmq_certsdir, Config) ++ "/client/cert.pem", + KeyFile = ?config(rmq_certsdir, Config) ++ "/client/key.pem", + OpnConf = #{address => Host, + port => Port, + container_id => atom_to_binary(?FUNCTION_NAME), + hostname => <<"vhost:", Vhost/binary>>, + sasl => external, + tls_opts => {secure_port, [{cacertfile, CACertFile}, + {certfile, CertFile}, + {keyfile, KeyFile}]} + }, + {ok, Connection} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, Connection, opened}} -> ok + after 5000 -> ct:fail(missing_opened) + end, + ok = amqp10_client:close_connection(Connection). + init_pair(Config) -> OpnConf = connection_config(Config), {ok, Connection} = amqp10_client:open_connection(OpnConf), diff --git a/deps/rabbitmq_auth_mechanism_ssl/src/rabbit_auth_mechanism_ssl.erl b/deps/rabbitmq_auth_mechanism_ssl/src/rabbit_auth_mechanism_ssl.erl index 6fc78d9bdeb3..11a7e79ee700 100644 --- a/deps/rabbitmq_auth_mechanism_ssl/src/rabbit_auth_mechanism_ssl.erl +++ b/deps/rabbitmq_auth_mechanism_ssl/src/rabbit_auth_mechanism_ssl.erl @@ -23,7 +23,9 @@ {cleanup, {rabbit_registry, unregister, [auth_mechanism, <<"EXTERNAL">>]}}]}). --record(state, {username = undefined}). +-record(state, { + username = undefined :: undefined | rabbit_types:username() | {refused, none, string(), [term()]} + }). description() -> [{description, <<"TLS peer verification-based authentication plugin. Used in combination with the EXTERNAL SASL mechanism.">>}]. From a373396eba5755d52e13225cd980f28e0b62892e Mon Sep 17 00:00:00 2001 From: David Ansari Date: Tue, 13 Aug 2024 14:30:43 +0200 Subject: [PATCH 2/2] Move test to plugin rabbitmq_auth_mechanism_ssl In theory, there can be other plugin that offer SASL mechanism EXTERNAL. Therefore, instead of adding a test dependency from app rabbit to app rabbitmq_auth_mechanism_ssl, it's better to test this plugin specific functionality directly in the plugin itself. --- deps/rabbit/BUILD.bazel | 1 - deps/rabbit/Makefile | 2 +- deps/rabbit/test/amqp_auth_SUITE.erl | 68 ++---------- deps/rabbitmq_auth_mechanism_ssl/BUILD.bazel | 36 +++++- deps/rabbitmq_auth_mechanism_ssl/Makefile | 1 + deps/rabbitmq_auth_mechanism_ssl/app.bzl | 9 +- .../test/system_SUITE.erl | 104 ++++++++++++++++++ 7 files changed, 157 insertions(+), 64 deletions(-) create mode 100644 deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl diff --git a/deps/rabbit/BUILD.bazel b/deps/rabbit/BUILD.bazel index 138228227a49..6d42d7b9f511 100644 --- a/deps/rabbit/BUILD.bazel +++ b/deps/rabbit/BUILD.bazel @@ -277,7 +277,6 @@ rabbitmq_home( ":test_erlang_app", "//deps/rabbitmq_ct_client_helpers:erlang_app", "//deps/rabbitmq_amqp1_0:erlang_app", - "//deps/rabbitmq_auth_mechanism_ssl:erlang_app", "@inet_tcp_proxy_dist//:erlang_app", "@meck//:erlang_app", ], diff --git a/deps/rabbit/Makefile b/deps/rabbit/Makefile index 6a9b0422bf91..92d2b27aa80f 100644 --- a/deps/rabbit/Makefile +++ b/deps/rabbit/Makefile @@ -134,7 +134,7 @@ LOCAL_DEPS = sasl os_mon inets compiler public_key crypto ssl syntax_tools xmerl BUILD_DEPS = rabbitmq_cli DEPS = ranch rabbit_common amqp10_common rabbitmq_prelaunch ra sysmon_handler stdout_formatter recon redbug observer_cli osiris syslog systemd seshat khepri khepri_mnesia_migration cuttlefish gen_batch_server -TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers meck proper amqp_client rabbitmq_amqp_client rabbitmq_amqp1_0 rabbitmq_auth_mechanism_ssl +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers meck proper amqp_client rabbitmq_amqp_client rabbitmq_amqp1_0 PLT_APPS += mnesia runtime_tools diff --git a/deps/rabbit/test/amqp_auth_SUITE.erl b/deps/rabbit/test/amqp_auth_SUITE.erl index 9aec79ac7a9f..0ff70bf0c520 100644 --- a/deps/rabbit/test/amqp_auth_SUITE.erl +++ b/deps/rabbit/test/amqp_auth_SUITE.erl @@ -87,11 +87,6 @@ groups() -> unbind_queue_target, unbind_from_topic_exchange ] - }, - {sasl_external, [shuffle], - [ - sasl_external_success - ] } ]. @@ -103,40 +98,15 @@ init_per_suite(Config) -> end_per_suite(Config) -> Config. -init_per_group(address_v1, Config) -> - init_per_group0(true, Config); -init_per_group(address_v2, Config) -> - init_per_group0(false, Config); -init_per_group(sasl_external, Config0) -> - %% Command `deps/rabbitmq_ct_helpers/tools/tls-certs$ make` - %% will put our hostname as common name in the client cert. - Config1 = rabbit_ct_helpers:merge_app_env( - Config0, - {rabbit, - [ - {permit_deprecated_features, #{amqp_address_v1 => false}}, - {auth_mechanisms, ['EXTERNAL']}, - {ssl_cert_login_from, common_name} - ] - }), - Config = rabbit_ct_helpers:run_setup_steps( - Config1, - rabbit_ct_broker_helpers:setup_steps() ++ - rabbit_ct_client_helpers:setup_steps()), - {ok, UserString} = inet:gethostname(), - User = unicode:characters_to_binary(UserString), - ok = rabbit_ct_broker_helpers:add_user(Config, User), - Vhost = <<"test vhost">>, - ok = rabbit_ct_broker_helpers:add_vhost(Config, Vhost), - [{test_vhost, Vhost}, - {test_user, User}] ++ Config. - -init_per_group0(PermitV1, Config0) -> +init_per_group(Group, Config0) -> + PermitV1 = case Group of + address_v1 -> true; + address_v2 -> false + end, Config1 = rabbit_ct_helpers:merge_app_env( - Config0, - {rabbit, - [{permit_deprecated_features, #{amqp_address_v1 => PermitV1}}] - }), + Config0, {rabbit, + [{permit_deprecated_features, + #{amqp_address_v1 => PermitV1}}]}), Config = rabbit_ct_helpers:run_setup_steps( Config1, rabbit_ct_broker_helpers:setup_steps() ++ @@ -1047,28 +1017,6 @@ unbind_from_topic_exchange(Config) -> ok = close_connection_sync(Conn). -sasl_external_success(Config) -> - Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp_tls), - Host = ?config(rmq_hostname, Config), - Vhost = ?config(test_vhost, Config), - CACertFile = ?config(rmq_certsdir, Config) ++ "/testca/cacert.pem", - CertFile = ?config(rmq_certsdir, Config) ++ "/client/cert.pem", - KeyFile = ?config(rmq_certsdir, Config) ++ "/client/key.pem", - OpnConf = #{address => Host, - port => Port, - container_id => atom_to_binary(?FUNCTION_NAME), - hostname => <<"vhost:", Vhost/binary>>, - sasl => external, - tls_opts => {secure_port, [{cacertfile, CACertFile}, - {certfile, CertFile}, - {keyfile, KeyFile}]} - }, - {ok, Connection} = amqp10_client:open_connection(OpnConf), - receive {amqp10_event, {connection, Connection, opened}} -> ok - after 5000 -> ct:fail(missing_opened) - end, - ok = amqp10_client:close_connection(Connection). - init_pair(Config) -> OpnConf = connection_config(Config), {ok, Connection} = amqp10_client:open_connection(OpnConf), diff --git a/deps/rabbitmq_auth_mechanism_ssl/BUILD.bazel b/deps/rabbitmq_auth_mechanism_ssl/BUILD.bazel index 778774f9e63b..6127cccd64ec 100644 --- a/deps/rabbitmq_auth_mechanism_ssl/BUILD.bazel +++ b/deps/rabbitmq_auth_mechanism_ssl/BUILD.bazel @@ -1,17 +1,22 @@ +load("@rules_erlang//:eunit2.bzl", "eunit") load("@rules_erlang//:xref2.bzl", "xref") load("@rules_erlang//:dialyze.bzl", "dialyze", "plt") +load("//:rabbitmq_home.bzl", "rabbitmq_home") +load("//:rabbitmq_run.bzl", "rabbitmq_run") load( "//:rabbitmq.bzl", "BROKER_VERSION_REQUIREMENTS_ANY", "RABBITMQ_DIALYZER_OPTS", "assert_suites", "rabbitmq_app", + "rabbitmq_integration_suite", ) load( ":app.bzl", "all_beam_files", "all_srcs", "all_test_beam_files", + "test_suite_beam_files", ) APP_NAME = "rabbitmq_auth_mechanism_ssl" @@ -26,7 +31,7 @@ APP_ENV = """[ all_beam_files(name = "all_beam_files") -all_test_beam_files() +all_test_beam_files(name = "all_test_beam_files") all_srcs(name = "all_srcs") @@ -70,6 +75,28 @@ dialyze( target = ":erlang_app", ) +rabbitmq_home( + name = "broker-for-tests-home", + testonly = True, + plugins = [ + ":test_erlang_app", + ], +) + +rabbitmq_run( + name = "rabbitmq-for-tests-run", + testonly = True, + home = ":broker-for-tests-home", +) + +rabbitmq_integration_suite( + name = "system_SUITE", + shard_count = 1, + runtime_deps = [ + "//deps/amqp10_client:erlang_app", + ], +) + assert_suites() alias( @@ -77,3 +104,10 @@ alias( actual = ":erlang_app", visibility = ["//visibility:public"], ) + +test_suite_beam_files(name = "test_suite_beam_files") + +eunit( + name = "eunit", + target = ":test_erlang_app", +) diff --git a/deps/rabbitmq_auth_mechanism_ssl/Makefile b/deps/rabbitmq_auth_mechanism_ssl/Makefile index 9b540fdaf716..f6705d7c3a6a 100644 --- a/deps/rabbitmq_auth_mechanism_ssl/Makefile +++ b/deps/rabbitmq_auth_mechanism_ssl/Makefile @@ -14,6 +14,7 @@ endef LOCAL_DEPS = public_key DEPS = rabbit_common rabbit +TEST_DEPS = rabbitmq_ct_helpers rabbitmq_ct_client_helpers amqp10_client DEP_EARLY_PLUGINS = rabbit_common/mk/rabbitmq-early-plugin.mk DEP_PLUGINS = rabbit_common/mk/rabbitmq-plugin.mk diff --git a/deps/rabbitmq_auth_mechanism_ssl/app.bzl b/deps/rabbitmq_auth_mechanism_ssl/app.bzl index 6a95279a2cff..335857be922e 100644 --- a/deps/rabbitmq_auth_mechanism_ssl/app.bzl +++ b/deps/rabbitmq_auth_mechanism_ssl/app.bzl @@ -75,4 +75,11 @@ def all_test_beam_files(name = "all_test_beam_files"): ) def test_suite_beam_files(name = "test_suite_beam_files"): - pass + erlang_bytecode( + name = "system_SUITE_beam_files", + testonly = True, + srcs = ["test/system_SUITE.erl"], + outs = ["test/system_SUITE.beam"], + app_name = "rabbitmq_auth_mechanism_ssl", + erlc_opts = "//:test_erlc_opts", + ) diff --git a/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl b/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl new file mode 100644 index 000000000000..b5f1a5696110 --- /dev/null +++ b/deps/rabbitmq_auth_mechanism_ssl/test/system_SUITE.erl @@ -0,0 +1,104 @@ +%% This Source Code Form is subject to the terms of the Mozilla Public +%% License, v. 2.0. If a copy of the MPL was not distributed with this +%% file, You can obtain one at https://mozilla.org/MPL/2.0/. +%% +%% Copyright (c) 2007-2023 VMware, Inc. or its affiliates. All rights reserved. + +-module(system_SUITE). + +-compile([export_all, + nowarn_export_all]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +all() -> + [{group, tests}]. + +groups() -> + [ + {tests, [shuffle], + [amqp] + } + ]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(amqp10_client), + rabbit_ct_helpers:log_environment(), + Config. + +end_per_suite(Config) -> + Config. + +init_per_group(_Group, Config0) -> + %% Command `deps/rabbitmq_ct_helpers/tools/tls-certs$ make` + %% will put our hostname as common name in the client cert. + Config1 = rabbit_ct_helpers:merge_app_env( + Config0, + {rabbit, + [ + {auth_mechanisms, ['EXTERNAL']}, + {ssl_cert_login_from, common_name} + ]}), + Config = rabbit_ct_helpers:run_setup_steps( + Config1, + rabbit_ct_broker_helpers:setup_steps() ++ + rabbit_ct_client_helpers:setup_steps()), + {ok, UserString} = inet:gethostname(), + User = unicode:characters_to_binary(UserString), + ok = rabbit_ct_broker_helpers:add_user(Config, User), + Vhost = <<"test vhost">>, + ok = rabbit_ct_broker_helpers:add_vhost(Config, Vhost), + [{test_vhost, Vhost}, + {test_user, User}] ++ Config. + +end_per_group(_Group, Config) -> + ok = rabbit_ct_broker_helpers:delete_user(Config, ?config(test_user, Config)), + ok = rabbit_ct_broker_helpers:delete_vhost(Config, ?config(test_vhost, Config)), + rabbit_ct_helpers:run_teardown_steps( + Config, + rabbit_ct_client_helpers:teardown_steps() ++ + rabbit_ct_broker_helpers:teardown_steps()). + +init_per_testcase(Testcase, Config) -> + ok = set_permissions(Config, <<>>, <<>>, <<"^some vhost permission">>), + rabbit_ct_helpers:testcase_started(Config, Testcase). + +end_per_testcase(Testcase, Config) -> + ok = clear_permissions(Config), + rabbit_ct_helpers:testcase_finished(Config, Testcase). + +amqp(Config) -> + Port = rabbit_ct_broker_helpers:get_node_config(Config, 0, tcp_port_amqp_tls), + Host = ?config(rmq_hostname, Config), + Vhost = ?config(test_vhost, Config), + CACertFile = ?config(rmq_certsdir, Config) ++ "/testca/cacert.pem", + CertFile = ?config(rmq_certsdir, Config) ++ "/client/cert.pem", + KeyFile = ?config(rmq_certsdir, Config) ++ "/client/key.pem", + OpnConf = #{address => Host, + port => Port, + container_id => atom_to_binary(?FUNCTION_NAME), + hostname => <<"vhost:", Vhost/binary>>, + sasl => external, + tls_opts => {secure_port, [{cacertfile, CACertFile}, + {certfile, CertFile}, + {keyfile, KeyFile}]} + }, + {ok, Connection} = amqp10_client:open_connection(OpnConf), + receive {amqp10_event, {connection, Connection, opened}} -> ok + after 5000 -> ct:fail(missing_opened) + end, + ok = amqp10_client:close_connection(Connection). + +set_permissions(Config, ConfigurePerm, WritePerm, ReadPerm) -> + ok = rabbit_ct_broker_helpers:set_permissions(Config, + ?config(test_user, Config), + ?config(test_vhost, Config), + ConfigurePerm, + WritePerm, + ReadPerm). + +clear_permissions(Config) -> + User = ?config(test_user, Config), + Vhost = ?config(test_vhost, Config), + ok = rabbit_ct_broker_helpers:clear_permissions(Config, User, Vhost).