Skip to content

Commit

Permalink
Add authorization support with rest_auth
Browse files Browse the repository at this point in the history
  • Loading branch information
n.fraison committed Jun 10, 2021
1 parent 687d482 commit 25d4e38
Show file tree
Hide file tree
Showing 14 changed files with 879 additions and 139 deletions.
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -310,6 +310,7 @@ set(scylla_sources
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
Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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/guides/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.
Expand Down
100 changes: 62 additions & 38 deletions auth/rest_authenticator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ namespace auth {

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, {{}}, ?)",
"INSERT INTO {} ({}, can_login, is_superuser, {}) VALUES (?, true, false, ?)",
meta::roles_table::qualified_name,
meta::roles_table::role_col_name,
SALTED_HASH);
Expand Down Expand Up @@ -137,7 +137,7 @@ namespace auth {
// 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?" );
"Did you call set_authenticator_config before calling start?");
}
// Init rest http client
_rest_http_client = rest_http_client(_authenticator_config.rest_authenticator_endpoint_host,
Expand Down Expand Up @@ -266,8 +266,10 @@ namespace auth {

// 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 && !role_name) || !salted_hash ||
!passwords::check(password, *salted_hash)) {
if (username == DEFAULT_USER_NAME && (!salted_hash || !passwords::check(password, *salted_hash))) {
std::throw_with_nested(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();

// TODO manage retry?
Expand All @@ -278,20 +280,18 @@ namespace auth {
return with_timeout(
timer<>::clock::now() +
std::chrono::seconds(_authenticator_config.rest_authenticator_endpoint_timeout),
_rest_http_client.connect()
.then([username, password](
std::unique_ptr <rest_http_client::connection> c) {
return seastar::do_with(
std::move(c),
[username, password](auto &c) {
return c->do_get_groups(username, password);
});
})
.then([this, create_user, username, password](
std::vector <std::string> groups) {
return create_or_update(create_user, username, password, groups);
})
);
_rest_http_client.connect())
.then([username, password](
std::unique_ptr <rest_http_client::connection> c) {
return seastar::do_with(
std::move(c),
[username, password](auto &c) {
return c->do_get_groups(username, password);
});
})
.then([this, create_user, username, password](role_set roles) {
return create_or_update(create_user, username, password, roles);
});
}

return make_ready_future<authenticated_user>(username);
Expand All @@ -308,29 +308,30 @@ namespace auth {
}

future <authenticated_user>
rest_authenticator::create_or_update(bool create_user, sstring username, sstring password,
std::vector <std::string> groups) const {
authentication_options authen_options;
authen_options.password = std::optional < std::string > {password};

if (create_user) {
plogger.info("Create role for username {}", username);
return rest_authenticator::create_with_groups(username, groups, authen_options).then([username] {
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) {
plogger.info("Create role for username {}", username);
return rest_authenticator::create_with_groups(username, roles, authen_options).then([username] {
return make_ready_future<authenticated_user>(username);
});
}
plogger.info("Update password for username {}", username);
return rest_authenticator::alter_with_groups(username, roles, authen_options).then([username] {
return make_ready_future<authenticated_user>(username);
});
}
plogger.info("Update password for username {}", username);
return rest_authenticator::alter_with_groups(username, groups, authen_options).then([username] {
return make_ready_future<authenticated_user>(username);
});
}

future<> rest_authenticator::create(std::string_view role_name, const authentication_options &options) const {
std::vector <std::string> groups;
return create_with_groups(sstring(role_name), groups, options);
role_set roles;
return create_with_groups(sstring(role_name), roles, options);
}

future<> rest_authenticator::create_with_groups(sstring role_name, std::vector <std::string> groups,
future<> rest_authenticator::create_with_groups(sstring role_name, role_set &roles,
const authentication_options &options) const {
if (!options.password) {
return make_ready_future<>();
Expand All @@ -347,15 +348,17 @@ namespace auth {
consistency_for_user(role_name),
internal_distributed_timeout_config(),
{role_name})
).discard_result();
).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 {
std::vector <std::string> groups;
return alter_with_groups(sstring(role_name), groups, options);
role_set roles;
return alter_with_groups(sstring(role_name), roles, options);
}

future<> rest_authenticator::alter_with_groups(sstring role_name, std::vector <std::string> groups,
future<> rest_authenticator::alter_with_groups(sstring role_name, role_set &roles,
const authentication_options &options) const {
if (!options.password) {
return make_ready_future<>();
Expand All @@ -377,7 +380,9 @@ namespace auth {
consistency_for_user(role_name),
internal_distributed_timeout_config(),
{role_name})
).discard_result();
).then([this, role_name, &roles](auto f) {
return modify_membership(role_name, roles);
}).discard_result();
}

future<> rest_authenticator::drop(std::string_view name) const {
Expand All @@ -392,6 +397,25 @@ namespace auth {
{sstring(name)}).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_timeout_config(),
{roles, grantee_name});
};

return modify_roles().discard_result();
}

future <custom_options> rest_authenticator::query_custom_options(std::string_view role_name) const {
return make_ready_future<custom_options>();
}
Expand Down
19 changes: 12 additions & 7 deletions auth/rest_authenticator.hh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@

#include "auth/authenticator.hh"
#include "auth/rest_http_client.hh"
#include "auth/role_manager.hh"
#include "cql3/query_processor.hh"

namespace service {
Expand Down Expand Up @@ -91,17 +92,21 @@ public:


private:
future<> create_default_if_missing() const;

enum class membership_change {
add, remove
};

future <authenticated_user>
create_or_update(bool user_to_create, sstring username, sstring password,
std::vector <std::string> groups) const;
create_or_update(bool user_to_create, sstring username, sstring password, role_set &roles) const;

future<> create_with_groups(sstring role_name, std::vector <std::string> groups,
const authentication_options &options) const;
future<> create_with_groups(sstring role_name, role_set &roles, const authentication_options &options) const;

future<> alter_with_groups(sstring role_name, std::vector <std::string> groups,
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;

};

Expand Down
16 changes: 9 additions & 7 deletions auth/rest_http_client.hh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
#include <seastar/core/future-util.hh>
#include "picojson/picojson.h"
#include "auth/rest_response_parser.hh"
#include "auth/role_manager.hh"
#include "alternator/base64.hh"


Expand Down Expand Up @@ -53,7 +54,7 @@ namespace auth {
rest_response_parser _parser;
sstring _request;

future <std::vector<std::string>> extract_groups(temporary_buffer<char> buf, uint content_len) {
future <role_set> extract_groups(temporary_buffer<char> buf, uint content_len) {
const char *json = buf.get();
picojson::value v;
std::string err;
Expand Down Expand Up @@ -83,20 +84,20 @@ namespace auth {
}

const picojson::value::array &p_groups = pv_groups.get<picojson::array>();
std::vector <std::string> groups;

role_set groups;
// TODO filter groups to add (For ex. only add groups containing scylla... to avoid storing all groups)
for (auto p_group : p_groups) {
if (p_group.is<std::string>()) {
auto group = p_group.get<std::string>();
groups.push_back(group);
groups.insert(sstring(group));
}
}

return make_ready_future < std::vector < std::string >> (groups);
return make_ready_future<role_set>(groups);
}

future <std::vector<std::string>>
future <role_set>
get_groups_from_body(std::unique_ptr <auth::http_response, std::default_delete<auth::http_response>> rsp) {
auto it = rsp->_headers.find("content-length");
if (it == rsp->_headers.end()) {
Expand Down Expand Up @@ -125,7 +126,7 @@ namespace auth {

~connection() {}

future <std::vector<std::string>> do_get_groups(sstring username, sstring password) {
future <role_set> do_get_groups(sstring username, sstring password) {
std::string b64_authorization = get_authorization_header(username, password);
sstring request = format(_request.c_str(), b64_authorization);

Expand Down Expand Up @@ -173,7 +174,8 @@ namespace auth {
_server, _port);

// Load system CA trust
// TODO see tls.credentials_builder in Seastar
// TODO see tls.credentials_builder in Seastar in order to create
// a reloadable creds: tls::credentials_builder::build_reloadable_server_credentials
auto f = _creds->set_system_trust();
if (_ca_file != "") {
f = f.then([this] { return _creds->set_x509_trust_file(_ca_file, tls::x509_crt_format::PEM); });
Expand Down
36 changes: 20 additions & 16 deletions auth/rest_response_parser.rl
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
/*
* Copyright (C) 2021 Criteo
*/

/*
* This file is part of Scylla.
* This file is open source software, licensed to you under the terms
* of the Apache License, Version 2.0 (the "License"). See the NOTICE file
* distributed with this work for additional information regarding copyright
* ownership. You may not use this file except in compliance with the License.
*
* 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.
* You may obtain a copy of the License at
*
* 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.
* http://www.apache.org/licenses/LICENSE-2.0
*
* You should have received a copy of the GNU General Public License
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/*
* Copyright (C) 2015 Cloudius Systems, Ltd.
*/

/*
* Modified by Criteo: June 2021
* Manage status and enforce all header fields to be in lowercase
*/

// Added to manage status and enforce all header fields to be in lowercase
#include <seastar/core/ragel.hh>
#include <memory>
#include <unordered_map>
Expand Down
Loading

0 comments on commit 25d4e38

Please sign in to comment.