From 507e1ef8d56c4973a8bfefae5c00c191282941b1 Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 11:46:58 -0500 Subject: [PATCH 01/11] auth: Extract `delayed_tasks` from `auth.cc` This simple task scheduler is used by the auth module to delay metadata creation until the system is settled. Extracting it out allows the `auth` module to be refactored into a sharded service and for other components of `auth` to make use of it. Fixes #2965. --- auth/auth.cc | 63 +++------------------------- delayed_tasks.hh | 107 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 113 insertions(+), 57 deletions(-) create mode 100644 delayed_tasks.hh diff --git a/auth/auth.cc b/auth/auth.cc index c67b15f7bc..523ec95377 100644 --- a/auth/auth.cc +++ b/auth/auth.cc @@ -51,6 +51,7 @@ #include "cql3/statements/raw/cf_statement.hh" #include "cql3/statements/create_table_statement.hh" #include "db/config.hh" +#include "delayed_tasks.hh" #include "service/migration_manager.hh" #include "utils/loading_cache.hh" #include "utils/hash.hh" @@ -154,65 +155,13 @@ std::ostream& operator<<(std::ostream& os, const std::pair perm_cache; -/** - * Poor mans job schedule. For maximum 2 jobs. Sic. - * Still does nothing more clever than waiting 10 seconds - * like origin, then runs the submitted tasks. - * - * Only difference compared to sleep (from which this - * borrows _heavily_) is that if tasks have not run by the time - * we exit (and do static clean up) we delete the promise + cont - * - * Should be abstracted to some sort of global server function - * probably. - */ -struct waiter { - promise<> done; - timer<> tmr; - waiter() : tmr([this] {done.set_value();}) - { - tmr.arm(auth::auth::SUPERUSER_SETUP_DELAY); - } - ~waiter() { - if (tmr.armed()) { - tmr.cancel(); - done.set_exception(std::runtime_error("shutting down")); - } - alogger.trace("Deleting scheduled task"); - } - void kill() { - } -}; - -typedef std::unique_ptr waiter_ptr; - -static std::vector & thread_waiters() { - static thread_local std::vector the_waiters; - return the_waiters; +static delayed_tasks<>& get_local_delayed_tasks() { + static thread_local delayed_tasks<> instance; + return instance; } void auth::auth::schedule_when_up(scheduled_func f) { - alogger.trace("Adding scheduled task"); - - auto & waiters = thread_waiters(); - - waiters.emplace_back(std::make_unique()); - auto* w = waiters.back().get(); - - w->done.get_future().finally([w] { - auto & waiters = thread_waiters(); - auto i = std::find_if(waiters.begin(), waiters.end(), [w](const waiter_ptr& p) { - return p.get() == w; - }); - if (i != waiters.end()) { - waiters.erase(i); - } - }).then([f = std::move(f)] { - alogger.trace("Running scheduled task"); - return f(); - }).handle_exception([](auto ep) { - return make_ready_future(); - }); + get_local_delayed_tasks().schedule_after(auth::SUPERUSER_SETUP_DELAY, std::move(f)); } future<> auth::auth::setup() { @@ -278,7 +227,7 @@ future<> auth::auth::shutdown() { // this is mostly relevant for test cases where // db-env-shutdown != process shutdown return smp::invoke_on_all([] { - thread_waiters().clear(); + get_local_delayed_tasks().cancel_all(); }).then([] { return perm_cache.stop(); }); diff --git a/delayed_tasks.hh b/delayed_tasks.hh new file mode 100644 index 0000000000..949e440f81 --- /dev/null +++ b/delayed_tasks.hh @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + +#include "log.hh" +#include "seastarx.hh" + +// +// Delay asynchronous tasks. +// +template +class delayed_tasks final { + static logging::logger _logger; + + // A waiter is destroyed before the timer has elapsed. + class cancelled : public std::exception { + public: + }; + + class waiter final { + timer _timer; + + promise<> _done{}; + + public: + explicit waiter(typename Clock::duration d) : _timer([this] { _done.set_value(); }) { + _timer.arm(d); + } + + ~waiter() { + if (_timer.armed()) { + _timer.cancel(); + _done.set_exception(cancelled()); + } + } + + future<> get_future() noexcept { + return _done.get_future(); + } + }; + + // `std::list` because iterators are not invalidated. We assume that look-up time is not a bottleneck. + std::list> _waiters{}; + +public: + // + // Schedule the task `f` after d` has elapsed. If the instance goes out of scope before + // the duration has elapsed, then the task is cancelled. + // + template + void schedule_after(std::chrono::duration d, Task&& f) { + _logger.trace("Adding scheduled task."); + + auto iter = _waiters.insert(_waiters.end(), std::make_unique(d)); + auto& w = *iter; + + w->get_future().then([this, f = std::move(f)] { + _logger.trace("Running scheduled task."); + return f(); + }).then([this, iter] { + // We'll only get here if the instance is still alive, since otherwise the future will be resolved to + // `cancelled`. + _waiters.erase(iter); + }).template handle_exception_type([](const cancelled&) { + // Nothing. + return make_ready_future<>(); + }); + } + + // + // Cancel all scheduled tasks. + // + void cancel_all() { + _waiters.clear(); + } +}; + +template +logging::logger delayed_tasks::_logger("delayed_tasks"); From 5c39a2cc15fd860ebb40e0541a25cb443adcc85a Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 12:58:23 -0500 Subject: [PATCH 02/11] auth: Fix static constant initialization Using "Meyer's singletons" eliminate the problem of static constant initialization order because static variables inside functions are initialized only the first time control flow passes over their declaration. Fixes #2966. --- auth/auth.cc | 2 +- auth/authenticator.cc | 10 +++++++--- auth/authenticator.hh | 3 ++- auth/authorizer.cc | 7 +++++-- auth/authorizer.hh | 4 ++-- auth/default_authorizer.cc | 10 +++++++--- auth/default_authorizer.hh | 4 ++-- auth/password_authenticator.cc | 10 +++++++--- auth/password_authenticator.hh | 4 ++-- auth/transitional.cc | 17 ++++++++++++----- tests/auth_test.cc | 12 ++++++------ 11 files changed, 53 insertions(+), 30 deletions(-) diff --git a/auth/auth.cc b/auth/auth.cc index 523ec95377..365674abc8 100644 --- a/auth/auth.cc +++ b/auth/auth.cc @@ -173,7 +173,7 @@ future<> auth::auth::setup() { qualified_name authenticator_name(AUTH_PACKAGE_NAME, cfg.authenticator()), authorizer_name(AUTH_PACKAGE_NAME, cfg.authorizer()); - if (authenticator::ALLOW_ALL_AUTHENTICATOR_NAME == authenticator_name && authorizer::ALLOW_ALL_AUTHORIZER_NAME == authorizer_name) { + if (allow_all_authenticator_name() == authenticator_name && allow_all_authorizer_name() == authorizer_name) { // just create the objects return f.then([authenticator_name = std::move(authenticator_name)] { return authenticator::setup(authenticator_name); diff --git a/auth/authenticator.cc b/auth/authenticator.cc index 1d530212df..329c284507 100644 --- a/auth/authenticator.cc +++ b/auth/authenticator.cc @@ -48,7 +48,11 @@ const sstring auth::authenticator::USERNAME_KEY("username"); const sstring auth::authenticator::PASSWORD_KEY("password"); -const sstring auth::authenticator::ALLOW_ALL_AUTHENTICATOR_NAME(auth::AUTH_PACKAGE_NAME + "AllowAllAuthenticator"); + +const sstring& auth::allow_all_authenticator_name() { + static const sstring name = auth::AUTH_PACKAGE_NAME + "AllowAllAuthenticator"; + return name; +} auth::authenticator::option auth::authenticator::string_to_option(const sstring& name) { if (strcasecmp(name.c_str(), "password") == 0) { @@ -76,11 +80,11 @@ using authenticator_registry = class_registry; future<> auth::authenticator::setup(const sstring& type) { - if (type == ALLOW_ALL_AUTHENTICATOR_NAME) { + if (type == allow_all_authenticator_name()) { class allow_all_authenticator : public authenticator { public: const sstring& class_name() const override { - return ALLOW_ALL_AUTHENTICATOR_NAME; + return allow_all_authenticator_name(); } bool require_authentication() const override { return false; diff --git a/auth/authenticator.hh b/auth/authenticator.hh index 502de56be8..1e3c512a0b 100644 --- a/auth/authenticator.hh +++ b/auth/authenticator.hh @@ -65,11 +65,12 @@ namespace auth { class authenticated_user; +const sstring& allow_all_authenticator_name(); + class authenticator { public: static const sstring USERNAME_KEY; static const sstring PASSWORD_KEY; - static const sstring ALLOW_ALL_AUTHENTICATOR_NAME; /** * Supported CREATE USER/ALTER USER options. diff --git a/auth/authorizer.cc b/auth/authorizer.cc index e48dc4485a..5df440a814 100644 --- a/auth/authorizer.cc +++ b/auth/authorizer.cc @@ -46,7 +46,10 @@ #include "db/config.hh" #include "utils/class_registrator.hh" -const sstring auth::authorizer::ALLOW_ALL_AUTHORIZER_NAME(auth::AUTH_PACKAGE_NAME + "AllowAllAuthorizer"); +const sstring& auth::allow_all_authorizer_name() { + static const sstring name = auth::auth::AUTH_PACKAGE_NAME + "AllowAllAuthorizer"; + return name; +} /** * Authenticator is assumed to be a fully state-less immutable object (note all the const). @@ -57,7 +60,7 @@ using authorizer_registry = class_registry; future<> auth::authorizer::setup(const sstring& type) { - if (type == ALLOW_ALL_AUTHORIZER_NAME) { + if (type == allow_all_authorizer_name()) { class allow_all_authorizer : public authorizer { public: future authorize(::shared_ptr, data_resource) const override { diff --git a/auth/authorizer.hh b/auth/authorizer.hh index 250693451b..043dd25751 100644 --- a/auth/authorizer.hh +++ b/auth/authorizer.hh @@ -69,10 +69,10 @@ struct permission_details { using std::experimental::optional; +const sstring& allow_all_authorizer_name(); + class authorizer { public: - static const sstring ALLOW_ALL_AUTHORIZER_NAME; - virtual ~authorizer() {} virtual future<> init() { diff --git a/auth/default_authorizer.cc b/auth/default_authorizer.cc index b200a61d02..b72f0aa52b 100644 --- a/auth/default_authorizer.cc +++ b/auth/default_authorizer.cc @@ -55,8 +55,10 @@ #include "exceptions/exceptions.hh" #include "log.hh" -const sstring auth::default_authorizer::DEFAULT_AUTHORIZER_NAME( - auth::AUTH_PACKAGE_NAME + "CassandraAuthorizer"); +const sstring& auth::default_authorizer_name() { + static const sstring name = auth::auth::AUTH_PACKAGE_NAME + "CassandraAuthorizer"; + return name; +} static const sstring USER_NAME = "username"; static const sstring RESOURCE_NAME = "resource"; @@ -64,8 +66,10 @@ static const sstring PERMISSIONS_NAME = "permissions"; static const sstring PERMISSIONS_CF = "permissions"; static logging::logger alogger("default_authorizer"); + +// To ensure correct initialization order, we unfortunately need to use a string literal. static const class_registrator password_auth_reg( - auth::default_authorizer::DEFAULT_AUTHORIZER_NAME); + "org.apache.cassandra.auth.CassandraAuthorizer"); auth::default_authorizer::default_authorizer() { } diff --git a/auth/default_authorizer.hh b/auth/default_authorizer.hh index fa31370301..bfef08ff65 100644 --- a/auth/default_authorizer.hh +++ b/auth/default_authorizer.hh @@ -45,10 +45,10 @@ namespace auth { +const sstring& default_authorizer_name(); + class default_authorizer : public authorizer { public: - static const sstring DEFAULT_AUTHORIZER_NAME; - default_authorizer(); ~default_authorizer(); diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index 1a41484629..19d865d655 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -54,7 +54,10 @@ #include "log.hh" #include "utils/class_registrator.hh" -const sstring auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME(auth::AUTH_PACKAGE_NAME + "PasswordAuthenticator"); +const sstring& auth::password_authenticator_name() { + static const sstring name = auth::AUTH_PACKAGE_NAME + "PasswordAuthenticator"; + return name; +} // name of the hash column. static const sstring SALTED_HASH = "salted_hash"; @@ -65,8 +68,9 @@ static const sstring CREDENTIALS_CF = "credentials"; static logging::logger plogger("password_authenticator"); +// To ensure correct initialization order, we unfortunately need to use a string literal. static const class_registrator password_auth_reg( - auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME); + "org.apache.cassandra.auth.PasswordAuthenticator"); auth::password_authenticator::~password_authenticator() {} @@ -185,7 +189,7 @@ db::consistency_level auth::password_authenticator::consistency_for_user(const s } const sstring& auth::password_authenticator::class_name() const { - return PASSWORD_AUTHENTICATOR_NAME; + return password_authenticator_name(); } bool auth::password_authenticator::require_authentication() const { diff --git a/auth/password_authenticator.hh b/auth/password_authenticator.hh index 3b7f19ccb8..2687dfd2c8 100644 --- a/auth/password_authenticator.hh +++ b/auth/password_authenticator.hh @@ -45,10 +45,10 @@ namespace auth { +const sstring& password_authenticator_name(); + class password_authenticator : public authenticator { public: - static const sstring PASSWORD_AUTHENTICATOR_NAME; - password_authenticator(); ~password_authenticator(); diff --git a/auth/transitional.cc b/auth/transitional.cc index 56c66d11d8..b623511f97 100644 --- a/auth/transitional.cc +++ b/auth/transitional.cc @@ -53,8 +53,11 @@ namespace auth { static const sstring PACKAGE_NAME("com.scylladb.auth."); -static const sstring TRANSITIONAL_AUTHENTICATOR_NAME(PACKAGE_NAME + "TransitionalAuthenticator"); -static const sstring TRANSITIONAL_AUTHORIZER_NAME(PACKAGE_NAME + "TransitionalAuthorizer"); + +static const sstring& transitional_authenticator_name() { + static const sstring name = PACKAGE_NAME + "TransitionalAuthenticator"; + return name; +} class transitional_authenticator : public authenticator { std::unique_ptr _authenticator; @@ -71,7 +74,7 @@ public: return _authenticator->init(); } const sstring& class_name() const override { - return TRANSITIONAL_AUTHENTICATOR_NAME; + return transitional_authenticator_name(); } bool require_authentication() const override { return true; @@ -188,9 +191,13 @@ public: } +// +// To ensure correct initialization order, we unfortunately need to use string literals. +// + static const class_registrator transitional_authenticator_reg( - auth::TRANSITIONAL_AUTHENTICATOR_NAME); + "com.scylladb.auth.TransitionalAuthenticator"); static const class_registrator transitional_authorizer_reg( - auth::TRANSITIONAL_AUTHORIZER_NAME); + "com.scylladb.auth.TransitionalAuthorizer"); diff --git a/tests/auth_test.cc b/tests/auth_test.cc index c820c0b1ea..e54563ee8f 100644 --- a/tests/auth_test.cc +++ b/tests/auth_test.cc @@ -75,25 +75,25 @@ SEASTAR_TEST_CASE(test_data_resource) { SEASTAR_TEST_CASE(test_default_authenticator) { return do_with_cql_env([](cql_test_env&) { BOOST_REQUIRE_EQUAL(auth::authenticator::get().require_authentication(), false); - BOOST_REQUIRE_EQUAL(auth::authenticator::get().class_name(), auth::authenticator::ALLOW_ALL_AUTHENTICATOR_NAME); + BOOST_REQUIRE_EQUAL(auth::authenticator::get().class_name(), auth::allow_all_authenticator_name()); return make_ready_future(); }); } SEASTAR_TEST_CASE(test_password_authenticator_attributes) { db::config cfg; - cfg.authenticator(auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME); + cfg.authenticator(auth::password_authenticator_name()); return do_with_cql_env([](cql_test_env&) { BOOST_REQUIRE_EQUAL(auth::authenticator::get().require_authentication(), true); - BOOST_REQUIRE_EQUAL(auth::authenticator::get().class_name(), auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME); + BOOST_REQUIRE_EQUAL(auth::authenticator::get().class_name(), auth::password_authenticator_name()); return make_ready_future(); }, cfg); } SEASTAR_TEST_CASE(test_auth_users) { db::config cfg; - cfg.authenticator(auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME); + cfg.authenticator(auth::password_authenticator_name()); return do_with_cql_env([](cql_test_env&) { return seastar::async([] { @@ -115,7 +115,7 @@ SEASTAR_TEST_CASE(test_auth_users) { SEASTAR_TEST_CASE(test_password_authenticator_operations) { db::config cfg; - cfg.authenticator(auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME); + cfg.authenticator(auth::password_authenticator_name()); /** * Not using seastar::async due to apparent ASan bug. @@ -197,7 +197,7 @@ SEASTAR_TEST_CASE(test_password_authenticator_operations) { SEASTAR_TEST_CASE(test_cassandra_hash) { db::config cfg; - cfg.authenticator(auth::password_authenticator::PASSWORD_AUTHENTICATOR_NAME); + cfg.authenticator(auth::password_authenticator_name()); return do_with_cql_env([](cql_test_env& env) { /** From 6f4241574cd0b06a849c1e282318bc9f8f5661a5 Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 7 Nov 2017 17:33:00 -0500 Subject: [PATCH 03/11] utils/loading_cache: Include necessary dependency --- utils/loading_cache.hh | 1 + 1 file changed, 1 insertion(+) diff --git a/utils/loading_cache.hh b/utils/loading_cache.hh index 9f008e81b9..d8a96ce2e9 100644 --- a/utils/loading_cache.hh +++ b/utils/loading_cache.hh @@ -26,6 +26,7 @@ #include #include +#include #include #include From 20b7f92b9cb46e700478c80f6b1a94895403a0ac Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 14:18:50 -0500 Subject: [PATCH 04/11] auth: Extract `permissions_cache` In addition to improving clarity, this makes the cache testable. There shouldn't be any functional changes. --- auth/auth.cc | 77 ++++++++----------------------- auth/permissions_cache.cc | 37 +++++++++++++++ auth/permissions_cache.hh | 96 +++++++++++++++++++++++++++++++++++++++ configure.py | 1 + 4 files changed, 152 insertions(+), 59 deletions(-) create mode 100644 auth/permissions_cache.cc create mode 100644 auth/permissions_cache.hh diff --git a/auth/auth.cc b/auth/auth.cc index 365674abc8..a4006988da 100644 --- a/auth/auth.cc +++ b/auth/auth.cc @@ -52,6 +52,7 @@ #include "cql3/statements/create_table_statement.hh" #include "db/config.hh" #include "delayed_tasks.hh" +#include "permissions_cache.hh" #include "service/migration_manager.hh" #include "utils/loading_cache.hh" #include "utils/hash.hh" @@ -99,62 +100,19 @@ class auth_migration_listener : public service::migration_listener { static auth_migration_listener auth_migration; -namespace std { -template <> -struct hash { - size_t operator()(const auth::data_resource & v) const { - return v.hash_value(); - } -}; +static sharded perm_cache; -template <> -struct hash { - size_t operator()(const auth::authenticated_user & v) const { - return utils::tuple_hash()(v.name(), v.is_anonymous()); - } -}; +static future<> start_permission_cache() { + auto& db_config = cql3::get_local_query_processor().db().local().get_config(); + + auth::permissions_cache_config c; + c.max_entries = db_config.permissions_cache_max_entries(); + c.validity_period = std::chrono::milliseconds(db_config.permissions_validity_in_ms()); + c.update_period = std::chrono::milliseconds(db_config.permissions_update_interval_in_ms()); + + return perm_cache.start(c, std::ref(auth::authorizer::get()), std::ref(alogger)); } -class auth::auth::permissions_cache { -public: - typedef utils::loading_cache, permission_set, utils::loading_cache_reload_enabled::yes, utils::simple_entry_size, utils::tuple_hash> cache_type; - typedef typename cache_type::key_type key_type; - - permissions_cache() - : permissions_cache( - cql3::get_local_query_processor().db().local().get_config()) { - } - - permissions_cache(const db::config& cfg) - : _cache(cfg.permissions_cache_max_entries(), std::chrono::milliseconds(cfg.permissions_validity_in_ms()), std::chrono::milliseconds(cfg.permissions_update_interval_in_ms()), alogger, - [] (const key_type& k) { - alogger.debug("Refreshing permissions for {}", k.first.name()); - return authorizer::get().authorize(::make_shared(k.first), k.second); - }) {} - - future<> stop() { - return _cache.stop(); - } - - future get(::shared_ptr user, data_resource resource) { - return _cache.get(key_type(*user, std::move(resource))); - } - -private: - cache_type _cache; -}; - -namespace std { // for ADL, yuch - -std::ostream& operator<<(std::ostream& os, const std::pair& p) { - os << "{user: " << p.first.name() << ", data_resource: " << p.second << "}"; - return os; -} - -} - -static distributed perm_cache; - static delayed_tasks<>& get_local_delayed_tasks() { static thread_local delayed_tasks<> instance; return instance; @@ -168,20 +126,19 @@ future<> auth::auth::setup() { auto& db = cql3::get_local_query_processor().db().local(); auto& cfg = db.get_config(); - future<> f = perm_cache.start(); - qualified_name authenticator_name(AUTH_PACKAGE_NAME, cfg.authenticator()), authorizer_name(AUTH_PACKAGE_NAME, cfg.authorizer()); if (allow_all_authenticator_name() == authenticator_name && allow_all_authorizer_name() == authorizer_name) { - // just create the objects - return f.then([authenticator_name = std::move(authenticator_name)] { - return authenticator::setup(authenticator_name); - }).then([authorizer_name = std::move(authorizer_name)] { + return authenticator::setup(authenticator_name).then([authorizer_name = std::move(authorizer_name)] { return authorizer::setup(authorizer_name); + }).then([] { + return start_permission_cache(); }); } + future<> f = make_ready_future<>(); + if (!db.has_keyspace(AUTH_KS)) { std::map opts; opts["replication_factor"] = "1"; @@ -198,6 +155,8 @@ future<> auth::auth::setup() { return authenticator::setup(authenticator_name); }).then([authorizer_name = std::move(authorizer_name)] { return authorizer::setup(authorizer_name); + }).then([] { + return start_permission_cache(); }).then([] { service::get_local_migration_manager().register_listener(&auth_migration); // again, only one shard... // instead of once-timer, just schedule this later diff --git a/auth/permissions_cache.cc b/auth/permissions_cache.cc new file mode 100644 index 0000000000..d368107cf7 --- /dev/null +++ b/auth/permissions_cache.cc @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include "auth/permissions_cache.hh" + +namespace auth { + +permissions_cache::permissions_cache(const permissions_cache_config& c, authorizer& a, logging::logger& log) + : _cache(c.max_entries, c.validity_period, c.update_period, log, [&a, &log](const key_type& k) { + log.debug("Refreshing permissions for {}", k.first.name()); + return a.authorize(::make_shared(k.first), k.second); + }) { +} + +future permissions_cache::get(::shared_ptr user, data_resource r) { + return _cache.get(key_type(*user, r)); +} + +} diff --git a/auth/permissions_cache.hh b/auth/permissions_cache.hh new file mode 100644 index 0000000000..5fd4dbd2a0 --- /dev/null +++ b/auth/permissions_cache.hh @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "auth/authenticated_user.hh" +#include "auth/authorizer.hh" +#include "auth/data_resource.hh" +#include "auth/permission.hh" +#include "log.hh" +#include "utils/loading_cache.hh" + +namespace std { + +template <> +struct hash final { + size_t operator()(const auth::data_resource & v) const { + return v.hash_value(); + } +}; + +template <> +struct hash final { + size_t operator()(const auth::authenticated_user & v) const { + return utils::tuple_hash()(v.name(), v.is_anonymous()); + } +}; + +inline std::ostream& operator<<(std::ostream& os, const std::pair& p) { + os << "{user: " << p.first.name() << ", data_resource: " << p.second << "}"; + return os; +} + +} + +namespace auth { + +struct permissions_cache_config final { + std::size_t max_entries; + std::chrono::milliseconds validity_period; + std::chrono::milliseconds update_period; +}; + +class permissions_cache final { + using cache_type = utils::loading_cache< + std::pair, + permission_set, + utils::loading_cache_reload_enabled::yes, + utils::simple_entry_size, + utils::tuple_hash>; + + using key_type = typename cache_type::key_type; + + cache_type _cache; + +public: + explicit permissions_cache(const permissions_cache_config&, authorizer&, logging::logger&); + + future<> start() { + return make_ready_future<>(); + } + + future <> stop() { + return _cache.stop(); + } + + future get(::shared_ptr, data_resource); +}; + +} diff --git a/configure.py b/configure.py index 9663626b15..7c76a0ef31 100755 --- a/configure.py +++ b/configure.py @@ -532,6 +532,7 @@ scylla_core = (['database.cc', 'auth/data_resource.cc', 'auth/password_authenticator.cc', 'auth/permission.cc', + 'auth/permissions_cache.cc', 'auth/transitional.cc', 'tracing/tracing.cc', 'tracing/trace_keyspace_helper.cc', From 22670cae82f9e3ecf6159a8094f42dfe8741112e Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Wed, 8 Nov 2017 10:45:25 -0500 Subject: [PATCH 05/11] auth: Don't expose internal constant --- auth/auth.cc | 7 +++++-- auth/auth.hh | 2 -- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/auth/auth.cc b/auth/auth.cc index a4006988da..67ccb78214 100644 --- a/auth/auth.cc +++ b/auth/auth.cc @@ -38,6 +38,9 @@ * You should have received a copy of the GNU General Public License * along with Scylla. If not, see . */ + +#include + #include #include @@ -69,7 +72,7 @@ static logging::logger alogger("auth"); // TODO: configurable using namespace std::chrono_literals; -const std::chrono::milliseconds auth::auth::SUPERUSER_SETUP_DELAY = 10000ms; +static const std::chrono::milliseconds SUPERUSER_SETUP_DELAY = 10000ms; class auth_migration_listener : public service::migration_listener { void on_create_keyspace(const sstring& ks_name) override {} @@ -119,7 +122,7 @@ static delayed_tasks<>& get_local_delayed_tasks() { } void auth::auth::schedule_when_up(scheduled_func f) { - get_local_delayed_tasks().schedule_after(auth::SUPERUSER_SETUP_DELAY, std::move(f)); + get_local_delayed_tasks().schedule_after(SUPERUSER_SETUP_DELAY, std::move(f)); } future<> auth::auth::setup() { diff --git a/auth/auth.hh b/auth/auth.hh index 3409805471..c452f6baa3 100644 --- a/auth/auth.hh +++ b/auth/auth.hh @@ -41,7 +41,6 @@ #pragma once -#include #include #include #include @@ -62,7 +61,6 @@ public: static const sstring AUTH_KS; static const sstring USERS_CF; static const sstring AUTH_PACKAGE_NAME; - static const std::chrono::milliseconds SUPERUSER_SETUP_DELAY; static future get_permissions(::shared_ptr, data_resource); From 5825e373102b04e0ca1806448af29ecbd791d4d2 Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 14:58:00 -0500 Subject: [PATCH 06/11] auth: Move metadata constants This change is motivated partly be aesthetics, but more significantly due to the future work to refactor `auth` into a sharded service. Since doing so will require writing `auth::auth` from scratch, these constants (and other common functionality) need a new home. --- auth/auth.cc | 42 +++++++++---------- auth/auth.hh | 5 --- auth/authenticator.cc | 3 +- auth/authorizer.cc | 3 +- auth/common.cc | 35 ++++++++++++++++ auth/common.hh | 39 +++++++++++++++++ auth/default_authorizer.cc | 19 +++++---- auth/password_authenticator.cc | 21 +++++----- configure.py | 1 + cql3/statements/list_permissions_statement.cc | 3 +- cql3/statements/list_users_statement.cc | 6 +-- service/client_state.cc | 3 +- 12 files changed, 126 insertions(+), 54 deletions(-) create mode 100644 auth/common.cc create mode 100644 auth/common.hh diff --git a/auth/auth.cc b/auth/auth.cc index 67ccb78214..227683b136 100644 --- a/auth/auth.cc +++ b/auth/auth.cc @@ -48,6 +48,7 @@ #include "auth.hh" #include "authenticator.hh" #include "authorizer.hh" +#include "common.hh" #include "database.hh" #include "cql3/query_processor.hh" #include "cql3/untyped_result_set.hh" @@ -60,11 +61,6 @@ #include "utils/loading_cache.hh" #include "utils/hash.hh" -const sstring auth::auth::DEFAULT_SUPERUSER_NAME("cassandra"); -const sstring auth::auth::AUTH_KS("system_auth"); -const sstring auth::auth::USERS_CF("users"); -const sstring auth::auth::AUTH_PACKAGE_NAME("org.apache.cassandra.auth."); - static const sstring USER_NAME("name"); static const sstring SUPER("super"); @@ -129,8 +125,8 @@ future<> auth::auth::setup() { auto& db = cql3::get_local_query_processor().db().local(); auto& cfg = db.get_config(); - qualified_name authenticator_name(AUTH_PACKAGE_NAME, cfg.authenticator()), - authorizer_name(AUTH_PACKAGE_NAME, cfg.authorizer()); + qualified_name authenticator_name(meta::AUTH_PACKAGE_NAME, cfg.authenticator()), + authorizer_name(meta::AUTH_PACKAGE_NAME, cfg.authorizer()); if (allow_all_authenticator_name() == authenticator_name && allow_all_authorizer_name() == authorizer_name) { return authenticator::setup(authenticator_name).then([authorizer_name = std::move(authorizer_name)] { @@ -142,17 +138,17 @@ future<> auth::auth::setup() { future<> f = make_ready_future<>(); - if (!db.has_keyspace(AUTH_KS)) { + if (!db.has_keyspace(meta::AUTH_KS)) { std::map opts; opts["replication_factor"] = "1"; - auto ksm = keyspace_metadata::new_keyspace(AUTH_KS, "org.apache.cassandra.locator.SimpleStrategy", opts, true); + auto ksm = keyspace_metadata::new_keyspace(meta::AUTH_KS, "org.apache.cassandra.locator.SimpleStrategy", opts, true); // We use min_timestamp so that default keyspace metadata will loose with any manual adjustments. See issue #2129. f = service::get_local_migration_manager().announce_new_keyspace(ksm, api::min_timestamp, false); } return f.then([] { - return setup_table(USERS_CF, sprint("CREATE TABLE %s.%s (%s text, %s boolean, PRIMARY KEY(%s)) WITH gc_grace_seconds=%d", - AUTH_KS, USERS_CF, USER_NAME, SUPER, USER_NAME, + return setup_table(meta::USERS_CF, sprint("CREATE TABLE %s.%s (%s text, %s boolean, PRIMARY KEY(%s)) WITH gc_grace_seconds=%d", + meta::AUTH_KS, meta::USERS_CF, USER_NAME, SUPER, USER_NAME, 90 * 24 * 60 * 60)); // 3 months. }).then([authenticator_name = std::move(authenticator_name)] { return authenticator::setup(authenticator_name); @@ -165,12 +161,12 @@ future<> auth::auth::setup() { // instead of once-timer, just schedule this later schedule_when_up([] { // setup default super user - return has_existing_users(USERS_CF, DEFAULT_SUPERUSER_NAME, USER_NAME).then([](bool exists) { + return has_existing_users(meta::USERS_CF, meta::DEFAULT_SUPERUSER_NAME, USER_NAME).then([](bool exists) { if (!exists) { auto query = sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", - AUTH_KS, USERS_CF, USER_NAME, SUPER); - cql3::get_local_query_processor().process(query, db::consistency_level::ONE, {DEFAULT_SUPERUSER_NAME, true}).then([](auto) { - alogger.info("Created default superuser '{}'", DEFAULT_SUPERUSER_NAME); + meta::AUTH_KS, meta::USERS_CF, USER_NAME, SUPER); + cql3::get_local_query_processor().process(query, db::consistency_level::ONE, {meta::DEFAULT_SUPERUSER_NAME, true}).then([](auto) { + alogger.info("Created default superuser '{}'", meta::DEFAULT_SUPERUSER_NAME); }).handle_exception([](auto ep) { try { std::rethrow_exception(ep); @@ -200,7 +196,7 @@ future auth::auth::get_permissions(::shared_ptr> select_user(const sstring& // that a map lookup string->statement is not gonna kill us much. return cql3::get_local_query_processor().process( sprint("SELECT * FROM %s.%s WHERE %s = ?", - auth::auth::AUTH_KS, auth::auth::USERS_CF, + auth::meta::AUTH_KS, auth::meta::USERS_CF, USER_NAME), consistency_for_user(username), { username }, true); } @@ -235,13 +231,13 @@ future auth::auth::is_super_user(const sstring& username) { future<> auth::auth::insert_user(const sstring& username, bool is_super) { return cql3::get_local_query_processor().process(sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?)", - AUTH_KS, USERS_CF, USER_NAME, SUPER), + meta::AUTH_KS, meta::USERS_CF, USER_NAME, SUPER), consistency_for_user(username), { username, is_super }).discard_result(); } future<> auth::auth::delete_user(const sstring& username) { return cql3::get_local_query_processor().process(sprint("DELETE FROM %s.%s WHERE %s = ?", - AUTH_KS, USERS_CF, USER_NAME), + meta::AUTH_KS, meta::USERS_CF, USER_NAME), consistency_for_user(username), { username }).discard_result(); } @@ -249,13 +245,13 @@ future<> auth::auth::setup_table(const sstring& name, const sstring& cql) { auto& qp = cql3::get_local_query_processor(); auto& db = qp.db().local(); - if (db.has_schema(AUTH_KS, name)) { + if (db.has_schema(meta::AUTH_KS, name)) { return make_ready_future(); } ::shared_ptr parsed = static_pointer_cast< cql3::statements::raw::cf_statement>(cql3::query_processor::parse_statement(cql)); - parsed->prepare_keyspace(AUTH_KS); + parsed->prepare_keyspace(meta::AUTH_KS); ::shared_ptr statement = static_pointer_cast( parsed->prepare(db, qp.get_cql_stats())->statement); @@ -268,8 +264,8 @@ future<> auth::auth::setup_table(const sstring& name, const sstring& cql) { } future auth::auth::has_existing_users(const sstring& cfname, const sstring& def_user_name, const sstring& name_column) { - auto default_user_query = sprint("SELECT * FROM %s.%s WHERE %s = ?", AUTH_KS, cfname, name_column); - auto all_users_query = sprint("SELECT * FROM %s.%s LIMIT 1", AUTH_KS, cfname); + auto default_user_query = sprint("SELECT * FROM %s.%s WHERE %s = ?", meta::AUTH_KS, cfname, name_column); + auto all_users_query = sprint("SELECT * FROM %s.%s LIMIT 1", meta::AUTH_KS, cfname); return cql3::get_local_query_processor().process(default_user_query, db::consistency_level::ONE, { def_user_name }).then([=](::shared_ptr res) { if (!res->empty()) { diff --git a/auth/auth.hh b/auth/auth.hh index c452f6baa3..53eb775031 100644 --- a/auth/auth.hh +++ b/auth/auth.hh @@ -57,11 +57,6 @@ class auth { public: class permissions_cache; - static const sstring DEFAULT_SUPERUSER_NAME; - static const sstring AUTH_KS; - static const sstring USERS_CF; - static const sstring AUTH_PACKAGE_NAME; - static future get_permissions(::shared_ptr, data_resource); /** diff --git a/auth/authenticator.cc b/auth/authenticator.cc index 329c284507..1d34201c89 100644 --- a/auth/authenticator.cc +++ b/auth/authenticator.cc @@ -41,6 +41,7 @@ #include "authenticator.hh" #include "authenticated_user.hh" +#include "common.hh" #include "password_authenticator.hh" #include "auth.hh" #include "db/config.hh" @@ -50,7 +51,7 @@ const sstring auth::authenticator::USERNAME_KEY("username"); const sstring auth::authenticator::PASSWORD_KEY("password"); const sstring& auth::allow_all_authenticator_name() { - static const sstring name = auth::AUTH_PACKAGE_NAME + "AllowAllAuthenticator"; + static const sstring name = meta::AUTH_PACKAGE_NAME + "AllowAllAuthenticator"; return name; } diff --git a/auth/authorizer.cc b/auth/authorizer.cc index 5df440a814..9e0b2e54c6 100644 --- a/auth/authorizer.cc +++ b/auth/authorizer.cc @@ -41,13 +41,14 @@ #include "authorizer.hh" #include "authenticated_user.hh" +#include "common.hh" #include "default_authorizer.hh" #include "auth.hh" #include "db/config.hh" #include "utils/class_registrator.hh" const sstring& auth::allow_all_authorizer_name() { - static const sstring name = auth::auth::AUTH_PACKAGE_NAME + "AllowAllAuthorizer"; + static const sstring name = meta::AUTH_PACKAGE_NAME + "AllowAllAuthorizer"; return name; } diff --git a/auth/common.cc b/auth/common.cc new file mode 100644 index 0000000000..7f70f6353c --- /dev/null +++ b/auth/common.cc @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include "auth/common.hh" + +namespace auth { + +namespace meta { + +const sstring DEFAULT_SUPERUSER_NAME("cassandra"); +const sstring AUTH_KS("system_auth"); +const sstring USERS_CF("users"); +const sstring AUTH_PACKAGE_NAME("org.apache.cassandra.auth."); + +} + +} diff --git a/auth/common.hh b/auth/common.hh new file mode 100644 index 0000000000..c62a911662 --- /dev/null +++ b/auth/common.hh @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include + +#include "seastarx.hh" + +namespace auth { + +namespace meta { + +extern const sstring DEFAULT_SUPERUSER_NAME; +extern const sstring AUTH_KS; +extern const sstring USERS_CF; +extern const sstring AUTH_PACKAGE_NAME; + +} + +} diff --git a/auth/default_authorizer.cc b/auth/default_authorizer.cc index b72f0aa52b..d247247726 100644 --- a/auth/default_authorizer.cc +++ b/auth/default_authorizer.cc @@ -47,6 +47,7 @@ #include #include "auth.hh" +#include "common.hh" #include "default_authorizer.hh" #include "authenticated_user.hh" #include "permission.hh" @@ -56,7 +57,7 @@ #include "log.hh" const sstring& auth::default_authorizer_name() { - static const sstring name = auth::auth::AUTH_PACKAGE_NAME + "CassandraAuthorizer"; + static const sstring name = meta::AUTH_PACKAGE_NAME + "CassandraAuthorizer"; return name; } @@ -82,7 +83,7 @@ future<> auth::default_authorizer::init() { "%s text," "%s set," "PRIMARY KEY(%s, %s)" - ") WITH gc_grace_seconds=%d", auth::auth::AUTH_KS, + ") WITH gc_grace_seconds=%d", meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, USER_NAME, RESOURCE_NAME, 90 * 24 * 60 * 60); // 3 months. @@ -103,7 +104,7 @@ future auth::default_authorizer::authorize( */ auto& qp = cql3::get_local_query_processor(); auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? AND %s = ?" - , PERMISSIONS_NAME, auth::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME); + , PERMISSIONS_NAME, meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME); return qp.process(query, db::consistency_level::LOCAL_ONE, {user->name(), resource.name() }) .then_wrapped([=](future<::shared_ptr> f) { try { @@ -129,7 +130,7 @@ future<> auth::default_authorizer::modify( // TODO: why does this not check super user? auto& qp = cql3::get_local_query_processor(); auto query = sprint("UPDATE %s.%s SET %s = %s %s ? WHERE %s = ? AND %s = ?", - auth::AUTH_KS, PERMISSIONS_CF, PERMISSIONS_NAME, + meta::AUTH_KS, PERMISSIONS_CF, PERMISSIONS_NAME, PERMISSIONS_NAME, op, USER_NAME, RESOURCE_NAME); return qp.process(query, db::consistency_level::ONE, { permissions::to_strings(set), user, resource.name() }).discard_result(); @@ -156,7 +157,7 @@ future> auth::default_authorizer::list( throw exceptions::unauthorized_exception(sprint("You are not authorized to view %s's permissions", user ? *user : "everyone")); } - auto query = sprint("SELECT %s, %s, %s FROM %s.%s", USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, auth::AUTH_KS, PERMISSIONS_CF); + auto query = sprint("SELECT %s, %s, %s FROM %s.%s", USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, meta::AUTH_KS, PERMISSIONS_CF); auto& qp = cql3::get_local_query_processor(); // Oh, look, it is a case where it does not pay off to have @@ -196,7 +197,7 @@ future> auth::default_authorizer::list( future<> auth::default_authorizer::revoke_all(sstring dropped_user) { auto& qp = cql3::get_local_query_processor(); - auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", auth::AUTH_KS, + auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", meta::AUTH_KS, PERMISSIONS_CF, USER_NAME); return qp.process(query, db::consistency_level::ONE, { dropped_user }).discard_result().handle_exception( [dropped_user](auto ep) { @@ -211,14 +212,14 @@ future<> auth::default_authorizer::revoke_all(sstring dropped_user) { future<> auth::default_authorizer::revoke_all(data_resource resource) { auto& qp = cql3::get_local_query_processor(); auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? ALLOW FILTERING", - USER_NAME, auth::AUTH_KS, PERMISSIONS_CF, RESOURCE_NAME); + USER_NAME, meta::AUTH_KS, PERMISSIONS_CF, RESOURCE_NAME); return qp.process(query, db::consistency_level::LOCAL_ONE, { resource.name() }) .then_wrapped([resource, &qp](future<::shared_ptr> f) { try { auto res = f.get0(); return parallel_for_each(res->begin(), res->end(), [&qp, res, resource](const cql3::untyped_result_set::row& r) { auto query = sprint("DELETE FROM %s.%s WHERE %s = ? AND %s = ?" - , auth::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME); + , meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME); return qp.process(query, db::consistency_level::LOCAL_ONE, { r.get_as(USER_NAME), resource.name() }) .discard_result().handle_exception([resource](auto ep) { try { @@ -238,7 +239,7 @@ future<> auth::default_authorizer::revoke_all(data_resource resource) { const auth::resource_ids& auth::default_authorizer::protected_resources() { - static const resource_ids ids({ data_resource(auth::AUTH_KS, PERMISSIONS_CF) }); + static const resource_ids ids({ data_resource(meta::AUTH_KS, PERMISSIONS_CF) }); return ids; } diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index 19d865d655..52a0adde99 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -47,6 +47,7 @@ #include #include "auth.hh" +#include "common.hh" #include "password_authenticator.hh" #include "authenticated_user.hh" #include "cql3/query_processor.hh" @@ -55,15 +56,15 @@ #include "utils/class_registrator.hh" const sstring& auth::password_authenticator_name() { - static const sstring name = auth::AUTH_PACKAGE_NAME + "PasswordAuthenticator"; + static const sstring name = meta::AUTH_PACKAGE_NAME + "PasswordAuthenticator"; return name; } // name of the hash column. static const sstring SALTED_HASH = "salted_hash"; static const sstring USER_NAME = "username"; -static const sstring DEFAULT_USER_NAME = auth::auth::DEFAULT_SUPERUSER_NAME; -static const sstring DEFAULT_USER_PASSWORD = auth::auth::DEFAULT_SUPERUSER_NAME; +static const sstring DEFAULT_USER_NAME = auth::meta::DEFAULT_SUPERUSER_NAME; +static const sstring DEFAULT_USER_PASSWORD = auth::meta::DEFAULT_SUPERUSER_NAME; static const sstring CREDENTIALS_CF = "credentials"; static logging::logger plogger("password_authenticator"); @@ -158,7 +159,7 @@ future<> auth::password_authenticator::init() { "options map,"// for future extensions "PRIMARY KEY(%s)" ") WITH gc_grace_seconds=%d", - auth::auth::AUTH_KS, + meta::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH, USER_NAME, 90 * 24 * 60 * 60); // 3 months. @@ -168,7 +169,7 @@ future<> auth::password_authenticator::init() { return auth::has_existing_users(CREDENTIALS_CF, DEFAULT_USER_NAME, USER_NAME).then([](bool exists) { if (!exists) { cql3::get_local_query_processor().process(sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", - auth::AUTH_KS, + meta::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH ), @@ -224,7 +225,7 @@ future<::shared_ptr > auth::password_authenticator::au return futurize_apply([this, username, password] { auto& qp = cql3::get_local_query_processor(); return qp.process(sprint("SELECT %s FROM %s.%s WHERE %s = ?", SALTED_HASH, - auth::AUTH_KS, CREDENTIALS_CF, USER_NAME), + meta::AUTH_KS, CREDENTIALS_CF, USER_NAME), consistency_for_user(username), {username}, true); }).then_wrapped([=](future<::shared_ptr> f) { try { @@ -248,7 +249,7 @@ future<> auth::password_authenticator::create(sstring username, try { auto password = boost::any_cast(options.at(option::PASSWORD)); auto query = sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?)", - auth::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH); + meta::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH); auto& qp = cql3::get_local_query_processor(); return qp.process(query, consistency_for_user(username), { username, hashpw(password) }).discard_result(); } catch (std::out_of_range&) { @@ -261,7 +262,7 @@ future<> auth::password_authenticator::alter(sstring username, try { auto password = boost::any_cast(options.at(option::PASSWORD)); auto query = sprint("UPDATE %s.%s SET %s = ? WHERE %s = ?", - auth::AUTH_KS, CREDENTIALS_CF, SALTED_HASH, USER_NAME); + meta::AUTH_KS, CREDENTIALS_CF, SALTED_HASH, USER_NAME); auto& qp = cql3::get_local_query_processor(); return qp.process(query, consistency_for_user(username), { hashpw(password), username }).discard_result(); } catch (std::out_of_range&) { @@ -272,7 +273,7 @@ future<> auth::password_authenticator::alter(sstring username, future<> auth::password_authenticator::drop(sstring username) { try { auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", - auth::AUTH_KS, CREDENTIALS_CF, USER_NAME); + meta::AUTH_KS, CREDENTIALS_CF, USER_NAME); auto& qp = cql3::get_local_query_processor(); return qp.process(query, consistency_for_user(username), { username }).discard_result(); } catch (std::out_of_range&) { @@ -281,7 +282,7 @@ future<> auth::password_authenticator::drop(sstring username) { } const auth::resource_ids& auth::password_authenticator::protected_resources() const { - static const resource_ids ids({ data_resource(auth::AUTH_KS, CREDENTIALS_CF) }); + static const resource_ids ids({ data_resource(meta::AUTH_KS, CREDENTIALS_CF) }); return ids; } diff --git a/configure.py b/configure.py index 7c76a0ef31..0805fedf5f 100755 --- a/configure.py +++ b/configure.py @@ -528,6 +528,7 @@ scylla_core = (['database.cc', 'auth/authenticated_user.cc', 'auth/authenticator.cc', 'auth/authorizer.cc', + 'auth/common.cc', 'auth/default_authorizer.cc', 'auth/data_resource.cc', 'auth/password_authenticator.cc', diff --git a/cql3/statements/list_permissions_statement.cc b/cql3/statements/list_permissions_statement.cc index 1a9d29c7fe..c24f9a9ad5 100644 --- a/cql3/statements/list_permissions_statement.cc +++ b/cql3/statements/list_permissions_statement.cc @@ -45,6 +45,7 @@ #include "list_permissions_statement.hh" #include "auth/authorizer.hh" #include "auth/auth.hh" +#include "auth/common.hh" #include "cql3/result_set.hh" #include "transport/messages/result_message.hh" @@ -84,7 +85,7 @@ future<> cql3::statements::list_permissions_statement::check_access(const servic future<::shared_ptr> cql3::statements::list_permissions_statement::execute(distributed& proxy, service::query_state& state, const query_options& options) { static auto make_column = [](sstring name) { - return ::make_shared(auth::auth::AUTH_KS, "permissions", ::make_shared(std::move(name), true), utf8_type); + return ::make_shared(auth::meta::AUTH_KS, "permissions", ::make_shared(std::move(name), true), utf8_type); }; static thread_local const std::vector<::shared_ptr> metadata({ make_column("username"), make_column("resource"), make_column("permission") diff --git a/cql3/statements/list_users_statement.cc b/cql3/statements/list_users_statement.cc index 838b32d8bd..8cde44246b 100644 --- a/cql3/statements/list_users_statement.cc +++ b/cql3/statements/list_users_statement.cc @@ -42,7 +42,7 @@ #include "list_users_statement.hh" #include "cql3/query_processor.hh" #include "cql3/query_options.hh" -#include "auth/auth.hh" +#include "auth/common.hh" void cql3::statements::list_users_statement::validate(distributed& proxy, const service::client_state& state) { } @@ -57,7 +57,7 @@ cql3::statements::list_users_statement::execute(distributed(service::client_state::for_internal_calls()); auto io = std::make_unique(db::consistency_level::QUORUM, std::vector{}); auto f = get_local_query_processor().process( - sprint("SELECT * FROM %s.%s", auth::auth::AUTH_KS, - auth::auth::USERS_CF), *is, *io); + sprint("SELECT * FROM %s.%s", auth::meta::AUTH_KS, + auth::meta::USERS_CF), *is, *io); return f.finally([is = std::move(is), io = std::move(io)] {}); } diff --git a/service/client_state.cc b/service/client_state.cc index 65b8e88afb..6e8a8cc5b4 100644 --- a/service/client_state.cc +++ b/service/client_state.cc @@ -43,6 +43,7 @@ #include "auth/auth.hh" #include "auth/authorizer.hh" #include "auth/authenticator.hh" +#include "auth/common.hh" #include "exceptions/exceptions.hh" #include "validation.hh" #include "db/system_keyspace.hh" @@ -138,7 +139,7 @@ future<> service::client_state::has_access(const sstring& ks, auth::permission p } // we want to allow altering AUTH_KS and TRACING_KS. - for (auto& n : { auth::auth::AUTH_KS, tracing::trace_keyspace_helper::KEYSPACE_NAME }) { + for (auto& n : { auth::meta::AUTH_KS, tracing::trace_keyspace_helper::KEYSPACE_NAME }) { if (name == n && p == auth::permission::DROP) { throw exceptions::unauthorized_exception(sprint("Cannot %s %s", auth::permissions::to_string(p), resource)); } From 9aff5d9a77b5df41fecc176c6e154f2bdfc911d4 Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Wed, 8 Nov 2017 18:57:55 -0500 Subject: [PATCH 07/11] auth: Make life-time control more consistent --- auth/auth.cc | 2 ++ auth/authenticator.cc | 8 +++++++- auth/authenticator.hh | 6 +++--- auth/authorizer.cc | 8 +++++++- auth/authorizer.hh | 6 +++--- auth/default_authorizer.cc | 5 ++++- auth/default_authorizer.hh | 4 +++- auth/password_authenticator.cc | 6 +++++- auth/password_authenticator.hh | 4 +++- auth/transitional.cc | 14 ++++++++++---- 10 files changed, 47 insertions(+), 16 deletions(-) diff --git a/auth/auth.cc b/auth/auth.cc index 227683b136..75e1563523 100644 --- a/auth/auth.cc +++ b/auth/auth.cc @@ -188,6 +188,8 @@ future<> auth::auth::shutdown() { get_local_delayed_tasks().cancel_all(); }).then([] { return perm_cache.stop(); + }).then([] { + return when_all_succeed(authorizer::get().stop(), authenticator::get().stop()); }); } diff --git a/auth/authenticator.cc b/auth/authenticator.cc index 1d34201c89..45e192cd0b 100644 --- a/auth/authenticator.cc +++ b/auth/authenticator.cc @@ -84,6 +84,12 @@ auth::authenticator::setup(const sstring& type) { if (type == allow_all_authenticator_name()) { class allow_all_authenticator : public authenticator { public: + future<> start() override { + return make_ready_future<>(); + } + future<> stop() override { + return make_ready_future<>(); + } const sstring& class_name() const override { return allow_all_authenticator_name(); } @@ -120,7 +126,7 @@ auth::authenticator::setup(const sstring& type) { return make_ready_future(); } else { auto a = authenticator_registry::create(type); - auto f = a->init(); + auto f = a->start(); return f.then([a = std::move(a)]() mutable { global_authenticator = std::move(a); }); diff --git a/auth/authenticator.hh b/auth/authenticator.hh index 1e3c512a0b..d46d791598 100644 --- a/auth/authenticator.hh +++ b/auth/authenticator.hh @@ -103,9 +103,9 @@ public: virtual ~authenticator() {} - virtual future<> init() { - return make_ready_future(); - } + virtual future<> start() = 0; + + virtual future<> stop() = 0; virtual const sstring& class_name() const = 0; diff --git a/auth/authorizer.cc b/auth/authorizer.cc index 9e0b2e54c6..4935cf946d 100644 --- a/auth/authorizer.cc +++ b/auth/authorizer.cc @@ -64,6 +64,12 @@ auth::authorizer::setup(const sstring& type) { if (type == allow_all_authorizer_name()) { class allow_all_authorizer : public authorizer { public: + future<> start() override { + return make_ready_future<>(); + } + future<> stop() override { + return make_ready_future<>(); + } future authorize(::shared_ptr, data_resource) const override { return make_ready_future(permissions::ALL); } @@ -95,7 +101,7 @@ auth::authorizer::setup(const sstring& type) { return make_ready_future(); } else { auto a = authorizer_registry::create(type); - auto f = a->init(); + auto f = a->start(); return f.then([a = std::move(a)]() mutable { global_authorizer = std::move(a); }); diff --git a/auth/authorizer.hh b/auth/authorizer.hh index 043dd25751..49463edae4 100644 --- a/auth/authorizer.hh +++ b/auth/authorizer.hh @@ -75,9 +75,9 @@ class authorizer { public: virtual ~authorizer() {} - virtual future<> init() { - return make_ready_future(); - } + virtual future<> start() = 0; + + virtual future<> stop() = 0; /** * The primary Authorizer method. Returns a set of permissions of a user on a resource. diff --git a/auth/default_authorizer.cc b/auth/default_authorizer.cc index d247247726..0d0675230c 100644 --- a/auth/default_authorizer.cc +++ b/auth/default_authorizer.cc @@ -77,7 +77,7 @@ auth::default_authorizer::default_authorizer() { auth::default_authorizer::~default_authorizer() { } -future<> auth::default_authorizer::init() { +future<> auth::default_authorizer::start() { sstring create_table = sprint("CREATE TABLE %s.%s (" "%s text," "%s text," @@ -90,6 +90,9 @@ future<> auth::default_authorizer::init() { return auth::setup_table(PERMISSIONS_CF, create_table); } +future<> auth::default_authorizer::stop() { + return make_ready_future<>(); +} future auth::default_authorizer::authorize( ::shared_ptr user, data_resource resource) const { diff --git a/auth/default_authorizer.hh b/auth/default_authorizer.hh index bfef08ff65..79137b9608 100644 --- a/auth/default_authorizer.hh +++ b/auth/default_authorizer.hh @@ -52,7 +52,9 @@ public: default_authorizer(); ~default_authorizer(); - future<> init() override; + future<> start() override; + + future<> stop() override; future authorize(::shared_ptr, data_resource) const override; diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index 52a0adde99..e2aef5f726 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -149,7 +149,7 @@ static sstring hashpw(const sstring& pass) { return hashpw(pass, gensalt()); } -future<> auth::password_authenticator::init() { +future<> auth::password_authenticator::start() { gensalt(); // do this once to determine usable hashing sstring create_table = sprint( @@ -182,6 +182,10 @@ future<> auth::password_authenticator::init() { }); } +future<> auth::password_authenticator::stop() { + return make_ready_future<>(); +} + db::consistency_level auth::password_authenticator::consistency_for_user(const sstring& username) { if (username == DEFAULT_USER_NAME) { return db::consistency_level::QUORUM; diff --git a/auth/password_authenticator.hh b/auth/password_authenticator.hh index 2687dfd2c8..5410b54727 100644 --- a/auth/password_authenticator.hh +++ b/auth/password_authenticator.hh @@ -52,7 +52,9 @@ public: password_authenticator(); ~password_authenticator(); - future<> init() override; + future<> start() override; + + future<> stop() override; const sstring& class_name() const override; bool require_authentication() const override; diff --git a/auth/transitional.cc b/auth/transitional.cc index b623511f97..d25979ebcc 100644 --- a/auth/transitional.cc +++ b/auth/transitional.cc @@ -70,8 +70,11 @@ public: transitional_authenticator(std::unique_ptr a) : _authenticator(std::move(a)) {} - future<> init() override { - return _authenticator->init(); + future<> start() override { + return _authenticator->start(); + } + future<> stop() override { + return _authenticator->stop(); } const sstring& class_name() const override { return transitional_authenticator_name(); @@ -153,8 +156,11 @@ public: {} ~transitional_authorizer() {} - future<> init() override { - return _authorizer->init(); + future<> start() override { + return _authorizer->start(); + } + future<> stop() override { + return _authorizer->stop(); } future authorize(::shared_ptr user, data_resource resource) const override { return user->is_super().then([](bool s) { From 157e22a4f05a00f23201bb580c693e27d9bf13cc Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 16:20:54 -0500 Subject: [PATCH 08/11] auth: Unify Java class name attributes --- auth/authenticator.cc | 2 +- auth/authenticator.hh | 2 +- auth/authorizer.cc | 3 +++ auth/authorizer.hh | 2 ++ auth/default_authorizer.hh | 4 ++++ auth/password_authenticator.cc | 2 +- auth/password_authenticator.hh | 2 +- auth/transitional.cc | 10 +++++++++- cql3/user_options.cc | 2 +- tests/auth_test.cc | 4 ++-- transport/server.cc | 2 +- 11 files changed, 26 insertions(+), 9 deletions(-) diff --git a/auth/authenticator.cc b/auth/authenticator.cc index 45e192cd0b..a4523fda9c 100644 --- a/auth/authenticator.cc +++ b/auth/authenticator.cc @@ -90,7 +90,7 @@ auth::authenticator::setup(const sstring& type) { future<> stop() override { return make_ready_future<>(); } - const sstring& class_name() const override { + const sstring& qualified_java_name() const override { return allow_all_authenticator_name(); } bool require_authentication() const override { diff --git a/auth/authenticator.hh b/auth/authenticator.hh index d46d791598..db2fbe6e9c 100644 --- a/auth/authenticator.hh +++ b/auth/authenticator.hh @@ -107,7 +107,7 @@ public: virtual future<> stop() = 0; - virtual const sstring& class_name() const = 0; + virtual const sstring& qualified_java_name() const = 0; /** * Whether or not the authenticator requires explicit login. diff --git a/auth/authorizer.cc b/auth/authorizer.cc index 4935cf946d..b73d1f3398 100644 --- a/auth/authorizer.cc +++ b/auth/authorizer.cc @@ -70,6 +70,9 @@ auth::authorizer::setup(const sstring& type) { future<> stop() override { return make_ready_future<>(); } + const sstring& qualified_java_name() const override { + return allow_all_authorizer_name(); + } future authorize(::shared_ptr, data_resource) const override { return make_ready_future(permissions::ALL); } diff --git a/auth/authorizer.hh b/auth/authorizer.hh index 49463edae4..1fda736bde 100644 --- a/auth/authorizer.hh +++ b/auth/authorizer.hh @@ -79,6 +79,8 @@ public: virtual future<> stop() = 0; + virtual const sstring& qualified_java_name() const = 0; + /** * The primary Authorizer method. Returns a set of permissions of a user on a resource. * diff --git a/auth/default_authorizer.hh b/auth/default_authorizer.hh index 79137b9608..e0a3344e1e 100644 --- a/auth/default_authorizer.hh +++ b/auth/default_authorizer.hh @@ -56,6 +56,10 @@ public: future<> stop() override; + const sstring& qualified_java_name() const override { + return default_authorizer_name(); + } + future authorize(::shared_ptr, data_resource) const override; future<> grant(::shared_ptr, permission_set, data_resource, sstring) override; diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index e2aef5f726..da48520698 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -193,7 +193,7 @@ db::consistency_level auth::password_authenticator::consistency_for_user(const s return db::consistency_level::LOCAL_ONE; } -const sstring& auth::password_authenticator::class_name() const { +const sstring& auth::password_authenticator::qualified_java_name() const { return password_authenticator_name(); } diff --git a/auth/password_authenticator.hh b/auth/password_authenticator.hh index 5410b54727..1381a3a0ca 100644 --- a/auth/password_authenticator.hh +++ b/auth/password_authenticator.hh @@ -56,7 +56,7 @@ public: future<> stop() override; - const sstring& class_name() const override; + const sstring& qualified_java_name() const override; bool require_authentication() const override; option_set supported_options() const override; option_set alterable_options() const override; diff --git a/auth/transitional.cc b/auth/transitional.cc index d25979ebcc..5750f2f452 100644 --- a/auth/transitional.cc +++ b/auth/transitional.cc @@ -59,6 +59,11 @@ static const sstring& transitional_authenticator_name() { return name; } +static const sstring& transitional_authorizer_name() { + static const sstring name = PACKAGE_NAME + "TransitionalAuthorizer"; + return name; +} + class transitional_authenticator : public authenticator { std::unique_ptr _authenticator; public: @@ -76,7 +81,7 @@ public: future<> stop() override { return _authenticator->stop(); } - const sstring& class_name() const override { + const sstring& qualified_java_name() const override { return transitional_authenticator_name(); } bool require_authentication() const override { @@ -162,6 +167,9 @@ public: future<> stop() override { return _authorizer->stop(); } + const sstring& qualified_java_name() const override { + return transitional_authorizer_name(); + } future authorize(::shared_ptr user, data_resource resource) const override { return user->is_super().then([](bool s) { static const permission_set transitional_permissions = diff --git a/cql3/user_options.cc b/cql3/user_options.cc index 57b8735db5..86d8dc4878 100644 --- a/cql3/user_options.cc +++ b/cql3/user_options.cc @@ -55,7 +55,7 @@ void cql3::user_options::validate() const { if (!a.supported_options().contains(o)) { throw exceptions::invalid_request_exception( sprint("%s doesn't support %s option", - a.class_name(), + a.qualified_java_name(), a.option_to_string(o))); } } diff --git a/tests/auth_test.cc b/tests/auth_test.cc index e54563ee8f..8b68ee2597 100644 --- a/tests/auth_test.cc +++ b/tests/auth_test.cc @@ -75,7 +75,7 @@ SEASTAR_TEST_CASE(test_data_resource) { SEASTAR_TEST_CASE(test_default_authenticator) { return do_with_cql_env([](cql_test_env&) { BOOST_REQUIRE_EQUAL(auth::authenticator::get().require_authentication(), false); - BOOST_REQUIRE_EQUAL(auth::authenticator::get().class_name(), auth::allow_all_authenticator_name()); + BOOST_REQUIRE_EQUAL(auth::authenticator::get().qualified_java_name(), auth::allow_all_authenticator_name()); return make_ready_future(); }); } @@ -86,7 +86,7 @@ SEASTAR_TEST_CASE(test_password_authenticator_attributes) { return do_with_cql_env([](cql_test_env&) { BOOST_REQUIRE_EQUAL(auth::authenticator::get().require_authentication(), true); - BOOST_REQUIRE_EQUAL(auth::authenticator::get().class_name(), auth::password_authenticator_name()); + BOOST_REQUIRE_EQUAL(auth::authenticator::get().qualified_java_name(), auth::password_authenticator_name()); return make_ready_future(); }, cfg); } diff --git a/transport/server.cc b/transport/server.cc index 36d8860df5..5eccb943ab 100644 --- a/transport/server.cc +++ b/transport/server.cc @@ -750,7 +750,7 @@ future cql_server::connection::process_startup(uint16_t stream, b } auto& a = auth::authenticator::get(); if (a.require_authentication()) { - return make_ready_future(std::make_pair(make_autheticate(stream, a.class_name(), client_state.get_trace_state()), client_state)); + return make_ready_future(std::make_pair(make_autheticate(stream, a.qualified_java_name(), client_state.get_trace_state()), client_state)); } return make_ready_future(std::make_pair(make_ready(stream, client_state.get_trace_state()), client_state)); } From 41612ee577551501006814c12b3d30d41cc314b6 Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 16:29:18 -0500 Subject: [PATCH 09/11] auth: Make the QP an explicit dependency Rather than have all uses of the QP in auth reference global variables, we supply a QP reference to both the authenticator and authorizer on construction. The caller still references a global variable when constructing the instances, but fixing this problem is a much larger task that is out of scope of this change. --- auth/authenticator.cc | 5 +++-- auth/authorizer.cc | 5 +++-- auth/default_authorizer.cc | 33 +++++++++++++++------------------ auth/default_authorizer.hh | 5 ++++- auth/password_authenticator.cc | 26 +++++++++++--------------- auth/password_authenticator.hh | 5 ++++- auth/transitional.cc | 12 ++++++------ 7 files changed, 46 insertions(+), 45 deletions(-) diff --git a/auth/authenticator.cc b/auth/authenticator.cc index a4523fda9c..eec1212d4b 100644 --- a/auth/authenticator.cc +++ b/auth/authenticator.cc @@ -44,6 +44,7 @@ #include "common.hh" #include "password_authenticator.hh" #include "auth.hh" +#include "cql3/query_processor.hh" #include "db/config.hh" #include "utils/class_registrator.hh" @@ -77,7 +78,7 @@ sstring auth::authenticator::option_to_string(option opt) { */ static std::unique_ptr global_authenticator; -using authenticator_registry = class_registry; +using authenticator_registry = class_registry; future<> auth::authenticator::setup(const sstring& type) { @@ -125,7 +126,7 @@ auth::authenticator::setup(const sstring& type) { global_authenticator = std::make_unique(); return make_ready_future(); } else { - auto a = authenticator_registry::create(type); + auto a = authenticator_registry::create(type, cql3::get_local_query_processor()); auto f = a->start(); return f.then([a = std::move(a)]() mutable { global_authenticator = std::move(a); diff --git a/auth/authorizer.cc b/auth/authorizer.cc index b73d1f3398..77ae3b8741 100644 --- a/auth/authorizer.cc +++ b/auth/authorizer.cc @@ -44,6 +44,7 @@ #include "common.hh" #include "default_authorizer.hh" #include "auth.hh" +#include "cql3/query_processor.hh" #include "db/config.hh" #include "utils/class_registrator.hh" @@ -57,7 +58,7 @@ const sstring& auth::allow_all_authorizer_name() { * We thus store a single instance globally, since it should be safe/ok. */ static std::unique_ptr global_authorizer; -using authorizer_registry = class_registry; +using authorizer_registry = class_registry; future<> auth::authorizer::setup(const sstring& type) { @@ -103,7 +104,7 @@ auth::authorizer::setup(const sstring& type) { global_authorizer = std::make_unique(); return make_ready_future(); } else { - auto a = authorizer_registry::create(type); + auto a = authorizer_registry::create(type, cql3::get_local_query_processor()); auto f = a->start(); return f.then([a = std::move(a)]() mutable { global_authorizer = std::move(a); diff --git a/auth/default_authorizer.cc b/auth/default_authorizer.cc index 0d0675230c..87695d8632 100644 --- a/auth/default_authorizer.cc +++ b/auth/default_authorizer.cc @@ -69,11 +69,13 @@ static const sstring PERMISSIONS_CF = "permissions"; static logging::logger alogger("default_authorizer"); // To ensure correct initialization order, we unfortunately need to use a string literal. -static const class_registrator password_auth_reg( +static const class_registrator password_auth_reg( "org.apache.cassandra.auth.CassandraAuthorizer"); -auth::default_authorizer::default_authorizer() { +auth::default_authorizer::default_authorizer(cql3::query_processor& qp) + : _qp(qp) { } + auth::default_authorizer::~default_authorizer() { } @@ -105,10 +107,9 @@ future auth::default_authorizer::authorize( * TOOD: could create actual data type for permission (translating string<->perm), * but this seems overkill right now. We still must store strings so... */ - auto& qp = cql3::get_local_query_processor(); auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? AND %s = ?" , PERMISSIONS_NAME, meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME); - return qp.process(query, db::consistency_level::LOCAL_ONE, {user->name(), resource.name() }) + return _qp.process(query, db::consistency_level::LOCAL_ONE, {user->name(), resource.name() }) .then_wrapped([=](future<::shared_ptr> f) { try { auto res = f.get0(); @@ -131,11 +132,10 @@ future<> auth::default_authorizer::modify( ::shared_ptr performer, permission_set set, data_resource resource, sstring user, sstring op) { // TODO: why does this not check super user? - auto& qp = cql3::get_local_query_processor(); auto query = sprint("UPDATE %s.%s SET %s = %s %s ? WHERE %s = ? AND %s = ?", meta::AUTH_KS, PERMISSIONS_CF, PERMISSIONS_NAME, PERMISSIONS_NAME, op, USER_NAME, RESOURCE_NAME); - return qp.process(query, db::consistency_level::ONE, { + return _qp.process(query, db::consistency_level::ONE, { permissions::to_strings(set), user, resource.name() }).discard_result(); } @@ -161,7 +161,6 @@ future> auth::default_authorizer::list( } auto query = sprint("SELECT %s, %s, %s FROM %s.%s", USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, meta::AUTH_KS, PERMISSIONS_CF); - auto& qp = cql3::get_local_query_processor(); // Oh, look, it is a case where it does not pay off to have // parameters to process in an initializer list. @@ -169,15 +168,15 @@ future> auth::default_authorizer::list( if (resource && user) { query += sprint(" WHERE %s = ? AND %s = ?", USER_NAME, RESOURCE_NAME); - f = qp.process(query, db::consistency_level::ONE, {*user, resource->name()}); + f = _qp.process(query, db::consistency_level::ONE, {*user, resource->name()}); } else if (resource) { query += sprint(" WHERE %s = ? ALLOW FILTERING", RESOURCE_NAME); - f = qp.process(query, db::consistency_level::ONE, {resource->name()}); + f = _qp.process(query, db::consistency_level::ONE, {resource->name()}); } else if (user) { query += sprint(" WHERE %s = ?", USER_NAME); - f = qp.process(query, db::consistency_level::ONE, {*user}); + f = _qp.process(query, db::consistency_level::ONE, {*user}); } else { - f = qp.process(query, db::consistency_level::ONE, {}); + f = _qp.process(query, db::consistency_level::ONE, {}); } return f.then([set](::shared_ptr res) { @@ -199,10 +198,9 @@ future> auth::default_authorizer::list( } future<> auth::default_authorizer::revoke_all(sstring dropped_user) { - auto& qp = cql3::get_local_query_processor(); auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", meta::AUTH_KS, PERMISSIONS_CF, USER_NAME); - return qp.process(query, db::consistency_level::ONE, { dropped_user }).discard_result().handle_exception( + return _qp.process(query, db::consistency_level::ONE, { dropped_user }).discard_result().handle_exception( [dropped_user](auto ep) { try { std::rethrow_exception(ep); @@ -213,17 +211,16 @@ future<> auth::default_authorizer::revoke_all(sstring dropped_user) { } future<> auth::default_authorizer::revoke_all(data_resource resource) { - auto& qp = cql3::get_local_query_processor(); auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? ALLOW FILTERING", USER_NAME, meta::AUTH_KS, PERMISSIONS_CF, RESOURCE_NAME); - return qp.process(query, db::consistency_level::LOCAL_ONE, { resource.name() }) - .then_wrapped([resource, &qp](future<::shared_ptr> f) { + return _qp.process(query, db::consistency_level::LOCAL_ONE, { resource.name() }) + .then_wrapped([this, resource](future<::shared_ptr> f) { try { auto res = f.get0(); - return parallel_for_each(res->begin(), res->end(), [&qp, res, resource](const cql3::untyped_result_set::row& r) { + return parallel_for_each(res->begin(), res->end(), [this, res, resource](const cql3::untyped_result_set::row& r) { auto query = sprint("DELETE FROM %s.%s WHERE %s = ? AND %s = ?" , meta::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME); - return qp.process(query, db::consistency_level::LOCAL_ONE, { r.get_as(USER_NAME), resource.name() }) + return _qp.process(query, db::consistency_level::LOCAL_ONE, { r.get_as(USER_NAME), resource.name() }) .discard_result().handle_exception([resource](auto ep) { try { std::rethrow_exception(ep); diff --git a/auth/default_authorizer.hh b/auth/default_authorizer.hh index e0a3344e1e..4c81a9cb09 100644 --- a/auth/default_authorizer.hh +++ b/auth/default_authorizer.hh @@ -42,14 +42,17 @@ #pragma once #include "authorizer.hh" +#include "cql3/query_processor.hh" namespace auth { const sstring& default_authorizer_name(); class default_authorizer : public authorizer { + cql3::query_processor& _qp; + public: - default_authorizer(); + default_authorizer(cql3::query_processor&); ~default_authorizer(); future<> start() override; diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index da48520698..d7231dc2f8 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -50,7 +50,6 @@ #include "common.hh" #include "password_authenticator.hh" #include "authenticated_user.hh" -#include "cql3/query_processor.hh" #include "cql3/untyped_result_set.hh" #include "log.hh" #include "utils/class_registrator.hh" @@ -70,14 +69,15 @@ static const sstring CREDENTIALS_CF = "credentials"; static logging::logger plogger("password_authenticator"); // To ensure correct initialization order, we unfortunately need to use a string literal. -static const class_registrator password_auth_reg( +static const class_registrator password_auth_reg( "org.apache.cassandra.auth.PasswordAuthenticator"); auth::password_authenticator::~password_authenticator() {} -auth::password_authenticator::password_authenticator() -{} +auth::password_authenticator::password_authenticator(cql3::query_processor& qp) + : _qp(qp) { +} // TODO: blowfish // Origin uses Java bcrypt library, i.e. blowfish salt @@ -165,10 +165,10 @@ future<> auth::password_authenticator::start() { return auth::setup_table(CREDENTIALS_CF, create_table).then([this] { // instead of once-timer, just schedule this later - auth::schedule_when_up([] { - return auth::has_existing_users(CREDENTIALS_CF, DEFAULT_USER_NAME, USER_NAME).then([](bool exists) { + auth::schedule_when_up([this] { + return auth::has_existing_users(CREDENTIALS_CF, DEFAULT_USER_NAME, USER_NAME).then([this](bool exists) { if (!exists) { - cql3::get_local_query_processor().process(sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", + _qp.process(sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", meta::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH @@ -227,8 +227,7 @@ future<::shared_ptr > auth::password_authenticator::au // Rely on query processing caching statements instead, and lets assume // that a map lookup string->statement is not gonna kill us much. return futurize_apply([this, username, password] { - auto& qp = cql3::get_local_query_processor(); - return qp.process(sprint("SELECT %s FROM %s.%s WHERE %s = ?", SALTED_HASH, + return _qp.process(sprint("SELECT %s FROM %s.%s WHERE %s = ?", SALTED_HASH, meta::AUTH_KS, CREDENTIALS_CF, USER_NAME), consistency_for_user(username), {username}, true); }).then_wrapped([=](future<::shared_ptr> f) { @@ -254,8 +253,7 @@ future<> auth::password_authenticator::create(sstring username, auto password = boost::any_cast(options.at(option::PASSWORD)); auto query = sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?)", meta::AUTH_KS, CREDENTIALS_CF, USER_NAME, SALTED_HASH); - auto& qp = cql3::get_local_query_processor(); - return qp.process(query, consistency_for_user(username), { username, hashpw(password) }).discard_result(); + return _qp.process(query, consistency_for_user(username), { username, hashpw(password) }).discard_result(); } catch (std::out_of_range&) { throw exceptions::invalid_request_exception("PasswordAuthenticator requires PASSWORD option"); } @@ -267,8 +265,7 @@ future<> auth::password_authenticator::alter(sstring username, auto password = boost::any_cast(options.at(option::PASSWORD)); auto query = sprint("UPDATE %s.%s SET %s = ? WHERE %s = ?", meta::AUTH_KS, CREDENTIALS_CF, SALTED_HASH, USER_NAME); - auto& qp = cql3::get_local_query_processor(); - return qp.process(query, consistency_for_user(username), { hashpw(password), username }).discard_result(); + return _qp.process(query, consistency_for_user(username), { hashpw(password), username }).discard_result(); } catch (std::out_of_range&) { throw exceptions::invalid_request_exception("PasswordAuthenticator requires PASSWORD option"); } @@ -278,8 +275,7 @@ future<> auth::password_authenticator::drop(sstring username) { try { auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", meta::AUTH_KS, CREDENTIALS_CF, USER_NAME); - auto& qp = cql3::get_local_query_processor(); - return qp.process(query, consistency_for_user(username), { username }).discard_result(); + return _qp.process(query, consistency_for_user(username), { username }).discard_result(); } catch (std::out_of_range&) { throw exceptions::invalid_request_exception("PasswordAuthenticator requires PASSWORD option"); } diff --git a/auth/password_authenticator.hh b/auth/password_authenticator.hh index 1381a3a0ca..0c1abc549c 100644 --- a/auth/password_authenticator.hh +++ b/auth/password_authenticator.hh @@ -42,14 +42,17 @@ #pragma once #include "authenticator.hh" +#include "cql3/query_processor.hh" namespace auth { const sstring& password_authenticator_name(); class password_authenticator : public authenticator { + cql3::query_processor& _qp; + public: - password_authenticator(); + password_authenticator(cql3::query_processor&); ~password_authenticator(); future<> start() override; diff --git a/auth/transitional.cc b/auth/transitional.cc index 5750f2f452..c6714bd8e0 100644 --- a/auth/transitional.cc +++ b/auth/transitional.cc @@ -69,8 +69,8 @@ class transitional_authenticator : public authenticator { public: static const sstring PASSWORD_AUTHENTICATOR_NAME; - transitional_authenticator() - : transitional_authenticator(std::make_unique()) + transitional_authenticator(cql3::query_processor& qp) + : transitional_authenticator(std::make_unique(qp)) {} transitional_authenticator(std::unique_ptr a) : _authenticator(std::move(a)) @@ -153,8 +153,8 @@ public: class transitional_authorizer : public authorizer { std::unique_ptr _authorizer; public: - transitional_authorizer() - : transitional_authorizer(std::make_unique()) + transitional_authorizer(cql3::query_processor& qp) + : transitional_authorizer(std::make_unique(qp)) {} transitional_authorizer(std::unique_ptr a) : _authorizer(std::move(a)) @@ -210,8 +210,8 @@ public: // static const class_registrator transitional_authenticator_reg( + auth::transitional_authenticator, cql3::query_processor&> transitional_authenticator_reg( "com.scylladb.auth.TransitionalAuthenticator"); -static const class_registrator transitional_authorizer_reg( +static const class_registrator transitional_authorizer_reg( "com.scylladb.auth.TransitionalAuthorizer"); From 1dd181bd7b014c04398f91640c4cc8381c700c67 Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 17:12:46 -0500 Subject: [PATCH 10/11] tracing/trace_keyspace_helper: Use internal `client_state` --- tracing/trace_keyspace_helper.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tracing/trace_keyspace_helper.cc b/tracing/trace_keyspace_helper.cc index 4e25ea6e46..fc25953af2 100644 --- a/tracing/trace_keyspace_helper.cc +++ b/tracing/trace_keyspace_helper.cc @@ -64,7 +64,7 @@ struct trace_keyspace_backend_sesssion_state final : public backend_session_stat trace_keyspace_helper::trace_keyspace_helper(tracing& tr) : i_tracing_backend_helper(tr) - , _dummy_query_state(service::client_state(service::client_state::external_tag{})) + , _dummy_query_state(service::client_state(service::client_state::internal_tag{})) , _sessions(KEYSPACE_NAME, SESSIONS, sprint("CREATE TABLE IF NOT EXISTS %s.%s (" "session_id uuid," From ba6a41d397923c45045c4dddcbfa2c667a3a59ef Mon Sep 17 00:00:00 2001 From: Jesse Haber-Kucharsky Date: Tue, 14 Nov 2017 22:21:30 -0500 Subject: [PATCH 11/11] auth: Switch to sharded service This change appears quite large, but is logically fairly simple. Previously, the `auth` module was structured around global state in a number of ways: - There existed global instances for the authenticator and the authorizer, which were accessed pervasively throughout the system through `auth::authenticator::get()` and `auth::authorizer::get()`, respectively. These instances needed to be initialized before they could be used with `auth::authenticator::setup(sstring type_name)` and `auth::authorizer::setup(sstring type_name)`. - The implementation of the `auth::auth` functions and the authenticator and authorizer depended on resources accessed globally through `cql3::get_local_query_processor()` and `service::get_local_migration_manager()`. - CQL statements would check for access and manage users through static functions in `auth::auth`. These functions would access the global authenticator and authorizer instances and depended on the necessary systems being started before they were used. This change eliminates global state from all of these. The specific changes are: - Move out `allow_all_authenticator` and `allow_all_authorizer` into their own files so that they're constructed like any other authenticator or authorizer. - Delete `auth.hh` and `auth.cc`. Constants and helper functions useful for implementing functionality in the `auth` module have moved to `common.hh`. - Remove silent global dependency in `auth::authenticated_user::is_super()` on the auth* service in favour of a new function `auth::is_super_user()` with an explicit auth* service argument. - Remove global authenticator and authorizer instances, as well as the `setup()` functions. - Expose dependency on the auth* service in `auth::authorizer::authorize()` and `auth::authorizer::list()`, which is necessary to check for superuser status. - Add an explicit `service::migration_manager` argument to the authenticators and authorizers so they can announce metadata tables. - The permissions cache now requires an auth* service reference instead of just an authorizer since authorizing also requires this. - The permissions cache configuration can now easily be created from the DB configuration. - Move the static functions in `auth::auth` to the new `auth::service`. Where possible, previously static resources like the `delayed_tasks` are now members. - Validating `cql3::user_options` requires an authenticator, which was previously accessed globally. - Instances of the auth* service are accessed through `external` instances of `client_state` instead of globally. This includes several CQL statements including `alter_user_statement`, `create_user_statement`, `drop_user_statement`, `grant_statement`, `list_permissions_statement`, `permissions_altering_statement`, and `revoke_statement`. For `internal` `client_state`, this is `nullptr`. - Since the `cql_server` is responsible for instantiating connections and each connection gets a new `client_state`, the `cql_server` is instantiated with a reference to the auth* service. - Similarly, the Thrift server is now also instantiated with a reference to the auth* service. - Since the storage service is responsible for instantiating and starting the sharded servers, it is instantiated with the sharded auth* service which it threads through. All relevant factory functions have been updated. - The storage service is still responsible for starting the auth* service it has been provided, and shutting it down. - The `cql_test_env` is now instantiated with an instance of the auth* service, and can be accessed through a member function. - All unit tests have been updated and pass. Fixes #2929. --- auth/allow_all_authenticator.cc | 41 ++ auth/allow_all_authenticator.hh | 97 +++++ auth/allow_all_authorizer.cc | 41 ++ auth/allow_all_authorizer.hh | 98 +++++ auth/auth.cc | 286 -------------- auth/auth.hh | 117 ------ auth/authenticated_user.cc | 8 - auth/authenticated_user.hh | 8 - auth/authenticator.cc | 73 ---- auth/authenticator.hh | 15 - auth/authorizer.hh | 20 +- auth/common.cc | 35 ++ auth/common.hh | 35 ++ auth/default_authorizer.cc | 31 +- auth/default_authorizer.hh | 11 +- auth/password_authenticator.cc | 128 +++++-- auth/password_authenticator.hh | 14 +- auth/permissions_cache.cc | 20 +- auth/permissions_cache.hh | 11 +- auth/service.cc | 353 ++++++++++++++++++ auth/service.hh | 132 +++++++ auth/transitional.cc | 34 +- configure.py | 5 +- cql3/statements/alter_user_statement.cc | 22 +- cql3/statements/create_user_statement.cc | 15 +- cql3/statements/drop_user_statement.cc | 15 +- cql3/statements/grant_statement.cc | 5 +- cql3/statements/list_permissions_statement.cc | 6 +- .../permission_altering_statement.cc | 4 +- cql3/statements/revoke_statement.cc | 5 +- cql3/user_options.cc | 3 +- cql3/user_options.hh | 6 +- init.cc | 4 +- init.hh | 3 +- main.cc | 3 +- service/client_state.cc | 13 +- service/client_state.hh | 31 +- service/storage_service.cc | 31 +- service/storage_service.hh | 8 +- tests/auth_test.cc | 70 ++-- tests/cql_test_env.cc | 25 +- tests/cql_test_env.hh | 6 + tests/gossip.cc | 7 +- tests/gossip_test.cc | 3 +- tests/test_services.hh | 4 +- thrift/handler.cc | 19 +- thrift/handler.hh | 3 +- thrift/server.cc | 5 +- thrift/server.hh | 6 +- transport/server.cc | 9 +- transport/server.hh | 4 +- 51 files changed, 1236 insertions(+), 712 deletions(-) create mode 100644 auth/allow_all_authenticator.cc create mode 100644 auth/allow_all_authenticator.hh create mode 100644 auth/allow_all_authorizer.cc create mode 100644 auth/allow_all_authorizer.hh delete mode 100644 auth/auth.cc delete mode 100644 auth/auth.hh create mode 100644 auth/service.cc create mode 100644 auth/service.hh diff --git a/auth/allow_all_authenticator.cc b/auth/allow_all_authenticator.cc new file mode 100644 index 0000000000..c3eae17db3 --- /dev/null +++ b/auth/allow_all_authenticator.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include "auth/allow_all_authenticator.hh" + +#include "service/migration_manager.hh" +#include "utils/class_registrator.hh" + +namespace auth { + +const sstring& allow_all_authenticator_name() { + static const sstring name = meta::AUTH_PACKAGE_NAME + "AllowAllAuthenticator"; + return name; +} + +// To ensure correct initialization order, we unfortunately need to use a string literal. +static const class_registrator< + authenticator, + allow_all_authenticator, + cql3::query_processor&, + ::service::migration_manager&> registration("org.apache.cassandra.auth.AllowAllAuthenticator"); + +} diff --git a/auth/allow_all_authenticator.hh b/auth/allow_all_authenticator.hh new file mode 100644 index 0000000000..e5da1953a0 --- /dev/null +++ b/auth/allow_all_authenticator.hh @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include + +#include "auth/authenticator.hh" +#include "auth/authenticated_user.hh" +#include "auth/common.hh" + +namespace cql3 { +class query_processor; +} + +namespace service { +class migration_manager; +} + +namespace auth { + +const sstring& allow_all_authenticator_name(); + +class allow_all_authenticator final : public authenticator { +public: + allow_all_authenticator(cql3::query_processor&, ::service::migration_manager&) { + } + + future<> start() override { + return make_ready_future<>(); + } + + future<> stop() override { + return make_ready_future<>(); + } + + const sstring& qualified_java_name() const override { + return allow_all_authenticator_name(); + } + + bool require_authentication() const override { + return false; + } + + option_set supported_options() const override { + return option_set(); + } + + option_set alterable_options() const override { + return option_set(); + } + + future<::shared_ptr> authenticate(const credentials_map& credentials) const override { + return make_ready_future<::shared_ptr>(::make_shared()); + } + + future<> create(sstring username, const option_map& options) override { + return make_ready_future(); + } + + future<> alter(sstring username, const option_map& options) override { + return make_ready_future(); + } + + future<> drop(sstring username) override { + return make_ready_future(); + } + + const resource_ids& protected_resources() const override { + static const resource_ids ids; + return ids; + } + + ::shared_ptr new_sasl_challenge() const override { + throw std::runtime_error("Should not reach"); + } +}; + +} diff --git a/auth/allow_all_authorizer.cc b/auth/allow_all_authorizer.cc new file mode 100644 index 0000000000..6cd2ef2aa6 --- /dev/null +++ b/auth/allow_all_authorizer.cc @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include "auth/allow_all_authorizer.hh" + +#include "auth/common.hh" +#include "utils/class_registrator.hh" + +namespace auth { + +const sstring& allow_all_authorizer_name() { + static const sstring name = meta::AUTH_PACKAGE_NAME + "AllowAllAuthorizer"; + return name; +} + +// To ensure correct initialization order, we unfortunately need to use a string literal. +static const class_registrator< + authorizer, + allow_all_authorizer, + cql3::query_processor&, + ::service::migration_manager&> registration("org.apache.cassandra.auth.AllowAllAuthorizer"); + +} diff --git a/auth/allow_all_authorizer.hh b/auth/allow_all_authorizer.hh new file mode 100644 index 0000000000..a03535ec43 --- /dev/null +++ b/auth/allow_all_authorizer.hh @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include "authorizer.hh" +#include "exceptions/exceptions.hh" +#include "stdx.hh" + +namespace cql3 { +class query_processor; +} + +namespace service { +class migration_manager; +} + +namespace auth { + +class service; + +const sstring& allow_all_authorizer_name(); + +class allow_all_authorizer final : public authorizer { +public: + allow_all_authorizer(cql3::query_processor&, ::service::migration_manager&) { + } + + future<> start() override { + return make_ready_future<>(); + } + + future<> stop() override { + return make_ready_future<>(); + } + + const sstring& qualified_java_name() const override { + return allow_all_authorizer_name(); + } + + future authorize(service&, ::shared_ptr, data_resource) const override { + return make_ready_future(permissions::ALL); + } + + future<> grant(::shared_ptr, permission_set, data_resource, sstring) override { + throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer"); + } + + future<> revoke(::shared_ptr, permission_set, data_resource, sstring) override { + throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer"); + } + + future> list( + service&, + ::shared_ptr performer, + permission_set, + stdx::optional, + stdx::optional) const override { + throw exceptions::invalid_request_exception("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer"); + } + + future<> revoke_all(sstring dropped_user) override { + return make_ready_future(); + } + + future<> revoke_all(data_resource) override { + return make_ready_future(); + } + + const resource_ids& protected_resources() override { + static const resource_ids ids; + return ids; + } + + future<> validate_configuration() const override { + return make_ready_future(); + } +}; + +} diff --git a/auth/auth.cc b/auth/auth.cc deleted file mode 100644 index 75e1563523..0000000000 --- a/auth/auth.cc +++ /dev/null @@ -1,286 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Copyright (C) 2016 ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * This file is part of Scylla. - * - * Scylla is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Scylla is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Scylla. If not, see . - */ - -#include - -#include - -#include - -#include "auth.hh" -#include "authenticator.hh" -#include "authorizer.hh" -#include "common.hh" -#include "database.hh" -#include "cql3/query_processor.hh" -#include "cql3/untyped_result_set.hh" -#include "cql3/statements/raw/cf_statement.hh" -#include "cql3/statements/create_table_statement.hh" -#include "db/config.hh" -#include "delayed_tasks.hh" -#include "permissions_cache.hh" -#include "service/migration_manager.hh" -#include "utils/loading_cache.hh" -#include "utils/hash.hh" - -static const sstring USER_NAME("name"); -static const sstring SUPER("super"); - -static logging::logger alogger("auth"); - -// TODO: configurable -using namespace std::chrono_literals; -static const std::chrono::milliseconds SUPERUSER_SETUP_DELAY = 10000ms; - -class auth_migration_listener : public service::migration_listener { - 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 { - auth::authorizer::get().revoke_all(auth::data_resource(ks_name)); - } - void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override { - auth::authorizer::get().revoke_all(auth::data_resource(ks_name, cf_name)); - } - 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 auth_migration_listener auth_migration; - -static sharded perm_cache; - -static future<> start_permission_cache() { - auto& db_config = cql3::get_local_query_processor().db().local().get_config(); - - auth::permissions_cache_config c; - c.max_entries = db_config.permissions_cache_max_entries(); - c.validity_period = std::chrono::milliseconds(db_config.permissions_validity_in_ms()); - c.update_period = std::chrono::milliseconds(db_config.permissions_update_interval_in_ms()); - - return perm_cache.start(c, std::ref(auth::authorizer::get()), std::ref(alogger)); -} - -static delayed_tasks<>& get_local_delayed_tasks() { - static thread_local delayed_tasks<> instance; - return instance; -} - -void auth::auth::schedule_when_up(scheduled_func f) { - get_local_delayed_tasks().schedule_after(SUPERUSER_SETUP_DELAY, std::move(f)); -} - -future<> auth::auth::setup() { - auto& db = cql3::get_local_query_processor().db().local(); - auto& cfg = db.get_config(); - - qualified_name authenticator_name(meta::AUTH_PACKAGE_NAME, cfg.authenticator()), - authorizer_name(meta::AUTH_PACKAGE_NAME, cfg.authorizer()); - - if (allow_all_authenticator_name() == authenticator_name && allow_all_authorizer_name() == authorizer_name) { - return authenticator::setup(authenticator_name).then([authorizer_name = std::move(authorizer_name)] { - return authorizer::setup(authorizer_name); - }).then([] { - return start_permission_cache(); - }); - } - - future<> f = make_ready_future<>(); - - if (!db.has_keyspace(meta::AUTH_KS)) { - std::map opts; - opts["replication_factor"] = "1"; - auto ksm = keyspace_metadata::new_keyspace(meta::AUTH_KS, "org.apache.cassandra.locator.SimpleStrategy", opts, true); - // We use min_timestamp so that default keyspace metadata will loose with any manual adjustments. See issue #2129. - f = service::get_local_migration_manager().announce_new_keyspace(ksm, api::min_timestamp, false); - } - - return f.then([] { - return setup_table(meta::USERS_CF, sprint("CREATE TABLE %s.%s (%s text, %s boolean, PRIMARY KEY(%s)) WITH gc_grace_seconds=%d", - meta::AUTH_KS, meta::USERS_CF, USER_NAME, SUPER, USER_NAME, - 90 * 24 * 60 * 60)); // 3 months. - }).then([authenticator_name = std::move(authenticator_name)] { - return authenticator::setup(authenticator_name); - }).then([authorizer_name = std::move(authorizer_name)] { - return authorizer::setup(authorizer_name); - }).then([] { - return start_permission_cache(); - }).then([] { - service::get_local_migration_manager().register_listener(&auth_migration); // again, only one shard... - // instead of once-timer, just schedule this later - schedule_when_up([] { - // setup default super user - return has_existing_users(meta::USERS_CF, meta::DEFAULT_SUPERUSER_NAME, USER_NAME).then([](bool exists) { - if (!exists) { - auto query = sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", - meta::AUTH_KS, meta::USERS_CF, USER_NAME, SUPER); - cql3::get_local_query_processor().process(query, db::consistency_level::ONE, {meta::DEFAULT_SUPERUSER_NAME, true}).then([](auto) { - alogger.info("Created default superuser '{}'", meta::DEFAULT_SUPERUSER_NAME); - }).handle_exception([](auto ep) { - try { - std::rethrow_exception(ep); - } catch (exceptions::request_execution_exception&) { - alogger.warn("Skipped default superuser setup: some nodes were not ready"); - } - }); - } - }); - }); - }); -} - -future<> auth::auth::shutdown() { - // just make sure we don't have pending tasks. - // this is mostly relevant for test cases where - // db-env-shutdown != process shutdown - return smp::invoke_on_all([] { - get_local_delayed_tasks().cancel_all(); - }).then([] { - return perm_cache.stop(); - }).then([] { - return when_all_succeed(authorizer::get().stop(), authenticator::get().stop()); - }); -} - -future auth::auth::get_permissions(::shared_ptr user, data_resource resource) { - return perm_cache.local().get(std::move(user), std::move(resource)); -} - -static db::consistency_level consistency_for_user(const sstring& username) { - if (username == auth::meta::DEFAULT_SUPERUSER_NAME) { - return db::consistency_level::QUORUM; - } - return db::consistency_level::LOCAL_ONE; -} - -static future<::shared_ptr> select_user(const sstring& username) { - // Here was a thread local, explicit cache of prepared statement. In normal execution this is - // fine, but since we in testing set up and tear down system over and over, we'd start using - // obsolete prepared statements pretty quickly. - // Rely on query processing caching statements instead, and lets assume - // that a map lookup string->statement is not gonna kill us much. - return cql3::get_local_query_processor().process( - sprint("SELECT * FROM %s.%s WHERE %s = ?", - auth::meta::AUTH_KS, auth::meta::USERS_CF, - USER_NAME), consistency_for_user(username), - { username }, true); -} - -future auth::auth::is_existing_user(const sstring& username) { - return select_user(username).then( - [](::shared_ptr res) { - return make_ready_future(!res->empty()); - }); -} - -future auth::auth::is_super_user(const sstring& username) { - return select_user(username).then( - [](::shared_ptr res) { - return make_ready_future(!res->empty() && res->one().get_as(SUPER)); - }); -} - -future<> auth::auth::insert_user(const sstring& username, bool is_super) { - return cql3::get_local_query_processor().process(sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?)", - meta::AUTH_KS, meta::USERS_CF, USER_NAME, SUPER), - consistency_for_user(username), { username, is_super }).discard_result(); -} - -future<> auth::auth::delete_user(const sstring& username) { - return cql3::get_local_query_processor().process(sprint("DELETE FROM %s.%s WHERE %s = ?", - meta::AUTH_KS, meta::USERS_CF, USER_NAME), - consistency_for_user(username), { username }).discard_result(); -} - -future<> auth::auth::setup_table(const sstring& name, const sstring& cql) { - auto& qp = cql3::get_local_query_processor(); - auto& db = qp.db().local(); - - if (db.has_schema(meta::AUTH_KS, name)) { - return make_ready_future(); - } - - ::shared_ptr parsed = static_pointer_cast< - cql3::statements::raw::cf_statement>(cql3::query_processor::parse_statement(cql)); - parsed->prepare_keyspace(meta::AUTH_KS); - ::shared_ptr statement = - static_pointer_cast( - parsed->prepare(db, qp.get_cql_stats())->statement); - auto schema = statement->get_cf_meta_data(); - auto uuid = generate_legacy_id(schema->ks_name(), schema->cf_name()); - - schema_builder b(schema); - b.set_uuid(uuid); - return service::get_local_migration_manager().announce_new_column_family(b.build(), false); -} - -future auth::auth::has_existing_users(const sstring& cfname, const sstring& def_user_name, const sstring& name_column) { - auto default_user_query = sprint("SELECT * FROM %s.%s WHERE %s = ?", meta::AUTH_KS, cfname, name_column); - auto all_users_query = sprint("SELECT * FROM %s.%s LIMIT 1", meta::AUTH_KS, cfname); - - return cql3::get_local_query_processor().process(default_user_query, db::consistency_level::ONE, { def_user_name }).then([=](::shared_ptr res) { - if (!res->empty()) { - return make_ready_future(true); - } - return cql3::get_local_query_processor().process(default_user_query, db::consistency_level::QUORUM, { def_user_name }).then([all_users_query](::shared_ptr res) { - if (!res->empty()) { - return make_ready_future(true); - } - return cql3::get_local_query_processor().process(all_users_query, db::consistency_level::QUORUM).then([](::shared_ptr res) { - return make_ready_future(!res->empty()); - }); - }); - }); -} - diff --git a/auth/auth.hh b/auth/auth.hh deleted file mode 100644 index 53eb775031..0000000000 --- a/auth/auth.hh +++ /dev/null @@ -1,117 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/* - * Copyright (C) 2016 ScyllaDB - * - * Modified by ScyllaDB - */ - -/* - * This file is part of Scylla. - * - * Scylla is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Scylla is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with Scylla. If not, see . - */ - -#pragma once - -#include -#include -#include - - -#include "exceptions/exceptions.hh" -#include "permission.hh" -#include "data_resource.hh" -#include "authenticated_user.hh" - -namespace auth { - -class auth { -public: - class permissions_cache; - - static future get_permissions(::shared_ptr, data_resource); - - /** - * Checks if the username is stored in AUTH_KS.USERS_CF. - * - * @param username Username to query. - * @return whether or not Cassandra knows about the user. - */ - static future is_existing_user(const sstring& username); - - /** - * Checks if the user is a known superuser. - * - * @param username Username to query. - * @return true is the user is a superuser, false if they aren't or don't exist at all. - */ - static future is_super_user(const sstring& username); - - /** - * Inserts the user into AUTH_KS.USERS_CF (or overwrites their superuser status as a result of an ALTER USER query). - * - * @param username Username to insert. - * @param isSuper User's new status. - * @throws RequestExecutionException - */ - static future<> insert_user(const sstring& username, bool is_super); - - /** - * Deletes the user from AUTH_KS.USERS_CF. - * - * @param username Username to delete. - * @throws RequestExecutionException - */ - static future<> delete_user(const sstring& username); - - /** - * Sets up Authenticator and Authorizer. - */ - static future<> setup(); - static future<> shutdown(); - - /** - * Set up table from given CREATE TABLE statement under system_auth keyspace, if not already done so. - * - * @param name name of the table - * @param cql CREATE TABLE statement - */ - static future<> setup_table(const sstring& name, const sstring& cql); - - static future has_existing_users(const sstring& cfname, const sstring& def_user_name, const sstring& name_column_name); - - // For internal use. Run function "when system is up". - typedef std::function()> scheduled_func; - static void schedule_when_up(scheduled_func); -}; -} - -std::ostream& operator<<(std::ostream& os, const std::pair& p); diff --git a/auth/authenticated_user.cc b/auth/authenticated_user.cc index d956559b85..ab876b6d00 100644 --- a/auth/authenticated_user.cc +++ b/auth/authenticated_user.cc @@ -41,7 +41,6 @@ #include "authenticated_user.hh" -#include "auth.hh" const sstring auth::authenticated_user::ANONYMOUS_USERNAME("anonymous"); @@ -60,13 +59,6 @@ const sstring& auth::authenticated_user::name() const { return _anon ? ANONYMOUS_USERNAME : _name; } -future auth::authenticated_user::is_super() const { - if (is_anonymous()) { - return make_ready_future(false); - } - return auth::auth::is_super_user(_name); -} - bool auth::authenticated_user::operator==(const authenticated_user& v) const { return _anon ? v._anon : _name == v._name; } diff --git a/auth/authenticated_user.hh b/auth/authenticated_user.hh index b265537532..b9f1770826 100644 --- a/auth/authenticated_user.hh +++ b/auth/authenticated_user.hh @@ -58,14 +58,6 @@ public: const sstring& name() const; - /** - * Checks the user's superuser status. - * Only a superuser is allowed to perform CREATE USER and DROP USER queries. - * Im most cased, though not necessarily, a superuser will have Permission.ALL on every resource - * (depends on IAuthorizer implementation). - */ - future is_super() const; - /** * If IAuthenticator doesn't require authentication, this method may return true. */ diff --git a/auth/authenticator.cc b/auth/authenticator.cc index eec1212d4b..f89bbedd90 100644 --- a/auth/authenticator.cc +++ b/auth/authenticator.cc @@ -43,7 +43,6 @@ #include "authenticated_user.hh" #include "common.hh" #include "password_authenticator.hh" -#include "auth.hh" #include "cql3/query_processor.hh" #include "db/config.hh" #include "utils/class_registrator.hh" @@ -51,11 +50,6 @@ const sstring auth::authenticator::USERNAME_KEY("username"); const sstring auth::authenticator::PASSWORD_KEY("password"); -const sstring& auth::allow_all_authenticator_name() { - static const sstring name = meta::AUTH_PACKAGE_NAME + "AllowAllAuthenticator"; - return name; -} - auth::authenticator::option auth::authenticator::string_to_option(const sstring& name) { if (strcasecmp(name.c_str(), "password") == 0) { return option::PASSWORD; @@ -71,70 +65,3 @@ sstring auth::authenticator::option_to_string(option opt) { throw std::invalid_argument(sprint("Unknown option {}", opt)); } } - -/** - * Authenticator is assumed to be a fully state-less immutable object (note all the const). - * We thus store a single instance globally, since it should be safe/ok. - */ -static std::unique_ptr global_authenticator; - -using authenticator_registry = class_registry; - -future<> -auth::authenticator::setup(const sstring& type) { - if (type == allow_all_authenticator_name()) { - class allow_all_authenticator : public authenticator { - public: - future<> start() override { - return make_ready_future<>(); - } - future<> stop() override { - return make_ready_future<>(); - } - const sstring& qualified_java_name() const override { - return allow_all_authenticator_name(); - } - bool require_authentication() const override { - return false; - } - option_set supported_options() const override { - return option_set(); - } - option_set alterable_options() const override { - return option_set(); - } - future<::shared_ptr> authenticate(const credentials_map& credentials) const override { - return make_ready_future<::shared_ptr>(::make_shared()); - } - future<> create(sstring username, const option_map& options) override { - return make_ready_future(); - } - future<> alter(sstring username, const option_map& options) override { - return make_ready_future(); - } - future<> drop(sstring username) override { - return make_ready_future(); - } - const resource_ids& protected_resources() const override { - static const resource_ids ids; - return ids; - } - ::shared_ptr new_sasl_challenge() const override { - throw std::runtime_error("Should not reach"); - } - }; - global_authenticator = std::make_unique(); - return make_ready_future(); - } else { - auto a = authenticator_registry::create(type, cql3::get_local_query_processor()); - auto f = a->start(); - return f.then([a = std::move(a)]() mutable { - global_authenticator = std::move(a); - }); - } -} - -auth::authenticator& auth::authenticator::get() { - assert(global_authenticator); - return *global_authenticator; -} diff --git a/auth/authenticator.hh b/auth/authenticator.hh index db2fbe6e9c..55c2c42737 100644 --- a/auth/authenticator.hh +++ b/auth/authenticator.hh @@ -65,8 +65,6 @@ namespace auth { class authenticated_user; -const sstring& allow_all_authenticator_name(); - class authenticator { public: static const sstring USERNAME_KEY; @@ -87,19 +85,6 @@ public: using option_map = std::unordered_map>; using credentials_map = std::unordered_map; - /** - * Setup is called once upon system startup to initialize the IAuthenticator. - * - * For example, use this method to create any required keyspaces/column families. - * Note: Only call from main thread. - */ - static future<> setup(const sstring& type); - - /** - * Returns the system authenticator. Must have called setup before calling this. - */ - static authenticator& get(); - virtual ~authenticator() {} diff --git a/auth/authorizer.hh b/auth/authorizer.hh index 1fda736bde..e3ace8fb0d 100644 --- a/auth/authorizer.hh +++ b/auth/authorizer.hh @@ -55,6 +55,8 @@ namespace auth { +class service; + class authenticated_user; struct permission_details { @@ -69,8 +71,6 @@ struct permission_details { using std::experimental::optional; -const sstring& allow_all_authorizer_name(); - class authorizer { public: virtual ~authorizer() {} @@ -88,7 +88,7 @@ public: * @param resource Resource for which the authorization is being requested. @see DataResource. * @return Set of permissions of the user on the resource. Should never return empty. Use permission.NONE instead. */ - virtual future authorize(::shared_ptr, data_resource) const = 0; + virtual future authorize(service&, ::shared_ptr, data_resource) const = 0; /** * Grants a set of permissions on a resource to a user. @@ -132,7 +132,7 @@ public: * @throws RequestValidationException * @throws RequestExecutionException */ - virtual future> list(::shared_ptr performer, permission_set, optional, optional) const = 0; + virtual future> list(service&, ::shared_ptr performer, permission_set, optional, optional) const = 0; /** * This method is called before deleting a user with DROP USER query so that a new user with the same @@ -162,18 +162,6 @@ public: * @throws ConfigurationException when there is a configuration error. */ virtual future<> validate_configuration() const = 0; - - /** - * Setup is called once upon system startup to initialize the IAuthorizer. - * - * For example, use this method to create any required keyspaces/column families. - */ - static future<> setup(const sstring& type); - - /** - * Returns the system authorizer. Must have called setup before calling this. - */ - static authorizer& get(); }; } diff --git a/auth/common.cc b/auth/common.cc index 7f70f6353c..35acca4d3a 100644 --- a/auth/common.cc +++ b/auth/common.cc @@ -21,6 +21,13 @@ #include "auth/common.hh" +#include + +#include "cql3/query_processor.hh" +#include "cql3/statements/create_table_statement.hh" +#include "schema_builder.hh" +#include "service/migration_manager.hh" + namespace auth { namespace meta { @@ -32,4 +39,32 @@ const sstring AUTH_PACKAGE_NAME("org.apache.cassandra.auth."); } +future<> create_metadata_table_if_missing( + const sstring& table_name, + cql3::query_processor& qp, + const sstring& cql, + ::service::migration_manager& mm) { + auto& db = qp.db().local(); + + if (db.has_schema(meta::AUTH_KS, table_name)) { + return make_ready_future<>(); + } + + auto parsed_statement = static_pointer_cast( + cql3::query_processor::parse_statement(cql)); + + parsed_statement->prepare_keyspace(meta::AUTH_KS); + + auto statement = static_pointer_cast( + parsed_statement->prepare(db, qp.get_cql_stats())->statement); + + const auto schema = statement->get_cf_meta_data(); + const auto uuid = generate_legacy_id(schema->ks_name(), schema->cf_name()); + + schema_builder b(schema); + b.set_uuid(uuid); + + return mm.announce_new_column_family(b.build(), false); +} + } diff --git a/auth/common.hh b/auth/common.hh index c62a911662..1d8ab03810 100644 --- a/auth/common.hh +++ b/auth/common.hh @@ -21,10 +21,24 @@ #pragma once +#include + +#include +#include +#include #include +#include "delayed_tasks.hh" #include "seastarx.hh" +namespace service { +class migration_manager; +} + +namespace cql3 { +class query_processor; +} + namespace auth { namespace meta { @@ -36,4 +50,25 @@ extern const sstring AUTH_PACKAGE_NAME; } +template +future<> once_among_shards(Task&& f) { + if (engine().cpu_id() == 0u) { + return f(); + } + + return make_ready_future<>(); +} + +template +void delay_until_system_ready(delayed_tasks& ts, Task&& f) { + static const typename std::chrono::milliseconds delay_duration(10000); + ts.schedule_after(delay_duration, std::forward(f)); +} + +future<> create_metadata_table_if_missing( + const sstring& table_name, + cql3::query_processor&, + const sstring& cql, + ::service::migration_manager&); + } diff --git a/auth/default_authorizer.cc b/auth/default_authorizer.cc index 87695d8632..95d950d73d 100644 --- a/auth/default_authorizer.cc +++ b/auth/default_authorizer.cc @@ -46,7 +46,6 @@ #include -#include "auth.hh" #include "common.hh" #include "default_authorizer.hh" #include "authenticated_user.hh" @@ -69,18 +68,22 @@ static const sstring PERMISSIONS_CF = "permissions"; static logging::logger alogger("default_authorizer"); // To ensure correct initialization order, we unfortunately need to use a string literal. -static const class_registrator password_auth_reg( - "org.apache.cassandra.auth.CassandraAuthorizer"); +static const class_registrator< + auth::authorizer, + auth::default_authorizer, + cql3::query_processor&, + ::service::migration_manager&> password_auth_reg("org.apache.cassandra.auth.CassandraAuthorizer"); -auth::default_authorizer::default_authorizer(cql3::query_processor& qp) - : _qp(qp) { +auth::default_authorizer::default_authorizer(cql3::query_processor& qp, ::service::migration_manager& mm) + : _qp(qp) + , _migration_manager(mm) { } auth::default_authorizer::~default_authorizer() { } future<> auth::default_authorizer::start() { - sstring create_table = sprint("CREATE TABLE %s.%s (" + static const sstring create_table = sprint("CREATE TABLE %s.%s (" "%s text," "%s text," "%s set," @@ -89,7 +92,13 @@ future<> auth::default_authorizer::start() { PERMISSIONS_CF, USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, USER_NAME, RESOURCE_NAME, 90 * 24 * 60 * 60); // 3 months. - return auth::setup_table(PERMISSIONS_CF, create_table); + return auth::once_among_shards([this] { + return auth::create_metadata_table_if_missing( + PERMISSIONS_CF, + _qp, + create_table, + _migration_manager); + }); } future<> auth::default_authorizer::stop() { @@ -97,8 +106,8 @@ future<> auth::default_authorizer::stop() { } future auth::default_authorizer::authorize( - ::shared_ptr user, data_resource resource) const { - return user->is_super().then([this, user, resource = std::move(resource)](bool is_super) { + service& ser, ::shared_ptr user, data_resource resource) const { + return auth::is_super_user(ser, *user).then([this, user, resource = std::move(resource)](bool is_super) { if (is_super) { return make_ready_future(permissions::ALL); } @@ -153,9 +162,9 @@ future<> auth::default_authorizer::revoke( } future> auth::default_authorizer::list( - ::shared_ptr performer, permission_set set, + service& ser, ::shared_ptr performer, permission_set set, optional resource, optional user) const { - return performer->is_super().then([this, performer, set = std::move(set), resource = std::move(resource), user = std::move(user)](bool is_super) { + return auth::is_super_user(ser, *performer).then([this, performer, set = std::move(set), resource = std::move(resource), user = std::move(user)](bool is_super) { if (!is_super && (!user || performer->name() != *user)) { throw exceptions::unauthorized_exception(sprint("You are not authorized to view %s's permissions", user ? *user : "everyone")); } diff --git a/auth/default_authorizer.hh b/auth/default_authorizer.hh index 4c81a9cb09..5294f730c5 100644 --- a/auth/default_authorizer.hh +++ b/auth/default_authorizer.hh @@ -41,8 +41,11 @@ #pragma once +#include + #include "authorizer.hh" #include "cql3/query_processor.hh" +#include "service/migration_manager.hh" namespace auth { @@ -51,8 +54,10 @@ const sstring& default_authorizer_name(); class default_authorizer : public authorizer { cql3::query_processor& _qp; + ::service::migration_manager& _migration_manager; + public: - default_authorizer(cql3::query_processor&); + default_authorizer(cql3::query_processor&, ::service::migration_manager&); ~default_authorizer(); future<> start() override; @@ -63,13 +68,13 @@ public: return default_authorizer_name(); } - future authorize(::shared_ptr, data_resource) const override; + future authorize(service&, ::shared_ptr, data_resource) const override; future<> grant(::shared_ptr, permission_set, data_resource, sstring) override; future<> revoke(::shared_ptr, permission_set, data_resource, sstring) override; - future> list(::shared_ptr, permission_set, optional, optional) const override; + future> list(service&, ::shared_ptr, permission_set, optional, optional) const override; future<> revoke_all(sstring) override; diff --git a/auth/password_authenticator.cc b/auth/password_authenticator.cc index d7231dc2f8..e58e3407a3 100644 --- a/auth/password_authenticator.cc +++ b/auth/password_authenticator.cc @@ -46,12 +46,12 @@ #include -#include "auth.hh" #include "common.hh" #include "password_authenticator.hh" #include "authenticated_user.hh" #include "cql3/untyped_result_set.hh" #include "log.hh" +#include "service/migration_manager.hh" #include "utils/class_registrator.hh" const sstring& auth::password_authenticator_name() { @@ -69,14 +69,18 @@ static const sstring CREDENTIALS_CF = "credentials"; static logging::logger plogger("password_authenticator"); // To ensure correct initialization order, we unfortunately need to use a string literal. -static const class_registrator password_auth_reg( - "org.apache.cassandra.auth.PasswordAuthenticator"); +static const class_registrator< + auth::authenticator, + auth::password_authenticator, + cql3::query_processor&, + ::service::migration_manager&> password_auth_reg("org.apache.cassandra.auth.PasswordAuthenticator"); auth::password_authenticator::~password_authenticator() {} -auth::password_authenticator::password_authenticator(cql3::query_processor& qp) - : _qp(qp) { +auth::password_authenticator::password_authenticator(cql3::query_processor& qp, ::service::migration_manager& mm) + : _qp(qp) + , _migration_manager(mm) { } // TODO: blowfish @@ -150,33 +154,42 @@ static sstring hashpw(const sstring& pass) { } future<> auth::password_authenticator::start() { - gensalt(); // do this once to determine usable hashing + return auth::once_among_shards([this] { + gensalt(); // do this once to determine usable hashing - sstring create_table = sprint( - "CREATE TABLE %s.%s (" - "%s text," - "%s text," // salt + hash + number of rounds - "options map,"// for future extensions - "PRIMARY KEY(%s)" - ") WITH gc_grace_seconds=%d", - meta::AUTH_KS, - CREDENTIALS_CF, USER_NAME, SALTED_HASH, USER_NAME, - 90 * 24 * 60 * 60); // 3 months. + static const sstring create_table = sprint( + "CREATE TABLE %s.%s (" + "%s text," + "%s text," // salt + hash + number of rounds + "options map,"// for future extensions + "PRIMARY KEY(%s)" + ") WITH gc_grace_seconds=%d", + meta::AUTH_KS, + CREDENTIALS_CF, USER_NAME, SALTED_HASH, USER_NAME, + 90 * 24 * 60 * 60); // 3 months. - return auth::setup_table(CREDENTIALS_CF, create_table).then([this] { - // instead of once-timer, just schedule this later - auth::schedule_when_up([this] { - return auth::has_existing_users(CREDENTIALS_CF, DEFAULT_USER_NAME, USER_NAME).then([this](bool exists) { - if (!exists) { - _qp.process(sprint("INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", - meta::AUTH_KS, - CREDENTIALS_CF, - USER_NAME, SALTED_HASH - ), - db::consistency_level::ONE, {DEFAULT_USER_NAME, hashpw(DEFAULT_USER_PASSWORD)}).then([](auto) { - plogger.info("Created default user '{}'", DEFAULT_USER_NAME); - }); - } + return auth::create_metadata_table_if_missing( + CREDENTIALS_CF, + _qp, + create_table, + _migration_manager).then([this] { + auth::delay_until_system_ready(_delayed, [this] { + return has_existing_users().then([this](bool existing) { + if (!existing) { + return _qp.process( + sprint( + "INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", + meta::AUTH_KS, + CREDENTIALS_CF, + USER_NAME, SALTED_HASH), + db::consistency_level::ONE, + { DEFAULT_USER_NAME, hashpw(DEFAULT_USER_PASSWORD) }).then([](auto) { + plogger.info("Created default user '{}'", DEFAULT_USER_NAME); + }); + } + + return make_ready_future<>(); + }); }); }); }); @@ -288,8 +301,10 @@ const auth::resource_ids& auth::password_authenticator::protected_resources() co ::shared_ptr auth::password_authenticator::new_sasl_challenge() const { class plain_text_password_challenge: public sasl_challenge { + const password_authenticator& _self; + public: - plain_text_password_challenge() + plain_text_password_challenge(const password_authenticator& self) : _self(self) {} /** @@ -344,11 +359,58 @@ const auth::resource_ids& auth::password_authenticator::protected_resources() co return _complete; } future<::shared_ptr> get_authenticated_user() const override { - return authenticator::get().authenticate(_credentials); + return _self.authenticate(_credentials); } private: credentials_map _credentials; bool _complete = false; }; - return ::make_shared(); + return ::make_shared(*this); +} + + +// +// Similar in structure to `auth::service::has_existing_users()`, but trying to generalize the pattern breaks all kinds +// of module boundaries and leaks implementation details. +// +future auth::password_authenticator::has_existing_users() const { + static const sstring default_user_query = sprint( + "SELECT * FROM %s.%s WHERE %s = ?", + meta::AUTH_KS, + CREDENTIALS_CF, + USER_NAME); + + static const sstring all_users_query = sprint( + "SELECT * FROM %s.%s LIMIT 1", + meta::AUTH_KS, + CREDENTIALS_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.process( + default_user_query, + db::consistency_level::ONE, + { meta::DEFAULT_SUPERUSER_NAME }, + true).then([this](auto results) { + if (!results->empty()) { + return make_ready_future(true); + } + + return _qp.process( + default_user_query, + db::consistency_level::QUORUM, + { meta::DEFAULT_SUPERUSER_NAME }, + true).then([this](auto results) { + if (!results->empty()) { + return make_ready_future(true); + } + + return _qp.process( + all_users_query, + db::consistency_level::QUORUM).then([](auto results) { + return make_ready_future(!results->empty()); + }); + }); + }); } diff --git a/auth/password_authenticator.hh b/auth/password_authenticator.hh index 0c1abc549c..8d1c045df3 100644 --- a/auth/password_authenticator.hh +++ b/auth/password_authenticator.hh @@ -43,6 +43,11 @@ #include "authenticator.hh" #include "cql3/query_processor.hh" +#include "delayed_tasks.hh" + +namespace service { +class migration_manager; +} namespace auth { @@ -51,8 +56,12 @@ const sstring& password_authenticator_name(); class password_authenticator : public authenticator { cql3::query_processor& _qp; + ::service::migration_manager& _migration_manager; + + delayed_tasks<> _delayed{}; + public: - password_authenticator(cql3::query_processor&); + password_authenticator(cql3::query_processor&, ::service::migration_manager&); ~password_authenticator(); future<> start() override; @@ -72,6 +81,9 @@ public: static db::consistency_level consistency_for_user(const sstring& username); + +private: + future has_existing_users() const; }; } diff --git a/auth/permissions_cache.cc b/auth/permissions_cache.cc index d368107cf7..f86d0134e7 100644 --- a/auth/permissions_cache.cc +++ b/auth/permissions_cache.cc @@ -21,12 +21,26 @@ #include "auth/permissions_cache.hh" +#include "auth/authorizer.hh" +#include "auth/common.hh" +#include "auth/service.hh" +#include "db/config.hh" + namespace auth { -permissions_cache::permissions_cache(const permissions_cache_config& c, authorizer& a, logging::logger& log) - : _cache(c.max_entries, c.validity_period, c.update_period, log, [&a, &log](const key_type& k) { +permissions_cache_config permissions_cache_config::from_db_config(const db::config& dc) { + permissions_cache_config c; + c.max_entries = dc.permissions_cache_max_entries(); + c.validity_period = std::chrono::milliseconds(dc.permissions_validity_in_ms()); + c.update_period = std::chrono::milliseconds(dc.permissions_update_interval_in_ms()); + + return c; +} + +permissions_cache::permissions_cache(const permissions_cache_config& c, service& ser, logging::logger& log) + : _cache(c.max_entries, c.validity_period, c.update_period, log, [&ser, &log](const key_type& k) { log.debug("Refreshing permissions for {}", k.first.name()); - return a.authorize(::make_shared(k.first), k.second); + return ser.underlying_authorizer().authorize(ser, ::make_shared(k.first), k.second); }) { } diff --git a/auth/permissions_cache.hh b/auth/permissions_cache.hh index 5fd4dbd2a0..d4d24ec0ba 100644 --- a/auth/permissions_cache.hh +++ b/auth/permissions_cache.hh @@ -30,7 +30,6 @@ #include #include "auth/authenticated_user.hh" -#include "auth/authorizer.hh" #include "auth/data_resource.hh" #include "auth/permission.hh" #include "log.hh" @@ -59,9 +58,17 @@ inline std::ostream& operator<<(std::ostream& os, const std::pair start() { return make_ready_future<>(); diff --git a/auth/service.cc b/auth/service.cc new file mode 100644 index 0000000000..c038d7b41d --- /dev/null +++ b/auth/service.cc @@ -0,0 +1,353 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#include "auth/service.hh" + +#include + +#include +#include +#include + +#include "auth/allow_all_authenticator.hh" +#include "auth/allow_all_authorizer.hh" +#include "auth/common.hh" +#include "cql3/query_processor.hh" +#include "cql3/untyped_result_set.hh" +#include "db/config.hh" +#include "db/consistency_level.hh" +#include "exceptions/exceptions.hh" +#include "log.hh" +#include "service/migration_listener.hh" +#include "utils/class_registrator.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 { + _authorizer.revoke_all(auth::data_resource(ks_name)); + } + + void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override { + _authorizer.revoke_all(auth::data_resource(ks_name, cf_name)); + } + + 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 sharded sharded_permissions_cache{}; + +static db::consistency_level consistency_for_user(const sstring& name) { + if (name == meta::DEFAULT_SUPERUSER_NAME) { + return db::consistency_level::QUORUM; + } else { + return db::consistency_level::LOCAL_ONE; + } +} + +static future<::shared_ptr> select_user(cql3::query_processor& qp, const sstring& name) { + // Here was a thread local, explicit cache of prepared statement. In normal execution this is + // fine, but since we in testing set up and tear down system over and over, we'd start using + // obsolete prepared statements pretty quickly. + // Rely on query processing caching statements instead, and lets assume + // that a map lookup string->statement is not gonna kill us much. + return qp.process( + sprint( + "SELECT * FROM %s.%s WHERE %s = ?", + meta::AUTH_KS, + meta::USERS_CF, + meta::user_name_col_name), + consistency_for_user(name), + { name }, + true); +} + +service_config service_config::from_db_config(const db::config& dc) { + const qualified_name qualified_authorizer_name(meta::AUTH_PACKAGE_NAME, dc.authorizer()); + const qualified_name qualified_authenticator_name(meta::AUTH_PACKAGE_NAME, dc.authenticator()); + + service_config c; + c.authorizer_java_name = qualified_authorizer_name; + c.authenticator_java_name = qualified_authenticator_name; + + return c; +} + +service::service( + permissions_cache_config c, + cql3::query_processor& qp, + ::service::migration_manager& mm, + std::unique_ptr a, + std::unique_ptr b) + : _cache_config(std::move(c)) + , _qp(qp) + , _migration_manager(mm) + , _authorizer(std::move(a)) + , _authenticator(std::move(b)) + , _migration_listener(std::make_unique(*_authorizer)) { +} + +service::service( + permissions_cache_config cache_config, + cql3::query_processor& qp, + ::service::migration_manager& mm, + const service_config& sc) + : service( + std::move(cache_config), + qp, + mm, + create_object(sc.authorizer_java_name, qp, mm), + create_object(sc.authenticator_java_name, qp, mm)) { +} + +bool service::should_create_metadata() const { + const bool null_authorizer = _authorizer->qualified_java_name() == allow_all_authorizer_name(); + const bool null_authenticator = _authenticator->qualified_java_name() == allow_all_authenticator_name(); + return !null_authorizer || !null_authenticator; +} + +future<> service::create_metadata_if_missing() { + auto& db = _qp.db().local(); + + auto f = make_ready_future<>(); + + if (!db.has_keyspace(meta::AUTH_KS)) { + std::map opts{{"replication_factor", "1"}}; + + auto ksm = keyspace_metadata::new_keyspace( + meta::AUTH_KS, + "org.apache.cassandra.locator.SimpleStrategy", + opts, + true); + + // We use min_timestamp so that default keyspace metadata will loose with any manual adjustments. + // See issue #2129. + f = _migration_manager.announce_new_keyspace(ksm, api::min_timestamp, false); + } + + return f.then([this] { + // 3 months. + static const auto gc_grace_seconds = 90 * 24 * 60 * 60; + + static const sstring users_table_query = sprint( + "CREATE TABLE %s.%s (%s text, %s boolean, PRIMARY KEY (%s)) WITH gc_grace_seconds=%s", + meta::AUTH_KS, + meta::USERS_CF, + meta::user_name_col_name, + meta::superuser_col_name, + meta::user_name_col_name, + gc_grace_seconds); + + return create_metadata_table_if_missing( + meta::USERS_CF, + _qp, + users_table_query, + _migration_manager); + }).then([this] { + delay_until_system_ready(_delayed, [this] { + return has_existing_users().then([this](bool existing) { + if (!existing) { + // + // Create default superuser. + // + + static const sstring query = sprint( + "INSERT INTO %s.%s (%s, %s) VALUES (?, ?) USING TIMESTAMP 0", + meta::AUTH_KS, + meta::USERS_CF, + meta::user_name_col_name, + meta::superuser_col_name); + + return _qp.process( + query, + db::consistency_level::ONE, + { meta::DEFAULT_SUPERUSER_NAME, true }).then([](auto&&) { + log.info("Created default superuser '{}'", meta::DEFAULT_SUPERUSER_NAME); + }).handle_exception([](auto exn) { + try { + std::rethrow_exception(exn); + } catch (const exceptions::request_execution_exception&) { + log.warn("Skipped default superuser setup: some nodes were not ready"); + } + }).discard_result(); + } + + return make_ready_future<>(); + }); + }); + + return make_ready_future<>(); + }); +} + +future<> service::start() { + return once_among_shards([this] { + if (should_create_metadata()) { + return create_metadata_if_missing(); + } + + return make_ready_future<>(); + }).then([this] { + return when_all_succeed(_authorizer->start(), _authenticator->start()); + }).then([this] { + return once_among_shards([this] { + _migration_manager.register_listener(_migration_listener.get()); + return sharded_permissions_cache.start(std::ref(_cache_config), std::ref(*this), std::ref(log)); + }); + }); +} + +future<> service::stop() { + return once_among_shards([this] { + _delayed.cancel_all(); + return sharded_permissions_cache.stop(); + }).then([this] { + return when_all_succeed(_authorizer->stop(), _authenticator->stop()); + }); +} + +future service::has_existing_users() const { + static const sstring default_user_query = sprint( + "SELECT * FROM %s.%s WHERE %s = ?", + meta::AUTH_KS, + meta::USERS_CF, + meta::user_name_col_name); + + static const sstring all_users_query = sprint( + "SELECT * FROM %s.%s 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.process( + default_user_query, + db::consistency_level::ONE, + { meta::DEFAULT_SUPERUSER_NAME }, + true).then([this](auto results) { + if (!results->empty()) { + return make_ready_future(true); + } + + return _qp.process( + default_user_query, + db::consistency_level::QUORUM, + { meta::DEFAULT_SUPERUSER_NAME }, + true).then([this](auto results) { + if (!results->empty()) { + return make_ready_future(true); + } + + return _qp.process( + all_users_query, + db::consistency_level::QUORUM).then([](auto results) { + return make_ready_future(!results->empty()); + }); + }); + }); +} + +future service::is_existing_user(const sstring& name) const { + return select_user(_qp, name).then([](auto results) { + return !results->empty(); + }); +} + +future service::is_super_user(const sstring& name) const { + return select_user(_qp, name).then([](auto results) { + return !results->empty() && results->one().template get_as(meta::superuser_col_name); + }); +} + +future<> service::insert_user(const sstring& name, bool is_superuser) { + return _qp.process( + sprint( + "INSERT INTO %s.%s (%s, %s) VALUES (?, ?)", + meta::AUTH_KS, + meta::USERS_CF, + meta::user_name_col_name, + meta::superuser_col_name), + consistency_for_user(name), + { name, is_superuser }).discard_result(); +} + +future<> service::delete_user(const sstring& name) { + return _qp.process( + sprint( + "DELETE FROM %s.%s WHERE %s = ?", + meta::AUTH_KS, + meta::USERS_CF, + meta::user_name_col_name), + consistency_for_user(name), + { name }).discard_result(); +} + +future service::get_permissions(::shared_ptr u, data_resource r) const { + return sharded_permissions_cache.local().get(std::move(u), std::move(r)); +} + +// +// Free functions. +// + +future is_super_user(const service& ser, const authenticated_user& u) { + if (u.is_anonymous()) { + return make_ready_future(false); + } + + return ser.is_super_user(u.name()); +} + +} diff --git a/auth/service.hh b/auth/service.hh new file mode 100644 index 0000000000..833d71c165 --- /dev/null +++ b/auth/service.hh @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2017 ScyllaDB + */ + +/* + * This file is part of Scylla. + * + * Scylla is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Scylla is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Scylla. If not, see . + */ + +#pragma once + +#include + +#include +#include + +#include "auth/authenticator.hh" +#include "auth/authorizer.hh" +#include "auth/authenticated_user.hh" +#include "auth/permission.hh" +#include "auth/permissions_cache.hh" +#include "delayed_tasks.hh" +#include "seastarx.hh" + +namespace cql3 { +class query_processor; +} + +namespace db { +class config; +} + +namespace service { +class migration_manager; +class migration_listener; +} + +namespace auth { + +class authenticator; +class authorizer; + +struct service_config final { + static service_config from_db_config(const db::config&); + + sstring authorizer_java_name; + sstring authenticator_java_name; +}; + +class service final { + permissions_cache_config _cache_config; + + cql3::query_processor& _qp; + + ::service::migration_manager& _migration_manager; + + std::unique_ptr _authorizer; + + std::unique_ptr _authenticator; + + // Only one of these should be registered, so we end up with some unused instances. Not the end of the world. + std::unique_ptr<::service::migration_listener> _migration_listener; + + delayed_tasks<> _delayed{}; + +public: + service( + permissions_cache_config, + cql3::query_processor&, + ::service::migration_manager&, + std::unique_ptr, + std::unique_ptr); + + service( + permissions_cache_config, + cql3::query_processor&, + ::service::migration_manager&, + const service_config&); + + future<> start(); + + future<> stop(); + + future is_existing_user(const sstring& name) const; + + future is_super_user(const sstring& name) const; + + future<> insert_user(const sstring& name, bool is_superuser); + + future<> delete_user(const sstring& name); + + future get_permissions(::shared_ptr, data_resource) const; + + authenticator& underlying_authenticator() { + return *_authenticator; + } + + const authenticator& underlying_authenticator() const { + return *_authenticator; + } + + authorizer& underlying_authorizer() { + return *_authorizer; + } + + const authorizer& underlying_authorizer() const { + return *_authorizer; + } + +private: + future has_existing_users() const; + + bool should_create_metadata() const; + + future<> create_metadata_if_missing(); +}; + +future is_super_user(const service&, const authenticated_user&); + +} diff --git a/auth/transitional.cc b/auth/transitional.cc index c6714bd8e0..37d980a316 100644 --- a/auth/transitional.cc +++ b/auth/transitional.cc @@ -46,12 +46,13 @@ #include "password_authenticator.hh" #include "default_authorizer.hh" #include "permission.hh" -#include "auth.hh" #include "db/config.hh" #include "utils/class_registrator.hh" namespace auth { +class service; + static const sstring PACKAGE_NAME("com.scylladb.auth."); static const sstring& transitional_authenticator_name() { @@ -69,8 +70,8 @@ class transitional_authenticator : public authenticator { public: static const sstring PASSWORD_AUTHENTICATOR_NAME; - transitional_authenticator(cql3::query_processor& qp) - : transitional_authenticator(std::make_unique(qp)) + transitional_authenticator(cql3::query_processor& qp, ::service::migration_manager& mm) + : transitional_authenticator(std::make_unique(qp, mm)) {} transitional_authenticator(std::unique_ptr a) : _authenticator(std::move(a)) @@ -153,8 +154,8 @@ public: class transitional_authorizer : public authorizer { std::unique_ptr _authorizer; public: - transitional_authorizer(cql3::query_processor& qp) - : transitional_authorizer(std::make_unique(qp)) + transitional_authorizer(cql3::query_processor& qp, ::service::migration_manager& mm) + : transitional_authorizer(std::make_unique(qp, mm)) {} transitional_authorizer(std::unique_ptr a) : _authorizer(std::move(a)) @@ -170,8 +171,8 @@ public: const sstring& qualified_java_name() const override { return transitional_authorizer_name(); } - future authorize(::shared_ptr user, data_resource resource) const override { - return user->is_super().then([](bool s) { + future authorize(service& ser, ::shared_ptr user, data_resource resource) const override { + return is_super_user(ser, *user).then([](bool s) { static const permission_set transitional_permissions = permission_set::of revoke(::shared_ptr user, permission_set ps, data_resource r, sstring s) override { return _authorizer->revoke(std::move(user), std::move(ps), std::move(r), std::move(s)); } - future> list(::shared_ptr user, permission_set ps, optional r, optional s) const override { - return _authorizer->list(std::move(user), std::move(ps), std::move(r), std::move(s)); + future> list(service& ser, ::shared_ptr user, permission_set ps, optional r, optional s) const override { + return _authorizer->list(ser, std::move(user), std::move(ps), std::move(r), std::move(s)); } future<> revoke_all(sstring s) override { return _authorizer->revoke_all(std::move(s)); @@ -209,9 +210,14 @@ public: // To ensure correct initialization order, we unfortunately need to use string literals. // -static const class_registrator transitional_authenticator_reg( - "com.scylladb.auth.TransitionalAuthenticator"); +static const class_registrator< + auth::authenticator, + auth::transitional_authenticator, + cql3::query_processor&, + ::service::migration_manager&> transitional_authenticator_reg("com.scylladb.auth.TransitionalAuthenticator"); -static const class_registrator transitional_authorizer_reg( - "com.scylladb.auth.TransitionalAuthorizer"); +static const class_registrator< + auth::authorizer, + auth::transitional_authorizer, + cql3::query_processor&, + ::service::migration_manager&> transitional_authorizer_reg("com.scylladb.auth.TransitionalAuthorizer"); diff --git a/configure.py b/configure.py index 0805fedf5f..9a2c73d7a0 100755 --- a/configure.py +++ b/configure.py @@ -524,16 +524,17 @@ scylla_core = (['database.cc', 'lister.cc', 'repair/repair.cc', 'exceptions/exceptions.cc', - 'auth/auth.cc', + 'auth/allow_all_authenticator.cc', + 'auth/allow_all_authorizer.cc', 'auth/authenticated_user.cc', 'auth/authenticator.cc', - 'auth/authorizer.cc', 'auth/common.cc', 'auth/default_authorizer.cc', 'auth/data_resource.cc', 'auth/password_authenticator.cc', 'auth/permission.cc', 'auth/permissions_cache.cc', + 'auth/service.cc', 'auth/transitional.cc', 'tracing/tracing.cc', 'tracing/trace_keyspace_helper.cc', diff --git a/cql3/statements/alter_user_statement.cc b/cql3/statements/alter_user_statement.cc index c361c99f85..59d1085fc4 100644 --- a/cql3/statements/alter_user_statement.cc +++ b/cql3/statements/alter_user_statement.cc @@ -42,8 +42,8 @@ #include #include "alter_user_statement.hh" -#include "auth/auth.hh" #include "auth/authenticator.hh" +#include "auth/service.hh" cql3::statements::alter_user_statement::alter_user_statement(sstring username, ::shared_ptr opts, std::experimental::optional superuser) : _username(std::move(username)) @@ -52,7 +52,7 @@ cql3::statements::alter_user_statement::alter_user_statement(sstring username, : {} void cql3::statements::alter_user_statement::validate(distributed& proxy, const service::client_state& state) { - _opts->validate(); + _opts->validate(state.get_auth_service()->underlying_authenticator()); if (!_superuser && _opts->empty()) { throw exceptions::invalid_request_exception("ALTER USER can't be empty"); @@ -73,7 +73,10 @@ future<> cql3::statements::alter_user_statement::check_access(const service::cli // my disgust. throw exceptions::unauthorized_exception("You aren't allowed to alter your own superuser status"); } - return user->is_super().then([this, user](bool is_super) { + + const auto& auth_service = *state.get_auth_service(); + + return auth::is_super_user(auth_service, *user).then([this, user, &auth_service](bool is_super) { if (_superuser && !is_super) { throw exceptions::unauthorized_exception("Only superusers are allowed to alter superuser status"); } @@ -84,7 +87,7 @@ future<> cql3::statements::alter_user_statement::check_access(const service::cli if (!is_super) { for (auto o : _opts->options() | boost::adaptors::map_keys) { - if (!auth::authenticator::get().alterable_options().contains(o)) { + if (!auth_service.underlying_authenticator().alterable_options().contains(o)) { throw exceptions::unauthorized_exception(sprint("You aren't allowed to alter {} option", o)); } } @@ -94,14 +97,17 @@ future<> cql3::statements::alter_user_statement::check_access(const service::cli future<::shared_ptr> cql3::statements::alter_user_statement::execute(distributed& proxy, service::query_state& state, const query_options& options) { - return auth::auth::is_existing_user(_username).then([this](bool exists) { + auto& client_state = state.get_client_state(); + auto& auth_service = *client_state.get_auth_service(); + + return auth_service.is_existing_user(_username).then([this, &auth_service](bool exists) { if (!exists) { throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username)); } - auto f = _opts->options().empty() ? make_ready_future() : auth::authenticator::get().alter(_username, _opts->options()); + auto f = _opts->options().empty() ? make_ready_future() : auth_service.underlying_authenticator().alter(_username, _opts->options()); if (_superuser) { - f = f.then([this] { - return auth::auth::insert_user(_username, *_superuser); + f = f.then([this, &auth_service] { + return auth_service.insert_user(_username, *_superuser); }); } return f.then([] { return make_ready_future<::shared_ptr>(); }); diff --git a/cql3/statements/create_user_statement.cc b/cql3/statements/create_user_statement.cc index 2967a7b025..482fece0eb 100644 --- a/cql3/statements/create_user_statement.cc +++ b/cql3/statements/create_user_statement.cc @@ -40,8 +40,8 @@ */ #include "create_user_statement.hh" -#include "auth/auth.hh" #include "auth/authenticator.hh" +#include "auth/service.hh" cql3::statements::create_user_statement::create_user_statement(sstring username, ::shared_ptr opts, bool superuser, bool if_not_exists) : _username(std::move(username)) @@ -55,7 +55,7 @@ void cql3::statements::create_user_statement::validate(distributedvalidate(); + _opts->validate(state.get_auth_service()->underlying_authenticator()); // validate login here before checkAccess to avoid leaking user existence to anonymous users. state.ensure_not_anonymous(); @@ -66,19 +66,22 @@ void cql3::statements::create_user_statement::validate(distributed> cql3::statements::create_user_statement::execute(distributed& proxy, service::query_state& state, const query_options& options) { - return state.get_client_state().user()->is_super().then([this](bool is_super) { + auto& client_state = state.get_client_state(); + auto& auth_service = *client_state.get_auth_service(); + + return auth::is_super_user(auth_service, *client_state.user()).then([this, &auth_service](bool is_super) { if (!is_super) { throw exceptions::unauthorized_exception("Only superusers are allowed to perform CREATE USER queries"); } - return auth::auth::is_existing_user(_username).then([this](bool exists) { + return auth_service.is_existing_user(_username).then([this, &auth_service](bool exists) { if (exists && !_if_not_exists) { throw exceptions::invalid_request_exception(sprint("User %s already exists", _username)); } if (exists && _if_not_exists) { make_ready_future<::shared_ptr>(); } - return auth::authenticator::get().create(_username, _opts->options()).then([this] { - return auth::auth::insert_user(_username, _superuser).then([] { + return auth_service.underlying_authenticator().create(_username, _opts->options()).then([this, &auth_service] { + return auth_service.insert_user(_username, _superuser).then([] { return make_ready_future<::shared_ptr>(); }); }); diff --git a/cql3/statements/drop_user_statement.cc b/cql3/statements/drop_user_statement.cc index 5470130473..cac36b55aa 100644 --- a/cql3/statements/drop_user_statement.cc +++ b/cql3/statements/drop_user_statement.cc @@ -42,9 +42,9 @@ #include #include "drop_user_statement.hh" -#include "auth/auth.hh" #include "auth/authenticator.hh" #include "auth/authorizer.hh" +#include "auth/service.hh" cql3::statements::drop_user_statement::drop_user_statement(sstring username, bool if_exists) : _username(std::move(username)) @@ -65,12 +65,15 @@ void cql3::statements::drop_user_statement::validate(distributed> cql3::statements::drop_user_statement::execute(distributed& proxy, service::query_state& state, const query_options& options) { - return state.get_client_state().user()->is_super().then([this](bool is_super) { + auto& client_state = state.get_client_state(); + auto& auth_service = *client_state.get_auth_service(); + + return auth::is_super_user(auth_service, *client_state.user()).then([this, &auth_service](bool is_super) { if (!is_super) { throw exceptions::unauthorized_exception("Only superusers are allowed to perform DROP USER queries"); } - return auth::auth::is_existing_user(_username).then([this](bool exists) { + return auth_service.is_existing_user(_username).then([this, &auth_service](bool exists) { if (!_if_exists && !exists) { throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username)); } @@ -79,9 +82,9 @@ cql3::statements::drop_user_statement::execute(distributed>(); diff --git a/cql3/statements/grant_statement.cc b/cql3/statements/grant_statement.cc index 69d39aaf89..22d9b1c42d 100644 --- a/cql3/statements/grant_statement.cc +++ b/cql3/statements/grant_statement.cc @@ -44,7 +44,10 @@ future<::shared_ptr> cql3::statements::grant_statement::execute(distributed& proxy, service::query_state& state, const query_options& options) { - return auth::authorizer::get().grant(state.get_client_state().user(), _permissions, _resource, _username).then([] { + auto& client_state = state.get_client_state(); + auto& auth_service = *client_state.get_auth_service(); + + return auth_service.underlying_authorizer().grant(client_state.user(), _permissions, _resource, _username).then([] { return make_ready_future<::shared_ptr>(); }); } diff --git a/cql3/statements/list_permissions_statement.cc b/cql3/statements/list_permissions_statement.cc index c24f9a9ad5..1b745b5d60 100644 --- a/cql3/statements/list_permissions_statement.cc +++ b/cql3/statements/list_permissions_statement.cc @@ -44,7 +44,6 @@ #include "list_permissions_statement.hh" #include "auth/authorizer.hh" -#include "auth/auth.hh" #include "auth/common.hh" #include "cql3/result_set.hh" #include "transport/messages/result_message.hh" @@ -65,7 +64,7 @@ void cql3::statements::list_permissions_statement::validate(distributed cql3::statements::list_permissions_statement::check_access(const service::client_state& state) { auto f = make_ready_future(); if (_username) { - f = auth::auth::is_existing_user(*_username).then([this](bool exists) { + f = state.get_auth_service()->is_existing_user(*_username).then([this](bool exists) { if (!exists) { throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", *_username)); } @@ -105,7 +104,8 @@ cql3::statements::list_permissions_statement::execute(distributed(), [](std::vector details, std::vector pd) { details.insert(details.end(), pd.begin(), pd.end()); return std::move(details); diff --git a/cql3/statements/permission_altering_statement.cc b/cql3/statements/permission_altering_statement.cc index b5cd84ef0d..a4099c4f29 100644 --- a/cql3/statements/permission_altering_statement.cc +++ b/cql3/statements/permission_altering_statement.cc @@ -41,11 +41,11 @@ #include +#include "auth/service.hh" #include "permission_altering_statement.hh" #include "cql3/query_processor.hh" #include "cql3/query_options.hh" #include "cql3/selection/selection.hh" -#include "auth/auth.hh" cql3::statements::permission_altering_statement::permission_altering_statement( auth::permission_set permissions, auth::data_resource resource, @@ -60,7 +60,7 @@ void cql3::statements::permission_altering_statement::validate(distributed cql3::statements::permission_altering_statement::check_access(const service::client_state& state) { - return auth::auth::is_existing_user(_username).then([this, &state](bool exists) { + return state.get_auth_service()->is_existing_user(_username).then([this, &state](bool exists) { if (!exists) { throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username)); } diff --git a/cql3/statements/revoke_statement.cc b/cql3/statements/revoke_statement.cc index 0590cd7e12..7df2ce1131 100644 --- a/cql3/statements/revoke_statement.cc +++ b/cql3/statements/revoke_statement.cc @@ -44,7 +44,10 @@ future<::shared_ptr> cql3::statements::revoke_statement::execute(distributed& proxy, service::query_state& state, const query_options& options) { - return auth::authorizer::get().revoke(state.get_client_state().user(), _permissions, _resource, _username).then([] { + auto& client_state = state.get_client_state(); + auto& auth_service = *client_state.get_auth_service(); + + return auth_service.underlying_authorizer().revoke(client_state.user(), _permissions, _resource, _username).then([] { return make_ready_future<::shared_ptr>(); }); } diff --git a/cql3/user_options.cc b/cql3/user_options.cc index 86d8dc4878..92de1d7ada 100644 --- a/cql3/user_options.cc +++ b/cql3/user_options.cc @@ -49,8 +49,7 @@ void cql3::user_options::put(const sstring& name, const sstring& value) { _options[auth::authenticator::string_to_option(name)] = value; } -void cql3::user_options::validate() const { - auto& a = auth::authenticator::get(); +void cql3::user_options::validate(const auth::authenticator& a) const { for (auto o : _options | boost::adaptors::map_keys) { if (!a.supported_options().contains(o)) { throw exceptions::invalid_request_exception( diff --git a/cql3/user_options.hh b/cql3/user_options.hh index eecf102cff..03ebd12123 100644 --- a/cql3/user_options.hh +++ b/cql3/user_options.hh @@ -42,6 +42,10 @@ #include "auth/authenticator.hh" +namespace auth { +class authenticator; +} + namespace cql3 { class user_options { @@ -56,7 +60,7 @@ public: const auth::authenticator::option_map& options() const { return _options; } - void validate() const; + void validate(const auth::authenticator&) const; }; } diff --git a/init.cc b/init.cc index fb904bb561..0a94adbd73 100644 --- a/init.cc +++ b/init.cc @@ -34,8 +34,8 @@ logging::logger startlog("init"); // duplicated in cql_test_env.cc // until proper shutdown is done. -void init_storage_service(distributed& db) { - service::init_storage_service(db).get(); +void init_storage_service(distributed& db, sharded& auth_service) { + service::init_storage_service(db, auth_service).get(); // #293 - do not stop anything //engine().at_exit([] { return service::deinit_storage_service(); }); } diff --git a/init.hh b/init.hh index 093545a875..d073922f78 100644 --- a/init.hh +++ b/init.hh @@ -23,6 +23,7 @@ #include #include #include +#include "auth/service.hh" #include "db/config.hh" #include "database.hh" #include "log.hh" @@ -31,7 +32,7 @@ extern logging::logger startlog; class bad_configuration_error : public std::exception {}; -void init_storage_service(distributed& db); +void init_storage_service(distributed& db, sharded&); void init_ms_fd_gossiper(sstring listen_address , uint16_t storage_port , uint16_t ssl_storage_port diff --git a/main.cc b/main.cc index 148584a814..1976a757da 100644 --- a/main.cc +++ b/main.cc @@ -436,8 +436,9 @@ int main(int ac, char** av) { api::set_server_init(ctx).get(); ctx.http_server.listen(ipv4_addr{ip, api_port}).get(); startlog.info("Scylla API server listening on {}:{} ...", api_address, api_port); + static sharded auth_service; supervisor::notify("initializing storage service"); - init_storage_service(db); + init_storage_service(db, auth_service); supervisor::notify("starting per-shard database core"); // Note: changed from using a move here, because we want the config object intact. db.start(std::ref(*cfg)).get(); diff --git a/service/client_state.cc b/service/client_state.cc index 6e8a8cc5b4..12bb114d94 100644 --- a/service/client_state.cc +++ b/service/client_state.cc @@ -40,7 +40,6 @@ */ #include "client_state.hh" -#include "auth/auth.hh" #include "auth/authorizer.hh" #include "auth/authenticator.hh" #include "auth/common.hh" @@ -62,7 +61,7 @@ future<> service::client_state::check_user_exists() { return make_ready_future(); } - return auth::auth::is_existing_user(_user->name()).then([user = _user](bool exists) mutable { + return _auth_service->is_existing_user(_user->name()).then([user = _user](bool exists) mutable { if (!exists) { throw exceptions::authentication_exception( sprint("User %s doesn't exist - create it with CREATE USER query first", @@ -161,8 +160,8 @@ future<> service::client_state::has_access(const sstring& ks, auth::permission p return make_ready_future(); } if (auth::permissions::ALTERATIONS.contains(p)) { - for (auto& s : { auth::authorizer::get().protected_resources(), - auth::authenticator::get().protected_resources() }) { + for (auto& s : { _auth_service->underlying_authorizer().protected_resources(), + _auth_service->underlying_authorizer().protected_resources() }) { if (s.count(resource)) { throw exceptions::unauthorized_exception( sprint("%s schema is protected", @@ -175,12 +174,16 @@ future<> service::client_state::has_access(const sstring& ks, auth::permission p } future service::client_state::check_has_permission(auth::permission p, auth::data_resource resource) const { + if (_is_internal) { + return make_ready_future(true); + } + std::experimental::optional parent; if (resource.has_parent()) { parent = resource.get_parent(); } - return auth::auth::get_permissions(_user, resource).then([this, p, parent = std::move(parent)](auth::permission_set set) { + return _auth_service->get_permissions(_user, resource).then([this, p, parent = std::move(parent)](auth::permission_set set) { if (set.contains(p)) { return make_ready_future(true); } diff --git a/service/client_state.hh b/service/client_state.hh index e1a11b9d7a..a18f45656f 100644 --- a/service/client_state.hh +++ b/service/client_state.hh @@ -41,6 +41,7 @@ #pragma once +#include "auth/service.hh" #include "exceptions/exceptions.hh" #include "unimplemented.hh" #include "timestamp.hh" @@ -99,6 +100,8 @@ private: // Address of a client socket_address _remote_address; + // Only populated for external client state. + auth::service* _auth_service{nullptr}; public: struct internal_tag {}; struct external_tag {}; @@ -122,11 +125,12 @@ public: return _trace_state_ptr; } - client_state(external_tag, const socket_address& remote_address = socket_address(), bool thrift = false) + client_state(external_tag, auth::service& auth_service, const socket_address& remote_address = socket_address(), bool thrift = false) : _is_internal(false) , _is_thrift(thrift) - , _remote_address(remote_address) { - if (!auth::authenticator::get().require_authentication()) { + , _remote_address(remote_address) + , _auth_service(&auth_service) { + if (!auth_service.underlying_authenticator().require_authentication()) { _user = ::make_shared(); } } @@ -137,6 +141,16 @@ public: client_state(internal_tag) : _keyspace("system"), _is_internal(true), _is_thrift(false) {} + // `nullptr` for internal instances. + auth::service* get_auth_service() { + return _auth_service; + } + + // See above. + const auth::service* get_auth_service() const { + return _auth_service; + } + void merge(const client_state& other); bool is_thrift() const { @@ -155,13 +169,15 @@ public: } /** + * The `auth::service` should be non-`nullptr` for native protocol users. + * * @return a ClientState object for external clients (thrift/native protocol users). */ - static client_state for_external_calls() { - return client_state(external_tag()); + static client_state for_external_calls(auth::service& ser) { + return client_state(external_tag(), ser); } - static client_state for_external_thrift_calls() { - return client_state(external_tag(), socket_address(), true); + static client_state for_external_thrift_calls(auth::service& ser) { + return client_state(external_tag(), ser, socket_address(), true); } /** @@ -288,3 +304,4 @@ public: }; } + diff --git a/service/storage_service.cc b/service/storage_service.cc index c30944790c..f69f8aec02 100644 --- a/service/storage_service.cc +++ b/service/storage_service.cc @@ -65,7 +65,6 @@ #include #include "db/batchlog_manager.hh" #include "db/commitlog/commitlog.hh" -#include "auth/auth.hh" #include #include #include "utils/exceptions.hh" @@ -100,8 +99,9 @@ int get_generation_number() { return generation_number; } -storage_service::storage_service(distributed& db) +storage_service::storage_service(distributed& db, sharded& auth_service) : _db(db) + , _auth_service(auth_service) , _replicate_action([this] { return do_replicate_to_all_cores(); }) { sstable_read_error.connect([this] { isolate_on_error(); }); sstable_write_error.connect([this] { isolate_on_error(); }); @@ -521,7 +521,15 @@ void storage_service::join_token_ring(int delay) { slogger.error("{}", err); throw std::runtime_error(err); } - auth::auth::setup().get(); + + _auth_service.start( + auth::permissions_cache_config::from_db_config(_db.local().get_config()), + std::ref(cql3::get_query_processor()), + std::ref(service::get_migration_manager()), + auth::service_config::from_db_config(_db.local().get_config())).get(); + + _auth_service.invoke_on_all(&auth::service::start).get(); + supervisor::notify("starting tracing"); tracing::tracing::start_tracing().get(); } else { @@ -546,7 +554,14 @@ future<> storage_service::join_ring() { slogger.error("{}", err); throw std::runtime_error(err); } - auth::auth::setup().get(); + + ss._auth_service.start( + auth::permissions_cache_config::from_db_config(ss._db.local().get_config()), + std::ref(cql3::get_query_processor()), + std::ref(service::get_migration_manager()), + auth::service_config::from_db_config(ss._db.local().get_config())).get(); + + ss._auth_service.invoke_on_all(&auth::service::start).get(); } }); }); @@ -1178,7 +1193,7 @@ future<> storage_service::stop_transport() { ss.do_stop_stream_manager().get(); slogger.info("Stop transport: shutdown stream_manager done"); - auth::auth::shutdown().get(); + ss._auth_service.stop().get(); slogger.info("Stop transport: auth shutdown"); slogger.info("Stop transport: done"); @@ -1918,7 +1933,7 @@ future<> storage_service::start_rpc_server() { auto addr = cfg.rpc_address(); auto keepalive = cfg.rpc_keepalive(); return seastar::net::dns::resolve_name(addr).then([&ss, tserver, addr, port, keepalive] (seastar::net::inet_address ip) { - return tserver->start(std::ref(ss._db), std::ref(cql3::get_query_processor())).then([tserver, port, addr, ip, keepalive] { + return tserver->start(std::ref(ss._db), std::ref(cql3::get_query_processor()), std::ref(ss._auth_service)).then([tserver, port, addr, ip, keepalive] { // #293 - do not stop anything //engine().at_exit([tserver] { // return tserver->stop(); @@ -1969,8 +1984,8 @@ future<> storage_service::start_native_transport() { auto ceo = cfg.client_encryption_options(); auto keepalive = cfg.rpc_keepalive(); cql_transport::cql_load_balance lb = cql_transport::parse_load_balance(cfg.load_balance()); - return seastar::net::dns::resolve_name(addr).then([cserver, addr, &cfg, lb, keepalive, ceo = std::move(ceo)] (seastar::net::inet_address ip) { - return cserver->start(std::ref(service::get_storage_proxy()), std::ref(cql3::get_query_processor()), lb).then([cserver, &cfg, addr, ip, ceo, keepalive]() { + return seastar::net::dns::resolve_name(addr).then([&ss, cserver, addr, &cfg, lb, keepalive, ceo = std::move(ceo)] (seastar::net::inet_address ip) { + return cserver->start(std::ref(service::get_storage_proxy()), std::ref(cql3::get_query_processor()), lb, std::ref(ss._auth_service)).then([cserver, &cfg, addr, ip, ceo, keepalive]() { // #293 - do not stop anything //engine().at_exit([cserver] { // return cserver->stop(); diff --git a/service/storage_service.hh b/service/storage_service.hh index 2922091a6e..fc0765fbe8 100644 --- a/service/storage_service.hh +++ b/service/storage_service.hh @@ -39,6 +39,7 @@ #pragma once +#include "auth/service.hh" #include "gms/i_endpoint_state_change_subscriber.hh" #include "service/endpoint_lifecycle_subscriber.hh" #include "locator/token_metadata.hh" @@ -113,6 +114,7 @@ private: private final AtomicLong notificationSerialNumber = new AtomicLong(); #endif distributed& _db; + sharded& _auth_service; int _update_jobs{0}; // Note that this is obviously only valid for the current shard. Users of // this facility should elect a shard to be the coordinator based on any @@ -129,7 +131,7 @@ private: bool _ms_stopped = false; bool _stream_manager_stopped = false; public: - storage_service(distributed& db); + storage_service(distributed& db, sharded&); void isolate_on_error(); void isolate_on_commit_error(); @@ -2243,8 +2245,8 @@ public: } }; -inline future<> init_storage_service(distributed& db) { - return service::get_storage_service().start(std::ref(db)); +inline future<> init_storage_service(distributed& db, sharded& auth_service) { + return service::get_storage_service().start(std::ref(db), std::ref(auth_service)); } inline future<> deinit_storage_service() { diff --git a/tests/auth_test.cc b/tests/auth_test.cc index 8b68ee2597..63aadd9a42 100644 --- a/tests/auth_test.cc +++ b/tests/auth_test.cc @@ -34,10 +34,11 @@ #include "tests/cql_test_env.hh" #include "tests/cql_assertions.hh" -#include "auth/auth.hh" +#include "auth/allow_all_authenticator.hh" #include "auth/data_resource.hh" #include "auth/authenticator.hh" #include "auth/password_authenticator.hh" +#include "auth/service.hh" #include "auth/authenticated_user.hh" #include "db/config.hh" @@ -73,9 +74,10 @@ SEASTAR_TEST_CASE(test_data_resource) { } SEASTAR_TEST_CASE(test_default_authenticator) { - return do_with_cql_env([](cql_test_env&) { - BOOST_REQUIRE_EQUAL(auth::authenticator::get().require_authentication(), false); - BOOST_REQUIRE_EQUAL(auth::authenticator::get().qualified_java_name(), auth::allow_all_authenticator_name()); + return do_with_cql_env([](cql_test_env& env) { + auto& a = env.local_auth_service().underlying_authenticator(); + BOOST_REQUIRE_EQUAL(a.require_authentication(), false); + BOOST_REQUIRE_EQUAL(a.qualified_java_name(), auth::allow_all_authenticator_name()); return make_ready_future(); }); } @@ -84,9 +86,10 @@ SEASTAR_TEST_CASE(test_password_authenticator_attributes) { db::config cfg; cfg.authenticator(auth::password_authenticator_name()); - return do_with_cql_env([](cql_test_env&) { - BOOST_REQUIRE_EQUAL(auth::authenticator::get().require_authentication(), true); - BOOST_REQUIRE_EQUAL(auth::authenticator::get().qualified_java_name(), auth::password_authenticator_name()); + return do_with_cql_env([](cql_test_env& env) { + auto& a = env.local_auth_service().underlying_authenticator(); + BOOST_REQUIRE_EQUAL(a.require_authentication(), true); + BOOST_REQUIRE_EQUAL(a.qualified_java_name(), auth::password_authenticator_name()); return make_ready_future(); }, cfg); } @@ -95,20 +98,22 @@ SEASTAR_TEST_CASE(test_auth_users) { db::config cfg; cfg.authenticator(auth::password_authenticator_name()); - return do_with_cql_env([](cql_test_env&) { - return seastar::async([] { + return do_with_cql_env([](cql_test_env& env) { + return seastar::async([&env] { + auto& auth = env.local_auth_service(); + sstring username("fisk"); - auth::auth::insert_user(username, false).get(); - BOOST_REQUIRE_EQUAL(auth::auth::is_existing_user(username).get0(), true); - BOOST_REQUIRE_EQUAL(auth::auth::is_super_user(username).get0(), false); + auth.insert_user(username, false).get(); + BOOST_REQUIRE_EQUAL(auth.is_existing_user(username).get0(), true); + BOOST_REQUIRE_EQUAL(auth.is_super_user(username).get0(), false); - auth::auth::insert_user(username, true).get(); - BOOST_REQUIRE_EQUAL(auth::auth::is_existing_user(username).get0(), true); - BOOST_REQUIRE_EQUAL(auth::auth::is_super_user(username).get0(), true); + auth.insert_user(username, true).get(); + BOOST_REQUIRE_EQUAL(auth.is_existing_user(username).get0(), true); + BOOST_REQUIRE_EQUAL(auth.is_super_user(username).get0(), true); - auth::auth::delete_user(username).get(); - BOOST_REQUIRE_EQUAL(auth::auth::is_existing_user(username).get0(), false); - BOOST_REQUIRE_EQUAL(auth::auth::is_super_user(username).get0(), false); + auth.delete_user(username).get(); + BOOST_REQUIRE_EQUAL(auth.is_existing_user(username).get0(), false); + BOOST_REQUIRE_EQUAL(auth.is_super_user(username).get0(), false); }); }, cfg); } @@ -121,7 +126,7 @@ SEASTAR_TEST_CASE(test_password_authenticator_operations) { * Not using seastar::async due to apparent ASan bug. * Enjoy the slightly less readable code. */ - return do_with_cql_env([](cql_test_env&) { + return do_with_cql_env([](cql_test_env& env) { sstring username("fisk"); sstring password("notter"); @@ -132,25 +137,26 @@ SEASTAR_TEST_CASE(test_password_authenticator_operations) { auto USERNAME_KEY = authenticator::USERNAME_KEY; auto PASSWORD_KEY = authenticator::PASSWORD_KEY; + auto& a = env.local_auth_service().underlying_authenticator(); // check non-existing user - return authenticator::get().authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([](future&& f) { + return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([&a](future&& f) { try { f.get(); BOOST_FAIL("should not reach"); } catch (exceptions::authentication_exception&) { // ok } - }).then([=] { - return authenticator::get().create(username, { { option::PASSWORD, password} }).then([=] { - return authenticator::get().authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then([=](user_ptr user) { + }).then([=, &a] { + return a.create(username, { { option::PASSWORD, password} }).then([=, &a] { + return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then([=](user_ptr user) { BOOST_REQUIRE_EQUAL(user->name(), username); BOOST_REQUIRE_EQUAL(user->is_anonymous(), false); }); }); - }).then([=] { + }).then([=, &a] { // check wrong password - return authenticator::get().authenticate( { {USERNAME_KEY, username}, {PASSWORD_KEY, "hejkotte"}}).then_wrapped([](future&& f) { + return a.authenticate( { {USERNAME_KEY, username}, {PASSWORD_KEY, "hejkotte"}}).then_wrapped([](future&& f) { try { f.get(); BOOST_FAIL("should not reach"); @@ -158,9 +164,9 @@ SEASTAR_TEST_CASE(test_password_authenticator_operations) { // ok } }); - }).then([=] { + }).then([=, &a] { // sasl - auto sasl = authenticator::get().new_sasl_challenge(); + auto sasl = a.new_sasl_challenge(); BOOST_REQUIRE_EQUAL(sasl->is_complete(), false); @@ -178,10 +184,10 @@ SEASTAR_TEST_CASE(test_password_authenticator_operations) { BOOST_REQUIRE_EQUAL(user->name(), username); BOOST_REQUIRE_EQUAL(user->is_anonymous(), false); }); - }).then([=] { + }).then([=, &a] { // check deleted user - return authenticator::get().drop(username).then([=] { - return authenticator::get().authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([](future&& f) { + return a.drop(username).then([=, &a] { + return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([](future&& f) { try { f.get(); BOOST_FAIL("should not reach"); @@ -215,8 +221,8 @@ SEASTAR_TEST_CASE(test_cassandra_hash) { auto f = env.local_qp().process("INSERT into system_auth.credentials (username, salted_hash) values (?, ?)", db::consistency_level::ONE, { username, salted_hash }).discard_result(); - return f.then([=] { - auto& a = auth::authenticator::get(); + return f.then([=, &env] { + auto& a = env.local_auth_service().underlying_authenticator(); auto USERNAME_KEY = auth::authenticator::USERNAME_KEY; auto PASSWORD_KEY = auth::authenticator::PASSWORD_KEY; diff --git a/tests/cql_test_env.cc b/tests/cql_test_env.cc index 6f01272995..36cf18aa8a 100644 --- a/tests/cql_test_env.cc +++ b/tests/cql_test_env.cc @@ -43,7 +43,7 @@ #include "gms/failure_detector.hh" #include "gms/gossiper.hh" #include "service/storage_service.hh" -#include "auth/auth.hh" +#include "auth/service.hh" namespace sstables { @@ -82,13 +82,14 @@ public: static std::atomic active; private: ::shared_ptr> _db; + ::shared_ptr> _auth_service; lw_shared_ptr _data_dir; private: struct core_local_state { service::client_state client_state; - core_local_state() - : client_state(service::client_state::for_external_calls()) + core_local_state(auth::service& auth_service) + : client_state(service::client_state::for_external_calls(auth_service)) { client_state.set_login(::make_shared("cassandra")); } @@ -106,7 +107,7 @@ private: return ::make_shared(_core_local.local().client_state); } public: - single_node_cql_env(::shared_ptr> db) : _db(db) + single_node_cql_env(::shared_ptr> db, ::shared_ptr> auth_service) : _db(db), _auth_service(std::move(auth_service)) { } virtual future<::shared_ptr> execute_cql(const sstring& text) override { @@ -241,8 +242,12 @@ public: return cql3::get_query_processor(); } + auth::service& local_auth_service() override { + return _auth_service->local(); + } + future<> start() { - return _core_local.start(); + return _core_local.start(std::ref(*_auth_service)); } future<> stop() { @@ -292,8 +297,10 @@ public: ms.start(listen, std::move(7000)).get(); auto stop_ms = defer([&ms] { ms.stop().get(); }); + auto auth_service = ::make_shared>(); + auto& ss = service::get_storage_service(); - ss.start(std::ref(*db)).get(); + ss.start(std::ref(*db), std::ref(*auth_service)).get(); auto stop_storage_service = defer([&ss] { ss.stop().get(); }); db->start(std::move(*cfg)).get(); @@ -347,12 +354,12 @@ public: auto stop_local_cache = defer([] { db::system_keyspace::deinit_local_cache().get(); }); service::get_local_storage_service().init_server().get(); - auto deinit_storage_service_server = defer([] { + auto deinit_storage_service_server = defer([auth_service] { gms::stop_gossiping().get(); - auth::auth::shutdown().get(); + auth_service->stop().get(); }); - single_node_cql_env env(db); + single_node_cql_env env(db, auth_service); env.start().get(); auto stop_env = defer([&env] { env.stop().get(); }); diff --git a/tests/cql_test_env.hh b/tests/cql_test_env.hh index 956a22bfd2..bd0f8418df 100644 --- a/tests/cql_test_env.hh +++ b/tests/cql_test_env.hh @@ -38,6 +38,10 @@ class database; +namespace auth { +class service; +} + namespace cql3 { class query_processor; } @@ -87,6 +91,8 @@ public: virtual distributed& db() = 0; virtual distributed & qp() = 0; + + virtual auth::service& local_auth_service() = 0; }; future<> do_with_cql_env(std::function(cql_test_env&)> func); diff --git a/tests/gossip.cc b/tests/gossip.cc index 58c797eaa9..1d2e00d0bf 100644 --- a/tests/gossip.cc +++ b/tests/gossip.cc @@ -61,19 +61,20 @@ namespace bpo = boost::program_options; int main(int ac, char ** av) { distributed db; + sharded auth_service; app_template app; app.add_options() ("seed", bpo::value>(), "IP address of seed node") ("listen-address", bpo::value()->default_value("0.0.0.0"), "IP address to listen"); - return app.run_deprecated(ac, av, [&db, &app] { + return app.run_deprecated(ac, av, [&auth_service, &db, &app] { auto config = app.configuration(); logging::logger_registry().set_logger_level("gossip", logging::log_level::trace); const gms::inet_address listen = gms::inet_address(config["listen-address"].as()); utils::fb_utilities::set_broadcast_address(listen); utils::fb_utilities::set_broadcast_rpc_address(listen); auto vv = std::make_shared(); - locator::i_endpoint_snitch::create_snitch("SimpleSnitch").then([&db] { - return service::init_storage_service(db); + locator::i_endpoint_snitch::create_snitch("SimpleSnitch").then([&auth_service, &db] { + return service::init_storage_service(db, auth_service); }).then([vv, listen, config] { return netw::get_messaging_service().start(listen); }).then([config] { diff --git a/tests/gossip_test.cc b/tests/gossip_test.cc index 1613597fa8..10f0ee61ef 100644 --- a/tests/gossip_test.cc +++ b/tests/gossip_test.cc @@ -40,9 +40,10 @@ thread_local disk_error_signal_type general_disk_error; SEASTAR_TEST_CASE(test_boot_shutdown){ return seastar::async([] { distributed db; + sharded auth_service; utils::fb_utilities::set_broadcast_address(gms::inet_address("127.0.0.1")); locator::i_endpoint_snitch::create_snitch("SimpleSnitch").get(); - service::get_storage_service().start(std::ref(db)).get(); + service::get_storage_service().start(std::ref(db), std::ref(auth_service)).get(); db.start().get(); netw::get_messaging_service().start(gms::inet_address("127.0.0.1")).get(); gms::get_failure_detector().start().get(); diff --git a/tests/test_services.hh b/tests/test_services.hh index f24fc1d530..2ed3c47a7c 100644 --- a/tests/test_services.hh +++ b/tests/test_services.hh @@ -24,17 +24,19 @@ #include #include +#include "auth/service.hh" #include "service/storage_service.hh" #include "message/messaging_service.hh" class storage_service_for_tests { distributed _db; + sharded _auth_service; public: storage_service_for_tests() { auto thread = seastar::thread_impl::get(); assert(thread); netw::get_messaging_service().start(gms::inet_address("127.0.0.1")).get(); - service::get_storage_service().start(std::ref(_db)).get(); + service::get_storage_service().start(std::ref(_db), std::ref(_auth_service)).get(); service::get_storage_service().invoke_on_all([] (auto& ss) { ss.enable_all_features(); }).get(); diff --git a/thrift/handler.cc b/thrift/handler.cc index 9772549108..70481088e4 100644 --- a/thrift/handler.cc +++ b/thrift/handler.cc @@ -198,10 +198,10 @@ private: }); } public: - explicit thrift_handler(distributed& db, distributed& qp) + explicit thrift_handler(distributed& db, distributed& qp, auth::service& auth_service) : _db(db) , _query_processor(qp) - , _query_state(service::client_state::for_external_thrift_calls()) + , _query_state(service::client_state::for_external_thrift_calls(auth_service)) { } const sstring& current_keyspace() const { @@ -215,7 +215,8 @@ public: void login(tcxx::function cob, tcxx::function exn_cob, const AuthenticationRequest& auth_request) { with_cob(std::move(cob), std::move(exn_cob), [&] { auth::authenticator::credentials_map creds(auth_request.credentials.begin(), auth_request.credentials.end()); - return auth::authenticator::get().authenticate(creds).then([this] (auto user) { + auto& auth_service = *_query_state.get_client_state().get_auth_service(); + return auth_service.underlying_authenticator().authenticate(creds).then([this] (auto user) { _query_state.get_client_state().set_login(std::move(user)); }); }); @@ -1902,13 +1903,15 @@ private: class handler_factory : public CassandraCobSvIfFactory { distributed& _db; distributed& _query_processor; + auth::service& _auth_service; public: explicit handler_factory(distributed& db, - distributed& qp) - : _db(db), _query_processor(qp) {} + distributed& qp, + auth::service& auth_service) + : _db(db), _query_processor(qp), _auth_service(auth_service) {} typedef CassandraCobSvIf Handler; virtual CassandraCobSvIf* getHandler(const ::apache::thrift::TConnectionInfo& connInfo) { - return new thrift_handler(_db, _query_processor); + return new thrift_handler(_db, _query_processor, _auth_service); } virtual void releaseHandler(CassandraCobSvIf* handler) { delete handler; @@ -1916,6 +1919,6 @@ public: }; std::unique_ptr -create_handler_factory(distributed& db, distributed& qp) { - return std::make_unique(db, qp); +create_handler_factory(distributed& db, distributed& qp, auth::service& auth_service) { + return std::make_unique(db, qp, auth_service); } diff --git a/thrift/handler.hh b/thrift/handler.hh index 2267980881..dc1fb74efe 100644 --- a/thrift/handler.hh +++ b/thrift/handler.hh @@ -23,11 +23,12 @@ #define APPS_SEASTAR_THRIFT_HANDLER_HH_ #include "Cassandra.h" +#include "auth/service.hh" #include "database.hh" #include "core/distributed.hh" #include "cql3/query_processor.hh" #include -std::unique_ptr<::cassandra::CassandraCobSvIfFactory> create_handler_factory(distributed& db, distributed& qp); +std::unique_ptr<::cassandra::CassandraCobSvIfFactory> create_handler_factory(distributed& db, distributed& qp, auth::service&); #endif /* APPS_SEASTAR_THRIFT_HANDLER_HH_ */ diff --git a/thrift/server.cc b/thrift/server.cc index 1254d57aae..be89522306 100644 --- a/thrift/server.cc +++ b/thrift/server.cc @@ -59,9 +59,10 @@ public: }; thrift_server::thrift_server(distributed& db, - distributed& qp) + distributed& qp, + auth::service& auth_service) : _stats(new thrift_stats(*this)) - , _handler_factory(create_handler_factory(db, qp).release()) + , _handler_factory(create_handler_factory(db, qp, auth_service).release()) , _protocol_factory(new TBinaryProtocolFactoryT()) , _processor_factory(new CassandraAsyncProcessorFactory(_handler_factory)) { } diff --git a/thrift/server.hh b/thrift/server.hh index 62ffdeac5f..1248288b80 100644 --- a/thrift/server.hh +++ b/thrift/server.hh @@ -62,6 +62,10 @@ class TMemoryBuffer; }}} +namespace auth { +class service; +} + class thrift_server { class connection : public boost::intrusive::list_base_hook<> { struct fake_transport; @@ -100,7 +104,7 @@ private: boost::intrusive::list _connections_list; seastar::gate _stop_gate; public: - thrift_server(distributed& db, distributed& qp); + thrift_server(distributed& db, distributed& qp, auth::service&); ~thrift_server(); future<> listen(ipv4_addr addr, bool keepalive); future<> stop(); diff --git a/transport/server.cc b/transport/server.cc index 5eccb943ab..40712a0aea 100644 --- a/transport/server.cc +++ b/transport/server.cc @@ -260,13 +260,14 @@ private: } }; -cql_server::cql_server(distributed& proxy, distributed& qp, cql_load_balance lb) +cql_server::cql_server(distributed& proxy, distributed& qp, cql_load_balance lb, auth::service& auth_service) : _proxy(proxy) , _query_processor(qp) , _max_request_size(memory::stats().total_memory() / 10) , _memory_available(_max_request_size) , _notifier(std::make_unique()) , _lb(lb) + , _auth_service(auth_service) { namespace sm = seastar::metrics; @@ -557,7 +558,7 @@ cql_server::connection::connection(cql_server& server, ipv4_addr server_addr, co , _fd(std::move(fd)) , _read_buf(_fd.input()) , _write_buf(_fd.output()) - , _client_state(service::client_state::external_tag{}, addr) + , _client_state(service::client_state::external_tag{}, server._auth_service, addr) { ++_server._total_connections; ++_server._current_connections; @@ -748,7 +749,7 @@ future cql_server::connection::process_startup(uint16_t stream, b throw exceptions::protocol_exception(sprint("Unknown compression algorithm: %s", compression)); } } - auto& a = auth::authenticator::get(); + auto& a = client_state.get_auth_service()->underlying_authenticator(); if (a.require_authentication()) { return make_ready_future(std::make_pair(make_autheticate(stream, a.qualified_java_name(), client_state.get_trace_state()), client_state)); } @@ -758,7 +759,7 @@ future cql_server::connection::process_startup(uint16_t stream, b future cql_server::connection::process_auth_response(uint16_t stream, bytes_view buf, service::client_state client_state) { if (_sasl_challenge == nullptr) { - _sasl_challenge = auth::authenticator::get().new_sasl_challenge(); + _sasl_challenge = client_state.get_auth_service()->underlying_authenticator().new_sasl_challenge(); } auto challenge = _sasl_challenge->evaluate_response(buf); diff --git a/transport/server.hh b/transport/server.hh index 9a16409ded..be9ddc73b1 100644 --- a/transport/server.hh +++ b/transport/server.hh @@ -21,6 +21,7 @@ #pragma once +#include "auth/service.hh" #include "core/reactor.hh" #include "service/endpoint_lifecycle_subscriber.hh" #include "service/migration_listener.hh" @@ -118,8 +119,9 @@ private: uint64_t _unpaged_queries = 0; uint64_t _requests_serving = 0; cql_load_balance _lb; + auth::service& _auth_service; public: - cql_server(distributed& proxy, distributed& qp, cql_load_balance lb); + cql_server(distributed& proxy, distributed& qp, cql_load_balance lb, auth::service&); future<> listen(ipv4_addr addr, std::shared_ptr = {}, bool keepalive = false); future<> do_accepts(int which, bool keepalive, ipv4_addr server_addr); future<> stop();