From 7209135ed8df2abd66fe83d374185dbfcd0f060a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C3=A9ter=20G=C3=B6m=C3=B6ri?= Date: Fri, 1 Mar 2024 20:59:19 +0100 Subject: [PATCH] Optionally block enabling certain feature flags via Management UI/API Useful to avoid accidentally enabling for example experiemental feature flags from the Management UI on sensitive clusters. --- .../priv/schema/rabbitmq_management.schema | 7 +++++++ .../src/rabbit_mgmt_features.erl | 8 ++++++++ .../rabbit_mgmt_wm_feature_flag_enable.erl | 20 +++++++++++-------- .../test/rabbit_mgmt_http_SUITE.erl | 15 ++++++++++++++ 4 files changed, 42 insertions(+), 8 deletions(-) diff --git a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema index 1ee80516e200..2154c2f61541 100644 --- a/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema +++ b/deps/rabbitmq_management/priv/schema/rabbitmq_management.schema @@ -646,3 +646,10 @@ end}. {datatype, {enum, [true, false]}}, {include_default, false} ]}. + +%% Block enabling certain feature flags over API + +{mapping, "management.restrictions.feature_flag_blocked.$name", "rabbitmq_management.restrictions.feature_flag_blocked", [ + {datatype, [string, {enum, [false]}]}, + {include_default, false} +]}. diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_features.erl b/deps/rabbitmq_management/src/rabbit_mgmt_features.erl index 70c820de7dc2..137a21c0cd80 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_features.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_features.erl @@ -9,6 +9,7 @@ -export([is_op_policy_updating_disabled/0, is_qq_replica_operations_disabled/0, + is_feature_flag_blocked/1, are_stats_enabled/0]). is_qq_replica_operations_disabled() -> @@ -20,6 +21,13 @@ is_op_policy_updating_disabled() -> _ -> false end. +-spec is_feature_flag_blocked(rabbit_feature_flags:feature_name()) -> {true, string()} | false. +is_feature_flag_blocked(FeatureFlag) -> + case get_restriction([feature_flag_blocked, FeatureFlag]) of + Msg when is_list(Msg) -> {true, Msg}; + _ -> false + end. + are_stats_enabled() -> DisabledFromConf = application:get_env( rabbitmq_management, disable_management_stats, false), diff --git a/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl index 9922dbcb2104..7947f6442bf5 100644 --- a/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl +++ b/deps/rabbitmq_management/src/rabbit_mgmt_wm_feature_flag_enable.erl @@ -13,7 +13,6 @@ -export([variances/2]). -include_lib("rabbitmq_management_agent/include/rabbit_mgmt_records.hrl"). --include_lib("rabbit_common/include/rabbit.hrl"). %%-------------------------------------------------------------------- @@ -38,13 +37,18 @@ accept_content(ReqData, #context{} = Context) -> NameS = rabbit_mgmt_util:id(name, ReqData), try Name = list_to_existing_atom(binary_to_list(NameS)), - case rabbit_feature_flags:enable(Name) of - ok -> - {true, ReqData, Context}; - {error, Reason1} -> - FormattedReason1 = rabbit_ff_extra:format_error(Reason1), - rabbit_mgmt_util:bad_request( - list_to_binary(FormattedReason1), ReqData, Context) + case rabbit_mgmt_features:is_feature_flag_blocked(Name) of + {true, Message} -> + rabbit_mgmt_util:method_not_allowed(Message, ReqData, Context); + false -> + case rabbit_feature_flags:enable(Name) of + ok -> + {true, ReqData, Context}; + {error, Reason1} -> + FormattedReason1 = rabbit_ff_extra:format_error(Reason1), + rabbit_mgmt_util:bad_request( + list_to_binary(FormattedReason1), ReqData, Context) + end end catch _:badarg -> diff --git a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl index 59882068d487..9a14a2a946de 100644 --- a/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl +++ b/deps/rabbitmq_management/test/rabbit_mgmt_http_SUITE.erl @@ -159,6 +159,7 @@ all_tests() -> [ user_limit_set_test, config_environment_test, disabled_qq_replica_opers_test, + feature_flag_blocked_test, list_deprecated_features_test, list_used_deprecated_features_test ]. @@ -230,6 +231,11 @@ init_per_testcase(Testcase = disabled_qq_replica_opers_test, Config) -> rabbit_ct_broker_helpers:rpc_all(Config, application, set_env, [rabbitmq_management, restrictions, Restrictions]), rabbit_ct_helpers:testcase_started(Config, Testcase); +init_per_testcase(Testcase = feature_flag_blocked_test, Config) -> + Restrictions = [{feature_flag_blocked, [{khepri_db, "Khepri is blocked"}]}], + rabbit_ct_broker_helpers:rpc_all(Config, + application, set_env, [rabbitmq_management, restrictions, Restrictions]), + rabbit_ct_helpers:testcase_started(Config, Testcase); init_per_testcase(queues_detailed_test, Config) -> IsEnabled = rabbit_ct_broker_helpers:is_feature_flag_enabled( Config, detailed_queues_endpoint), @@ -302,6 +308,10 @@ end_per_testcase0(disabled_qq_replica_opers_test, Config) -> rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env, [rabbitmq_management, restrictions]), Config; +end_per_testcase0(feature_flag_blocked_test, Config) -> + rabbit_ct_broker_helpers:rpc(Config, 0, application, unset_env, + [rabbitmq_management, restrictions]), + Config; end_per_testcase0(Testcase, Config) when Testcase == list_deprecated_features_test; Testcase == list_used_deprecated_features_test -> @@ -3716,6 +3726,11 @@ disabled_qq_replica_opers_test(Config) -> http_delete(Config, "/queues/quorum/replicas/on/" ++ Nodename ++ "/shrink", ?METHOD_NOT_ALLOWED), passed. +feature_flag_blocked_test(Config) -> + Body = "", + http_put(Config, "/feature-flags/khepri_db/enable", Body, ?METHOD_NOT_ALLOWED), + passed. + list_deprecated_features_test(Config) -> Desc = "This is a deprecated feature", DocUrl = "https://rabbitmq.com/",