/* * Copyright (C) 2017-present ScyllaDB */ /* * SPDX-License-Identifier: AGPL-3.0-or-later */ #include #include "auth/service.hh" #include #include #include #include #include #include "auth/allow_all_authenticator.hh" #include "auth/allow_all_authorizer.hh" #include "auth/common.hh" #include "auth/role_or_anonymous.hh" #include "cql3/query_processor.hh" #include "cql3/untyped_result_set.hh" #include "db/consistency_level_type.hh" #include "exceptions/exceptions.hh" #include "log.hh" #include "service/migration_manager.hh" #include "utils/class_registrator.hh" #include "locator/abstract_replication_strategy.hh" #include "data_dictionary/keyspace_metadata.hh" #include "mutation.hh" namespace auth { namespace meta { static const sstring user_name_col_name("name"); static const sstring superuser_col_name("super"); } static logging::logger log("auth_service"); class auth_migration_listener final : public ::service::migration_listener { authorizer& _authorizer; public: explicit auth_migration_listener(authorizer& a) : _authorizer(a) { } private: void on_create_keyspace(const sstring& ks_name) override {} void on_create_column_family(const sstring& ks_name, const sstring& cf_name) override {} void on_create_user_type(const sstring& ks_name, const sstring& type_name) override {} void on_create_function(const sstring& ks_name, const sstring& function_name) override {} void on_create_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {} void on_create_view(const sstring& ks_name, const sstring& view_name) override {} void on_update_keyspace(const sstring& ks_name) override {} void on_update_column_family(const sstring& ks_name, const sstring& cf_name, bool) override {} void on_update_user_type(const sstring& ks_name, const sstring& type_name) override {} void on_update_function(const sstring& ks_name, const sstring& function_name) override {} void on_update_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {} void on_update_view(const sstring& ks_name, const sstring& view_name, bool columns_changed) override {} void on_drop_keyspace(const sstring& ks_name) override { // Do it in the background. (void)_authorizer.revoke_all( auth::make_data_resource(ks_name)).handle_exception_type([](const unsupported_authorization_operation&) { // Nothing. }).handle_exception([] (std::exception_ptr e) { log.error("Unexpected exception while revoking all permissions on dropped keyspace: {}", e); }); } void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override { // Do it in the background. (void)_authorizer.revoke_all( auth::make_data_resource( ks_name, cf_name)).handle_exception_type([](const unsupported_authorization_operation&) { // Nothing. }).handle_exception([] (std::exception_ptr e) { log.error("Unexpected exception while revoking all permissions on dropped table: {}", e); }); } void on_drop_user_type(const sstring& ks_name, const sstring& type_name) override {} void on_drop_function(const sstring& ks_name, const sstring& function_name) override {} void on_drop_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {} void on_drop_view(const sstring& ks_name, const sstring& view_name) override {} }; static future<> validate_role_exists(const service& ser, std::string_view role_name) { return ser.underlying_role_manager().exists(role_name).then([role_name](bool exists) { if (!exists) { throw nonexistant_role(role_name); } }); } service::service( permissions_cache_config c, cql3::query_processor& qp, ::service::migration_notifier& mn, std::unique_ptr z, std::unique_ptr a, std::unique_ptr r) : _permissions_cache_config(std::move(c)) , _permissions_cache(nullptr) , _qp(qp) , _mnotifier(mn) , _authorizer(std::move(z)) , _authenticator(std::move(a)) , _role_manager(std::move(r)) , _migration_listener(std::make_unique(*_authorizer)) {} service::service( permissions_cache_config c, cql3::query_processor& qp, ::service::migration_notifier& mn, ::service::migration_manager& mm, const service_config& sc) : service( std::move(c), qp, mn, create_object(sc.authorizer_java_name, qp, mm), create_object(sc.authenticator_java_name, qp, mm), create_object(sc.role_manager_java_name, qp, mm)) { } future<> service::create_keyspace_if_missing(::service::migration_manager& mm) const { assert(this_shard_id() == 0); // once_among_shards makes sure a function is executed on shard 0 only auto db = _qp.db(); if (!db.has_keyspace(meta::AUTH_KS)) { auto group0_guard = co_await mm.start_group0_operation(); auto ts = group0_guard.write_timestamp(); if (!db.has_keyspace(meta::AUTH_KS)) { locator::replication_strategy_config_options opts{{"replication_factor", "1"}}; auto ksm = data_dictionary::keyspace_metadata::new_keyspace( meta::AUTH_KS, "org.apache.cassandra.locator.SimpleStrategy", opts, true); co_return co_await mm.announce(mm.prepare_new_keyspace_announcement(ksm, ts), std::move(group0_guard)); } } } future<> service::start(::service::migration_manager& mm) { return once_among_shards([this, &mm] { return create_keyspace_if_missing(mm); }).then([this] { return _role_manager->start().then([this] { return when_all_succeed(_authorizer->start(), _authenticator->start()).discard_result(); }); }).then([this] { _permissions_cache = std::make_unique(_permissions_cache_config, *this, log); }).then([this] { return once_among_shards([this] { _mnotifier.register_listener(_migration_listener.get()); return make_ready_future<>(); }); }); } future<> service::stop() { // Only one of the shards has the listener registered, but let's try to // unregister on each one just to make sure. return _mnotifier.unregister_listener(_migration_listener.get()).then([this] { if (_permissions_cache) { return _permissions_cache->stop(); } return make_ready_future<>(); }).then([this] { return when_all_succeed(_role_manager->stop(), _authorizer->stop(), _authenticator->stop()).discard_result(); }); } future service::has_existing_legacy_users() const { if (!_qp.db().has_schema(meta::AUTH_KS, meta::USERS_CF)) { return make_ready_future(false); } static const sstring default_user_query = format("SELECT * FROM {}.{} WHERE {} = ?", meta::AUTH_KS, meta::USERS_CF, meta::user_name_col_name); static const sstring all_users_query = format("SELECT * FROM {}.{} LIMIT 1", meta::AUTH_KS, meta::USERS_CF); // This logic is borrowed directly from Apache Cassandra. By first checking for the presence of the default user, we // can potentially avoid doing a range query with a high consistency level. return _qp.execute_internal( default_user_query, db::consistency_level::ONE, {meta::DEFAULT_SUPERUSER_NAME}, cql3::query_processor::cache_internal::yes).then([this](auto results) { if (!results->empty()) { return make_ready_future(true); } return _qp.execute_internal( default_user_query, db::consistency_level::QUORUM, {meta::DEFAULT_SUPERUSER_NAME}, cql3::query_processor::cache_internal::yes).then([this](auto results) { if (!results->empty()) { return make_ready_future(true); } return _qp.execute_internal( all_users_query, db::consistency_level::QUORUM, cql3::query_processor::cache_internal::no).then([](auto results) { return make_ready_future(!results->empty()); }); }); }); } future service::get_uncached_permissions(const role_or_anonymous& maybe_role, const resource& r) const { if (is_anonymous(maybe_role)) { return _authorizer->authorize(maybe_role, r); } const std::string_view role_name = *maybe_role.name; return has_superuser(role_name).then([this, role_name, &r](bool superuser) { if (superuser) { return make_ready_future(r.applicable_permissions()); } // // Aggregate the permissions from all granted roles. // return do_with(permission_set(), [this, role_name, &r](auto& all_perms) { return get_roles(role_name).then([this, &r, &all_perms](role_set all_roles) { return do_with(std::move(all_roles), [this, &r, &all_perms](const auto& all_roles) { return parallel_for_each(all_roles, [this, &r, &all_perms](std::string_view role_name) { return _authorizer->authorize(role_name, r).then([&all_perms](permission_set perms) { all_perms = permission_set::from_mask(all_perms.mask() | perms.mask()); }); }); }); }).then([&all_perms] { return all_perms; }); }); }); } future service::get_permissions(const role_or_anonymous& maybe_role, const resource& r) const { return _permissions_cache->get(maybe_role, r); } future service::has_superuser(std::string_view role_name) const { return this->get_roles(std::move(role_name)).then([this](role_set roles) { return do_with(std::move(roles), [this](const role_set& roles) { return do_with(false, roles.begin(), [this, &roles](bool& any_super, auto& iter) { return do_until( [&roles, &any_super, &iter] { return any_super || (iter == roles.end()); }, [this, &any_super, &iter] { return _role_manager->is_superuser(*iter++).then([&any_super](bool super) { any_super = super; }); }).then([&any_super] { return any_super; }); }); }); }); } future service::get_roles(std::string_view role_name) const { // // We may wish to cache this information in the future (as Apache Cassandra does). // return _role_manager->query_granted(role_name, recursive_role_query::yes); } future service::exists(const resource& r) const { switch (r.kind()) { case resource_kind::data: { const auto& db = _qp.db(); data_resource_view v(r); const auto keyspace = v.keyspace(); const auto table = v.table(); if (table) { return make_ready_future(db.has_schema(sstring(*keyspace), sstring(*table))); } if (keyspace) { return make_ready_future(db.has_keyspace(sstring(*keyspace))); } return make_ready_future(true); } case resource_kind::role: { role_resource_view v(r); const auto role = v.role(); if (role) { return _role_manager->exists(*role); } return make_ready_future(true); } case resource_kind::service_level: return make_ready_future(true); } return make_ready_future(false); } // // Free functions. // future has_superuser(const service& ser, const authenticated_user& u) { if (is_anonymous(u)) { return make_ready_future(false); } return ser.has_superuser(*u.name); } future get_roles(const service& ser, const authenticated_user& u) { if (is_anonymous(u)) { return make_ready_future(); } return ser.get_roles(*u.name); } future get_permissions(const service& ser, const authenticated_user& u, const resource& r) { return do_with(role_or_anonymous(), [&ser, &u, &r](auto& maybe_role) { maybe_role.name = u.name; return ser.get_permissions(maybe_role, r); }); } bool is_enforcing(const service& ser) { const bool enforcing_authorizer = ser.underlying_authorizer().qualified_java_name() != allow_all_authorizer_name; const bool enforcing_authenticator = ser.underlying_authenticator().qualified_java_name() != allow_all_authenticator_name; return enforcing_authorizer || enforcing_authenticator; } bool is_protected(const service& ser, command_desc cmd) noexcept { if (cmd.type_ == command_desc::type::ALTER_WITH_OPTS) { return false; // Table attributes are OK to modify; see #7057. } return ser.underlying_role_manager().protected_resources().contains(cmd.resource) || ser.underlying_authenticator().protected_resources().contains(cmd.resource) || ser.underlying_authorizer().protected_resources().contains(cmd.resource); } static void validate_authentication_options_are_supported( const authentication_options& options, const authentication_option_set& supported) { const auto check = [&supported](authentication_option k) { if (!supported.contains(k)) { throw unsupported_authentication_option(k); } }; if (options.password) { check(authentication_option::password); } if (options.options) { check(authentication_option::options); } } future<> create_role( const service& ser, std::string_view name, const role_config& config, const authentication_options& options) { return ser.underlying_role_manager().create(name, config).then([&ser, name, &options] { if (!auth::any_authentication_options(options)) { return make_ready_future<>(); } return futurize_invoke( &validate_authentication_options_are_supported, options, ser.underlying_authenticator().supported_options()).then([&ser, name, &options] { return ser.underlying_authenticator().create(name, options); }).handle_exception([&ser, &name](std::exception_ptr ep) { // Roll-back. return ser.underlying_role_manager().drop(name).then([ep = std::move(ep)] { std::rethrow_exception(ep); }); }); }); } future<> alter_role( const service& ser, std::string_view name, const role_config_update& config_update, const authentication_options& options) { return ser.underlying_role_manager().alter(name, config_update).then([&ser, name, &options] { if (!any_authentication_options(options)) { return make_ready_future<>(); } return futurize_invoke( &validate_authentication_options_are_supported, options, ser.underlying_authenticator().supported_options()).then([&ser, name, &options] { return ser.underlying_authenticator().alter(name, options); }); }); } future<> drop_role(const service& ser, std::string_view name) { return do_with(make_role_resource(name), [&ser, name](const resource& r) { auto& a = ser.underlying_authorizer(); return when_all_succeed( a.revoke_all(name), a.revoke_all(r)) .discard_result() .handle_exception_type([](const unsupported_authorization_operation&) { // Nothing. }); }).then([&ser, name] { return ser.underlying_authenticator().drop(name); }).then([&ser, name] { return ser.underlying_role_manager().drop(name); }); } future has_role(const service& ser, std::string_view grantee, std::string_view name) { return when_all_succeed( validate_role_exists(ser, name), ser.get_roles(grantee)).then_unpack([name](role_set all_roles) { return make_ready_future(all_roles.contains(sstring(name))); }); } future has_role(const service& ser, const authenticated_user& u, std::string_view name) { if (is_anonymous(u)) { return make_ready_future(false); } return has_role(ser, *u.name, name); } future<> grant_permissions( const service& ser, std::string_view role_name, permission_set perms, const resource& r) { return validate_role_exists(ser, role_name).then([&ser, role_name, perms, &r] { return ser.underlying_authorizer().grant(role_name, perms, r); }); } future<> grant_applicable_permissions(const service& ser, std::string_view role_name, const resource& r) { return grant_permissions(ser, role_name, r.applicable_permissions(), r); } future<> grant_applicable_permissions(const service& ser, const authenticated_user& u, const resource& r) { if (is_anonymous(u)) { return make_ready_future<>(); } return grant_applicable_permissions(ser, *u.name, r); } future<> revoke_permissions( const service& ser, std::string_view role_name, permission_set perms, const resource& r) { return validate_role_exists(ser, role_name).then([&ser, role_name, perms, &r] { return ser.underlying_authorizer().revoke(role_name, perms, r); }); } future> list_filtered_permissions( const service& ser, permission_set perms, std::optional role_name, const std::optional>& resource_filter) { return ser.underlying_authorizer().list_all().then([&ser, perms, role_name, &resource_filter]( std::vector all_details) { if (resource_filter) { const resource r = resource_filter->first; const auto resources = resource_filter->second ? auth::expand_resource_family(r) : auth::resource_set{r}; std::erase_if(all_details, [&resources](const permission_details& pd) { return !resources.contains(pd.resource); }); } std::transform( std::make_move_iterator(all_details.begin()), std::make_move_iterator(all_details.end()), all_details.begin(), [perms](permission_details pd) { pd.permissions = permission_set::from_mask(pd.permissions.mask() & perms.mask()); return pd; }); // Eliminate rows with an empty permission set. std::erase_if(all_details, [](const permission_details& pd) { return pd.permissions.mask() == 0; }); if (!role_name) { return make_ready_future>(std::move(all_details)); } // // Filter out rows based on whether permissions have been granted to this role (directly or indirectly). // return do_with(std::move(all_details), [&ser, role_name](auto& all_details) { return ser.get_roles(*role_name).then([&all_details](role_set all_roles) { std::erase_if(all_details, [&all_roles](const permission_details& pd) { return !all_roles.contains(pd.role_name); }); return make_ready_future>(std::move(all_details)); }); }); }); } }