From 1686c5f499d400bd40310daaf3e82e2792e7672a 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 serie 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) --- CMakeLists.txt | 2 + README.md | 4 + auth/authenticator.hh | 19 + auth/rest_authenticator.cc | 550 ++++++++++++++++++ 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 | 78 +++ 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 | 30 + .../schema/__init__.py | 0 .../rest_authenticator_server/schema/auth.py | 29 + .../scylla_client.sh | 25 + 44 files changed, 2453 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..29cadbf5c963 --- /dev/null +++ b/auth/rest_authenticator.cc @@ -0,0 +1,550 @@ +/* + * 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 +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 rest_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); + + // in & out aren't really required here (they are managed by connection object), + // we get them only to mitigate wrong exception management in connection::close(). + // problem visible with expired_tls_certs unit test, + auto in = fd.input(); + auto out = fd.output(); + 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)); + if (rep._status == reply::status_type::ok) { + content = to_sstring(co_await http_client.in(rep).read_exactly(rep.content_length)); + } + } catch(...) { + ep = std::current_exception(); + ralogger.warn("failure to invoke rest auth api: {}", ep); + } + + // manually close in and out to close underlying TLS session and socket + co_await in.close().handle_exception([](auto ep){ ralogger.warn("failure to close http_client in stream: {}", ep); }); + co_await out.close().handle_exception([](auto ep){ ralogger.warn("failure to close http_client out stream: {}", 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: { + rjson::value json = rjson::parse(std::move(content)); + if (!json.IsObject()) { + throw exceptions::authentication_exception("Bad response: response is not a dict"); + } + + const rjson::value* groups_json = rjson::find(json, "groups"); + if (!groups_json) { + throw exceptions::authentication_exception("Bad response: doesn't contain groups field"); + } + if (!groups_json->IsArray()) { + throw exceptions::authentication_exception("Bad response: groups field doesn't contain an array"); + } + + 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("Issue to authenticate with http status code {}", 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& e) { + std::throw_with_nested(e); + } 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 2aaf658c7d19..a1170571941d 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 @@ -439,6 +443,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', @@ -979,6 +984,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 30defa200e47..56efa75cc687 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 @@ -709,8 +713,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", @@ -921,6 +926,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 46dba107fc69..0bb238f65323 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 @@ -410,6 +414,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..733fda1fbf89 --- /dev/null +++ b/docs/dev/rest_authc_authz.md @@ -0,0 +1,78 @@ +# 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 +$ ./tools/rest_authenticator_server/rest_server.sh +``` + +Run Test client + +```bash +$ ./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 0ebbc1df0319..1b63ecdc2075 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 @@ -1508,6 +1511,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..c5968013e5b9 --- /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(), "com.criteo.scylladb.auth.RestAuthenticator"); + 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----- +MIIJQwIBADANBgkqhkiG9w0BAQEFAASCCS0wggkpAgEAAoICAQDec4pBNe+F14XX +ugIRsaP/FCs6iSE5CkbQPyabeFZnIp3LlvzwTTFYK+AYu9WrQ6rZOMXwVytSEMfE +sodNXOeEgpLXL1C5a6RPjifcXXNP5yYoXWIShZEFbqHMCjNFpcpgEImRbjtst+Dw +WhYQ1OLUWv43S7B/bcd0vGvxbJZlSyG8boHAiTs6XIH4FNGO7GddSVE5UhzVEwE3 +Y+RqGnEivxLJQqZXhUb4PhaUm1Lj5EiNN0IOH3CNQuUnPh0Ud9320OJQI7NfFvwP +di2LWapfmA9yS3NRf8NMBeZE7pXK9KTJ/XNHZud8rnezX5j0Bm5lSEhtQ5JNqN0D +1fki9DCyHLAZjTVJvw2Ft0ko8sNIOhFMkwHGO7+XOPsQfWKqKrots5s7vHKyGNmI +72XjSlT9VYOacL5tLsCvXPpzzfz69o5M9tIwFcL/pubxQQS4MW0QaT20KPfFSI/6 +VNZD4ybScktxhq6PAcTYqVCGKvwmUFPEo8EzvnTYLwmoZjFJ7ysLptzFXSaOWUnY +Cn3hLKAejCE9kw5ceUgWqQXAVrELBZ5ZdKLLvsLGN7nn2QJW+hN5ZwScvcFR7lPf +KF5qy72iHgBTuulcxzli7X46e6+S5FDIt3+2yH/kAP85K/6L44mTwEuu0i/D6Z9q +HK2kdzENvsXEO/23jSjNcm6nermPWQIDAQABAoICACOLgsP4wlU4owJdjocVQ4c2 +ydGIp+5dXgNYk+qzx9qjBWPKtJARRZk5KY3KNdIv9dTP5ZEJ3Lo4RDOB82zBM51Q +3/XScauvL4omxOfJZuuVm9tHUKx0+z9K9j+MQbUXUMcY3WKHoqc0mpTNUElQOw6D +m6tqRMGX4Q8eLIFfQIjfAH0Di2gghX5F2d6yNOoJjFqzpG7sV/8UElWveIgbwYgz +z+So6buCaFyyTxlf2Fbh21X1RnDLRUjNL6CdpDfpD2ao6gC0M602FwDBeXWS1k6A +FbfFpp+XMrWrG6hYF0de4BYGka0PHF4n4qie1enoPSuh6TDMY0xVdzttrWmQa1c+ +Sq1bIgDeYY1pl4A+pZFMD43h7IjzUuG3rtHBA+USWcKK8N1Fkqkq68RkznliI+4S +uUWtYzzVrrIvYKNvQgd8SarwGpUcSnwN9Z8ZysdbA5O+lVPa93E1mCFrNqS8iNeT +/zlaGQ2fFIaXl7rfH3wIoSUtqirOOSbS4jBvzYfsbttYXzDNDiInhUOQSHbREEhS +mfqJXpx6W7+RQEyo6kBzAKOj5rXo8hcKZicN+OCZ+TdLl3YDz0CNfttQB3kkMF3d +E1SsXGxaKxZ1MTHHL1yzCDZYcMDPzA1NsVV/xw1uoeWGyG7y4ubBruNLwUxjpDE9 +kxKL7TdQJtdLPEY3jfSBAoIBAQDvdyBhTI7bDUgDSrZA0WCWvfoh1gb0tHV3S7KU +wRwvxtjY2vghUMK6PiZ400wRg42ejzhYYvaT2+4qoV1dq64Tp7Q5pT7FKYu/GeIb +aD87yoUJSbqEGy1C9lnKpIZF55q6aVlk//K7EPkYNMixcz9hvo0PdQUQtv7khCRG +27I4+3yE9buJDIxDDNeqelapzaXHdjmjtNdORjeKdjK8QiLOLFRAbfFpR91GEa+O +5L7LHFIm8RB67ocZrG9TgxRaSNcruhy5ztz5msVW8s1Jv6QDM7wvbfSc4G+JboQV +Z4lAwR/B+JRuOvqvKMoSnfeS/RIcdkvoU84n252RPhQJhA0tAoIBAQDtz6r/VcKC +KD1uQE9XpT48PV7sH/FYvyC3ToZlRB6Me4YF43YZ3O4PR+SyvOC9rNwXxwBX5Rbz +AknyU+8hwsLbDMBlbPY7StNVFUvJpdTuU8KrBn6fsj+yc1HW2xrSsk6tWgr/Ae4F +VH4Snbu+HzezANVuw6ngsDssLv5N7fKacdzKz+68drosvjKALyzLyp12+Ifcvhho +aRXBqy3wtLEiGgwiKBi6luBsvRw46D0skv2sD5jkoDqUhZVocYGTDqfOdLeyQykB +Q3cN2a3rTxSQz1/T+OP9i5PpOWfPrr4MNhDSwzg7O4q60HcZyj6qTOV91EItuqbF +Xv9T5s+Ve55dAoIBAQCUVlH7k+YmcYq7Z0uQyWKw3kwVK7SxXxwo4v5jwWAJhH8N +jk8IrDOEFT3VuOY1Ly3KG+QGdH9URne5dq4UeWXjDQ7Kar+AeOT1Yz46RhBtpF1W +zJI6hW87PMmQiuwWX4rskJfsxdQB51i2kvQDYGXR0a0l9xJFMWah/e1O64byX27a +GjKqidhVLJ5oH1KR6y8XaPf5neuFtF9xaRiyn7FZ0AH8y14xHnBGmGwM80vxG8EU +GF5iGGIeGD/6FofECb0ofCiRPUCLakF2Q//BAHLD/QdExjMx/qF5G7m8XJ+cOkXG +7Ypc4jIp0PWkJLCZICVY9jq9VzSsYhGwSCk3X9KdAoIBAEnX2CMdAB7mW9zmPzzZ +5K9T5yuu54RFJOWGjpjXRB0fJgiiDsQNJCa+jjcqCycai+UeN+8TBcNDjK6gfXqg +PV1DYmKcKQFURLcAu93LjojKnYH5rEvuT3Ub9eefdX7DO0b81LlGYiBpkhQ5wlhQ +3TzKH3wbaZ6JWnZnyTNtlY17mbGoS1teCoVmMcw32dMWATs2BQn4RR/2sXjHrKY3 +lWEfcXERvkwCGYHqXt8UzhoPMpHA7hrf3hDMQg6CYFfHjze+amQCErN/vXIhi62r +iplEq17ow/Kw6qba2m9UtVKZXzPyxn0uCe/kV3c8o6TB5+jghgQpFyvmSaZF32Nt +VUkCggEBANnpzSQIT4cNvQEbPw3zhuhKupi8DL5xf2GDYO0fnEH/I7+gPHAwoK82 +4Bt4PIgWtO6ayHXq6LCJk3xhHLUXvnyD1J42CiCvXtD9ywMPAC6lrqu918f1zuw+ +V891lbJDWsLj39BAP5bVaNkH00OoF0pFjtm8YtuyTFLmreIQTycJ9HKEe5r8JUmm +kJcp07ETHPDNLaXeKH1gJHGOO7rxBKGEyXz0r6vBIwYIs58yssqHUa+O7un3XNYt +Fg+sc7c7v+e/e0R23ZFRmTVqUE8dF1mfisDYu3tx9Vrw+QssZX6cOTYs/IUf484R +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..c1d2aacf2601 --- /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=2) + 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..b76a203b0a37 --- /dev/null +++ b/tools/rest_authenticator_server/rest_server.sh @@ -0,0 +1,30 @@ +#!/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 . +# + +deactivate +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 \ No newline at end of file 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