From 68e71337c2c852c3731b72e0be7535c5bd2ef3dc Mon Sep 17 00:00:00 2001 From: Peter Goron Date: Mon, 14 Aug 2023 09:14:41 +0200 Subject: [PATCH] auth: add support for authc/authz via external api (#10) Squash a series of commits related to authentication found on v5.0.1-criteo branch: * 34fe8d4ef11962a5f56772641193886bbbf07c9e Add attributes support * bf5cf176fb8deaee04e70f9c7f24eb18e4ccd496 Implement List Roles * fe21809bc5d7288545ea9aa21ef05a0d29f07336 Add alter user implementation to the rest role manager * 69fd387e7103245cce998216af706b70c9c242b3 Update ssl cert * 6a32079f6d6e3e2b7809b021f32c433b67e76c4d Add authorization support with rest_auth * 2f92f8b8d182aa23992c9ac6d7de5775de16498a deprecate internal_distributed_timeout_config * a39e509b4fa3df5da84300d75d444aa4c92e3f8c Add rest_authenticator to manage authentication with a rest endpoint validating credentials Few changes introduced compared to 5.0.1 version: * fixed runtime assertion related to pending flush when scylla fails to communicate with rest auth api * picojson replaced by rapidjson (used by scylla) * rest_http_client replaced by seastar::http::experimental::connection * unit tests fixed and enriched * formatting aligned with rest of scylla code base * tools/rest_authenticator_server updated to match actual implementation (usage of TLS and of GET http verb instead of POST) * 73d02b12e5708cf9bd28609edd1c34255c78664f make our rest_authenticator accepted by some clients make our rest_authenticator accepted by some clients authenticator_name is checked by some clients (go, rust) and connections are rejected if not in an allowed list on client side. We spoof cassandra authenticator name as scylla is doing for password authenticator. --- CMakeLists.txt | 2 + README.md | 4 + auth/authenticator.hh | 19 + auth/rest_authenticator.cc | 551 ++++++++++++++++++ auth/rest_authenticator.hh | 117 ++++ auth/rest_role_manager.cc | 379 ++++++++++++ auth/rest_role_manager.hh | 147 +++++ auth/service.cc | 8 + auth/service.hh | 5 + configure.py | 7 + db/config.cc | 15 +- db/config.hh | 10 + docs/dev/rest_authc_authz.md | 80 +++ main.cc | 8 + test/boost/rest_authenticator_test.cc | 536 +++++++++++++++++ test/lib/cql_test_env.cc | 9 + tools/rest_authenticator_server/.gitignore | 1 + .../rest_authenticator_server/api/__init__.py | 0 .../api/v1/__init__.py | 0 tools/rest_authenticator_server/api/v1/api.py | 29 + .../api/v1/endpoints/__init__.py | 0 .../api/v1/endpoints/auth.py | 42 ++ tools/rest_authenticator_server/certs/ca.crt | 30 + tools/rest_authenticator_server/certs/ca.key | 52 ++ tools/rest_authenticator_server/certs/ca.pem | 30 + tools/rest_authenticator_server/certs/ca.srl | 1 + .../certs/rest_api.crt | 29 + .../certs/rest_api.csr | 27 + .../certs/rest_api.key | 52 ++ tools/rest_authenticator_server/client.py | 70 +++ .../core/__init__.py | 0 .../rest_authenticator_server/core/config.py | 24 + tools/rest_authenticator_server/main.py | 40 ++ .../outdated_certs/ca.crt | 14 + .../outdated_certs/ca.key | 5 + .../outdated_certs/ca.srl | 1 + .../outdated_certs/rest_api.crt | 12 + .../outdated_certs/rest_api.csr | 8 + .../outdated_certs/rest_api.key | 5 + .../requirements.txt | 4 + .../rest_authenticator_server/rest_server.sh | 28 + .../schema/__init__.py | 0 .../rest_authenticator_server/schema/auth.py | 29 + .../scylla_client.sh | 25 + 44 files changed, 2454 insertions(+), 1 deletion(-) create mode 100644 auth/rest_authenticator.cc create mode 100644 auth/rest_authenticator.hh create mode 100644 auth/rest_role_manager.cc create mode 100644 auth/rest_role_manager.hh create mode 100644 docs/dev/rest_authc_authz.md create mode 100644 test/boost/rest_authenticator_test.cc create mode 100644 tools/rest_authenticator_server/.gitignore create mode 100644 tools/rest_authenticator_server/api/__init__.py create mode 100644 tools/rest_authenticator_server/api/v1/__init__.py create mode 100644 tools/rest_authenticator_server/api/v1/api.py create mode 100644 tools/rest_authenticator_server/api/v1/endpoints/__init__.py create mode 100644 tools/rest_authenticator_server/api/v1/endpoints/auth.py create mode 100644 tools/rest_authenticator_server/certs/ca.crt create mode 100644 tools/rest_authenticator_server/certs/ca.key create mode 100644 tools/rest_authenticator_server/certs/ca.pem create mode 100644 tools/rest_authenticator_server/certs/ca.srl create mode 100644 tools/rest_authenticator_server/certs/rest_api.crt create mode 100644 tools/rest_authenticator_server/certs/rest_api.csr create mode 100644 tools/rest_authenticator_server/certs/rest_api.key create mode 100644 tools/rest_authenticator_server/client.py create mode 100644 tools/rest_authenticator_server/core/__init__.py create mode 100644 tools/rest_authenticator_server/core/config.py create mode 100644 tools/rest_authenticator_server/main.py create mode 100644 tools/rest_authenticator_server/outdated_certs/ca.crt create mode 100644 tools/rest_authenticator_server/outdated_certs/ca.key create mode 100644 tools/rest_authenticator_server/outdated_certs/ca.srl create mode 100644 tools/rest_authenticator_server/outdated_certs/rest_api.crt create mode 100644 tools/rest_authenticator_server/outdated_certs/rest_api.csr create mode 100644 tools/rest_authenticator_server/outdated_certs/rest_api.key create mode 100644 tools/rest_authenticator_server/requirements.txt create mode 100755 tools/rest_authenticator_server/rest_server.sh create mode 100644 tools/rest_authenticator_server/schema/__init__.py create mode 100644 tools/rest_authenticator_server/schema/auth.py create mode 100755 tools/rest_authenticator_server/scylla_client.sh diff --git a/CMakeLists.txt b/CMakeLists.txt index 81ced0fe8f6a..42d61e6201cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -305,6 +305,8 @@ set(scylla_sources auth/common.cc auth/default_authorizer.cc auth/password_authenticator.cc + auth/rest_authenticator.cc + auth/rest_role_manager.cc auth/passwords.cc auth/permission.cc auth/permissions_cache.cc diff --git a/README.md b/README.md index 81d000ea5ed4..b5648beed870 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,10 @@ [![Slack](https://img.shields.io/badge/slack-scylla-brightgreen.svg?logo=slack)](http://slack.scylladb.com) [![Twitter](https://img.shields.io/twitter/follow/ScyllaDB.svg?style=social&label=Follow)](https://twitter.com/intent/follow?screen_name=ScyllaDB) +## This is a fork of scylla + +Scylla forks adding support of a specific rest authenticator: [rest_authc_authz](docs/dev/rest_authc_authz.md) + ## What is Scylla? Scylla is the real-time big data database that is API-compatible with Apache Cassandra and Amazon DynamoDB. diff --git a/auth/authenticator.hh b/auth/authenticator.hh index 1f944beb9b1c..ee902e9074ee 100644 --- a/auth/authenticator.hh +++ b/auth/authenticator.hh @@ -8,6 +8,10 @@ * SPDX-License-Identifier: (AGPL-3.0-or-later and Apache-2.0) */ +/* + * Modified by Criteo: June 2021 + */ + #pragma once #include @@ -36,6 +40,14 @@ namespace auth { class authenticated_user; +struct authenticator_config { + sstring rest_authenticator_endpoint_host; + uint16_t rest_authenticator_endpoint_port; + sstring rest_authenticator_endpoint_cafile_path; + uint32_t rest_authenticator_endpoint_ttl; + uint32_t rest_authenticator_endpoint_timeout; +}; + /// /// Abstract client for authenticating role identity. /// @@ -121,6 +133,13 @@ public: virtual const resource_set& protected_resources() const = 0; virtual ::shared_ptr new_sasl_challenge() const = 0; + + virtual void set_authenticator_config(const authenticator_config &ac) { _authenticator_config = ac; } + + virtual const authenticator_config & get_authenticator_config() const { return _authenticator_config; } + +protected: + authenticator_config _authenticator_config; }; } diff --git a/auth/rest_authenticator.cc b/auth/rest_authenticator.cc new file mode 100644 index 000000000000..2d65acf79a28 --- /dev/null +++ b/auth/rest_authenticator.cc @@ -0,0 +1,551 @@ +/* + * Copyright (C) 2021 Criteo + */ + +/* + * 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/rest_authenticator.hh" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include "auth/authenticated_user.hh" +#include "auth/common.hh" +#include "auth/passwords.hh" +#include "auth/roles-metadata.hh" +#include "cql3/untyped_result_set.hh" +#include "log.hh" +#include "service/migration_manager.hh" +#include "utils/class_registrator.hh" +#include "replica/database.hh" +#include "utils/base64.hh" +#include "utils/rjson.hh" + +namespace auth { +namespace meta { +namespace roles_valid_table { + std::string_view creation_query() { + static const sstring instance = format( + "CREATE TABLE {} (" + " {} text PRIMARY KEY" + ")", + qualified_name, + role_col_name); + return instance; + } + + constexpr std::string_view + qualified_name("system_auth.roles_valid"); +} +} + +constexpr std::string_view cassandra_authenticator_name("org.apache.cassandra.auth.PasswordAuthenticator"); +constexpr std::string_view rest_authenticator_name("com.criteo.scylladb.auth.RestAuthenticator"); + +// name of the hash column. +static constexpr std::string_view SALTED_HASH = "salted_hash"; +static constexpr std::string_view DEFAULT_USER_NAME = meta::DEFAULT_SUPERUSER_NAME; +static const sstring DEFAULT_USER_PASSWORD = sstring(meta::DEFAULT_SUPERUSER_NAME); + +logging::logger ralogger("rest_authenticator"); + +// To ensure correct initialization order, we unfortunately need to use a string literal. +static const class_registrator< + authenticator, + rest_authenticator, + cql3::query_processor&, + ::service::migration_manager&> rest_auth_reg("com.criteo.scylladb.auth.RestAuthenticator"); + +static thread_local auto rng_for_salt = std::default_random_engine(std::random_device{}()); + +rest_authenticator::~rest_authenticator() { +} + +rest_authenticator::rest_authenticator(cql3::query_processor& qp, ::service::migration_manager& mm) + : _qp(qp) + , _migration_manager(mm) + , _credentials_builder(std::make_unique()) + , _stopped(make_ready_future<>()) {} + +static bool has_salted_hash(const cql3::untyped_result_set_row& row) { + return !row.get_or(SALTED_HASH, "").empty(); +} + +static const sstring& update_row_query() { + static const sstring update_row_query = format("UPDATE {} SET {} = ? WHERE {} = ?", + meta::roles_table::qualified_name, + SALTED_HASH, + meta::roles_table::role_col_name); + return update_row_query; +} + +static const sstring& create_row_query_roles() { + static const sstring create_row_query_roles = format( + "INSERT INTO {} ({}, can_login, is_superuser, member_of, {}) VALUES (?, true, false, {{}}, ?)", + meta::roles_table::qualified_name, + meta::roles_table::role_col_name, + SALTED_HASH); + + return create_row_query_roles; +} + +static const sstring& create_row_query_roles_valid(uint32_t ttl) { + static const sstring create_row_query_roles_valid = format( + "INSERT INTO {} ({}) VALUES (?) USING TTL {}", + meta::roles_valid_table::qualified_name, + meta::roles_valid_table::role_col_name, + ttl); + + return create_row_query_roles_valid; +} + +static const sstring legacy_table_name{"credentials"}; + +future<> rest_authenticator::create_default_if_missing() const { + return default_role_row_satisfies(_qp, &has_salted_hash).then([this](bool exists) { + if (!exists) { + return _qp.execute_internal( + update_row_query(), + db::consistency_level::QUORUM, + internal_distributed_query_state(), + {passwords::hash(DEFAULT_USER_PASSWORD, rng_for_salt), DEFAULT_USER_NAME}, + cql3::query_processor::cache_internal::no).then([](auto&&) { + ralogger.info("Created default superuser authentication record."); + }); + } + + return make_ready_future<>(); + }); +} + +future<> rest_authenticator::setup_reloadable_certs_credentials() { + // Load CA + if (_authenticator_config.rest_authenticator_endpoint_cafile_path != "") { + co_await _credentials_builder->set_x509_trust_file(_authenticator_config.rest_authenticator_endpoint_cafile_path, tls::x509_crt_format::PEM); + } else { + co_await _credentials_builder->set_system_trust(); + } + + _certs = co_await _credentials_builder->build_reloadable_certificate_credentials([this](const std::unordered_set& files, std::exception_ptr ep) { + if (ep) { + ralogger.warn("Exception while reloading {}: {}", files, ep); + } else { + ralogger.info("Reloaded {}", files); + } + }); +} + +future<> rest_authenticator::start() { + // Ensure the _authenticator_config has been well initialized + if (_authenticator_config.rest_authenticator_endpoint_host == "") { + throw std::invalid_argument("Missing configuration for rest_authenticator_endpoint_host. " + "Did you call set_authenticator_config before calling start?"); + } + + return once_among_shards([this] { + auto f = create_metadata_table_if_missing(meta::roles_table::name, + _qp, + meta::roles_table::creation_query(), + _migration_manager).then([this]() { + return create_metadata_table_if_missing(meta::roles_valid_table::name, + _qp, + meta::roles_valid_table::creation_query(), + _migration_manager); + } + ); + + _stopped = do_after_system_ready(_as, [this] { + return async([this] { + wait_for_schema_agreement(_migration_manager, _qp.db().real_database(), _as).get0(); + + if (any_nondefault_role_row_satisfies(_qp, &has_salted_hash).get0()) { + return; + } + + create_default_if_missing().get0(); + }); + }); + + return f; + }).then([this] () { + return setup_reloadable_certs_credentials(); + }); +} + +future<> rest_authenticator::stop() { + _as.request_abort(); + return _stopped.handle_exception_type([] (const sleep_aborted&) { }).handle_exception_type([](const abort_requested_exception&) {}); +} + +db::consistency_level rest_authenticator::consistency_for_user(std::string_view role_name) { + if (role_name == DEFAULT_USER_NAME) { + return db::consistency_level::QUORUM; + } + return db::consistency_level::LOCAL_ONE; +} + +std::string_view rest_authenticator::qualified_java_name() const { + return cassandra_authenticator_name; +} + +bool rest_authenticator::require_authentication() const { + return true; +} + +authentication_option_set rest_authenticator::supported_options() const { + return authentication_option_set{authentication_option::password}; +} + +authentication_option_set rest_authenticator::alterable_options() const { + return authentication_option_set{authentication_option::password}; +} + +future get_user_roles(sstring username, sstring password, const authenticator_config& conf, shared_ptr certs) { + using namespace seastar::http; + using namespace seastar::http::experimental; + + auto ip = co_await net::dns::resolve_name(conf.rest_authenticator_endpoint_host); + + socket_address sa(ip, conf.rest_authenticator_endpoint_port); + auto fd = co_await tls::connect(certs, sa, conf.rest_authenticator_endpoint_host); + + connection http_client(std::move(fd)); + + auto req = request::make("GET", conf.rest_authenticator_endpoint_host, "/api/v1/auth/user/groups"); + sstring authorization = format("{}:{}", username, password); + auto bytes_authorization = bytes_view(reinterpret_cast(authorization.c_str()), authorization.size()); + req._headers["Authorization"] = format("Basic {}", base64_encode(bytes_authorization)); + req._headers["Accept"] = "application/json"; + + std::exception_ptr ep; + reply rep; + sstring content; + try { + rep = co_await http_client.make_request(std::move(req)); + ralogger.debug("rest_authenticator response for user {} status={}", username, rep._status); + + if (rep._status == reply::status_type::ok) { + ralogger.debug("rest_authenticator response for user {} content_length={}", username, rep.content_length); + + content = to_sstring(co_await http_client.in(rep).read_exactly(rep.content_length)); + ralogger.debug("rest_authenticator response for user {} body={}", username, content); + } + } catch(...) { + ep = std::current_exception(); + ralogger.warn("failure to invoke rest auth api: {}", ep); + } + + try { + co_await http_client.close(); + } catch(...) { + auto ex = std::current_exception(); + ralogger.warn("failure to close http_client: {}", ex); + } + + if (ep) { + std::rethrow_exception(ep); + } + + switch (rep._status) + { + case reply::status_type::ok: { + if (content == "") { + throw exceptions::authentication_exception("Empty response from rest authenticator"); + } + + rjson::value json = rjson::parse(std::move(content)); + if (!json.IsObject()) { + throw exceptions::authentication_exception("Bad response from rest authenticator, json object expected"); + } + + const rjson::value* groups_json = rjson::find(json, "groups"); + if (!groups_json) { + throw exceptions::authentication_exception("Bad response from rest authenticator, groups field expected"); + } + if (!groups_json->IsArray()) { + throw exceptions::authentication_exception("Bad response from rest authenticator, groups field expected to be an arry"); + } + + role_set groups; + // TODO filter groups to add (For ex. only add groups containing scylla... to avoid storing all groups) + for (auto it = groups_json->Begin(); it != groups_json->End(); ++it) { + if (it->IsString()) { + groups.insert(sstring(rjson::to_string_view(*it))); + } + } + co_return groups; + break; + } + + case reply::status_type::unauthorized: + throw exceptions::authentication_exception("Bad password"); + break; + + case reply::status_type::not_found: + throw exceptions::authentication_exception("Unknown username"); + break; + + default: + throw exceptions::authentication_exception(format("Bad response from rest authenticator: {}", rep._status)); + } +} + +future rest_authenticator::authenticate( + const credentials_map& credentials) const { + if (!credentials.contains(USERNAME_KEY)) { + ralogger.info("Required key 'USERNAME' is missing"); + throw exceptions::authentication_exception(format("Required key '{}' is missing", USERNAME_KEY)); + } + if (!credentials.contains(PASSWORD_KEY)) { + ralogger.info("Required key 'PASSWORD' is missing"); + throw exceptions::authentication_exception(format("Required key '{}' is missing", PASSWORD_KEY)); + } + + auto& username = credentials.at(USERNAME_KEY); + auto& password = credentials.at(PASSWORD_KEY); + + // 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 futurize_invoke([this, username, password] { + static const sstring query_roles = format("SELECT {} FROM {} WHERE {} = ?", + SALTED_HASH, + meta::roles_table::qualified_name, + meta::roles_table::role_col_name); + static const sstring query_roles_valid = format("SELECT {} FROM {} WHERE {} = ?", + meta::roles_valid_table::role_col_name, + meta::roles_valid_table::qualified_name, + meta::roles_valid_table::role_col_name); + + return when_all( + _qp.execute_internal( + query_roles, + consistency_for_user(username), + internal_distributed_query_state(), + {username}, + cql3::query_processor::cache_internal::yes), + _qp.execute_internal( + query_roles_valid, + consistency_for_user(username), + internal_distributed_query_state(), + {username}, + cql3::query_processor::cache_internal::yes) + ); + }).then_wrapped([=](future>, + future<::shared_ptr>>> f) { + auto tup = f.get0(); + auto res_roles = std::get<0>(tup).get0(); + auto res_roles_valid = std::get<1>(tup).get0(); + + auto salted_hash = std::optional(); + if (!res_roles->empty()) { + salted_hash = res_roles->one().get_opt(SALTED_HASH); + } + auto role_name = std::optional(); + if (!res_roles_valid->empty()) { + role_name = res_roles_valid->one().get_opt(meta::roles_valid_table::role_col_name); + } + + // If not super user (super user is local only) and the username is not in roles_valid or salted_hash empty or bad password + // call external endpoint + if (username == DEFAULT_USER_NAME && (!salted_hash || !passwords::check(password, *salted_hash))) { + throw exceptions::authentication_exception("Bad password for superuser"); + } else if (username != DEFAULT_USER_NAME && + (!role_name || !salted_hash || !passwords::check(password, *salted_hash))) { + bool create_user = res_roles->empty(); + + ralogger.info("Authenticating username {} from rest endpoint", username); + auto f = get_user_roles(username, password, _authenticator_config, _certs); + auto timeout = std::chrono::seconds(_authenticator_config.rest_authenticator_endpoint_timeout); + // TODO manage retry? + // TODO add prometheus metrics on auth failure/success...? + // TODO better delete only if date passed and repopulate async instead of ttl that just remove the entry from the table + return with_timeout(timer<>::clock::now() + timeout, std::move(f)) + .then([this, create_user, username, password](role_set roles) { + return create_or_update(create_user, username, password, roles); + }); + } + + return make_ready_future(username); + }).handle_exception([username] (std::exception_ptr ep) { + try { + try { + std::rethrow_exception(ep); + } catch (std::system_error&) { + std::throw_with_nested(exceptions::authentication_exception("Could not verify password")); + } catch (exceptions::authentication_exception&) { + throw; + } catch (std::exception& e) { + std::throw_with_nested(exceptions::authentication_exception(e.what())); + } catch (...) { + std::throw_with_nested(exceptions::authentication_exception("authentication failed")); + } + } catch (...) { + ralogger.warn("Exception while authenticating user {}: {}", username, std::current_exception()); + return current_exception_as_future(); + } + }); +} + +future +rest_authenticator::create_or_update(bool create_user, sstring username, sstring password, role_set &roles) const { + return do_with(std::move(roles), [this, create_user, username, password](role_set &roles) { + authentication_options authen_options; + authen_options.password = std::optional < std::string > {password}; + + if (create_user) { + ralogger.info("Create role for username {}", username); + return rest_authenticator::create_with_groups(username, roles, authen_options).then([username] { + return make_ready_future(username); + }); + } + ralogger.info("Update password for username {}", username); + return rest_authenticator::alter_with_groups(username, roles, authen_options).then([username] { + return make_ready_future(username); + }); + }); +} + +future<> rest_authenticator::create(std::string_view role_name, const authentication_options &options) const { + role_set roles; + return do_with(std::move(roles), [this, role_name, options](role_set &roles) { + return create_with_groups(sstring(role_name), roles, options); + }); +} + +future<> rest_authenticator::create_with_groups(sstring role_name, role_set &roles, + const authentication_options &options) const { + if (!options.password) { + return make_ready_future<>(); + } + + return when_all( + _qp.execute_internal( + create_row_query_roles(), + consistency_for_user(role_name), + internal_distributed_query_state(), + {role_name, passwords::hash(*options.password, rng_for_salt)}, + cql3::query_processor::cache_internal::no), + _qp.execute_internal( + create_row_query_roles_valid(_authenticator_config.rest_authenticator_endpoint_ttl), + consistency_for_user(role_name), + internal_distributed_query_state(), + {role_name}, + cql3::query_processor::cache_internal::no) + ).then([this, role_name, &roles](auto f) { + return modify_membership(role_name, roles); + }).discard_result(); +} + +future<> rest_authenticator::alter(std::string_view role_name, const authentication_options &options) const { + role_set roles; + return do_with(std::move(roles), [this, role_name, options](role_set &roles) { + return alter_with_groups(sstring(role_name), roles, options); + }); +} + +future<> rest_authenticator::alter_with_groups(sstring role_name, role_set &roles, + const authentication_options &options) const { + if (!options.password) { + return make_ready_future<>(); + } + + static const sstring query = format("UPDATE {} SET {} = ? WHERE {} = ?", + meta::roles_table::qualified_name, + SALTED_HASH, + meta::roles_table::role_col_name); + + return when_all( + _qp.execute_internal( + query, + consistency_for_user(role_name), + internal_distributed_query_state(), + {passwords::hash(*options.password, rng_for_salt), sstring(role_name)}, + cql3::query_processor::cache_internal::no), + _qp.execute_internal( + create_row_query_roles_valid(_authenticator_config.rest_authenticator_endpoint_ttl), + consistency_for_user(role_name), + internal_distributed_query_state(), + {sstring(role_name)}, + cql3::query_processor::cache_internal::no) + ).then([this, role_name, &roles](auto f) { + return modify_membership(role_name, roles); + }).discard_result(); +} + +future<> rest_authenticator::drop(std::string_view name) const { + static const sstring query = format("DELETE {} FROM {} WHERE {} = ?", + SALTED_HASH, + meta::roles_table::qualified_name, + meta::roles_table::role_col_name); + + return _qp.execute_internal( + query, consistency_for_user(name), + internal_distributed_query_state(), + {sstring(name)}, + cql3::query_processor::cache_internal::no).discard_result(); +} + +future<> +rest_authenticator::modify_membership(sstring grantee_name, role_set &roles) const { + const auto modify_roles = [this, grantee_name, &roles] { + const auto query = format( + "UPDATE {} SET member_of = ? WHERE {} = ?", + meta::roles_table::qualified_name, + meta::roles_table::role_col_name); + + return _qp.execute_internal( + query, + consistency_for_user(grantee_name), + internal_distributed_query_state(), + {roles, grantee_name}, + cql3::query_processor::cache_internal::no); + }; + + return modify_roles().discard_result(); +} + +future rest_authenticator::query_custom_options(std::string_view role_name) const { + return make_ready_future(); +} + +const resource_set& rest_authenticator::protected_resources() const { + static const resource_set resources({make_data_resource(meta::AUTH_KS, meta::roles_table::name)}); + return resources; +} + +::shared_ptr rest_authenticator::new_sasl_challenge() const { + return ::make_shared([this](std::string_view username, std::string_view password) { + credentials_map credentials{}; + credentials[USERNAME_KEY] = sstring(username); + credentials[PASSWORD_KEY] = sstring(password); + return this->authenticate(credentials); + }); +} + +} diff --git a/auth/rest_authenticator.hh b/auth/rest_authenticator.hh new file mode 100644 index 000000000000..a88f601bcab5 --- /dev/null +++ b/auth/rest_authenticator.hh @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2021 Criteo + */ + +/* + * 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 "auth/authenticator.hh" +#include "cql3/query_processor.hh" + +namespace service { + class migration_manager; +} + +namespace auth { + + namespace meta { + namespace roles_valid_table { + std::string_view creation_query(); + + constexpr std::string_view + name { + "roles_valid", 11 + }; + extern const std::string_view qualified_name; + constexpr std::string_view + role_col_name { + "role", 4 + }; +} +} + +extern const std::string_view rest_authenticator_name; + +class rest_authenticator : public authenticator { +private: + cql3::query_processor &_qp; + ::service::migration_manager &_migration_manager; + std::unique_ptr _credentials_builder; + shared_ptr _certs; + future<> _stopped; + seastar::abort_source _as; + +public: + static db::consistency_level consistency_for_user(std::string_view role_name); + + rest_authenticator(cql3::query_processor &, ::service::migration_manager &); + + ~rest_authenticator(); + + virtual future<> start() override; + + virtual future<> stop() override; + + virtual std::string_view qualified_java_name() const override; + + virtual bool require_authentication() const override; + + virtual authentication_option_set supported_options() const override; + + virtual authentication_option_set alterable_options() const override; + + virtual future authenticate(const credentials_map& credentials) const override; + + virtual future<> create(std::string_view role_name, const authentication_options& options) const override; + + virtual future<> alter(std::string_view role_name, const authentication_options& options) const override; + + virtual future<> drop(std::string_view role_name) const override; + + virtual future query_custom_options(std::string_view role_name) const override; + + virtual const resource_set& protected_resources() const override; + + virtual ::shared_ptr new_sasl_challenge() const override; + + +private: + + enum class membership_change { + add, remove + }; + + future<> setup_reloadable_certs_credentials(); + + future + create_or_update(bool user_to_create, sstring username, sstring password, role_set &roles) const; + + future<> create_with_groups(sstring role_name, role_set &roles, const authentication_options &options) const; + + future<> alter_with_groups(sstring role_name, role_set &roles, const authentication_options &options) const; + + future<> modify_membership(sstring grantee_name, role_set &roles) const; + + future<> create_default_if_missing() const; +}; + +} + diff --git a/auth/rest_role_manager.cc b/auth/rest_role_manager.cc new file mode 100644 index 000000000000..9ad56c5361f9 --- /dev/null +++ b/auth/rest_role_manager.cc @@ -0,0 +1,379 @@ +/* + * Copyright (C) 2021 Criteo + */ + +/* + * 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/rest_role_manager.hh" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "auth/common.hh" +#include "auth/roles-metadata.hh" +#include "cql3/query_processor.hh" +#include "cql3/untyped_result_set.hh" +#include "db/consistency_level_type.hh" +#include "exceptions/exceptions.hh" +#include "log.hh" +#include "utils/class_registrator.hh" +#include "replica/database.hh" + +namespace auth { +namespace meta { +// +// NB: role attribute managment replicated from standard_role_manager.cc +// +namespace role_attributes_table { +constexpr std::string_view name{"role_attributes", 15}; + +static std::string_view qualified_name() noexcept { + static const sstring instance = format("{}.{}", AUTH_KS, name); + return instance; +} +static std::string_view creation_query() noexcept { + static const sstring instance = format( + "CREATE TABLE {} (" + " role text," + " name text," + " value text," + " PRIMARY KEY(role, name)" + ")", + qualified_name()); + + return instance; +} +} +} + +static logging::logger log("rest_role_manager"); + +static const class_registrator< + role_manager, + rest_role_manager, + cql3::query_processor &, + ::service::migration_manager &> registration("com.criteo.scylladb.auth.RestManager"); + +struct record final { + sstring name; + bool is_superuser; + bool can_login; + role_set member_of; +}; + +static db::consistency_level consistency_for_role(std::string_view role_name) noexcept { + if (role_name == meta::DEFAULT_SUPERUSER_NAME) { + return db::consistency_level::QUORUM; + } + + return db::consistency_level::LOCAL_ONE; +} + +static bool has_can_login(const cql3::untyped_result_set_row &row) { + return row.has("can_login") && !(boolean_type->deserialize(row.get_blob("can_login")).is_null()); +} + +std::string_view rest_role_manager::qualified_java_name() const noexcept { + return "com.criteo.scylladb.auth.RestManager"; +} + +const resource_set &rest_role_manager::protected_resources() const { + static const resource_set resources({make_data_resource(meta::AUTH_KS, meta::roles_table::name)}); + return resources; +} + +future<> rest_role_manager::create_metadata_tables_if_missing() const { + return when_all_succeed( + create_metadata_table_if_missing( + meta::roles_table::name, + _qp, + meta::roles_table::creation_query(), + _migration_manager), + create_metadata_table_if_missing( + meta::role_attributes_table::name, + _qp, + meta::role_attributes_table::creation_query(), + _migration_manager)).discard_result(); +} + +future<> rest_role_manager::create_default_role_if_missing() const { + return default_role_row_satisfies(_qp, &has_can_login).then([this](bool exists) { + if (!exists) { + static const sstring query = format("INSERT INTO {} ({}, is_superuser, can_login) VALUES (?, true, true)", + meta::roles_table::qualified_name, + meta::roles_table::role_col_name); + + return _qp.execute_internal( + query, + db::consistency_level::QUORUM, + internal_distributed_query_state(), + {meta::DEFAULT_SUPERUSER_NAME}, + cql3::query_processor::cache_internal::no).then([](auto&&) { + log.info("Created default superuser role '{}'.", meta::DEFAULT_SUPERUSER_NAME); + return make_ready_future<>(); + }); + } + + return make_ready_future<>(); + }).handle_exception_type([](const exceptions::unavailable_exception& e) { + log.warn("Skipped default role setup: some nodes were not ready; will retry"); + return make_exception_future<>(e); + }); +} + +future<> rest_role_manager::start() { + return once_among_shards([this] { + return this->create_metadata_tables_if_missing().then([this] { + _stopped = auth::do_after_system_ready(_as, [this] { + return seastar::async([this] { + wait_for_schema_agreement(_migration_manager, _qp.db().real_database(), _as).get0(); + + create_default_role_if_missing().get0(); + }); + }); + }); + }); +} + +future<> rest_role_manager::stop() { + _as.request_abort(); + return _stopped.handle_exception_type([](const sleep_aborted &) {}).handle_exception_type( + [](const abort_requested_exception &) {});; +} + +static future > find_record(cql3::query_processor &qp, std::string_view role_name) { + static const sstring query = format("SELECT * FROM {} WHERE {} = ?", + meta::roles_table::qualified_name, + meta::roles_table::role_col_name); + + return qp.execute_internal( + query, + consistency_for_role(role_name), + internal_distributed_query_state(), + {sstring(role_name)}, + cql3::query_processor::cache_internal::yes).then([](::shared_ptr results) { + if (results->empty()) { + return std::optional(); + } + + const cql3::untyped_result_set_row &row = results->one(); + + return std::make_optional( + record{ + row.get_as(sstring(meta::roles_table::role_col_name)), + row.get_or("is_superuser", false), + row.get_or("can_login", false), + (row.has("member_of") + ? row.get_set("member_of") + : role_set())}); + }); +} + +static future require_record(cql3::query_processor &qp, std::string_view role_name) { + return find_record(qp, role_name).then([role_name](std::optional mr) { + if (!mr) { + throw nonexistant_role(role_name); + } + + return make_ready_future(*mr); + }); +} + +static future<> collect_roles(cql3::query_processor &qp, std::string_view grantee_name, role_set &roles) { + return require_record(qp, grantee_name).then([&qp, &roles](record r) { + return do_with(std::move(r.member_of), [&qp, &roles](const role_set &memberships) { + return do_for_each(memberships.begin(), memberships.end(), [&qp, &roles](const sstring &role_name) { + roles.insert(role_name); + return make_ready_future<>(); + }); + }); + }); +} + +future rest_role_manager::query_granted(std::string_view grantee_name, recursive_role_query m) { + // Our implementation of roles does not support recursive role query + return do_with( + role_set{sstring(grantee_name)}, + [this, grantee_name](role_set &roles) { + return collect_roles(_qp, grantee_name, roles).then([&roles] { return roles; }); + }); +} + +future rest_role_manager::exists(std::string_view role_name) { + // Used in grant revoke permissions to add permission if role exist + // but we do not create role for groups so not checking if it exists + // Also used after authentication to check if user has been well created + // but user is created by the rest authenticator so not required also + return make_ready_future(true); +} + +future rest_role_manager::is_superuser(std::string_view role_name) { + return find_record(_qp, role_name).then([](std::optional mr) { + if (mr) { + record r = *mr; + return r.is_superuser; + } + return false; + }); +} + +future rest_role_manager::can_login(std::string_view role_name) { + return find_record(_qp, role_name).then([](std::optional mr) { + if (mr) { + record r = *mr; + return r.can_login; + } + return false; + }); +} + +// Needed for unittest +future<> rest_role_manager::create_or_replace(std::string_view role_name, const role_config& c) const { + static const sstring query = format("INSERT INTO {} ({}, is_superuser, can_login) VALUES (?, ?, ?)", + meta::roles_table::qualified_name, + meta::roles_table::role_col_name); + return _qp.execute_internal( + query, + consistency_for_role(role_name), + internal_distributed_query_state(), + {sstring(role_name), c.is_superuser, c.can_login}, + cql3::query_processor::cache_internal::yes).discard_result(); +} + +// Needed for unittest +future<> rest_role_manager::create(std::string_view role_name, const role_config &c) { + return this->create_or_replace(role_name, c); +} + +future<> +rest_role_manager::alter(std::string_view role_name, const role_config_update &u) { + // Role manager only managed update of can_login and is_superuser field + // Those fields must not be managed by us but set by the rest authenticator when creating user + return make_ready_future<>(); +} + +future<> rest_role_manager::drop(std::string_view role_name) { + throw std::logic_error("Not Implemented"); +} + +future<> rest_role_manager::grant(std::string_view grantee_name, std::string_view role_name) { + throw std::logic_error("Not Implemented"); +} + +future<> rest_role_manager::revoke(std::string_view revokee_name, std::string_view role_name) { + throw std::logic_error("Not Implemented"); +} + +future rest_role_manager::query_all() { + static const sstring query = format("SELECT {},member_of from {}", + meta::roles_table::role_col_name, + meta::roles_table::qualified_name); + + // To avoid many copies of a view. + static const auto role_col_name_string = sstring(meta::roles_table::role_col_name); + static const auto member_of_col_name_string = sstring("member_of"); + + return _qp.execute_internal( + query, + db::consistency_level::QUORUM, + internal_distributed_query_state(), + cql3::query_processor::cache_internal::yes) + .then([](::shared_ptr results) { + role_set roles; + + std::for_each( + results->begin(), + results->end(), + [&roles] (const cql3::untyped_result_set_row &row) { + roles.insert(row.get_as(role_col_name_string)); + if (row.has(member_of_col_name_string)) { + for (auto member : row.get_set(member_of_col_name_string)) { + roles.insert(member); + } + } + }); + return roles; + }); +} + + +// +// NB: role attribute managment replicated from standard_role_manager.cc +// + +future> rest_role_manager::get_attribute(std::string_view role_name, std::string_view attribute_name) { + static const sstring query = format("SELECT name, value FROM {} WHERE role = ? AND name = ?", meta::role_attributes_table::qualified_name()); + return _qp.execute_internal(query, {sstring(role_name), sstring(attribute_name)}, cql3::query_processor::cache_internal::yes).then([] (shared_ptr result_set) { + if (!result_set->empty()) { + const cql3::untyped_result_set_row &row = result_set->one(); + return std::optional(row.get_as("value")); + } + return std::optional{}; + }); +} + +future rest_role_manager::query_attribute_for_all (std::string_view attribute_name) { + return query_all().then([this, attribute_name] (role_set roles) { + return do_with(attribute_vals{}, [this, attribute_name, roles = std::move(roles)] (attribute_vals &role_to_att_val) { + return parallel_for_each(roles.begin(), roles.end(), [this, &role_to_att_val, attribute_name] (sstring role) { + return get_attribute(role, attribute_name).then([&role_to_att_val, role] (std::optional att_val) { + if (att_val) { + role_to_att_val.emplace(std::move(role), std::move(*att_val)); + } + }); + }).then([&role_to_att_val] () { + return make_ready_future(std::move(role_to_att_val)); + }); + }); + }); +} + +future<> rest_role_manager::set_attribute(std::string_view role_name, std::string_view attribute_name, std::string_view attribute_value) { + static const sstring query = format("INSERT INTO {} (role, name, value) VALUES (?, ?, ?)", meta::role_attributes_table::qualified_name()); + return do_with(sstring(role_name), sstring(attribute_name), sstring(attribute_value), [this] (sstring& role_name, sstring &attribute_name, + sstring &attribute_value) { + return exists(role_name).then([&role_name, &attribute_name, &attribute_value, this] (bool role_exists) { + if (!role_exists) { + throw auth::nonexistant_role(role_name); + } + return _qp.execute_internal(query, {sstring(role_name), sstring(attribute_name), sstring(attribute_value)}, cql3::query_processor::cache_internal::yes).discard_result(); + }); + }); + +} + +future<> rest_role_manager::remove_attribute(std::string_view role_name, std::string_view attribute_name) { + static const sstring query = format("DELETE FROM {} WHERE role = ? AND name = ?", meta::role_attributes_table::qualified_name()); + return do_with(sstring(role_name), sstring(attribute_name), [this] (sstring& role_name, sstring &attribute_name) { + return exists(role_name).then([&role_name, &attribute_name, this] (bool role_exists) { + if (!role_exists) { + throw auth::nonexistant_role(role_name); + } + return _qp.execute_internal(query, {sstring(role_name), sstring(attribute_name)}, cql3::query_processor::cache_internal::yes).discard_result(); + }); + }); +} +} diff --git a/auth/rest_role_manager.hh b/auth/rest_role_manager.hh new file mode 100644 index 000000000000..2dd0a782d61b --- /dev/null +++ b/auth/rest_role_manager.hh @@ -0,0 +1,147 @@ +/* + * Copyright (C) 2021 Criteo + */ + +/* + * 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 "auth/role_manager.hh" + +#include +#include + +#include +#include +#include + +#include "seastarx.hh" + +namespace cql3 { +class query_processor; +} + +namespace service { +class migration_manager; +} + +namespace auth { + +class rest_role_manager final : public role_manager { + cql3::query_processor &_qp; + ::service::migration_manager &_migration_manager; + future<> _stopped; + seastar::abort_source _as; + +public: + rest_role_manager(cql3::query_processor &qp, ::service::migration_manager &mm) + : _qp(qp), _migration_manager(mm), _stopped(make_ready_future<>()) { + } + + + virtual std::string_view qualified_java_name() const noexcept override; + + virtual const resource_set& protected_resources() const override; + + virtual future<> start() override; + + virtual future<> stop() override; + + /// + /// \returns an exceptional future with \ref role_already_exists for a role that has previously been created. + /// + virtual future<> create(std::string_view role_name, const role_config&) override; + + /// + /// \returns an exceptional future with \ref nonexistant_role if the role does not exist. + /// + virtual future<> drop(std::string_view role_name) override; + + /// + /// \returns an exceptional future with \ref nonexistant_role if the role does not exist. + /// + virtual future<> alter(std::string_view role_name, const role_config_update&) override; + + /// + /// Grant `role_name` to `grantee_name`. + /// + /// \returns an exceptional future with \ref nonexistant_role if either the role or the grantee do not exist. + /// + /// \returns an exceptional future with \ref role_already_included if granting the role would be redundant, or + /// create a cycle. + /// + virtual future<> grant(std::string_view grantee_name, std::string_view role_name) override; + + /// + /// Revoke `role_name` from `revokee_name`. + /// + /// \returns an exceptional future with \ref nonexistant_role if either the role or the revokee do not exist. + /// + /// \returns an exceptional future with \ref revoke_ungranted_role if the role was not granted. + /// + virtual future<> revoke(std::string_view revokee_name, std::string_view role_name) override; + + /// + /// \returns an exceptional future with \ref nonexistant_role if the role does not exist. + /// + virtual future query_granted(std::string_view grantee, recursive_role_query) override; + + virtual future query_all() override; + + virtual future exists(std::string_view role_name) override; + + /// + /// \returns an exceptional future with \ref nonexistant_role if the role does not exist. + /// + virtual future is_superuser(std::string_view role_name) override; + + /// + /// \returns an exceptional future with \ref nonexistant_role if the role does not exist. + /// + virtual future can_login(std::string_view role_name) override; + + /// + /// \returns the value of the named attribute, if one is set. + /// + virtual future> get_attribute(std::string_view role_name, std::string_view attribute_name) override; + + /// + /// \returns a mapping of each role's value for the named attribute, if one is set for the role. + /// + virtual future query_attribute_for_all(std::string_view attribute_name) override; + + /// Sets `attribute_name` with `attribute_value` for `role_name`. + /// \returns an exceptional future with nonexistant_role if the role does not exist. + /// + virtual future<> set_attribute(std::string_view role_name, std::string_view attribute_name, std::string_view attribute_value) override; + + /// Removes `attribute_name` for `role_name`. + /// \returns an exceptional future with nonexistant_role if the role does not exist. + /// \note: This is a no-op if the role does not have the named attribute set. + /// + virtual future<> remove_attribute(std::string_view role_name, std::string_view attribute_name) override; + +private: + future<> create_metadata_tables_if_missing() const; + + future<> create_or_replace(std::string_view role_name, const role_config &) const; + + future<> create_default_role_if_missing() const; + +}; +} diff --git a/auth/service.cc b/auth/service.cc index 8547962740d6..fa61a0f2c4fc 100644 --- a/auth/service.cc +++ b/auth/service.cc @@ -6,6 +6,11 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/* + * Modified by Criteo: June 2021 + */ + + #include #include "auth/service.hh" @@ -134,6 +139,9 @@ service::service( create_object(sc.authorizer_java_name, qp, mm), create_object(sc.authenticator_java_name, qp, mm), create_object(sc.role_manager_java_name, qp, mm)) { + // authenticator_config pass via a setter instead of constructor to ease maintenance of our fork + // by reducing number of modified lines + _authenticator->set_authenticator_config(authenticator_config(sc.authenticator_config)); } future<> service::create_keyspace_if_missing(::service::migration_manager& mm) const { diff --git a/auth/service.hh b/auth/service.hh index 3f26a192d324..43a1c1823df0 100644 --- a/auth/service.hh +++ b/auth/service.hh @@ -6,6 +6,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/* + * Modified by Criteo: June 2021 + */ + #pragma once #include @@ -44,6 +48,7 @@ struct service_config final { sstring authorizer_java_name; sstring authenticator_java_name; sstring role_manager_java_name; + authenticator_config authenticator_config; }; /// diff --git a/configure.py b/configure.py index fde40fa8e547..73a62bb540e2 100755 --- a/configure.py +++ b/configure.py @@ -8,6 +8,10 @@ # SPDX-License-Identifier: AGPL-3.0-or-later # +# +# Modified by Criteo: June 2021 +# + import argparse import os import platform @@ -440,6 +444,7 @@ def find_headers(repodir, excluded_dirs): 'test/boost/reusable_buffer_test', 'test/boost/restrictions_test', 'test/boost/repair_test', + 'test/boost/rest_authenticator_test', 'test/boost/role_manager_test', 'test/boost/row_cache_test', 'test/boost/rust_test', @@ -980,6 +985,8 @@ def find_headers(repodir, excluded_dirs): 'auth/roles-metadata.cc', 'auth/passwords.cc', 'auth/password_authenticator.cc', + 'auth/rest_authenticator.cc', + 'auth/rest_role_manager.cc', 'auth/permission.cc', 'auth/permissions_cache.cc', 'auth/service.cc', diff --git a/db/config.cc b/db/config.cc index 579cc3d15066..e180f2883bd7 100644 --- a/db/config.cc +++ b/db/config.cc @@ -7,6 +7,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/* + * Modified by Criteo: June 2021 + */ + #include #include #include @@ -712,8 +716,9 @@ db::config::config(std::shared_ptr exts) "\torg.apache.cassandra.auth.AllowAllAuthenticator : Disables authentication; no checks are performed.\n" "\torg.apache.cassandra.auth.PasswordAuthenticator : Authenticates users with user names and hashed passwords stored in the system_auth.credentials table. If you use the default, 1, and the node with the lone replica goes down, you will not be able to log into the cluster because the system_auth keyspace was not replicated.\n" "\tcom.scylladb.auth.TransitionalAuthenticator : Wraps around the PasswordAuthenticator, logging them in if username/password pair provided is correct and treating them as anonymous users otherwise.\n" + "\tcom.criteo.scylladb.auth.RestAuthenticator : Call an external rest endpoints to authenticate user.\n" "Related information: Internal authentication" - , {"AllowAllAuthenticator", "PasswordAuthenticator", "org.apache.cassandra.auth.PasswordAuthenticator", "org.apache.cassandra.auth.AllowAllAuthenticator", "com.scylladb.auth.TransitionalAuthenticator"}) + , {"AllowAllAuthenticator", "PasswordAuthenticator", "org.apache.cassandra.auth.PasswordAuthenticator", "org.apache.cassandra.auth.AllowAllAuthenticator", "com.scylladb.auth.TransitionalAuthenticator", "com.criteo.scylladb.auth.RestAuthenticator"}) , internode_authenticator(this, "internode_authenticator", value_status::Unused, "enabled", "Internode authentication backend. It implements org.apache.cassandra.auth.AllowAllInternodeAuthenticator to allows or disallow connections from peer nodes.") , authorizer(this, "authorizer", value_status::Used, "org.apache.cassandra.auth.AllowAllAuthorizer", @@ -926,6 +931,14 @@ db::config::config(std::shared_ptr exts) , wasm_udf_yield_fuel(this, "wasm_udf_yield_fuel", value_status::Used, 100000, "Wasmtime fuel a WASM UDF can consume before yielding") , wasm_udf_total_fuel(this, "wasm_udf_total_fuel", value_status::Used, 100000000, "Wasmtime fuel a WASM UDF can consume before termination") , wasm_udf_memory_limit(this, "wasm_udf_memory_limit", value_status::Used, 2*1024*1024, "How much memory each WASM UDF can allocate at most") + + // REST AUTHENTICATOR + , rest_authenticator_endpoint_host(this, "rest_authenticator_endpoint_host", value_status::Used, "localhost", "Host to contact the external rest endpoint used to authenticate users.") + , rest_authenticator_endpoint_port(this, "rest_authenticator_endpoint_port", value_status::Used, 443, "Port to contact the external rest endpoint used to authenticate users.") + , rest_authenticator_endpoint_cafile_path(this, "rest_authenticator_endpoint_cafile_path", value_status::Used, "", "Path to the file containing the CA to trust to contact the external authenticator rest endpoint.") + , rest_authenticator_endpoint_ttl(this, "rest_authenticator_endpoint_ttl", value_status::Used, 86400, "TTL used to define how many time user roles from rest endpoint authenticator should be kept.") + , rest_authenticator_endpoint_timeout(this, "rest_authenticator_endpoint_timeout", value_status::Used, 5, "Duration (in second) before to timeout when calling rest auth endpoint.") + , default_log_level(this, "default_log_level", value_status::Used) , logger_log_level(this, "logger_log_level", value_status::Used) , log_to_stdout(this, "log_to_stdout", value_status::Used) diff --git a/db/config.hh b/db/config.hh index 2763ff910e65..1055f9146071 100644 --- a/db/config.hh +++ b/db/config.hh @@ -7,6 +7,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/* + * Modified by Criteo: June 2021 + */ + #pragma once #include @@ -412,6 +416,12 @@ public: seastar::logging_settings logging_settings(const log_cli::options&) const; + named_value rest_authenticator_endpoint_host; + named_value rest_authenticator_endpoint_port; + named_value rest_authenticator_endpoint_cafile_path; + named_value rest_authenticator_endpoint_ttl; + named_value rest_authenticator_endpoint_timeout; + const db::extensions& extensions() const; locator::host_id host_id; diff --git a/docs/dev/rest_authc_authz.md b/docs/dev/rest_authc_authz.md new file mode 100644 index 000000000000..3f380c74647e --- /dev/null +++ b/docs/dev/rest_authc_authz.md @@ -0,0 +1,80 @@ +# rest_authc_authz + +## Description + +The rest authenticator relies on an external rest endpoint to validate user credentials and retrieves user groups. + +It will: + +- check in the system_auth.roles table if the role exists. If the role exists check password with the stored hashed one +- If the role doesn't exists, it calls an external rest endpoint (https only) +- If creds are fine it stores the user/hashed password in the system_auth.roles table and returns the authenticated object user + +The rest role manager only query for member roles so the default authorizer can assign permission to the uer role depending to it's member roles. +Difference with the standard role manager: + +- do not manage creation, update and deletion of roles. This is performed by rest_authentication from ldap informations +- do not managed nested roles. Only takes in account roles in the member_of field of a user while the standard create a specific role for any roles in member_fo field and provide nested capability + +## Rest Endpoint definition + +Endpoint: GET /api/v1/user/groups, Protected with Basic Auth + +Response: + - 200 if ok with body: +`{ + "groups": [ "group1", "group2" ] +}` +- 404 if user not found +- 401 if auth failed + +## Internal DB + +It relies on: + +- system_auth.roles DB to store the role and the list of member_of roles: [text (role_name), boolean (can_login), boolean (is_superuser), set (member_of),text (salted_hash)] +- system_auth.roles_validation: role name are inserted in it with a TTL. When the role is not available in that table the rest_authenticator re check the user from the external endpoint [text (role_name)] +- system_auth.permissions to store permissions set for each roles: [text (role_name), text (resource), set (permissions)] + +## Test + +Building Scylla with the frozen toolchain `dbuild` is as easy as: + +```bash +$ git submodule update --init --force --recursive +$ ./tools/toolchain/dbuild ./configure.py +$ ./tools/toolchain/dbuild ninja build/release/scylla +``` + +Run scylla with RestAuthenticator + +```bash +$ ./tools/toolchain/dbuild ./build/release/scylla --workdir tmp --smp 2 --developer-mode 1 \ +--logger-log-level rest_authenticator=debug \ +--authenticator com.criteo.scylladb.auth.RestAuthenticator \ +--rest-authenticator-endpoint-host localhost \ +--rest-authenticator-endpoint-port 8000 \ +--rest-authenticator-endpoint-cafile-path ./tools/rest_authenticator_server/certs/ca.crt \ +--rest-authenticator-endpoint-ttl 30 \ +--role-manager com.criteo.scylladb.auth.RestManager --authorizer CassandraAuthorizer +``` + +Run FastAPI rest server + +```bash +$ cd tools/rest_authenticator_server +$ ./rest_server.sh +``` + +Run Test client + +```bash +$ cd tools/rest_authenticator_server +$ ./tools/rest_authenticator_server/scylla_client.sh +``` + +## UnitTest + +```bash +./tools/toolchain/dbuild ninja build/debug/test/boost/rest_authenticator_test && ./tools/toolchain/dbuild ./build/debug/test/boost/rest_authenticator_test +``` \ No newline at end of file diff --git a/main.cc b/main.cc index 16483c32e79d..b58723523dc1 100644 --- a/main.cc +++ b/main.cc @@ -6,6 +6,9 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/* + * Modified by Criteo: June 2021 + */ #include #include @@ -1509,6 +1512,11 @@ To start the scylla server proper, simply invoke as: scylla server (or just scyl auth_config.authorizer_java_name = qualified_authorizer_name; auth_config.authenticator_java_name = qualified_authenticator_name; auth_config.role_manager_java_name = qualified_role_manager_name; + auth_config.authenticator_config.rest_authenticator_endpoint_host = cfg->rest_authenticator_endpoint_host(); + auth_config.authenticator_config.rest_authenticator_endpoint_port = cfg->rest_authenticator_endpoint_port(); + auth_config.authenticator_config.rest_authenticator_endpoint_cafile_path = cfg->rest_authenticator_endpoint_cafile_path(); + auth_config.authenticator_config.rest_authenticator_endpoint_ttl = cfg->rest_authenticator_endpoint_ttl(); + auth_config.authenticator_config.rest_authenticator_endpoint_timeout = cfg->rest_authenticator_endpoint_timeout(); auth_service.start(std::move(perm_cache_config), std::ref(qp), std::ref(mm_notifier), std::ref(mm), auth_config).get(); diff --git a/test/boost/rest_authenticator_test.cc b/test/boost/rest_authenticator_test.cc new file mode 100644 index 000000000000..096fc9392ad7 --- /dev/null +++ b/test/boost/rest_authenticator_test.cc @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2021 Criteo + */ + +/* + * 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 "db/config.hh" +#include "utils/base64.hh" +#include "test/lib/cql_test_env.hh" +#include "cql3/query_processor.hh" +#include "cql3/untyped_result_set.hh" +#include "auth/common.hh" +#include "auth/passwords.hh" +#include "auth/rest_authenticator.hh" +#include "auth/rest_role_manager.hh" +#include "auth/roles-metadata.hh" + +using namespace seastar::testing; + +// +// Notes: +// to execute test suite, run ./tools/toolchain/dbuild ./test.py --no-parallel-cases --mode debug rest_authenticator +// to execute a specific test case, run ./tools/toolchain/dbuild build/debug/test/boost/rest_authenticator_test --run_test= +// + +cql_test_config rest_authenticator_on(int port, bool use_outdated_certs) { + cql_test_config cfg; + cfg.db_config->authenticator("com.criteo.scylladb.auth.RestAuthenticator"); + cfg.db_config->rest_authenticator_endpoint_host("localhost"); + cfg.db_config->rest_authenticator_endpoint_port(port); + if (use_outdated_certs) { + cfg.db_config->rest_authenticator_endpoint_cafile_path("tools/rest_authenticator_server/outdated_certs/ca.crt"); + } else { + cfg.db_config->rest_authenticator_endpoint_cafile_path("tools/rest_authenticator_server/certs/ca.crt"); + } + cfg.db_config->rest_authenticator_endpoint_ttl(10); + cfg.db_config->rest_authenticator_endpoint_timeout(10); + + cfg.db_config->role_manager("com.criteo.scylladb.auth.RestManager"); + cfg.db_config->authorizer("CassandraAuthorizer"); + return cfg; +} + +class rest_authentication_handler : public seastar::httpd::handler_base { + virtual future > + handle(const sstring &path, std::unique_ptr req, + std::unique_ptr rep) override { + using namespace seastar::httpd; + + auto auth_header = req->get_header("Authorization"); + if (auth_header.substr(0, 6) != "Basic ") { + rep->set_status(seastar::http::reply::status_type::bad_request); + rep->done(); + return make_ready_future>(std::move(rep)); + } + + auto auth_token = auth_header.substr(6); + auto auth_token_str = base64_decode(auth_token); + + if (auth_token_str.find("alice") != sstring::npos) { + rep->write_body(sstring("json"), sstring("{\"groups\": [\"scylla-rw\", \"other\"]}")); + } else if (auth_token_str.find("john.doe") != sstring::npos) { + rep->set_status(seastar::http::reply::status_type::not_found); + rep->done(); + } else { + rep->set_status(seastar::http::reply::status_type::unauthorized); + rep->done(); + } + + return make_ready_future>(std::move(rep)); + } +}; + +future<> with_dummy_authentication_server(std::function func, bool use_outdated_certs = false) { + return seastar::async([func, use_outdated_certs] { + int port = 54321; // TODO allocate a new port at each invocation to allow test execution in parallel + auto conf = std::move(rest_authenticator_on(port, use_outdated_certs)); + + seastar::global_logger_registry().set_logger_level("httpd", seastar::log_level::debug); + + httpd::http_server_control httpd; + httpd.start("dummy_authentication_server").get(); + auto stop_httpd = defer([&httpd] { httpd.stop().get(); }); + + // setup TLS for https + tls::credentials_builder b; + b.set_dh_level(tls::dh_params::level::MEDIUM); + if (use_outdated_certs) { + b.set_x509_key_file("tools/rest_authenticator_server/outdated_certs/rest_api.crt", + "tools/rest_authenticator_server/outdated_certs/rest_api.key", + seastar::tls::x509_crt_format::PEM).get(); + } else { + b.set_x509_key_file("tools/rest_authenticator_server/certs/rest_api.crt", + "tools/rest_authenticator_server/certs/rest_api.key", + seastar::tls::x509_crt_format::PEM).get(); + } + b.set_priority_string(db::config::default_tls_priority); + + httpd.server().invoke_on_all([b](http_server &server) { + auto creds = b.build_server_credentials(); + server.set_tls_credentials(creds); + }).get(); + + // setup http routes + httpd.set_routes([](routes &r) { + r.put(seastar::httpd::operation_type::GET, "/api/v1/auth/user/groups", new rest_authentication_handler()); + }).get(); + + httpd.listen(ipv4_addr("127.0.0.1", conf.db_config->rest_authenticator_endpoint_port())).get(); + + // start cql test environment and execute test function + do_with_cql_env_thread([func = std::move(func)](cql_test_env &env) { + return func(env); + }, conf).get(); + }); +} + +struct record final { + sstring name; + bool is_superuser; + bool can_login; + auth::role_set member_of; + sstring salted_hash; +}; + +static future > find_record(cql3::query_processor &qp, std::string_view role_name) { + static const sstring query = format("SELECT * FROM {} WHERE {} = ?", + auth::meta::roles_table::qualified_name, + auth::meta::roles_table::role_col_name); + return qp.execute_internal( + query, + db::consistency_level::LOCAL_ONE, + auth::internal_distributed_query_state(), + {sstring(role_name)}, + cql3::query_processor::cache_internal::yes).then([](::shared_ptr results) { + if (results->empty()) { + return std::optional(); + } + + const cql3::untyped_result_set_row &row = results->one(); + + return std::make_optional( + record{ + row.get_as(sstring(auth::meta::roles_table::role_col_name)), + row.get_or("is_superuser", false), + row.get_or("can_login", false), + (row.has("member_of") + ? row.get_set("member_of") + : auth::role_set()), + row.get_as("salted_hash")}); + }); +} + +static future can_login(cql3::query_processor &qp, std::string_view role_name) { + return find_record(qp, role_name).then([](std::optional mr) { + if (mr) { + record r = *mr; + return r.can_login; + } + return false; + }); +} + +static future is_superuser(cql3::query_processor &qp, std::string_view role_name) { + return find_record(qp, role_name).then([](std::optional mr) { + if (mr) { + record r = *mr; + return r.is_superuser; + } + return false; + }); +} + +static future get_role_set(cql3::query_processor &qp, std::string_view role_name) { + return find_record(qp, role_name).then([](std::optional mr) { + if (mr) { + record r = *mr; + return r.member_of; + } + return auth::role_set(); + }); +} + +static future get_salted_hash(cql3::query_processor &qp, std::string_view role_name) { + return find_record(qp, role_name).then([](std::optional mr) { + if (mr) { + record r = *mr; + return r.salted_hash; + } + return sstring(); + }); +} + +static future > find_record_valid(cql3::query_processor &qp, std::string_view role_name) { + static const sstring query = format("SELECT * FROM {} WHERE {} = ?", + auth::meta::roles_valid_table::qualified_name, + auth::meta::roles_valid_table::role_col_name); + return qp.execute_internal( + query, + db::consistency_level::LOCAL_ONE, + auth::internal_distributed_query_state(), + {sstring(role_name)}, + cql3::query_processor::cache_internal::yes).then([](::shared_ptr results) { + if (results->empty()) { + return std::optional(); + } + + const cql3::untyped_result_set_row &row = results->one(); + return std::make_optional(row.get_as(sstring(auth::meta::roles_valid_table::role_col_name))); + }); +}; + +static future require_record_valid(cql3::query_processor &qp, std::string_view role_name) { + return find_record_valid(qp, role_name).then([role_name](std::optional mr) { + if (!mr) { + throw auth::nonexistant_role(role_name); + } + return make_ready_future(*mr); + }); +} + +static thread_local auto rng_for_salt = std::default_random_engine(std::random_device{}()); + +static future<> create_superuser_role(cql3::query_processor &qp) { + static const sstring query = format( + "INSERT INTO {} ({}, is_superuser, can_login, salted_hash) VALUES (?, true, true, ?)", + auth::meta::roles_table::qualified_name, + auth::meta::roles_table::role_col_name); + return qp.execute_internal( + query, + db::consistency_level::QUORUM, + auth::internal_distributed_query_state(), + {auth::meta::DEFAULT_SUPERUSER_NAME, + auth::passwords::hash(sstring(auth::meta::DEFAULT_SUPERUSER_NAME), rng_for_salt)}, + cql3::query_processor::cache_internal::no).discard_result(); +} + +SEASTAR_TEST_CASE(rest_authenticator_conf) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + BOOST_REQUIRE_EQUAL(a.qualified_java_name(), "org.apache.cassandra.auth.PasswordAuthenticator"); + BOOST_REQUIRE(a.require_authentication()); + + auto &authenticator_config = a.get_authenticator_config(); + BOOST_REQUIRE_EQUAL(authenticator_config.rest_authenticator_endpoint_host, "localhost"); + BOOST_REQUIRE_EQUAL(authenticator_config.rest_authenticator_endpoint_port, 54321); + BOOST_REQUIRE_EQUAL(authenticator_config.rest_authenticator_endpoint_cafile_path, + "tools/rest_authenticator_server/certs/ca.crt"); + BOOST_REQUIRE_EQUAL(authenticator_config.rest_authenticator_endpoint_ttl, 10); + BOOST_REQUIRE_EQUAL(authenticator_config.rest_authenticator_endpoint_timeout, 10); + + auto &authorizer = env.local_auth_service().underlying_authorizer(); + BOOST_REQUIRE_EQUAL(authorizer.qualified_java_name(), + "org.apache.cassandra.auth.CassandraAuthorizer"); + + auto &rm = env.local_auth_service().underlying_role_manager(); + BOOST_REQUIRE_EQUAL(rm.qualified_java_name(), "com.criteo.scylladb.auth.RestManager"); + }); +} + +SEASTAR_TEST_CASE(valid_user) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + auto auth_user = a.authenticate(creds).get(); + BOOST_REQUIRE_EQUAL(auth_user.name.value(), "alice"); + + // Check state in DB + auto &qp = env.local_qp(); + BOOST_REQUIRE(can_login(qp, "alice").get()); + BOOST_REQUIRE(can_login(qp, "norole").get() == false); + + // Check state through role_manager should be align with DB state + auto &rm = env.local_auth_service().underlying_role_manager(); + BOOST_REQUIRE(rm.can_login("alice").get()); + BOOST_REQUIRE(rm.can_login("norole").get() == false); + }); +} + +SEASTAR_TEST_CASE(valid_superuser) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &qp = env.local_qp(); + create_superuser_role(qp).get(); + + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("cassandra")}, + {auth::authenticator::PASSWORD_KEY, sstring("cassandra")} + }; + + auto auth_user = a.authenticate(creds).get(); + BOOST_REQUIRE_EQUAL(auth_user.name.value(), "cassandra"); + BOOST_REQUIRE(is_superuser(qp, "cassandra").get()); + }); +} + +SEASTAR_TEST_CASE(bad_password_superuser) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("cassandra")}, + {auth::authenticator::PASSWORD_KEY, sstring("bad_password")} + }; + + BOOST_REQUIRE_EXCEPTION(a.authenticate(creds).get(), exceptions::authentication_exception, + exception_predicate::message_contains( + "Bad password for superuser")); + }); +} + +SEASTAR_TEST_CASE(expired_tls_certs) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + BOOST_REQUIRE_EXCEPTION(a.authenticate(creds).get(), exceptions::authentication_exception, + exception_predicate::message_contains("The certificate is NOT trusted. The certificate chain uses expired certificate.")); + }, true); +} + +SEASTAR_TEST_CASE(unknown_user) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("john.doe")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + BOOST_REQUIRE_EXCEPTION(a.authenticate(creds).get(), exceptions::authentication_exception, + exception_predicate::message_contains( + "Unknown username")); + + // Check state in DB + auto &qp = env.local_qp(); + BOOST_REQUIRE(can_login(qp, "john.doe").get() == false); + + // Check state through role_manager should be align with DB state + auto &rm = env.local_auth_service().underlying_role_manager(); + BOOST_REQUIRE(rm.can_login("john.doe").get() == false); + }); +} + +SEASTAR_TEST_CASE(invalid_credentials) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("foo.bar")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + BOOST_REQUIRE_EXCEPTION(a.authenticate(creds).get(), exceptions::authentication_exception, + exception_predicate::message_contains("Bad password")); + + // Check state in DB + auto &qp = env.local_qp(); + BOOST_REQUIRE(can_login(qp, "foo.bar").get() == false); + + // Check state through role_manager should be align with DB state + auto &rm = env.local_auth_service().underlying_role_manager(); + BOOST_REQUIRE(rm.can_login("foo.bar").get() == false); + }); +} + +SEASTAR_TEST_CASE(user_has_roles) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + a.authenticate(creds).discard_result().get(); + + auth::role_set roles; + roles.insert(sstring("scylla-rw")); + roles.insert(sstring("other")); + + // Check state in DB + auto &qp = env.local_qp(); + BOOST_REQUIRE_EQUAL(get_role_set(qp, "alice").get(), roles); + + // Check state through role_manager + roles.insert(sstring("alice")); // query_granted also return current role name in the role set + auto &rm = env.local_auth_service().underlying_role_manager(); + BOOST_REQUIRE_EQUAL(rm.query_granted("alice", auth::recursive_role_query::no).get(), roles); + }); +} + +SEASTAR_TEST_CASE(user_expired_is_well_recreated) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + a.authenticate(creds).discard_result().get(); + + auto &qp = env.local_qp(); + BOOST_REQUIRE_EQUAL(require_record_valid(qp, "alice").get(), "alice"); + + // To ensure deletion of expired rows (TTL) + forward_jump_clocks(20s); + BOOST_REQUIRE_EXCEPTION(require_record_valid(qp, "alice").get(), exceptions::invalid_request_exception, + exception_predicate::message_contains( + "Role alice doesn't exist.")); + + a.authenticate(creds).get(); + // Entry has been well recreated in the DB + BOOST_REQUIRE_EQUAL(require_record_valid(qp, "alice").get(), "alice"); + }); +} + +SEASTAR_TEST_CASE(user_password_is_updated) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + a.authenticate(creds).discard_result().get(); + + auto &qp = env.local_qp(); + BOOST_REQUIRE_EQUAL(require_record_valid(qp, "alice").get(), "alice"); + sstring salted_hash = get_salted_hash(qp, "alice").get(); + + auto creds2 = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password2")} + }; + + a.authenticate(creds2).discard_result().get(); + BOOST_REQUIRE_EQUAL(require_record_valid(qp, "alice").get(), "alice"); + + sstring salted_hash2 = get_salted_hash(qp, "alice").get(); + BOOST_REQUIRE(salted_hash != salted_hash2); + }); +} + +SEASTAR_TEST_CASE(update_superuser_password) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &qp = env.local_qp(); + create_superuser_role(qp).get(); + + auto &a = env.local_auth_service().underlying_authenticator(); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("cassandra")}, + {auth::authenticator::PASSWORD_KEY, sstring("cassandra")} + }; + + auto auth_user = a.authenticate(creds).get(); + BOOST_REQUIRE_EQUAL(auth_user.name.value(), "cassandra"); + BOOST_REQUIRE(is_superuser(qp, "cassandra").get()); + + // Alter superuser password + auth::authentication_options authen_options; + authen_options.password = std::optional < std::string > {"123456"}; + a.alter("cassandra", authen_options).get(); + + // Ensure old password doesn't work + BOOST_REQUIRE_EXCEPTION(a.authenticate(creds).get(), exceptions::authentication_exception, + exception_predicate::message_contains( + "Bad password for superuser")); + + // Ensure new password works and user rights haven't been affected + auto creds_new = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("cassandra")}, + {auth::authenticator::PASSWORD_KEY, sstring("123456")} + }; + + auto auth_user_new = a.authenticate(creds_new).get(); + BOOST_REQUIRE_EQUAL(auth_user_new.name.value(), "cassandra"); + BOOST_REQUIRE(is_superuser(qp, "cassandra").get()); + }); +} + +SEASTAR_TEST_CASE(get_list_of_roles) { + return with_dummy_authentication_server([] (cql_test_env &env) { + auto &a = env.local_auth_service().underlying_authenticator(); + auto &rm = env.local_auth_service().underlying_role_manager(); + + auth::role_set roles; + roles.insert(sstring("cassandra")); + roles.insert(sstring("tester")); + + BOOST_REQUIRE_EQUAL(rm.query_all().get(), roles); + + auto creds = auth::authenticator::credentials_map{ + {auth::authenticator::USERNAME_KEY, sstring("alice")}, + {auth::authenticator::PASSWORD_KEY, sstring("password")} + }; + + a.authenticate(creds).get(); + + roles.insert(sstring("scylla-rw")); + roles.insert(sstring("other")); + roles.insert(sstring("alice")); + + BOOST_REQUIRE_EQUAL(rm.query_all().get(), roles); + }); +} diff --git a/test/lib/cql_test_env.cc b/test/lib/cql_test_env.cc index 7225d1060182..a99b6d4e6765 100644 --- a/test/lib/cql_test_env.cc +++ b/test/lib/cql_test_env.cc @@ -6,6 +6,10 @@ * SPDX-License-Identifier: AGPL-3.0-or-later */ +/* + * Modified by Criteo: June 2021 + */ + #include #include #include @@ -876,6 +880,11 @@ class single_node_cql_env : public cql_test_env { auth_config.authorizer_java_name = qualified_authorizer_name; auth_config.authenticator_java_name = qualified_authenticator_name; auth_config.role_manager_java_name = qualified_role_manager_name; + auth_config.authenticator_config.rest_authenticator_endpoint_host = cfg->rest_authenticator_endpoint_host(); + auth_config.authenticator_config.rest_authenticator_endpoint_port = cfg->rest_authenticator_endpoint_port(); + auth_config.authenticator_config.rest_authenticator_endpoint_cafile_path = cfg->rest_authenticator_endpoint_cafile_path(); + auth_config.authenticator_config.rest_authenticator_endpoint_ttl = cfg->rest_authenticator_endpoint_ttl(); + auth_config.authenticator_config.rest_authenticator_endpoint_timeout = cfg->rest_authenticator_endpoint_timeout(); auth_service.start(perm_cache_config, std::ref(qp), std::ref(mm_notif), std::ref(mm), auth_config).get(); auth_service.invoke_on_all([&mm] (auth::service& auth) { diff --git a/tools/rest_authenticator_server/.gitignore b/tools/rest_authenticator_server/.gitignore new file mode 100644 index 000000000000..f5e96dbfaec8 --- /dev/null +++ b/tools/rest_authenticator_server/.gitignore @@ -0,0 +1 @@ +venv \ No newline at end of file diff --git a/tools/rest_authenticator_server/api/__init__.py b/tools/rest_authenticator_server/api/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/rest_authenticator_server/api/v1/__init__.py b/tools/rest_authenticator_server/api/v1/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/rest_authenticator_server/api/v1/api.py b/tools/rest_authenticator_server/api/v1/api.py new file mode 100644 index 000000000000..2f3f87419169 --- /dev/null +++ b/tools/rest_authenticator_server/api/v1/api.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +from fastapi import APIRouter + +from api.v1.endpoints.auth import router as auth_router + +api_router = APIRouter() +api_router.include_router(auth_router, prefix='/auth', tags=['auth']) diff --git a/tools/rest_authenticator_server/api/v1/endpoints/__init__.py b/tools/rest_authenticator_server/api/v1/endpoints/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/rest_authenticator_server/api/v1/endpoints/auth.py b/tools/rest_authenticator_server/api/v1/endpoints/auth.py new file mode 100644 index 000000000000..6d92cc2d3de8 --- /dev/null +++ b/tools/rest_authenticator_server/api/v1/endpoints/auth.py @@ -0,0 +1,42 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +from fastapi import APIRouter, HTTPException, Depends +from fastapi.security import HTTPBasicCredentials, HTTPBasic +from schema.auth import ResponseAuth + +router = APIRouter() + +security = HTTPBasic() + + +@router.get('/user/groups', response_model=ResponseAuth) +async def get_user_groups(credentials: HTTPBasicCredentials = Depends(security)) -> ResponseAuth: + print(f'Call for user {credentials.username}') + if credentials.username == 'scylla_user': + if credentials.password == 'not_cassandra': + return ResponseAuth(groups=['group1', 'group2']) + else: + raise HTTPException(status_code=401, detail=f'Bad password for {credentials.username} user') + else: + raise HTTPException(status_code=404, detail=f'User {credentials.username} not found') diff --git a/tools/rest_authenticator_server/certs/ca.crt b/tools/rest_authenticator_server/certs/ca.crt new file mode 100644 index 000000000000..4850c29740e0 --- /dev/null +++ b/tools/rest_authenticator_server/certs/ca.crt @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFKTCCAxGgAwIBAgIUI2iQhjy/ALQcea13c0hx85UkRuwwDQYJKoZIhvcNAQEL +BQAwIzEPMA0GA1UECgwGQ3JpdGVvMRAwDgYDVQQDDAdSb290LUNBMCAXDTIxMDYx +MDE0MTEwMloYDzIwNTEwNjAzMTQxMTAyWjAjMQ8wDQYDVQQKDAZDcml0ZW8xEDAO +BgNVBAMMB1Jvb3QtQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDe +c4pBNe+F14XXugIRsaP/FCs6iSE5CkbQPyabeFZnIp3LlvzwTTFYK+AYu9WrQ6rZ +OMXwVytSEMfEsodNXOeEgpLXL1C5a6RPjifcXXNP5yYoXWIShZEFbqHMCjNFpcpg +EImRbjtst+DwWhYQ1OLUWv43S7B/bcd0vGvxbJZlSyG8boHAiTs6XIH4FNGO7Gdd +SVE5UhzVEwE3Y+RqGnEivxLJQqZXhUb4PhaUm1Lj5EiNN0IOH3CNQuUnPh0Ud932 +0OJQI7NfFvwPdi2LWapfmA9yS3NRf8NMBeZE7pXK9KTJ/XNHZud8rnezX5j0Bm5l +SEhtQ5JNqN0D1fki9DCyHLAZjTVJvw2Ft0ko8sNIOhFMkwHGO7+XOPsQfWKqKrot +s5s7vHKyGNmI72XjSlT9VYOacL5tLsCvXPpzzfz69o5M9tIwFcL/pubxQQS4MW0Q +aT20KPfFSI/6VNZD4ybScktxhq6PAcTYqVCGKvwmUFPEo8EzvnTYLwmoZjFJ7ysL +ptzFXSaOWUnYCn3hLKAejCE9kw5ceUgWqQXAVrELBZ5ZdKLLvsLGN7nn2QJW+hN5 +ZwScvcFR7lPfKF5qy72iHgBTuulcxzli7X46e6+S5FDIt3+2yH/kAP85K/6L44mT +wEuu0i/D6Z9qHK2kdzENvsXEO/23jSjNcm6nermPWQIDAQABo1MwUTAdBgNVHQ4E +FgQUdqAFEdqZHH0tej4zg5BBCEDMEt8wHwYDVR0jBBgwFoAUdqAFEdqZHH0tej4z +g5BBCEDMEt8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAlaO9 +vrh7mSpbK5nGfdRszv4WgfBV7vhnKO0KtbCF5i6uQ4MUmt7DJlotjyJ/epdTsTNc +/i3rYUjiZfwHCjciFekK4yjFxcMknP7ivHlgEheFE5UvMa2TkB0t48gVmY5LCpBT +YPvrZhBpgn7FkWugtSDS6ny/+9qV9kx1mAAagn9kjJCjGuPN/OzCNwHd/PZLZIHK +RExJ7rEo9A0PJ52wxnyHorNckLJwVOCvgSfDXVu6ZePg3NW4BAn+hLaCw1HqPBrU +xSbXektCz6g4e1w9LcA5Jy95Pk7qG/YgoinT/SeETvW7u5v01G/bRDrwQIcr2gzY +EvZ2/VYsK5ZXEmIZllOMJnOamjFRBwfbO/LFy+ocbBNaKvoRIXbTVs0SZ/iEhWrs +MpmwQJ0JSAYU0YFktjoodgMjCMMMwpRrG6QSLZrl2P6WmSJiKitH5neZubDxWwvZ +C2eyy0YQ2Hth+bTEi8IyZYhvSz9/rvnY+rwzeL9yrQ2uCQQ1/2e5kmfJvHh22yME +D2xeru3Iw40alwpub9axy7CgfDOxXXc/hCg2xkLOvb8n8d64zMY9s5XRu1ExkofB +0Bv+Ea78bm4XA+jN1WsLZ5dGcHEr4EIRk6AXSSsdkBsHwJxLsMikqKPi9t5fSnjg +AQU4cVuLtfXwwIxUccDL96z98VRUYHB4x3OtfR4= +-----END CERTIFICATE----- diff --git a/tools/rest_authenticator_server/certs/ca.key b/tools/rest_authenticator_server/certs/ca.key new file mode 100644 index 000000000000..30c3a005caae --- /dev/null +++ b/tools/rest_authenticator_server/certs/ca.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +4ehka3HmS2OsyPr2KaUrUpQQlx2nDlg= +-----END PRIVATE KEY----- diff --git a/tools/rest_authenticator_server/certs/ca.pem b/tools/rest_authenticator_server/certs/ca.pem new file mode 100644 index 000000000000..4850c29740e0 --- /dev/null +++ b/tools/rest_authenticator_server/certs/ca.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFKTCCAxGgAwIBAgIUI2iQhjy/ALQcea13c0hx85UkRuwwDQYJKoZIhvcNAQEL +BQAwIzEPMA0GA1UECgwGQ3JpdGVvMRAwDgYDVQQDDAdSb290LUNBMCAXDTIxMDYx +MDE0MTEwMloYDzIwNTEwNjAzMTQxMTAyWjAjMQ8wDQYDVQQKDAZDcml0ZW8xEDAO +BgNVBAMMB1Jvb3QtQ0EwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDe +c4pBNe+F14XXugIRsaP/FCs6iSE5CkbQPyabeFZnIp3LlvzwTTFYK+AYu9WrQ6rZ +OMXwVytSEMfEsodNXOeEgpLXL1C5a6RPjifcXXNP5yYoXWIShZEFbqHMCjNFpcpg +EImRbjtst+DwWhYQ1OLUWv43S7B/bcd0vGvxbJZlSyG8boHAiTs6XIH4FNGO7Gdd +SVE5UhzVEwE3Y+RqGnEivxLJQqZXhUb4PhaUm1Lj5EiNN0IOH3CNQuUnPh0Ud932 +0OJQI7NfFvwPdi2LWapfmA9yS3NRf8NMBeZE7pXK9KTJ/XNHZud8rnezX5j0Bm5l +SEhtQ5JNqN0D1fki9DCyHLAZjTVJvw2Ft0ko8sNIOhFMkwHGO7+XOPsQfWKqKrot +s5s7vHKyGNmI72XjSlT9VYOacL5tLsCvXPpzzfz69o5M9tIwFcL/pubxQQS4MW0Q +aT20KPfFSI/6VNZD4ybScktxhq6PAcTYqVCGKvwmUFPEo8EzvnTYLwmoZjFJ7ysL +ptzFXSaOWUnYCn3hLKAejCE9kw5ceUgWqQXAVrELBZ5ZdKLLvsLGN7nn2QJW+hN5 +ZwScvcFR7lPfKF5qy72iHgBTuulcxzli7X46e6+S5FDIt3+2yH/kAP85K/6L44mT +wEuu0i/D6Z9qHK2kdzENvsXEO/23jSjNcm6nermPWQIDAQABo1MwUTAdBgNVHQ4E +FgQUdqAFEdqZHH0tej4zg5BBCEDMEt8wHwYDVR0jBBgwFoAUdqAFEdqZHH0tej4z +g5BBCEDMEt8wDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAgEAlaO9 +vrh7mSpbK5nGfdRszv4WgfBV7vhnKO0KtbCF5i6uQ4MUmt7DJlotjyJ/epdTsTNc +/i3rYUjiZfwHCjciFekK4yjFxcMknP7ivHlgEheFE5UvMa2TkB0t48gVmY5LCpBT +YPvrZhBpgn7FkWugtSDS6ny/+9qV9kx1mAAagn9kjJCjGuPN/OzCNwHd/PZLZIHK +RExJ7rEo9A0PJ52wxnyHorNckLJwVOCvgSfDXVu6ZePg3NW4BAn+hLaCw1HqPBrU +xSbXektCz6g4e1w9LcA5Jy95Pk7qG/YgoinT/SeETvW7u5v01G/bRDrwQIcr2gzY +EvZ2/VYsK5ZXEmIZllOMJnOamjFRBwfbO/LFy+ocbBNaKvoRIXbTVs0SZ/iEhWrs +MpmwQJ0JSAYU0YFktjoodgMjCMMMwpRrG6QSLZrl2P6WmSJiKitH5neZubDxWwvZ +C2eyy0YQ2Hth+bTEi8IyZYhvSz9/rvnY+rwzeL9yrQ2uCQQ1/2e5kmfJvHh22yME +D2xeru3Iw40alwpub9axy7CgfDOxXXc/hCg2xkLOvb8n8d64zMY9s5XRu1ExkofB +0Bv+Ea78bm4XA+jN1WsLZ5dGcHEr4EIRk6AXSSsdkBsHwJxLsMikqKPi9t5fSnjg +AQU4cVuLtfXwwIxUccDL96z98VRUYHB4x3OtfR4= +-----END CERTIFICATE----- diff --git a/tools/rest_authenticator_server/certs/ca.srl b/tools/rest_authenticator_server/certs/ca.srl new file mode 100644 index 000000000000..e414a6496520 --- /dev/null +++ b/tools/rest_authenticator_server/certs/ca.srl @@ -0,0 +1 @@ +7C9852EE4DAD43CA7EC33C52BC12F08EF5845E5E diff --git a/tools/rest_authenticator_server/certs/rest_api.crt b/tools/rest_authenticator_server/certs/rest_api.crt new file mode 100644 index 000000000000..49e7f813b364 --- /dev/null +++ b/tools/rest_authenticator_server/certs/rest_api.crt @@ -0,0 +1,29 @@ +-----BEGIN CERTIFICATE----- +MIIFCjCCAvICFHyYUu5NrUPKfsM8UrwS8I71hF5eMA0GCSqGSIb3DQEBCwUAMCMx +DzANBgNVBAoMBkNyaXRlbzEQMA4GA1UEAwwHUm9vdC1DQTAgFw0yMTA2MTAxNDEx +MDNaGA8yMDUxMDYwMzE0MTEwM1owXjELMAkGA1UEBhMCRlIxDDAKBgNVBAgMA0lk +RjEOMAwGA1UEBwwFUGFyaXMxDzANBgNVBAoMBkNyaXRlbzEMMAoGA1UECwwDU1JF +MRIwEAYDVQQDDAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIK +AoICAQDC1aVdno2fFABHH2dy6A4zeQloLMAX0kmBkwYJn4//148r2NwXNT7IWlYM +3ERG8WfvpuUjusIv3yB0+V4smnTnKaKzDTf85u80yzZ7FFX+BrNP+vDBkpKQ9pds +XUkbSe6nvafJgQox6TsDNgghTxW50+qqiyvC0qCqC4yomtOENIHPDSMRbmjBIvP/ +nOb6r3reU3QeZutdxjOg+M37w/4j525yd6c6CtTOZb/xPdwKhLqC6Wlf/vb+g/LM +5beDH3MixEbDRFPZWJU/pcettzo65mKVtx18Mw3v7f2Ow8ozPudgv6mnVrDBToUe +Zc5HFFItaahCC2dGaVfNtRoRyIjAmk6v76gD6+rOvJp304czoS/eGURDgJV8neqD +la9yhoZhounf6T3o85XuLg2W2dBNYkwO3ug3ur93nN/ucYGT6W496XTk4HKyoZkz +JKwLJIT2SPQCH/lYoEGair2+vZedo717fWKI45DIu7zXpfxwCP0QqTt7QgFDUqDl +2NgdVjmFGOI8++UjotykY+Oi2aKKfmUX6ARnDsktbnhy6HaVVEOgCIkmTCbuaOPa +EC0ghLrs+rWiO+jtuy7qgQ65ANY2hH4HVs+az90ec8TjpKAhzF9RNZiWDr3STJa9 +Tm7BT+TBgAwUwH9Rnvh3GKltHVnWvpHvPfGdtqyZlDjqriEJPwIDAQABMA0GCSqG +SIb3DQEBCwUAA4ICAQBDlbpFBM94NNoSbqbHvs1166c+KhXHuzPEfhL0suAxT8yp +FRcTD7RlB+MXr2hAThI04IYAbwRSNE1VViI+5CaXyfYFwraotiSBkeLkOhh7BNc7 +bTGW7oEwUx33AOlpGlvqOPn0zj4HJTpijQeU1tNZgFMqyZjRqUVX0jN05idmEPGG +0TvSrsLCxaXR4QRVR1tHARaqsCuZiWD5BeQuB6TzZzby3PzJD3TsXGDrNYUgOY7A +8Q4GQ8uo9uMOikT1OuiEqCvHP/bE9wdn30QA8px/p+OAjtBxBcLFO8yscSCo4tg+ +OJldWy6rLwDEtkiEdAly+vn7867FhMZIdiwqrp7Ep6WB+5SRi7PQDZFGkIOBnRD4 +7Tpi15uJzpW6GHYI4mL6D3Av4LTzHQg3/LPTEMCAHU0ldB2cc1NUSEWUTdT/nEPz +HslkyLnJpNWi+PeFlrw5XZmVnVvEVGQnr5MHWne6vmZisuHk8SKL3jNdQFEm2T7I +oXw1LyAifatwT7aSGy+BEDI3XfGbazUcpCgDhhHWJx3VwwU3OJ00n649O7IbSjTY +3Ed6ikmuQJZx02BmdDrlcf1TyfHltgfC9tVEDeD3eC9s8JfjNukv/+UXi5fuPKpE +rDEEqdRRaBZNtkHXV5Xo6S5mr2yf5/fquOzJFK8AOcJgPuroT22cGwsOfKZBtQ== +-----END CERTIFICATE----- diff --git a/tools/rest_authenticator_server/certs/rest_api.csr b/tools/rest_authenticator_server/certs/rest_api.csr new file mode 100644 index 000000000000..cff0650a09b1 --- /dev/null +++ b/tools/rest_authenticator_server/certs/rest_api.csr @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIEozCCAosCAQAwXjELMAkGA1UEBhMCRlIxDDAKBgNVBAgMA0lkRjEOMAwGA1UE +BwwFUGFyaXMxDzANBgNVBAoMBkNyaXRlbzEMMAoGA1UECwwDU1JFMRIwEAYDVQQD +DAlsb2NhbGhvc3QwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDC1aVd +no2fFABHH2dy6A4zeQloLMAX0kmBkwYJn4//148r2NwXNT7IWlYM3ERG8WfvpuUj +usIv3yB0+V4smnTnKaKzDTf85u80yzZ7FFX+BrNP+vDBkpKQ9pdsXUkbSe6nvafJ +gQox6TsDNgghTxW50+qqiyvC0qCqC4yomtOENIHPDSMRbmjBIvP/nOb6r3reU3Qe +ZutdxjOg+M37w/4j525yd6c6CtTOZb/xPdwKhLqC6Wlf/vb+g/LM5beDH3MixEbD +RFPZWJU/pcettzo65mKVtx18Mw3v7f2Ow8ozPudgv6mnVrDBToUeZc5HFFItaahC +C2dGaVfNtRoRyIjAmk6v76gD6+rOvJp304czoS/eGURDgJV8neqDla9yhoZhounf +6T3o85XuLg2W2dBNYkwO3ug3ur93nN/ucYGT6W496XTk4HKyoZkzJKwLJIT2SPQC +H/lYoEGair2+vZedo717fWKI45DIu7zXpfxwCP0QqTt7QgFDUqDl2NgdVjmFGOI8 +++UjotykY+Oi2aKKfmUX6ARnDsktbnhy6HaVVEOgCIkmTCbuaOPaEC0ghLrs+rWi +O+jtuy7qgQ65ANY2hH4HVs+az90ec8TjpKAhzF9RNZiWDr3STJa9Tm7BT+TBgAwU +wH9Rnvh3GKltHVnWvpHvPfGdtqyZlDjqriEJPwIDAQABoAAwDQYJKoZIhvcNAQEL +BQADggIBABKyFwrmO6dWU8zPyRHYCTz50r94ymkK4ST9Hhsb4K94YMkXFT3pz+K8 +7iiI7ExdLNInnYx5ipomJUmzYVqegDSC9d0WUXva6jPGmfl6dj47ll/SizbKeiFk +OHnkd334qSQpCbjgwvtOSGfAbhiT3alP4FfW5AB6jUjYWkt5GlrLGaBNvqYnJLpH +s/yWqygqPZ+RZeiLBX59f7jF92UNSo5/sxqQM73Oh/jdgFSgXnMCyHFRoqIf4CLm +nf/9GuFzb++Wt/QoB77toiNrc+3jFoOJY3tBi5ThlalalVA+DXKlen7RaMgaHZK3 +ybpjIzvR7flWrt/mHzpVzmvRP1JOhSs/m6og1Y5tA82MIJo74zweJClTDcRRPv6V +3fV8Twf0c8t/lUlFkEywf/tCPDWgQLgB43G9Lk/kpQNuefiiGMHa2tdMrAC5luuG +MmLLyrxwfrE08hIDuu1ij0N+kEzqXMu5zyCztBLw4NVnM0usDbGnW9zpTdSRq1GP +ZhymuLMaRZUTy5AIdatBg53pPJ4/hqfvwwbFwI3Ge23QB2TyUQP7E5kJXUX0BXbQ +xDAfUp4Z2ecQ5ah7JYm3fImlVY9mRisNkLiVRpU1AKdveqbP+ewZUOVplEG2gRgp +0J9L52LojhkcgO7rP8DoajhPlPXPaDq8HrfhBALi+Y0F4qj+s5R0 +-----END CERTIFICATE REQUEST----- diff --git a/tools/rest_authenticator_server/certs/rest_api.key b/tools/rest_authenticator_server/certs/rest_api.key new file mode 100644 index 000000000000..d70d0504d268 --- /dev/null +++ b/tools/rest_authenticator_server/certs/rest_api.key @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +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 +3nIiK5cwqC2i1VgNGaLJsUJMZdh1OCo= +-----END PRIVATE KEY----- diff --git a/tools/rest_authenticator_server/client.py b/tools/rest_authenticator_server/client.py new file mode 100644 index 000000000000..18cde5830f24 --- /dev/null +++ b/tools/rest_authenticator_server/client.py @@ -0,0 +1,70 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +from cassandra.cluster import Cluster +from cassandra.auth import PlainTextAuthProvider + + +def contact_scylla(username='scylla_user', password='not_cassandra'): + print(f'Run with user {username}') + auth_provider = PlainTextAuthProvider(username=username, password=password) + cluster = Cluster(auth_provider=auth_provider, protocol_version=3) + session = cluster.connect() + try: + print('roles') + rows = session.execute('SELECT * FROM system_auth.roles') + for user_row in rows: + print(user_row) + + print('role_members') + rows = session.execute('SELECT * FROM system_auth.roles_valid') + for user_row in rows: + print(user_row) + + if username == 'cassandra': + print('permissions') + session.execute('GRANT ALL PERMISSIONS ON system_auth.roles TO group1') + session.execute('GRANT ALL PERMISSIONS ON system_auth.roles_valid TO group1') + + # print('Delete scylla role') + # session.execute("DELETE FROM system_auth.roles where role='scylla_user'") + # session.execute("DELETE FROM system_auth.roles_valid where role='scylla_user'") + + # print('role_permissions') + # rows = session.execut + # e('SELECT * FROM system_auth.role_permissions') + # for user_row in rows: + # print(user_row) + + # print(session.execute('LIST ALL PERMISSIONS OF scylla_user;')) + # rows = session.execute('LIST ROLES OF scylla_user;') + # for user_row in rows: + # print(user_row) + finally: + session.shutdown() + + +if __name__ == '__main__': + contact_scylla(username='cassandra', password='cassandra') + contact_scylla() + contact_scylla(username='scylla_user2', password='not_cassandra') diff --git a/tools/rest_authenticator_server/core/__init__.py b/tools/rest_authenticator_server/core/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/rest_authenticator_server/core/config.py b/tools/rest_authenticator_server/core/config.py new file mode 100644 index 000000000000..f32148544390 --- /dev/null +++ b/tools/rest_authenticator_server/core/config.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +API_V1_STR = '/api/v1' diff --git a/tools/rest_authenticator_server/main.py b/tools/rest_authenticator_server/main.py new file mode 100644 index 000000000000..ee92da8f6afb --- /dev/null +++ b/tools/rest_authenticator_server/main.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +import uvicorn +from pathlib import Path +from fastapi import FastAPI + +from api.v1.api import api_router +from core.config import API_V1_STR + +app = FastAPI(title='RestScylla') +app.include_router(api_router, prefix=API_V1_STR) + +if __name__ == "__main__": + cert_dir = Path(__file__).parent / 'certs' + ssl_keyfile=str(cert_dir / 'rest_api.key') + ssl_certfile=str(cert_dir / 'rest_api.crt') + uvicorn.run(app, host="0.0.0.0", port=8000, ssl_keyfile=ssl_keyfile, + ssl_certfile=ssl_certfile, + ssl_ca_certs=ssl_ca_certs, log_level="trace") diff --git a/tools/rest_authenticator_server/outdated_certs/ca.crt b/tools/rest_authenticator_server/outdated_certs/ca.crt new file mode 100644 index 000000000000..26da45c7c6c5 --- /dev/null +++ b/tools/rest_authenticator_server/outdated_certs/ca.crt @@ -0,0 +1,14 @@ +-----BEGIN CERTIFICATE----- +MIICDjCCAbWgAwIBAgIUFO+uLkEc3iNsaK8nPbuTWv4iz8EwCgYIKoZIzj0EAwIw +XTELMAkGA1UEBhMCRlIxDDAKBgNVBAgMA0lkRjEOMAwGA1UEBwwFUGFyaXMxDzAN +BgNVBAoMBkNyaXRlbzEMMAoGA1UECwwDU1JFMREwDwYDVQQDDAh0ZXN0LmNvbTAe +Fw0yMTA1MTAxNDA2MDJaFw0yMTA2MDkxNDA2MDJaMF0xCzAJBgNVBAYTAkZSMQww +CgYDVQQIDANJZEYxDjAMBgNVBAcMBVBhcmlzMQ8wDQYDVQQKDAZDcml0ZW8xDDAK +BgNVBAsMA1NSRTERMA8GA1UEAwwIdGVzdC5jb20wWTATBgcqhkjOPQIBBggqhkjO +PQMBBwNCAARRCmPNTbZ/9f/kAvLohixmxiqwrrUptZQTxAGCov/sis6MCrWY7SbZ +PY5UAU4lPhaqxnH5TAZ3zrNAixA39vPro1MwUTAdBgNVHQ4EFgQUpIX49MeQihBt +xa35DXdqBZZ2CPgwHwYDVR0jBBgwFoAUpIX49MeQihBtxa35DXdqBZZ2CPgwDwYD +VR0TAQH/BAUwAwEB/zAKBggqhkjOPQQDAgNHADBEAiABDd7WoB/y+gIVLvhJGgTS +S3/2cWLfWbEO4kn6sKLrpgIgX5rrvnAZStk/DluIFcZkh4SkmmV49+0iP0BzVNYP +0ms= +-----END CERTIFICATE----- diff --git a/tools/rest_authenticator_server/outdated_certs/ca.key b/tools/rest_authenticator_server/outdated_certs/ca.key new file mode 100644 index 000000000000..d27c27f63c5a --- /dev/null +++ b/tools/rest_authenticator_server/outdated_certs/ca.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIIULr9DASVDyvfk+q8Adce376qVE6pfnm5A9D3XcyI9BoAoGCCqGSM49 +AwEHoUQDQgAEUQpjzU22f/X/5ALy6IYsZsYqsK61KbWUE8QBgqL/7IrOjAq1mO0m +2T2OVAFOJT4WqsZx+UwGd86zQIsQN/bz6w== +-----END EC PRIVATE KEY----- diff --git a/tools/rest_authenticator_server/outdated_certs/ca.srl b/tools/rest_authenticator_server/outdated_certs/ca.srl new file mode 100644 index 000000000000..c83210b73eb8 --- /dev/null +++ b/tools/rest_authenticator_server/outdated_certs/ca.srl @@ -0,0 +1 @@ +23AC06A350DEE1DA44B6A947BDCEBC4154CE3A65 diff --git a/tools/rest_authenticator_server/outdated_certs/rest_api.crt b/tools/rest_authenticator_server/outdated_certs/rest_api.crt new file mode 100644 index 000000000000..a6f22f632a53 --- /dev/null +++ b/tools/rest_authenticator_server/outdated_certs/rest_api.crt @@ -0,0 +1,12 @@ +-----BEGIN CERTIFICATE----- +MIIBtTCCAVwCFCOsBqNQ3uHaRLapR73OvEFUzjplMAoGCCqGSM49BAMCMF0xCzAJ +BgNVBAYTAkZSMQwwCgYDVQQIDANJZEYxDjAMBgNVBAcMBVBhcmlzMQ8wDQYDVQQK +DAZDcml0ZW8xDDAKBgNVBAsMA1NSRTERMA8GA1UEAwwIdGVzdC5jb20wHhcNMjEw +NTEwMTQwNjU1WhcNMjQwMjA0MTQwNjU1WjBeMQswCQYDVQQGEwJGUjEMMAoGA1UE +CAwDSWRGMQ4wDAYDVQQHDAVQYXJpczEPMA0GA1UECgwGQ3JpdGVvMQwwCgYDVQQL +DANTUkUxEjAQBgNVBAMMCWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEH +A0IABDSjbxxlH36iL85tjtGIDBEg24WXnQQlLIYVmC84GpCIXxMrpGGaZZki9cD8 +m9NRB0DGbB+SA6NWKwruN6fIyXkwCgYIKoZIzj0EAwIDRwAwRAIgGa39nb7tlQFZ +Yf7qPeSQIqFHWIkHEpvZWTGxSXO6T/MCIB28jxf1z+rn1unzMOcc5zf+23cxFwLE +HvH8JYtygEH4 +-----END CERTIFICATE----- diff --git a/tools/rest_authenticator_server/outdated_certs/rest_api.csr b/tools/rest_authenticator_server/outdated_certs/rest_api.csr new file mode 100644 index 000000000000..fcff849191be --- /dev/null +++ b/tools/rest_authenticator_server/outdated_certs/rest_api.csr @@ -0,0 +1,8 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIIBGDCBwAIBADBeMQswCQYDVQQGEwJGUjEMMAoGA1UECAwDSWRGMQ4wDAYDVQQH +DAVQYXJpczEPMA0GA1UECgwGQ3JpdGVvMQwwCgYDVQQLDANTUkUxEjAQBgNVBAMM +CWxvY2FsaG9zdDBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABDSjbxxlH36iL85t +jtGIDBEg24WXnQQlLIYVmC84GpCIXxMrpGGaZZki9cD8m9NRB0DGbB+SA6NWKwru +N6fIyXmgADAKBggqhkjOPQQDAgNHADBEAiAZC2B+MHK9RHT9oAUZTqi8DrD9PuoK +ElzFajzYlvrZoQIgaeXk+ec6DLQVbaW5DEZMhksp9hns3o2YrBwOUugCTL0= +-----END CERTIFICATE REQUEST----- diff --git a/tools/rest_authenticator_server/outdated_certs/rest_api.key b/tools/rest_authenticator_server/outdated_certs/rest_api.key new file mode 100644 index 000000000000..d1f99f28a2e4 --- /dev/null +++ b/tools/rest_authenticator_server/outdated_certs/rest_api.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEII6WUgoJ82hTXLFHbHtgltt02Q0ltkMTIp3Rdza+VYSloAoGCCqGSM49 +AwEHoUQDQgAENKNvHGUffqIvzm2O0YgMESDbhZedBCUshhWYLzgakIhfEyukYZpl +mSL1wPyb01EHQMZsH5IDo1YrCu43p8jJeQ== +-----END EC PRIVATE KEY----- diff --git a/tools/rest_authenticator_server/requirements.txt b/tools/rest_authenticator_server/requirements.txt new file mode 100644 index 000000000000..4e72c976a5ad --- /dev/null +++ b/tools/rest_authenticator_server/requirements.txt @@ -0,0 +1,4 @@ +fastapi +pydantic +scylla-driver +uvicorn \ No newline at end of file diff --git a/tools/rest_authenticator_server/rest_server.sh b/tools/rest_authenticator_server/rest_server.sh new file mode 100755 index 000000000000..fe2369f0e915 --- /dev/null +++ b/tools/rest_authenticator_server/rest_server.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# +python3 -m venv --clear venv +source venv/bin/activate +pip3 install -r requirements.txt + +echo "Start Rest Api Serevr" +uvicorn main:app --reload --port 8000 --ssl-keyfile certs/rest_api.key --ssl-certfile certs/rest_api.crt diff --git a/tools/rest_authenticator_server/schema/__init__.py b/tools/rest_authenticator_server/schema/__init__.py new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/tools/rest_authenticator_server/schema/auth.py b/tools/rest_authenticator_server/schema/auth.py new file mode 100644 index 000000000000..8d9e8aebdf4e --- /dev/null +++ b/tools/rest_authenticator_server/schema/auth.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +from typing import List, Optional + +from pydantic import BaseModel + +class ResponseAuth(BaseModel): + groups: Optional[List[str]] diff --git a/tools/rest_authenticator_server/scylla_client.sh b/tools/rest_authenticator_server/scylla_client.sh new file mode 100755 index 000000000000..1d4c856ba783 --- /dev/null +++ b/tools/rest_authenticator_server/scylla_client.sh @@ -0,0 +1,25 @@ +#!/usr/bin/env bash +# -*- coding: utf-8 -*- +# +# Copyright (C) 2021 Criteo +# + +# +# 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 . +# + +echo "Run client" +python3 client.py \ No newline at end of file