Merge "Authorizer support" from Calle
"Conversion/implementation of "authorizer" code from origin, handling
permissions management for users/resources.
Default implementation keeps mapping of <user.resource>->{permissions}
in a table, contents of which is cached for slightly quicker checks.
Adds access control to all (existing) cql statements.
Adds access management support to the CQL impl. (GRANT/REVOKE/LIST)
Verified manually and with dtest auth_test.py. Note that several of these
still fail due to (unrelated) unimplemented features, like index, types
etc.
Fixes #1138"
This commit is contained in:
102
auth/auth.cc
102
auth/auth.cc
@@ -40,14 +40,19 @@
|
||||
*/
|
||||
#include <seastar/core/sleep.hh>
|
||||
|
||||
#include <seastar/core/distributed.hh>
|
||||
|
||||
#include "auth.hh"
|
||||
#include "authenticator.hh"
|
||||
#include "authorizer.hh"
|
||||
#include "database.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/statements/cf_statement.hh"
|
||||
#include "cql3/statements/create_table_statement.hh"
|
||||
#include "db/config.hh"
|
||||
#include "service/migration_manager.hh"
|
||||
#include "utils/loading_cache.hh"
|
||||
#include "utils/hash.hh"
|
||||
|
||||
const sstring auth::auth::DEFAULT_SUPERUSER_NAME("cassandra");
|
||||
const sstring auth::auth::AUTH_KS("system_auth");
|
||||
@@ -76,13 +81,10 @@ class auth_migration_listener : public service::migration_listener {
|
||||
void on_update_aggregate(const sstring& ks_name, const sstring& aggregate_name) override {}
|
||||
|
||||
void on_drop_keyspace(const sstring& ks_name) override {
|
||||
// TODO:
|
||||
//DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.keyspace(ksName));
|
||||
|
||||
auth::authorizer::get().revoke_all(auth::data_resource(ks_name));
|
||||
}
|
||||
void on_drop_column_family(const sstring& ks_name, const sstring& cf_name) override {
|
||||
// TODO:
|
||||
//DatabaseDescriptor.getAuthorizer().revokeAll(DataResource.columnFamily(ksName, cfName));
|
||||
auth::authorizer::get().revoke_all(auth::data_resource(ks_name, cf_name));
|
||||
}
|
||||
void on_drop_user_type(const sstring& ks_name, const sstring& type_name) override {}
|
||||
void on_drop_function(const sstring& ks_name, const sstring& function_name) override {}
|
||||
@@ -91,6 +93,64 @@ class auth_migration_listener : public service::migration_listener {
|
||||
|
||||
static auth_migration_listener auth_migration;
|
||||
|
||||
namespace std {
|
||||
template <>
|
||||
struct hash<auth::data_resource> {
|
||||
size_t operator()(const auth::data_resource & v) const {
|
||||
return std::hash<sstring>()(v.name());
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<auth::authenticated_user> {
|
||||
size_t operator()(const auth::authenticated_user & v) const {
|
||||
return utils::tuple_hash()(v.name(), v.is_anonymous());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
class auth::auth::permissions_cache {
|
||||
public:
|
||||
typedef utils::loading_cache<std::pair<authenticated_user, data_resource>, permission_set, utils::tuple_hash> cache_type;
|
||||
typedef typename cache_type::key_type key_type;
|
||||
|
||||
permissions_cache()
|
||||
: permissions_cache(
|
||||
cql3::get_local_query_processor().db().local().get_config()) {
|
||||
}
|
||||
|
||||
permissions_cache(const db::config& cfg)
|
||||
: _cache(cfg.permissions_cache_max_entries(), expiry(cfg),
|
||||
std::chrono::milliseconds(
|
||||
cfg.permissions_validity_in_ms()),
|
||||
[](const key_type& k) {
|
||||
logger.debug("Refreshing permissions for {}", k.first.name());
|
||||
return authorizer::get().authorize(::make_shared<authenticated_user>(k.first), k.second);
|
||||
}) {
|
||||
}
|
||||
|
||||
static std::chrono::milliseconds expiry(const db::config& cfg) {
|
||||
auto exp = cfg.permissions_update_interval_in_ms();
|
||||
if (exp == 0 || exp == std::numeric_limits<uint32_t>::max()) {
|
||||
exp = cfg.permissions_validity_in_ms();
|
||||
}
|
||||
return std::chrono::milliseconds(exp);
|
||||
}
|
||||
|
||||
future<> stop() {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
future<permission_set> get(::shared_ptr<authenticated_user> user, data_resource resource) {
|
||||
return _cache.get(key_type(*user, std::move(resource)));
|
||||
}
|
||||
|
||||
private:
|
||||
cache_type _cache;
|
||||
};
|
||||
|
||||
static distributed<auth::auth::permissions_cache> perm_cache;
|
||||
|
||||
/**
|
||||
* Poor mans job schedule. For maximum 2 jobs. Sic.
|
||||
* Still does nothing more clever than waiting 10 seconds
|
||||
@@ -163,14 +223,22 @@ bool auth::auth::is_class_type(const sstring& type, const sstring& classname) {
|
||||
future<> auth::auth::setup() {
|
||||
auto& db = cql3::get_local_query_processor().db().local();
|
||||
auto& cfg = db.get_config();
|
||||
auto type = cfg.authenticator();
|
||||
|
||||
if (is_class_type(type, authenticator::ALLOW_ALL_AUTHENTICATOR_NAME)) {
|
||||
return authenticator::setup(type).discard_result(); // just create the object
|
||||
future<> f = perm_cache.start();
|
||||
|
||||
if (is_class_type(cfg.authenticator(),
|
||||
authenticator::ALLOW_ALL_AUTHENTICATOR_NAME)
|
||||
&& is_class_type(cfg.authorizer(),
|
||||
authorizer::ALLOW_ALL_AUTHORIZER_NAME)
|
||||
) {
|
||||
// just create the objects
|
||||
return f.then([&cfg] {
|
||||
return authenticator::setup(cfg.authenticator());
|
||||
}).then([&cfg] {
|
||||
return authorizer::setup(cfg.authorizer());
|
||||
});
|
||||
}
|
||||
|
||||
future<> f = make_ready_future();
|
||||
|
||||
if (!db.has_keyspace(AUTH_KS)) {
|
||||
std::map<sstring, sstring> opts;
|
||||
opts["replication_factor"] = "1";
|
||||
@@ -182,10 +250,10 @@ future<> auth::auth::setup() {
|
||||
return setup_table(USERS_CF, sprint("CREATE TABLE %s.%s (%s text, %s boolean, PRIMARY KEY(%s)) WITH gc_grace_seconds=%d",
|
||||
AUTH_KS, USERS_CF, USER_NAME, SUPER, USER_NAME,
|
||||
90 * 24 * 60 * 60)); // 3 months.
|
||||
}).then([type] {
|
||||
return authenticator::setup(type).discard_result();
|
||||
}).then([] {
|
||||
// TODO authorizer
|
||||
}).then([&cfg] {
|
||||
return authenticator::setup(cfg.authenticator());
|
||||
}).then([&cfg] {
|
||||
return authorizer::setup(cfg.authorizer());
|
||||
}).then([] {
|
||||
service::get_local_migration_manager().register_listener(&auth_migration); // again, only one shard...
|
||||
// instead of once-timer, just schedule this later
|
||||
@@ -216,9 +284,15 @@ future<> auth::auth::shutdown() {
|
||||
// db-env-shutdown != process shutdown
|
||||
return smp::invoke_on_all([] {
|
||||
thread_waiters().clear();
|
||||
}).then([] {
|
||||
return perm_cache.stop();
|
||||
});
|
||||
}
|
||||
|
||||
future<auth::permission_set> auth::auth::get_permissions(::shared_ptr<authenticated_user> user, data_resource resource) {
|
||||
return perm_cache.local().get(std::move(user), std::move(resource));
|
||||
}
|
||||
|
||||
static db::consistency_level consistency_for_user(const sstring& username) {
|
||||
if (username == auth::auth::DEFAULT_SUPERUSER_NAME) {
|
||||
return db::consistency_level::QUORUM;
|
||||
|
||||
15
auth/auth.hh
15
auth/auth.hh
@@ -44,13 +44,21 @@
|
||||
#include <chrono>
|
||||
#include <seastar/core/sstring.hh>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "permission.hh"
|
||||
#include "data_resource.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class authenticated_user;
|
||||
|
||||
class auth {
|
||||
public:
|
||||
class permissions_cache;
|
||||
|
||||
static const sstring DEFAULT_SUPERUSER_NAME;
|
||||
static const sstring AUTH_KS;
|
||||
static const sstring USERS_CF;
|
||||
@@ -58,12 +66,7 @@ public:
|
||||
|
||||
static bool is_class_type(const sstring& type, const sstring& classname);
|
||||
|
||||
#if 0
|
||||
public static Set<Permission> getPermissions(AuthenticatedUser user, IResource resource)
|
||||
{
|
||||
return permissionsCache.getPermissions(user, resource);
|
||||
}
|
||||
#endif
|
||||
static future<permission_set> get_permissions(::shared_ptr<authenticated_user>, data_resource);
|
||||
|
||||
/**
|
||||
* Checks if the username is stored in AUTH_KS.USERS_CF.
|
||||
|
||||
@@ -53,6 +53,9 @@ auth::authenticated_user::authenticated_user(sstring name)
|
||||
: _name(name), _anon(false)
|
||||
{}
|
||||
|
||||
auth::authenticated_user::authenticated_user(authenticated_user&&) = default;
|
||||
auth::authenticated_user::authenticated_user(const authenticated_user&) = default;
|
||||
|
||||
const sstring& auth::authenticated_user::name() const {
|
||||
return _anon ? ANONYMOUS_USERNAME : _name;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public:
|
||||
|
||||
authenticated_user();
|
||||
authenticated_user(sstring name);
|
||||
authenticated_user(authenticated_user&&);
|
||||
authenticated_user(const authenticated_user&);
|
||||
|
||||
const sstring& name() const;
|
||||
|
||||
|
||||
@@ -100,8 +100,9 @@ auth::authenticator::setup(const sstring& type) throw (exceptions::configuration
|
||||
future<> drop(sstring username) throw(exceptions::request_validation_exception, exceptions::request_execution_exception) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
resource_ids protected_resources() const override {
|
||||
return resource_ids();
|
||||
const resource_ids& protected_resources() const override {
|
||||
static const resource_ids ids;
|
||||
return ids;
|
||||
}
|
||||
::shared_ptr<sasl_challenge> new_sasl_challenge() const override {
|
||||
throw std::runtime_error("Should not reach");
|
||||
|
||||
@@ -86,11 +86,6 @@ public:
|
||||
using option_map = std::unordered_map<option, boost::any, enum_hash<option>>;
|
||||
using credentials_map = std::unordered_map<sstring, sstring>;
|
||||
|
||||
/**
|
||||
* Resource id mappings, i.e. keyspace and/or column families.
|
||||
*/
|
||||
using resource_ids = std::set<data_resource>;
|
||||
|
||||
/**
|
||||
* Setup is called once upon system startup to initialize the IAuthenticator.
|
||||
*
|
||||
@@ -177,7 +172,7 @@ public:
|
||||
* @return Keyspaces, column families that will be unmodifiable by users; other resources.
|
||||
* @see resource_ids
|
||||
*/
|
||||
virtual resource_ids protected_resources() const = 0;
|
||||
virtual const resource_ids& protected_resources() const = 0;
|
||||
|
||||
class sasl_challenge {
|
||||
public:
|
||||
|
||||
104
auth/authorizer.cc
Normal file
104
auth/authorizer.cc
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "authorizer.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "default_authorizer.hh"
|
||||
#include "auth.hh"
|
||||
#include "db/config.hh"
|
||||
|
||||
const sstring auth::authorizer::ALLOW_ALL_AUTHORIZER_NAME("org.apache.cassandra.auth.AllowAllAuthorizer");
|
||||
|
||||
/**
|
||||
* Authenticator is assumed to be a fully state-less immutable object (note all the const).
|
||||
* We thus store a single instance globally, since it should be safe/ok.
|
||||
*/
|
||||
static std::unique_ptr<auth::authorizer> global_authorizer;
|
||||
|
||||
future<>
|
||||
auth::authorizer::setup(const sstring& type) {
|
||||
if (auth::auth::is_class_type(type, ALLOW_ALL_AUTHORIZER_NAME)) {
|
||||
class allow_all_authorizer : public authorizer {
|
||||
public:
|
||||
future<permission_set> authorize(::shared_ptr<authenticated_user>, data_resource) const override {
|
||||
return make_ready_future<permission_set>(permissions::ALL);
|
||||
}
|
||||
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
|
||||
throw exceptions::invalid_request_exception("GRANT operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override {
|
||||
throw exceptions::invalid_request_exception("REVOKE operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
future<std::vector<permission_details>> list(::shared_ptr<authenticated_user> performer, permission_set, optional<data_resource>, optional<sstring>) const override {
|
||||
throw exceptions::invalid_request_exception("LIST PERMISSIONS operation is not supported by AllowAllAuthorizer");
|
||||
}
|
||||
future<> revoke_all(sstring dropped_user) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
future<> revoke_all(data_resource) override {
|
||||
return make_ready_future();
|
||||
}
|
||||
const resource_ids& protected_resources() override {
|
||||
static const resource_ids ids;
|
||||
return ids;
|
||||
}
|
||||
future<> validate_configuration() const override {
|
||||
return make_ready_future();
|
||||
}
|
||||
};
|
||||
|
||||
global_authorizer = std::make_unique<allow_all_authorizer>();
|
||||
} else if (auth::auth::is_class_type(type, default_authorizer::DEFAULT_AUTHORIZER_NAME)) {
|
||||
auto da = std::make_unique<default_authorizer>();
|
||||
auto f = da->init();
|
||||
return f.then([da = std::move(da)]() mutable {
|
||||
global_authorizer = std::move(da);
|
||||
});
|
||||
} else {
|
||||
throw exceptions::configuration_exception("Invalid authorizer type: " + type);
|
||||
}
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
auth::authorizer& auth::authorizer::get() {
|
||||
assert(global_authorizer);
|
||||
return *global_authorizer;
|
||||
}
|
||||
171
auth/authorizer.hh
Normal file
171
auth/authorizer.hh
Normal file
@@ -0,0 +1,171 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <tuple>
|
||||
|
||||
#include <experimental/optional>
|
||||
#include <seastar/core/future.hh>
|
||||
#include <seastar/core/shared_ptr.hh>
|
||||
|
||||
#include "permission.hh"
|
||||
#include "data_resource.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class authenticated_user;
|
||||
|
||||
struct permission_details {
|
||||
sstring user;
|
||||
data_resource resource;
|
||||
permission_set permissions;
|
||||
|
||||
bool operator<(const permission_details& v) const {
|
||||
return std::tie(user, resource, permissions) < std::tie(v.user, v.resource, v.permissions);
|
||||
}
|
||||
};
|
||||
|
||||
using std::experimental::optional;
|
||||
|
||||
class authorizer {
|
||||
public:
|
||||
static const sstring ALLOW_ALL_AUTHORIZER_NAME;
|
||||
|
||||
virtual ~authorizer() {}
|
||||
|
||||
/**
|
||||
* The primary Authorizer method. Returns a set of permissions of a user on a resource.
|
||||
*
|
||||
* @param user Authenticated user requesting authorization.
|
||||
* @param resource Resource for which the authorization is being requested. @see DataResource.
|
||||
* @return Set of permissions of the user on the resource. Should never return empty. Use permission.NONE instead.
|
||||
*/
|
||||
virtual future<permission_set> authorize(::shared_ptr<authenticated_user>, data_resource) const = 0;
|
||||
|
||||
/**
|
||||
* Grants a set of permissions on a resource to a user.
|
||||
* The opposite of revoke().
|
||||
*
|
||||
* @param performer User who grants the permissions.
|
||||
* @param permissions Set of permissions to grant.
|
||||
* @param to Grantee of the permissions.
|
||||
* @param resource Resource on which to grant the permissions.
|
||||
*
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
virtual future<> grant(::shared_ptr<authenticated_user> performer, permission_set, data_resource, sstring to) = 0;
|
||||
|
||||
/**
|
||||
* Revokes a set of permissions on a resource from a user.
|
||||
* The opposite of grant().
|
||||
*
|
||||
* @param performer User who revokes the permissions.
|
||||
* @param permissions Set of permissions to revoke.
|
||||
* @param from Revokee of the permissions.
|
||||
* @param resource Resource on which to revoke the permissions.
|
||||
*
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
virtual future<> revoke(::shared_ptr<authenticated_user> performer, permission_set, data_resource, sstring from) = 0;
|
||||
|
||||
/**
|
||||
* Returns a list of permissions on a resource of a user.
|
||||
*
|
||||
* @param performer User who wants to see the permissions.
|
||||
* @param permissions Set of Permission values the user is interested in. The result should only include the matching ones.
|
||||
* @param resource The resource on which permissions are requested. Can be null, in which case permissions on all resources
|
||||
* should be returned.
|
||||
* @param of The user whose permissions are requested. Can be null, in which case permissions of every user should be returned.
|
||||
*
|
||||
* @return All of the matching permission that the requesting user is authorized to know about.
|
||||
*
|
||||
* @throws RequestValidationException
|
||||
* @throws RequestExecutionException
|
||||
*/
|
||||
virtual future<std::vector<permission_details>> list(::shared_ptr<authenticated_user> performer, permission_set, optional<data_resource>, optional<sstring>) const = 0;
|
||||
|
||||
/**
|
||||
* This method is called before deleting a user with DROP USER query so that a new user with the same
|
||||
* name wouldn't inherit permissions of the deleted user in the future.
|
||||
*
|
||||
* @param droppedUser The user to revoke all permissions from.
|
||||
*/
|
||||
virtual future<> revoke_all(sstring dropped_user) = 0;
|
||||
|
||||
/**
|
||||
* This method is called after a resource is removed (i.e. keyspace or a table is dropped).
|
||||
*
|
||||
* @param droppedResource The resource to revoke all permissions on.
|
||||
*/
|
||||
virtual future<> revoke_all(data_resource) = 0;
|
||||
|
||||
/**
|
||||
* Set of resources that should be made inaccessible to users and only accessible internally.
|
||||
*
|
||||
* @return Keyspaces, column families that will be unmodifiable by users; other resources.
|
||||
*/
|
||||
virtual const resource_ids& protected_resources() = 0;
|
||||
|
||||
/**
|
||||
* Validates configuration of IAuthorizer implementation (if configurable).
|
||||
*
|
||||
* @throws ConfigurationException when there is a configuration error.
|
||||
*/
|
||||
virtual future<> validate_configuration() const = 0;
|
||||
|
||||
/**
|
||||
* Setup is called once upon system startup to initialize the IAuthorizer.
|
||||
*
|
||||
* For example, use this method to create any required keyspaces/column families.
|
||||
*/
|
||||
static future<> setup(const sstring& type);
|
||||
|
||||
/**
|
||||
* Returns the system authorizer. Must have called setup before calling this.
|
||||
*/
|
||||
static authorizer& get();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -158,7 +158,15 @@ bool auth::data_resource::exists() const {
|
||||
}
|
||||
|
||||
sstring auth::data_resource::to_string() const {
|
||||
return name();
|
||||
switch (get_level()) {
|
||||
case level::ROOT:
|
||||
return "<all keyspaces>";
|
||||
case level::KEYSPACE:
|
||||
return sprint("<keyspace %s>", _ks);
|
||||
case level::COLUMN_FAMILY:
|
||||
default:
|
||||
return sprint("<table %s.%s>", _ks, _cf);
|
||||
}
|
||||
}
|
||||
|
||||
bool auth::data_resource::operator==(const data_resource& v) const {
|
||||
@@ -170,6 +178,6 @@ bool auth::data_resource::operator<(const data_resource& v) const {
|
||||
}
|
||||
|
||||
std::ostream& auth::operator<<(std::ostream& os, const data_resource& r) {
|
||||
return os << r.name();
|
||||
return os << r.to_string();
|
||||
}
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <iosfwd>
|
||||
#include <set>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
namespace auth {
|
||||
@@ -138,6 +139,11 @@ public:
|
||||
bool operator<(const data_resource&) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* Resource id mappings, i.e. keyspace and/or column families.
|
||||
*/
|
||||
using resource_ids = std::set<data_resource>;
|
||||
|
||||
std::ostream& operator<<(std::ostream&, const data_resource&);
|
||||
|
||||
}
|
||||
|
||||
240
auth/default_authorizer.cc
Normal file
240
auth/default_authorizer.cc
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <unistd.h>
|
||||
#include <crypt.h>
|
||||
#include <random>
|
||||
#include <chrono>
|
||||
|
||||
#include <seastar/core/reactor.hh>
|
||||
|
||||
#include "auth.hh"
|
||||
#include "default_authorizer.hh"
|
||||
#include "authenticated_user.hh"
|
||||
#include "permission.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "log.hh"
|
||||
|
||||
const sstring auth::default_authorizer::DEFAULT_AUTHORIZER_NAME(
|
||||
"org.apache.cassandra.auth.CassandraAuthorizer");
|
||||
|
||||
static const sstring USER_NAME = "username";
|
||||
static const sstring RESOURCE_NAME = "resource";
|
||||
static const sstring PERMISSIONS_NAME = "permissions";
|
||||
static const sstring PERMISSIONS_CF = "permissions";
|
||||
|
||||
static logging::logger logger("default_authorizer");
|
||||
|
||||
auth::default_authorizer::default_authorizer() {
|
||||
}
|
||||
auth::default_authorizer::~default_authorizer() {
|
||||
}
|
||||
|
||||
future<> auth::default_authorizer::init() {
|
||||
sstring create_table = sprint("CREATE TABLE %s.%s ("
|
||||
"%s text,"
|
||||
"%s text,"
|
||||
"%s set<text>,"
|
||||
"PRIMARY KEY(%s, %s)"
|
||||
") WITH gc_grace_seconds=%d", auth::auth::AUTH_KS,
|
||||
PERMISSIONS_CF, USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME,
|
||||
USER_NAME, RESOURCE_NAME, 90 * 24 * 60 * 60); // 3 months.
|
||||
|
||||
return auth::setup_table(PERMISSIONS_CF, create_table);
|
||||
}
|
||||
|
||||
|
||||
future<auth::permission_set> auth::default_authorizer::authorize(
|
||||
::shared_ptr<authenticated_user> user, data_resource resource) const {
|
||||
return user->is_super().then([this, user, resource = std::move(resource)](bool is_super) {
|
||||
if (is_super) {
|
||||
return make_ready_future<permission_set>(permissions::ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* TOOD: could create actual data type for permission (translating string<->perm),
|
||||
* but this seems overkill right now. We still must store strings so...
|
||||
*/
|
||||
auto& qp = cql3::get_local_query_processor();
|
||||
auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? AND %s = ?"
|
||||
, PERMISSIONS_NAME, auth::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME);
|
||||
return qp.process(query, db::consistency_level::LOCAL_ONE, {user->name(), resource.name() })
|
||||
.then_wrapped([=](future<::shared_ptr<cql3::untyped_result_set>> f) {
|
||||
try {
|
||||
auto res = f.get0();
|
||||
|
||||
if (res->empty() || !res->one().has(PERMISSIONS_NAME)) {
|
||||
return make_ready_future<permission_set>(permissions::NONE);
|
||||
}
|
||||
return make_ready_future<permission_set>(permissions::from_strings(res->one().get_set<sstring>(PERMISSIONS_NAME)));
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
logger.warn("CassandraAuthorizer failed to authorize {} for {}", user->name(), resource);
|
||||
return make_ready_future<permission_set>(permissions::NONE);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#include <boost/range.hpp>
|
||||
|
||||
future<> auth::default_authorizer::modify(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
data_resource resource, sstring user, sstring op) {
|
||||
// TODO: why does this not check super user?
|
||||
auto& qp = cql3::get_local_query_processor();
|
||||
auto query = sprint("UPDATE %s.%s SET %s = %s %s ? WHERE %s = ? AND %s = ?",
|
||||
auth::AUTH_KS, PERMISSIONS_CF, PERMISSIONS_NAME,
|
||||
PERMISSIONS_NAME, op, USER_NAME, RESOURCE_NAME);
|
||||
return qp.process(query, db::consistency_level::ONE, {
|
||||
permissions::to_strings(set), user, resource.name() }).discard_result();
|
||||
}
|
||||
|
||||
|
||||
future<> auth::default_authorizer::grant(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
data_resource resource, sstring to) {
|
||||
return modify(std::move(performer), std::move(set), std::move(resource), std::move(to), "+");
|
||||
}
|
||||
|
||||
future<> auth::default_authorizer::revoke(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
data_resource resource, sstring from) {
|
||||
return modify(std::move(performer), std::move(set), std::move(resource), std::move(from), "-");
|
||||
}
|
||||
|
||||
future<std::vector<auth::permission_details>> auth::default_authorizer::list(
|
||||
::shared_ptr<authenticated_user> performer, permission_set set,
|
||||
optional<data_resource> resource, optional<sstring> user) const {
|
||||
return performer->is_super().then([this, performer, set = std::move(set), resource = std::move(resource), user = std::move(user)](bool is_super) {
|
||||
if (!is_super && (!user || performer->name() != *user)) {
|
||||
throw exceptions::unauthorized_exception(sprint("You are not authorized to view %s's permissions", user ? *user : "everyone"));
|
||||
}
|
||||
|
||||
auto query = sprint("SELECT %s, %s, %s FROM %s.%s", USER_NAME, RESOURCE_NAME, PERMISSIONS_NAME, auth::AUTH_KS, PERMISSIONS_CF);
|
||||
auto& qp = cql3::get_local_query_processor();
|
||||
|
||||
// Oh, look, it is a case where it does not pay off to have
|
||||
// parameters to process in an initializer list.
|
||||
future<::shared_ptr<cql3::untyped_result_set>> f = make_ready_future<::shared_ptr<cql3::untyped_result_set>>();
|
||||
|
||||
if (resource && user) {
|
||||
query += sprint(" WHERE %s = ? AND %s = ?", USER_NAME, RESOURCE_NAME);
|
||||
f = qp.process(query, db::consistency_level::ONE, {*user, resource->name()});
|
||||
} else if (resource) {
|
||||
query += sprint(" WHERE %s = ? ALLOW FILTERING", RESOURCE_NAME);
|
||||
f = qp.process(query, db::consistency_level::ONE, {resource->name()});
|
||||
} else if (user) {
|
||||
query += sprint(" WHERE %s = ?", USER_NAME);
|
||||
f = qp.process(query, db::consistency_level::ONE, {*user});
|
||||
} else {
|
||||
f = qp.process(query, db::consistency_level::ONE, {});
|
||||
}
|
||||
|
||||
return f.then([set](::shared_ptr<cql3::untyped_result_set> res) {
|
||||
std::vector<permission_details> result;
|
||||
|
||||
for (auto& row : *res) {
|
||||
if (row.has(PERMISSIONS_NAME)) {
|
||||
auto username = row.get_as<sstring>(USER_NAME);
|
||||
auto resource = data_resource::from_name(row.get_as<sstring>(RESOURCE_NAME));
|
||||
auto ps = permissions::from_strings(row.get_set<sstring>(PERMISSIONS_NAME));
|
||||
ps = permission_set::from_mask(ps.mask() & set.mask());
|
||||
|
||||
result.emplace_back(permission_details {username, resource, ps});
|
||||
}
|
||||
}
|
||||
return make_ready_future<std::vector<permission_details>>(std::move(result));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<> auth::default_authorizer::revoke_all(sstring dropped_user) {
|
||||
auto& qp = cql3::get_local_query_processor();
|
||||
auto query = sprint("DELETE FROM %s.%s WHERE %s = ?", auth::AUTH_KS,
|
||||
PERMISSIONS_CF, USER_NAME);
|
||||
return qp.process(query, db::consistency_level::ONE, { dropped_user }).discard_result().handle_exception(
|
||||
[dropped_user](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions of {}: {}", dropped_user, e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
future<> auth::default_authorizer::revoke_all(data_resource resource) {
|
||||
auto& qp = cql3::get_local_query_processor();
|
||||
auto query = sprint("SELECT %s FROM %s.%s WHERE %s = ? ALLOW FILTERING",
|
||||
USER_NAME, auth::AUTH_KS, PERMISSIONS_CF, RESOURCE_NAME);
|
||||
return qp.process(query, db::consistency_level::LOCAL_ONE, { resource.name() })
|
||||
.then_wrapped([resource, &qp](future<::shared_ptr<cql3::untyped_result_set>> f) {
|
||||
try {
|
||||
auto res = f.get0();
|
||||
return parallel_for_each(res->begin(), res->end(), [&qp, res, resource](const cql3::untyped_result_set::row& r) {
|
||||
auto query = sprint("DELETE FROM %s.%s WHERE %s = ? AND %s = ?"
|
||||
, auth::AUTH_KS, PERMISSIONS_CF, USER_NAME, RESOURCE_NAME);
|
||||
return qp.process(query, db::consistency_level::LOCAL_ONE, { r.get_as<sstring>(USER_NAME), resource.name() })
|
||||
.discard_result().handle_exception([resource](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", resource, e);
|
||||
}
|
||||
|
||||
});
|
||||
});
|
||||
} catch (exceptions::request_execution_exception& e) {
|
||||
logger.warn("CassandraAuthorizer failed to revoke all permissions on {}: {}", resource, e);
|
||||
return make_ready_future();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const auth::resource_ids& auth::default_authorizer::protected_resources() {
|
||||
static const resource_ids ids({ data_resource(auth::AUTH_KS, PERMISSIONS_CF) });
|
||||
return ids;
|
||||
}
|
||||
|
||||
future<> auth::default_authorizer::validate_configuration() const {
|
||||
return make_ready_future();
|
||||
}
|
||||
78
auth/default_authorizer.hh
Normal file
78
auth/default_authorizer.hh
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "authorizer.hh"
|
||||
|
||||
namespace auth {
|
||||
|
||||
class default_authorizer : public authorizer {
|
||||
public:
|
||||
static const sstring DEFAULT_AUTHORIZER_NAME;
|
||||
|
||||
default_authorizer();
|
||||
~default_authorizer();
|
||||
|
||||
future<> init();
|
||||
|
||||
future<permission_set> authorize(::shared_ptr<authenticated_user>, data_resource) const override;
|
||||
|
||||
future<> grant(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override;
|
||||
|
||||
future<> revoke(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring) override;
|
||||
|
||||
future<std::vector<permission_details>> list(::shared_ptr<authenticated_user>, permission_set, optional<data_resource>, optional<sstring>) const override;
|
||||
|
||||
future<> revoke_all(sstring) override;
|
||||
|
||||
future<> revoke_all(data_resource) override;
|
||||
|
||||
const resource_ids& protected_resources() override;
|
||||
|
||||
future<> validate_configuration() const override;
|
||||
|
||||
private:
|
||||
future<> modify(::shared_ptr<authenticated_user>, permission_set, data_resource, sstring, sstring);
|
||||
};
|
||||
|
||||
} /* namespace auth */
|
||||
|
||||
@@ -281,8 +281,9 @@ future<> auth::password_authenticator::drop(sstring username)
|
||||
}
|
||||
}
|
||||
|
||||
auth::authenticator::resource_ids auth::password_authenticator::protected_resources() const {
|
||||
return { data_resource(auth::AUTH_KS, CREDENTIALS_CF) };
|
||||
const auth::resource_ids& auth::password_authenticator::protected_resources() const {
|
||||
static const resource_ids ids({ data_resource(auth::AUTH_KS, CREDENTIALS_CF) });
|
||||
return ids;
|
||||
}
|
||||
|
||||
::shared_ptr<auth::authenticator::sasl_challenge> auth::password_authenticator::new_sasl_challenge() const {
|
||||
|
||||
@@ -62,7 +62,7 @@ public:
|
||||
future<> create(sstring username, const option_map& options) throw(exceptions::request_validation_exception, exceptions::request_execution_exception) override;
|
||||
future<> alter(sstring username, const option_map& options) throw(exceptions::request_validation_exception, exceptions::request_execution_exception) override;
|
||||
future<> drop(sstring username) throw(exceptions::request_validation_exception, exceptions::request_execution_exception) override;
|
||||
resource_ids protected_resources() const override;
|
||||
const resource_ids& protected_resources() const override;
|
||||
::shared_ptr<sasl_challenge> new_sasl_challenge() const override;
|
||||
|
||||
|
||||
|
||||
@@ -39,11 +39,63 @@
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <unordered_map>
|
||||
#include "permission.hh"
|
||||
|
||||
const auth::permission_set auth::ALL_DATA = auth::permission_set::of
|
||||
< auth::permission::CREATE, auth::permission::ALTER,
|
||||
auth::permission::DROP, auth::permission::SELECT,
|
||||
auth::permission::MODIFY, auth::permission::AUTHORIZE>();
|
||||
const auth::permission_set auth::ALL = auth::ALL_DATA;
|
||||
const auth::permission_set auth::NONE;
|
||||
const auth::permission_set auth::permissions::ALL_DATA =
|
||||
auth::permission_set::of<auth::permission::CREATE,
|
||||
auth::permission::ALTER, auth::permission::DROP,
|
||||
auth::permission::SELECT,
|
||||
auth::permission::MODIFY,
|
||||
auth::permission::AUTHORIZE>();
|
||||
const auth::permission_set auth::permissions::ALL = auth::permissions::ALL_DATA;
|
||||
const auth::permission_set auth::permissions::NONE;
|
||||
const auth::permission_set auth::permissions::ALTERATIONS =
|
||||
auth::permission_set::of<auth::permission::CREATE,
|
||||
auth::permission::ALTER, auth::permission::DROP>();
|
||||
|
||||
static const std::unordered_map<sstring, auth::permission> permission_names({
|
||||
{ "READ", auth::permission::READ },
|
||||
{ "WRITE", auth::permission::WRITE },
|
||||
{ "CREATE", auth::permission::CREATE },
|
||||
{ "ALTER", auth::permission::ALTER },
|
||||
{ "DROP", auth::permission::DROP },
|
||||
{ "SELECT", auth::permission::SELECT },
|
||||
{ "MODIFY", auth::permission::MODIFY },
|
||||
{ "AUTHORIZE", auth::permission::AUTHORIZE },
|
||||
});
|
||||
|
||||
const sstring& auth::permissions::to_string(permission p) {
|
||||
for (auto& v : permission_names) {
|
||||
if (v.second == p) {
|
||||
return v.first;
|
||||
}
|
||||
}
|
||||
throw std::out_of_range("unknown permission");
|
||||
}
|
||||
|
||||
auth::permission auth::permissions::from_string(const sstring& s) {
|
||||
return permission_names.at(s);
|
||||
}
|
||||
|
||||
std::unordered_set<sstring> auth::permissions::to_strings(const permission_set& set) {
|
||||
std::unordered_set<sstring> res;
|
||||
for (auto& v : permission_names) {
|
||||
if (set.contains(v.second)) {
|
||||
res.emplace(v.first);
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
auth::permission_set auth::permissions::from_strings(const std::unordered_set<sstring>& set) {
|
||||
permission_set res = auth::permissions::NONE;
|
||||
for (auto& s : set) {
|
||||
res.set(from_string(s));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool auth::operator<(const permission_set& p1, const permission_set& p2) {
|
||||
return p1.mask() < p2.mask();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,9 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <unordered_set>
|
||||
#include <seastar/core/sstring.hh>
|
||||
|
||||
#include "enum_set.hh"
|
||||
|
||||
namespace auth {
|
||||
@@ -74,8 +77,22 @@ typedef enum_set<super_enum<permission,
|
||||
permission::MODIFY,
|
||||
permission::AUTHORIZE>> permission_set;
|
||||
|
||||
bool operator<(const permission_set&, const permission_set&);
|
||||
|
||||
namespace permissions {
|
||||
|
||||
extern const permission_set ALL_DATA;
|
||||
extern const permission_set ALL;
|
||||
extern const permission_set NONE;
|
||||
extern const permission_set ALTERATIONS;
|
||||
|
||||
const sstring& to_string(permission);
|
||||
permission from_string(const sstring&);
|
||||
|
||||
std::unordered_set<sstring> to_strings(const permission_set&);
|
||||
permission_set from_strings(const std::unordered_set<sstring>&);
|
||||
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -192,6 +192,24 @@ api_address: 127.0.0.1
|
||||
# Caution should be taken on increasing the size of this threshold as it can lead to node instability.
|
||||
batch_size_warn_threshold_in_kb: 5
|
||||
|
||||
# Authentication backend, identifying users
|
||||
# Out of the box, Scylla provides org.apache.cassandra.auth.{AllowAllAuthenticator,
|
||||
# PasswordAuthenticator}.
|
||||
#
|
||||
# - AllowAllAuthenticator performs no checks - set it to disable authentication.
|
||||
# - PasswordAuthenticator relies on username/password pairs to authenticate
|
||||
# users. It keeps usernames and hashed passwords in system_auth.credentials table.
|
||||
# Please increase system_auth keyspace replication factor if you use this authenticator.
|
||||
# authenticator: AllowAllAuthenticator
|
||||
|
||||
# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions
|
||||
# Out of the box, Scylla provides org.apache.cassandra.auth.{AllowAllAuthorizer,
|
||||
# CassandraAuthorizer}.
|
||||
#
|
||||
# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.
|
||||
# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please
|
||||
# increase system_auth keyspace replication factor if you use this authorizer.
|
||||
# authorizer: AllowAllAuthorizer
|
||||
|
||||
###################################################
|
||||
## Not currently supported, reserved for future use
|
||||
@@ -229,25 +247,6 @@ batch_size_warn_threshold_in_kb: 5
|
||||
# reduced proportionally to the number of nodes in the cluster.
|
||||
# batchlog_replay_throttle_in_kb: 1024
|
||||
|
||||
# Authentication backend, identifying users
|
||||
# Out of the box, Scylla provides org.apache.cassandra.auth.{AllowAllAuthenticator,
|
||||
# PasswordAuthenticator}.
|
||||
#
|
||||
# - AllowAllAuthenticator performs no checks - set it to disable authentication.
|
||||
# - PasswordAuthenticator relies on username/password pairs to authenticate
|
||||
# users. It keeps usernames and hashed passwords in system_auth.credentials table.
|
||||
# Please increase system_auth keyspace replication factor if you use this authenticator.
|
||||
# authenticator: AllowAllAuthenticator
|
||||
|
||||
# Authorization backend, implementing IAuthorizer; used to limit access/provide permissions
|
||||
# Out of the box, Scylla provides org.apache.cassandra.auth.{AllowAllAuthorizer,
|
||||
# CassandraAuthorizer}.
|
||||
#
|
||||
# - AllowAllAuthorizer allows any action to any user - set it to disable authorization.
|
||||
# - CassandraAuthorizer stores permissions in system_auth.permissions table. Please
|
||||
# increase system_auth keyspace replication factor if you use this authorizer.
|
||||
# authorizer: AllowAllAuthorizer
|
||||
|
||||
# Validity period for permissions cache (fetching permissions can be an
|
||||
# expensive operation depending on the authorizer, CassandraAuthorizer is
|
||||
# one example). Defaults to 2000, set to 0 to disable.
|
||||
|
||||
@@ -341,6 +341,11 @@ scylla_core = (['database.cc',
|
||||
'cql3/statements/alter_user_statement.cc',
|
||||
'cql3/statements/drop_user_statement.cc',
|
||||
'cql3/statements/list_users_statement.cc',
|
||||
'cql3/statements/authorization_statement.cc',
|
||||
'cql3/statements/permission_altering_statement.cc',
|
||||
'cql3/statements/list_permissions_statement.cc',
|
||||
'cql3/statements/grant_statement.cc',
|
||||
'cql3/statements/revoke_statement.cc',
|
||||
'cql3/update_parameters.cc',
|
||||
'cql3/ut_name.cc',
|
||||
'cql3/user_options.cc',
|
||||
@@ -455,6 +460,8 @@ scylla_core = (['database.cc',
|
||||
'auth/auth.cc',
|
||||
'auth/authenticated_user.cc',
|
||||
'auth/authenticator.cc',
|
||||
'auth/authorizer.cc',
|
||||
'auth/default_authorizer.cc',
|
||||
'auth/data_resource.cc',
|
||||
'auth/password_authenticator.cc',
|
||||
'auth/permission.cc',
|
||||
|
||||
50
cql3/Cql.g
50
cql3/Cql.g
@@ -51,6 +51,9 @@ options {
|
||||
#include "cql3/statements/alter_user_statement.hh"
|
||||
#include "cql3/statements/drop_user_statement.hh"
|
||||
#include "cql3/statements/list_users_statement.hh"
|
||||
#include "cql3/statements/grant_statement.hh"
|
||||
#include "cql3/statements/revoke_statement.hh"
|
||||
#include "cql3/statements/list_permissions_statement.hh"
|
||||
#include "cql3/statements/index_target.hh"
|
||||
#include "cql3/statements/ks_prop_defs.hh"
|
||||
#include "cql3/selection/raw_selector.hh"
|
||||
@@ -313,10 +316,10 @@ cqlStatement returns [shared_ptr<parsed_statement> stmt]
|
||||
| st14=alterTableStatement { $stmt = st14; }
|
||||
#if 0
|
||||
| st15=alterKeyspaceStatement { $stmt = st15; }
|
||||
#endif
|
||||
| st16=grantStatement { $stmt = st16; }
|
||||
| st17=revokeStatement { $stmt = st17; }
|
||||
| st18=listPermissionsStatement { $stmt = st18; }
|
||||
#endif
|
||||
| st19=createUserStatement { $stmt = st19; }
|
||||
| st20=alterUserStatement { $stmt = st20; }
|
||||
| st21=dropUserStatement { $stmt = st21; }
|
||||
@@ -905,70 +908,67 @@ truncateStatement returns [::shared_ptr<truncate_statement> stmt]
|
||||
: K_TRUNCATE (K_COLUMNFAMILY)? cf=columnFamilyName { $stmt = ::make_shared<truncate_statement>(cf); }
|
||||
;
|
||||
|
||||
#if 0
|
||||
/**
|
||||
* GRANT <permission> ON <resource> TO <username>
|
||||
*/
|
||||
grantStatement returns [GrantStatement stmt]
|
||||
grantStatement returns [::shared_ptr<grant_statement> stmt]
|
||||
: K_GRANT
|
||||
permissionOrAll
|
||||
K_ON
|
||||
resource
|
||||
K_TO
|
||||
username
|
||||
{ $stmt = new GrantStatement($permissionOrAll.perms, $resource.res, $username.text); }
|
||||
{ $stmt = ::make_shared<grant_statement>($permissionOrAll.perms, $resource.res, $username.text); }
|
||||
;
|
||||
|
||||
/**
|
||||
* REVOKE <permission> ON <resource> FROM <username>
|
||||
*/
|
||||
revokeStatement returns [RevokeStatement stmt]
|
||||
revokeStatement returns [::shared_ptr<revoke_statement> stmt]
|
||||
: K_REVOKE
|
||||
permissionOrAll
|
||||
K_ON
|
||||
resource
|
||||
K_FROM
|
||||
username
|
||||
{ $stmt = new RevokeStatement($permissionOrAll.perms, $resource.res, $username.text); }
|
||||
{ $stmt = ::make_shared<revoke_statement>($permissionOrAll.perms, $resource.res, $username.text); }
|
||||
;
|
||||
|
||||
listPermissionsStatement returns [ListPermissionsStatement stmt]
|
||||
listPermissionsStatement returns [::shared_ptr<list_permissions_statement> stmt]
|
||||
@init {
|
||||
IResource resource = null;
|
||||
String username = null;
|
||||
boolean recursive = true;
|
||||
std::experimental::optional<auth::data_resource> r;
|
||||
std::experimental::optional<sstring> u;
|
||||
bool recursive = true;
|
||||
}
|
||||
: K_LIST
|
||||
permissionOrAll
|
||||
( K_ON resource { resource = $resource.res; } )?
|
||||
( K_OF username { username = $username.text; } )?
|
||||
( K_ON resource { r = $resource.res; } )?
|
||||
( K_OF username { u = sstring($username.text); } )?
|
||||
( K_NORECURSIVE { recursive = false; } )?
|
||||
{ $stmt = new ListPermissionsStatement($permissionOrAll.perms, resource, username, recursive); }
|
||||
{ $stmt = ::make_shared<list_permissions_statement>($permissionOrAll.perms, std::move(r), std::move(u), recursive); }
|
||||
;
|
||||
|
||||
permission returns [Permission perm]
|
||||
permission returns [auth::permission perm]
|
||||
: p=(K_CREATE | K_ALTER | K_DROP | K_SELECT | K_MODIFY | K_AUTHORIZE)
|
||||
{ $perm = Permission.valueOf($p.text.toUpperCase()); }
|
||||
{ $perm = auth::permissions::from_string($p.text); }
|
||||
;
|
||||
|
||||
permissionOrAll returns [Set<Permission> perms]
|
||||
: K_ALL ( K_PERMISSIONS )? { $perms = Permission.ALL_DATA; }
|
||||
| p=permission ( K_PERMISSION )? { $perms = EnumSet.of($p.perm); }
|
||||
permissionOrAll returns [auth::permission_set perms]
|
||||
: K_ALL ( K_PERMISSIONS )? { $perms = auth::permissions::ALL_DATA; }
|
||||
| p=permission ( K_PERMISSION )? { $perms = auth::permission_set::from_mask(auth::permission_set::mask_for($p.perm)); }
|
||||
;
|
||||
|
||||
resource returns [IResource res]
|
||||
resource returns [auth::data_resource res]
|
||||
: r=dataResource { $res = $r.res; }
|
||||
;
|
||||
|
||||
dataResource returns [DataResource res]
|
||||
: K_ALL K_KEYSPACES { $res = DataResource.root(); }
|
||||
| K_KEYSPACE ks = keyspaceName { $res = DataResource.keyspace($ks.id); }
|
||||
dataResource returns [auth::data_resource res]
|
||||
: K_ALL K_KEYSPACES { $res = auth::data_resource(); }
|
||||
| K_KEYSPACE ks = keyspaceName { $res = auth::data_resource($ks.id); }
|
||||
| ( K_COLUMNFAMILY )? cf = columnFamilyName
|
||||
{ $res = DataResource.columnFamily($cf.name.getKeyspace(), $cf.name.getColumnFamily()); }
|
||||
{ $res = auth::data_resource($cf.name->get_keyspace(), $cf.name->get_column_family()); }
|
||||
;
|
||||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* CREATE USER [IF NOT EXISTS] <username> [WITH PASSWORD <password>] [SUPERUSER|NOSUPERUSER]
|
||||
*/
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
*
|
||||
* @param state the current client state
|
||||
*/
|
||||
virtual void check_access(const service::client_state& state) = 0;
|
||||
virtual future<> check_access(const service::client_state& state) = 0;
|
||||
|
||||
/**
|
||||
* Perform additional validation required by the statment.
|
||||
|
||||
@@ -132,23 +132,26 @@ query_processor::process_statement(::shared_ptr<cql_statement> statement, servic
|
||||
#if 0
|
||||
logger.trace("Process {} @CL.{}", statement, options.getConsistency());
|
||||
#endif
|
||||
auto& client_state = query_state.get_client_state();
|
||||
statement->check_access(client_state);
|
||||
statement->validate(_proxy, client_state);
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> fut = make_ready_future<::shared_ptr<transport::messages::result_message>>();
|
||||
if (query_state.get_client_state()._is_internal) {
|
||||
fut = statement->execute_internal(_proxy, query_state, options);
|
||||
} else {
|
||||
fut = statement->execute(_proxy, query_state, options);
|
||||
}
|
||||
return statement->check_access(query_state.get_client_state()).then([this, statement, &query_state, &options]() {
|
||||
auto& client_state = query_state.get_client_state();
|
||||
|
||||
return fut.then([statement] (auto msg) {
|
||||
if (msg) {
|
||||
return make_ready_future<::shared_ptr<result_message>>(std::move(msg));
|
||||
statement->validate(_proxy, client_state);
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> fut = make_ready_future<::shared_ptr<transport::messages::result_message>>();
|
||||
if (client_state._is_internal) {
|
||||
fut = statement->execute_internal(_proxy, query_state, options);
|
||||
} else {
|
||||
fut = statement->execute(_proxy, query_state, options);
|
||||
}
|
||||
return make_ready_future<::shared_ptr<result_message>>(
|
||||
::make_shared<result_message::void_message>());
|
||||
|
||||
return fut.then([statement] (auto msg) {
|
||||
if (msg) {
|
||||
return make_ready_future<::shared_ptr<result_message>>(std::move(msg));
|
||||
}
|
||||
return make_ready_future<::shared_ptr<result_message>>(
|
||||
::make_shared<result_message::void_message>());
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -383,11 +386,11 @@ future<::shared_ptr<untyped_result_set>> query_processor::process(
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
query_processor::process_batch(::shared_ptr<statements::batch_statement> batch, service::query_state& query_state, query_options& options) {
|
||||
auto& client_state = query_state.get_client_state();
|
||||
batch->check_access(client_state);
|
||||
batch->validate();
|
||||
batch->validate(_proxy, client_state);
|
||||
return batch->execute(_proxy, query_state, options);
|
||||
return batch->check_access(query_state.get_client_state()).then([this, &query_state, &options, batch] {
|
||||
batch->validate();
|
||||
batch->validate(_proxy, query_state.get_client_state());
|
||||
return batch->execute(_proxy, query_state, options);
|
||||
});
|
||||
}
|
||||
|
||||
query_processor::migration_subscriber::migration_subscriber(query_processor* qp)
|
||||
|
||||
@@ -65,12 +65,8 @@ alter_table_statement::alter_table_statement(shared_ptr<cf_name> name,
|
||||
{
|
||||
}
|
||||
|
||||
void alter_table_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
warn(unimplemented::cause::PERMISSIONS);
|
||||
#if 0
|
||||
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.ALTER);
|
||||
#endif
|
||||
future<> alter_table_statement::check_access(const service::client_state& state) {
|
||||
return state.has_column_family_access(keyspace(), column_family(), auth::permission::ALTER);
|
||||
}
|
||||
|
||||
void alter_table_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state)
|
||||
@@ -279,4 +275,4 @@ shared_ptr<transport::event::schema_change> alter_table_statement::change_event(
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public:
|
||||
renames_type renames,
|
||||
bool is_static);
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
virtual void validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) override;
|
||||
virtual future<bool> announce_migration(distributed<service::storage_proxy>& proxy, bool is_local_only) override;
|
||||
virtual shared_ptr<transport::event::schema_change> change_event() override;
|
||||
@@ -84,4 +84,4 @@ public:
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,18 +65,14 @@ void cql3::statements::alter_user_statement::validate(distributed<service::stora
|
||||
// we need to query -> continuation, and this is not a continuation method
|
||||
}
|
||||
|
||||
void cql3::statements::alter_user_statement::check_access(const service::client_state& state) {
|
||||
if (_superuser && state.user()->name() == _username) {
|
||||
future<> cql3::statements::alter_user_statement::check_access(const service::client_state& state) {
|
||||
auto user = state.user();
|
||||
if (_superuser && user->name() == _username) {
|
||||
// using contractions in error messages is the ultimate sign of lowbrowness.
|
||||
// however, dtests depend on matching the exception messages. So we keep them despite
|
||||
// my disgust.
|
||||
throw exceptions::unauthorized_exception("You aren't allowed to alter your own superuser status");
|
||||
}
|
||||
}
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
cql3::statements::alter_user_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
auto user = state.get_client_state().user();
|
||||
return user->is_super().then([this, user](bool is_super) {
|
||||
if (_superuser && !is_super) {
|
||||
throw exceptions::unauthorized_exception("Only superusers are allowed to alter superuser status");
|
||||
@@ -93,18 +89,22 @@ cql3::statements::alter_user_statement::execute(distributed<service::storage_pro
|
||||
}
|
||||
}
|
||||
}
|
||||
return auth::auth::is_existing_user(_username).then([this](bool exists) {
|
||||
if (!exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username));
|
||||
}
|
||||
auto f = _opts->options().empty() ? make_ready_future() : auth::authenticator::get().alter(_username, _opts->options());
|
||||
if (_superuser) {
|
||||
f = f.then([this] {
|
||||
return auth::auth::insert_user(_username, *_superuser);
|
||||
});
|
||||
}
|
||||
return f.then([] { return make_ready_future<::shared_ptr<transport::messages::result_message>>(); });
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
cql3::statements::alter_user_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
return auth::auth::is_existing_user(_username).then([this](bool exists) {
|
||||
if (!exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username));
|
||||
}
|
||||
auto f = _opts->options().empty() ? make_ready_future() : auth::authenticator::get().alter(_username, _opts->options());
|
||||
if (_superuser) {
|
||||
f = f.then([this] {
|
||||
return auth::auth::insert_user(_username, *_superuser);
|
||||
});
|
||||
}
|
||||
return f.then([] { return make_ready_future<::shared_ptr<transport::messages::result_message>>(); });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -60,7 +60,7 @@ public:
|
||||
alter_user_statement(sstring, ::shared_ptr<user_options>, std::experimental::optional<bool> superuser = {});
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
void check_access(const service::client_state&) override;
|
||||
future<> check_access(const service::client_state&) override;
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
|
||||
@@ -71,7 +71,8 @@ void cql3::statements::authentication_statement::validate(
|
||||
const service::client_state& state) {
|
||||
}
|
||||
|
||||
void cql3::statements::authentication_statement::check_access(const service::client_state& state) {
|
||||
future<> cql3::statements::authentication_statement::check_access(const service::client_state& state) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> cql3::statements::authentication_statement::execute_internal(
|
||||
|
||||
@@ -61,7 +61,7 @@ public:
|
||||
|
||||
bool depends_on_column_family(const sstring& cf_name) const override;
|
||||
|
||||
void check_access(const service::client_state& state) override;
|
||||
future<> check_access(const service::client_state& state) override;
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
90
cql3/statements/authorization_statement.cc
Normal file
90
cql3/statements/authorization_statement.cc
Normal file
@@ -0,0 +1,90 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "authorization_statement.hh"
|
||||
#include "transport/messages/result_message.hh"
|
||||
|
||||
uint32_t cql3::statements::authorization_statement::get_bound_terms() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
::shared_ptr<cql3::statements::parsed_statement::prepared> cql3::statements::authorization_statement::prepare(
|
||||
database& db) {
|
||||
return ::make_shared<parsed_statement::prepared>(this->shared_from_this());
|
||||
}
|
||||
|
||||
bool cql3::statements::authorization_statement::uses_function(
|
||||
const sstring& ks_name, const sstring& function_name) const {
|
||||
return parsed_statement::uses_function(ks_name, function_name);
|
||||
}
|
||||
|
||||
bool cql3::statements::authorization_statement::depends_on_keyspace(
|
||||
const sstring& ks_name) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
bool cql3::statements::authorization_statement::depends_on_column_family(
|
||||
const sstring& cf_name) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
void cql3::statements::authorization_statement::validate(
|
||||
distributed<service::storage_proxy>&,
|
||||
const service::client_state& state) {
|
||||
}
|
||||
|
||||
future<> cql3::statements::authorization_statement::check_access(const service::client_state& state) {
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> cql3::statements::authorization_statement::execute_internal(
|
||||
distributed<service::storage_proxy>& proxy,
|
||||
service::query_state& state, const query_options& options) {
|
||||
// Internal queries are exclusively on the system keyspace and makes no sense here
|
||||
throw std::runtime_error("unsupported operation");
|
||||
}
|
||||
|
||||
void cql3::statements::authorization_statement::mayme_correct_resource(auth::data_resource& resource, const service::client_state& state) {
|
||||
if (resource.is_column_family_level() && resource.keyspace().empty()) {
|
||||
resource = auth::data_resource(state.get_keyspace(), resource.column_family());
|
||||
}
|
||||
}
|
||||
|
||||
81
cql3/statements/authorization_statement.hh
Normal file
81
cql3/statements/authorization_statement.hh
Normal file
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "parsed_statement.hh"
|
||||
#include "cql3/cql_statement.hh"
|
||||
#include "transport/messages_fwd.hh"
|
||||
|
||||
namespace auth {
|
||||
class data_resource;
|
||||
}
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class authorization_statement : public parsed_statement, public cql_statement, public ::enable_shared_from_this<authorization_statement> {
|
||||
public:
|
||||
uint32_t get_bound_terms() override;
|
||||
|
||||
::shared_ptr<prepared> prepare(database& db) override;
|
||||
|
||||
bool uses_function(const sstring& ks_name, const sstring& function_name) const override;
|
||||
|
||||
bool depends_on_keyspace(const sstring& ks_name) const override;
|
||||
|
||||
bool depends_on_column_family(const sstring& cf_name) const override;
|
||||
|
||||
future<> check_access(const service::client_state& state) override;
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
execute_internal(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) override;
|
||||
|
||||
protected:
|
||||
static void mayme_correct_resource(auth::data_resource&, const service::client_state&);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -104,10 +104,10 @@ public:
|
||||
return _bound_terms;
|
||||
}
|
||||
|
||||
virtual void check_access(const service::client_state& state) override {
|
||||
for (auto&& s : _statements) {
|
||||
s->check_access(state);
|
||||
}
|
||||
virtual future<> check_access(const service::client_state& state) override {
|
||||
return parallel_for_each(_statements.begin(), _statements.end(), [&state](auto&& s) {
|
||||
return s->check_access(state);
|
||||
});
|
||||
}
|
||||
|
||||
// Validates a prepared batch statement without validating its nested statements.
|
||||
|
||||
@@ -55,11 +55,9 @@ cql3::statements::create_index_statement::create_index_statement(
|
||||
if_not_exists) {
|
||||
}
|
||||
|
||||
void
|
||||
future<>
|
||||
cql3::statements::create_index_statement::check_access(const service::client_state& state) {
|
||||
warn(unimplemented::cause::MIGRATIONS);
|
||||
// TODO
|
||||
//state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.ALTER);
|
||||
return state.has_column_family_access(keyspace(), column_family(), auth::permission::ALTER);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -77,7 +77,7 @@ public:
|
||||
::shared_ptr<index_target::raw> raw_target,
|
||||
::shared_ptr<index_prop_defs> properties, bool if_not_exists);
|
||||
|
||||
void check_access(const service::client_state& state) override;
|
||||
future<> check_access(const service::client_state& state) override;
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
future<bool> announce_migration(distributed<service::storage_proxy>&, bool is_local_only) override;
|
||||
|
||||
|
||||
@@ -61,12 +61,9 @@ const sstring& create_keyspace_statement::keyspace() const
|
||||
return _name;
|
||||
}
|
||||
|
||||
void create_keyspace_statement::check_access(const service::client_state& state)
|
||||
future<> create_keyspace_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
warn(unimplemented::cause::PERMISSIONS);
|
||||
#if 0
|
||||
state.hasAllKeyspacesAccess(Permission.CREATE);
|
||||
#endif
|
||||
return state.has_all_keyspaces_access(auth::permission::CREATE);
|
||||
}
|
||||
|
||||
void create_keyspace_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state)
|
||||
|
||||
@@ -70,7 +70,7 @@ public:
|
||||
|
||||
virtual const sstring& keyspace() const override;
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
/**
|
||||
* The <code>CqlParser</code> only goes as far as extracting the keyword arguments
|
||||
|
||||
@@ -71,11 +71,8 @@ create_table_statement::create_table_statement(::shared_ptr<cf_name> name,
|
||||
}
|
||||
}
|
||||
|
||||
void create_table_statement::check_access(const service::client_state& state) {
|
||||
warn(unimplemented::cause::PERMISSIONS);
|
||||
#if 0
|
||||
state.hasKeyspaceAccess(keyspace(), Permission.CREATE);
|
||||
#endif
|
||||
future<> create_table_statement::check_access(const service::client_state& state) {
|
||||
return state.has_keyspace_access(keyspace(), auth::permission::CREATE);
|
||||
}
|
||||
|
||||
void create_table_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state) {
|
||||
|
||||
@@ -95,7 +95,7 @@ public:
|
||||
bool if_not_exists,
|
||||
column_set_type static_columns);
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
@@ -62,12 +62,9 @@ void create_type_statement::add_definition(::shared_ptr<column_identifier> name,
|
||||
_column_types.emplace_back(type);
|
||||
}
|
||||
|
||||
void create_type_statement::check_access(const service::client_state& state)
|
||||
future<> create_type_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
warn(unimplemented::cause::PERMISSIONS);
|
||||
#if 0
|
||||
state.hasKeyspaceAccess(keyspace(), Permission.CREATE);
|
||||
#endif
|
||||
return state.has_keyspace_access(keyspace(), auth::permission::CREATE);
|
||||
}
|
||||
|
||||
void create_type_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state)
|
||||
|
||||
@@ -59,7 +59,7 @@ public:
|
||||
|
||||
void add_definition(::shared_ptr<column_identifier> name, ::shared_ptr<cql3_type::raw> type);
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
@@ -54,12 +54,9 @@ drop_keyspace_statement::drop_keyspace_statement(const sstring& keyspace, bool i
|
||||
{
|
||||
}
|
||||
|
||||
void drop_keyspace_statement::check_access(const service::client_state& state)
|
||||
future<> drop_keyspace_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
warn(unimplemented::cause::AUTH);
|
||||
#if 0
|
||||
state.hasKeyspaceAccess(keyspace, Permission.DROP);
|
||||
#endif
|
||||
return state.has_keyspace_access(keyspace(), auth::permission::DROP);
|
||||
}
|
||||
|
||||
void drop_keyspace_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state)
|
||||
|
||||
@@ -53,7 +53,7 @@ class drop_keyspace_statement : public schema_altering_statement {
|
||||
public:
|
||||
drop_keyspace_statement(const sstring& keyspace, bool if_exists);
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
@@ -53,21 +53,18 @@ drop_table_statement::drop_table_statement(::shared_ptr<cf_name> cf_name, bool i
|
||||
{
|
||||
}
|
||||
|
||||
void drop_table_statement::check_access(const service::client_state& state)
|
||||
future<> drop_table_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
warn(unimplemented::cause::AUTH);
|
||||
#if 0
|
||||
try
|
||||
{
|
||||
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.DROP);
|
||||
}
|
||||
catch (InvalidRequestException e)
|
||||
{
|
||||
if (!ifExists)
|
||||
throw e;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return state.has_column_family_access(keyspace(), column_family(), auth::permission::DROP).handle_exception([this](auto ep) {
|
||||
try {
|
||||
std::rethrow_exception(ep);
|
||||
} catch (exceptions::invalid_request_exception&) {
|
||||
if (!_if_exists) {
|
||||
throw;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void drop_table_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state)
|
||||
{
|
||||
|
||||
@@ -54,7 +54,7 @@ class drop_table_statement : public schema_altering_statement {
|
||||
public:
|
||||
drop_table_statement(::shared_ptr<cf_name> cf_name, bool if_exists);
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
#include "drop_user_statement.hh"
|
||||
#include "auth/auth.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
|
||||
cql3::statements::drop_user_statement::drop_user_statement(sstring username, bool if_exists)
|
||||
: _username(std::move(username))
|
||||
@@ -78,11 +79,13 @@ cql3::statements::drop_user_statement::execute(distributed<service::storage_prox
|
||||
}
|
||||
|
||||
// clean up permissions after the dropped user.
|
||||
// TODO: authorizer
|
||||
//DatabaseDescriptor.getAuthorizer().revokeAll(username);
|
||||
auth::auth::delete_user(_username);
|
||||
auth::authenticator::get().drop(_username);
|
||||
return make_ready_future<::shared_ptr<transport::messages::result_message>>();
|
||||
return auth::authorizer::get().revoke_all(_username).then([this] {
|
||||
return auth::auth::delete_user(_username).then([this] {
|
||||
return auth::authenticator::get().drop(_username);
|
||||
});
|
||||
}).then([] {
|
||||
return make_ready_future<::shared_ptr<transport::messages::result_message>>();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
50
cql3/statements/grant_statement.cc
Normal file
50
cql3/statements/grant_statement.cc
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "grant_statement.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
cql3::statements::grant_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
return auth::authorizer::get().grant(state.get_client_state().user(), _permissions, _resource, _username).then([] {
|
||||
return make_ready_future<::shared_ptr<transport::messages::result_message>>();
|
||||
});
|
||||
}
|
||||
61
cql3/statements/grant_statement.hh
Normal file
61
cql3/statements/grant_statement.hh
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "permission_altering_statement.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class grant_statement : public permission_altering_statement {
|
||||
public:
|
||||
using permission_altering_statement::permission_altering_statement;
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
, const query_options&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
131
cql3/statements/list_permissions_statement.cc
Normal file
131
cql3/statements/list_permissions_statement.cc
Normal file
@@ -0,0 +1,131 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <seastar/core/future-util.hh>
|
||||
|
||||
#include "list_permissions_statement.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/auth.hh"
|
||||
#include "cql3/result_set.hh"
|
||||
#include "transport/messages/result_message.hh"
|
||||
|
||||
cql3::statements::list_permissions_statement::list_permissions_statement(
|
||||
auth::permission_set permissions,
|
||||
std::experimental::optional<auth::data_resource> resource,
|
||||
std::experimental::optional<sstring> username, bool recursive)
|
||||
: _permissions(permissions), _resource(std::move(resource)), _username(
|
||||
std::move(username)), _recursive(recursive) {
|
||||
}
|
||||
|
||||
void cql3::statements::list_permissions_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) {
|
||||
// a check to ensure the existence of the user isn't being leaked by user existence check.
|
||||
state.ensure_not_anonymous();
|
||||
}
|
||||
|
||||
future<> cql3::statements::list_permissions_statement::check_access(const service::client_state& state) {
|
||||
auto f = make_ready_future();
|
||||
if (_username) {
|
||||
f = auth::auth::is_existing_user(*_username).then([this](bool exists) {
|
||||
if (!exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", *_username));
|
||||
}
|
||||
});
|
||||
}
|
||||
return f.then([this, &state] {
|
||||
if (_resource) {
|
||||
mayme_correct_resource(*_resource, state);
|
||||
if (!_resource->exists()) {
|
||||
throw exceptions::invalid_request_exception(sprint("%s doesn't exist", *_resource));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
cql3::statements::list_permissions_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
static auto make_column = [](sstring name) {
|
||||
return ::make_shared<column_specification>(auth::auth::AUTH_KS, "permissions", ::make_shared<column_identifier>(std::move(name), true), utf8_type);
|
||||
};
|
||||
static thread_local const std::vector<::shared_ptr<column_specification>> metadata({
|
||||
make_column("username"), make_column("resource"), make_column("permission")
|
||||
});
|
||||
|
||||
typedef std::experimental::optional<auth::data_resource> opt_resource;
|
||||
|
||||
std::vector<opt_resource> resources;
|
||||
|
||||
auto r = _resource;
|
||||
for (;;) {
|
||||
resources.emplace_back(r);
|
||||
if (!r || !r->has_parent() || !_recursive) {
|
||||
break;
|
||||
}
|
||||
r = r->get_parent();
|
||||
}
|
||||
|
||||
return map_reduce(resources, [&state, this](opt_resource r) {
|
||||
return auth::authorizer::get().list(state.get_client_state().user(), _permissions, std::move(r), _username);
|
||||
}, std::vector<auth::permission_details>(), [](std::vector<auth::permission_details> details, std::vector<auth::permission_details> pd) {
|
||||
details.insert(details.end(), pd.begin(), pd.end());
|
||||
return std::move(details);
|
||||
}).then([this](std::vector<auth::permission_details> details) {
|
||||
std::sort(details.begin(), details.end());
|
||||
|
||||
auto rs = std::make_unique<result_set>(metadata);
|
||||
|
||||
for (auto& v : details) {
|
||||
// Make sure names are sorted.
|
||||
auto names = auth::permissions::to_strings(v.permissions);
|
||||
for (auto& p : std::set<sstring>(names.begin(), names.end())) {
|
||||
rs->add_row(
|
||||
std::vector<bytes_opt> { utf8_type->decompose(
|
||||
v.user), utf8_type->decompose(
|
||||
v.resource.to_string()),
|
||||
utf8_type->decompose(p), });
|
||||
}
|
||||
}
|
||||
|
||||
auto rows = ::make_shared<transport::messages::result_message::rows>(std::move(rs));
|
||||
return ::shared_ptr<transport::messages::result_message>(std::move(rows));
|
||||
});
|
||||
}
|
||||
73
cql3/statements/list_permissions_statement.hh
Normal file
73
cql3/statements/list_permissions_statement.hh
Normal file
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <experimental/optional>
|
||||
|
||||
#include "authorization_statement.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/data_resource.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class list_permissions_statement : public authorization_statement {
|
||||
private:
|
||||
auth::permission_set _permissions;
|
||||
std::experimental::optional<auth::data_resource> _resource;
|
||||
std::experimental::optional<sstring> _username;
|
||||
bool _recursive;
|
||||
|
||||
public:
|
||||
list_permissions_statement(auth::permission_set, std::experimental::optional<auth::data_resource>, std::experimental::optional<sstring>, bool);
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
future<> check_access(const service::client_state&) override;
|
||||
future<::shared_ptr<transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
, const query_options&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -47,8 +47,9 @@
|
||||
void cql3::statements::list_users_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) {
|
||||
}
|
||||
|
||||
void cql3::statements::list_users_statement::check_access(const service::client_state& state) {
|
||||
future<> cql3::statements::list_users_statement::check_access(const service::client_state& state) {
|
||||
state.ensure_not_anonymous();
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
|
||||
@@ -51,7 +51,7 @@ namespace statements {
|
||||
class list_users_statement : public authentication_statement {
|
||||
public:
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
void check_access(const service::client_state&) override;
|
||||
future<> check_access(const service::client_state&) override;
|
||||
future<::shared_ptr<transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
, const query_options&) override;
|
||||
|
||||
@@ -135,15 +135,14 @@ gc_clock::duration modification_statement::get_time_to_live(const query_options&
|
||||
return gc_clock::duration(attrs->get_time_to_live(options));
|
||||
}
|
||||
|
||||
void modification_statement::check_access(const service::client_state& state) {
|
||||
warn(unimplemented::cause::PERMISSIONS);
|
||||
#if 0
|
||||
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.MODIFY);
|
||||
|
||||
// CAS updates can be used to simulate a SELECT query, so should require Permission.SELECT as well.
|
||||
if (hasConditions())
|
||||
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.SELECT);
|
||||
#endif
|
||||
future<> modification_statement::check_access(const service::client_state& state) {
|
||||
auto f = state.has_column_family_access(keyspace(), column_family(), auth::permission::MODIFY);
|
||||
if (has_conditions()) {
|
||||
f = f.then([this, &state] {
|
||||
return state.has_column_family_access(keyspace(), column_family(), auth::permission::SELECT);
|
||||
});
|
||||
}
|
||||
return f;
|
||||
}
|
||||
|
||||
future<std::vector<mutation>>
|
||||
|
||||
@@ -129,7 +129,7 @@ public:
|
||||
|
||||
gc_clock::duration get_time_to_live(const query_options& options) const;
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
96
cql3/statements/permission_altering_statement.cc
Normal file
96
cql3/statements/permission_altering_statement.cc
Normal file
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <seastar/core/thread.hh>
|
||||
|
||||
#include "permission_altering_statement.hh"
|
||||
#include "cql3/query_processor.hh"
|
||||
#include "cql3/query_options.hh"
|
||||
#include "cql3/selection/selection.hh"
|
||||
#include "auth/auth.hh"
|
||||
|
||||
cql3::statements::permission_altering_statement::permission_altering_statement(
|
||||
auth::permission_set permissions, auth::data_resource resource,
|
||||
sstring username)
|
||||
: _permissions(permissions), _resource(std::move(resource)), _username(
|
||||
std::move(username)) {
|
||||
}
|
||||
|
||||
void cql3::statements::permission_altering_statement::validate(distributed<service::storage_proxy>& proxy, const service::client_state& state) {
|
||||
// a check to ensure the existence of the user isn't being leaked by user existence check.
|
||||
state.ensure_not_anonymous();
|
||||
}
|
||||
|
||||
future<> cql3::statements::permission_altering_statement::check_access(const service::client_state& state) {
|
||||
return auth::auth::is_existing_user(_username).then([this, &state](bool exists) {
|
||||
if (!exists) {
|
||||
throw exceptions::invalid_request_exception(sprint("User %s doesn't exist", _username));
|
||||
}
|
||||
mayme_correct_resource(_resource, state);
|
||||
if (!_resource.exists()) {
|
||||
throw exceptions::invalid_request_exception(sprint("%s doesn't exist", _resource));
|
||||
}
|
||||
|
||||
// check that the user has AUTHORIZE permission on the resource or its parents, otherwise reject GRANT/REVOKE.
|
||||
return state.ensure_has_permission(auth::permission::AUTHORIZE, _resource).then([this, &state] {
|
||||
static auto perm_list = {
|
||||
auth::permission::READ,
|
||||
auth::permission::WRITE,
|
||||
auth::permission::CREATE,
|
||||
auth::permission::ALTER,
|
||||
auth::permission::DROP,
|
||||
auth::permission::SELECT,
|
||||
auth::permission::MODIFY,
|
||||
};
|
||||
return do_for_each(perm_list, [this, &state](auth::permission p) {
|
||||
// TODO: how about we re-write the access check to check a set
|
||||
// right away. Might need some tweaking of enum_set to make it
|
||||
// neat/transparent, but still...
|
||||
// This is not critical code however.
|
||||
if (_permissions.contains(p)) {
|
||||
return state.ensure_has_permission(p, _resource);
|
||||
}
|
||||
return make_ready_future();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
67
cql3/statements/permission_altering_statement.hh
Normal file
67
cql3/statements/permission_altering_statement.hh
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "authorization_statement.hh"
|
||||
#include "auth/permission.hh"
|
||||
#include "auth/data_resource.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class permission_altering_statement : public authorization_statement {
|
||||
protected:
|
||||
auth::permission_set _permissions;
|
||||
auth::data_resource _resource;
|
||||
sstring _username;
|
||||
|
||||
public:
|
||||
permission_altering_statement(auth::permission_set, auth::data_resource, sstring);
|
||||
|
||||
void validate(distributed<service::storage_proxy>&, const service::client_state&) override;
|
||||
future<> check_access(const service::client_state&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
50
cql3/statements/revoke_statement.cc
Normal file
50
cql3/statements/revoke_statement.cc
Normal file
@@ -0,0 +1,50 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "revoke_statement.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>>
|
||||
cql3::statements::revoke_statement::execute(distributed<service::storage_proxy>& proxy, service::query_state& state, const query_options& options) {
|
||||
return auth::authorizer::get().revoke(state.get_client_state().user(), _permissions, _resource, _username).then([] {
|
||||
return make_ready_future<::shared_ptr<transport::messages::result_message>>();
|
||||
});
|
||||
}
|
||||
61
cql3/statements/revoke_statement.hh
Normal file
61
cql3/statements/revoke_statement.hh
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Copyright 2016 ScyllaDB
|
||||
*
|
||||
* Modified by ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "permission_altering_statement.hh"
|
||||
|
||||
namespace cql3 {
|
||||
|
||||
namespace statements {
|
||||
|
||||
class revoke_statement : public permission_altering_statement {
|
||||
public:
|
||||
using permission_altering_statement::permission_altering_statement;
|
||||
|
||||
future<::shared_ptr<transport::messages::result_message>> execute(distributed<service::storage_proxy>&
|
||||
, service::query_state&
|
||||
, const query_options&) override;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -126,11 +126,8 @@ uint32_t select_statement::get_bound_terms() {
|
||||
return _bound_terms;
|
||||
}
|
||||
|
||||
void select_statement::check_access(const service::client_state& state) {
|
||||
warn(unimplemented::cause::PERMISSIONS);
|
||||
#if 0
|
||||
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.SELECT);
|
||||
#endif
|
||||
future<> select_statement::check_access(const service::client_state& state) {
|
||||
return state.has_column_family_access(keyspace(), column_family(), auth::permission::SELECT);
|
||||
}
|
||||
|
||||
void select_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state) {
|
||||
|
||||
@@ -123,7 +123,7 @@ public:
|
||||
|
||||
::shared_ptr<cql3::metadata> get_result_metadata() const;
|
||||
virtual uint32_t get_bound_terms() override;
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
virtual bool depends_on_keyspace(const sstring& ks_name) const;
|
||||
virtual bool depends_on_column_family(const sstring& cf_name) const;
|
||||
|
||||
@@ -78,12 +78,9 @@ bool truncate_statement::depends_on_column_family(const sstring& cf_name) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void truncate_statement::check_access(const service::client_state& state)
|
||||
future<> truncate_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
warn(unimplemented::cause::AUTH);
|
||||
#if 0
|
||||
state.hasColumnFamilyAccess(keyspace(), columnFamily(), Permission.MODIFY);
|
||||
#endif
|
||||
return state.has_column_family_access(keyspace(), column_family(), auth::permission::MODIFY);
|
||||
}
|
||||
|
||||
void truncate_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state)
|
||||
|
||||
@@ -64,7 +64,7 @@ public:
|
||||
|
||||
virtual bool depends_on_column_family(const sstring& cf_name) const override;
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
@@ -77,9 +77,10 @@ bool use_statement::depends_on_column_family(const sstring& cf_name) const
|
||||
return false;
|
||||
}
|
||||
|
||||
void use_statement::check_access(const service::client_state& state)
|
||||
future<> use_statement::check_access(const service::client_state& state)
|
||||
{
|
||||
state.validate_login();
|
||||
return make_ready_future<>();
|
||||
}
|
||||
|
||||
void use_statement::validate(distributed<service::storage_proxy>&, const service::client_state& state)
|
||||
|
||||
@@ -66,7 +66,7 @@ public:
|
||||
|
||||
virtual bool depends_on_column_family(const sstring& cf_name) const override;
|
||||
|
||||
virtual void check_access(const service::client_state& state) override;
|
||||
virtual future<> check_access(const service::client_state& state) override;
|
||||
|
||||
virtual void validate(distributed<service::storage_proxy>&, const service::client_state& state) override;
|
||||
|
||||
|
||||
@@ -91,6 +91,26 @@ public:
|
||||
get_map_data<K, V>(name, std::inserter(res, res.end()), keytype, valtype);
|
||||
return res;
|
||||
}
|
||||
template<typename V, typename Iter>
|
||||
void get_set_data(const sstring& name, Iter out, data_type valtype =
|
||||
data_type_for<V>()) const {
|
||||
auto vec =
|
||||
value_cast<set_type_impl::native_type>(
|
||||
set_type_impl::get_instance(valtype,
|
||||
false)->deserialize(
|
||||
get_blob(name)));
|
||||
std::transform(vec.begin(), vec.end(), out, [](auto& p) {
|
||||
return value_cast<V>(p);
|
||||
});
|
||||
}
|
||||
template<typename V, typename ... Rest>
|
||||
std::unordered_set<V, Rest...> get_set(const sstring& name,
|
||||
data_type valtype =
|
||||
data_type_for<V>()) const {
|
||||
std::unordered_set<V, Rest...> res;
|
||||
get_set_data<V>(name, std::inserter(res, res.end()), valtype);
|
||||
return res;
|
||||
}
|
||||
const std::vector<::shared_ptr<column_specification>>& get_columns() const {
|
||||
return _columns;
|
||||
}
|
||||
|
||||
@@ -653,7 +653,7 @@ public:
|
||||
val(internode_authenticator, sstring, "enabled", Unused, \
|
||||
"Internode authentication backend. It implements org.apache.cassandra.auth.AllowAllInternodeAuthenticator to allows or disallow connections from peer nodes." \
|
||||
) \
|
||||
val(authorizer, sstring, "org.apache.cassandra.auth.AllowAllAuthorizer", Unused, \
|
||||
val(authorizer, sstring, "org.apache.cassandra.auth.AllowAllAuthorizer", Used, \
|
||||
"The authorization backend. It implements IAuthenticator, which limits access and provides permissions. The available authorizers are:\n" \
|
||||
"\n" \
|
||||
"\tAllowAllAuthorizer : Disables authorization; allows any action to any user.\n" \
|
||||
@@ -662,13 +662,16 @@ public:
|
||||
, "org.apache.cassandra.auth.AllowAllAuthorizer" \
|
||||
, "org.apache.cassandra.auth.CassandraAuthorizer" \
|
||||
) \
|
||||
val(permissions_validity_in_ms, uint32_t, 2000, Unused, \
|
||||
val(permissions_validity_in_ms, uint32_t, 2000, Used, \
|
||||
"How long permissions in cache remain valid. Depending on the authorizer, such as CassandraAuthorizer, fetching permissions can be resource intensive. This setting disabled when set to 0 or when AllowAllAuthorizer is set.\n" \
|
||||
"Related information: Object permissions" \
|
||||
) \
|
||||
val(permissions_update_interval_in_ms, uint32_t, 2000, Unused, \
|
||||
val(permissions_update_interval_in_ms, uint32_t, 2000, Used, \
|
||||
"Refresh interval for permissions cache (if enabled). After this interval, cache entries become eligible for refresh. On next access, an async reload is scheduled and the old value is returned until it completes. If permissions_validity_in_ms , then this property must benon-zero." \
|
||||
) \
|
||||
val(permissions_cache_max_entries, uint32_t, 1000, Used, \
|
||||
"Maximum cached permission entries" \
|
||||
) \
|
||||
val(server_encryption_options, string_map, /*none*/, Used, \
|
||||
"Enable or disable inter-node encryption. You must also generate keys and provide the appropriate key and trust store locations and passwords. No custom encryption options are currently enabled. The available options are:\n" \
|
||||
"\n" \
|
||||
|
||||
9
log.cc
9
log.cc
@@ -271,4 +271,13 @@ std::ostream& operator<<(std::ostream& out, const std::exception_ptr& eptr) {
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const std::exception& e) {
|
||||
return out << logging::pretty_type_name(typeid(e)) << " (" << e.what() << ")";
|
||||
}
|
||||
|
||||
std::ostream& operator<<(std::ostream& out, const std::system_error& e) {
|
||||
return out << logging::pretty_type_name(typeid(e)) << " (error " << e.code() << ", " << e.code().message() << ")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
2
log.hh
2
log.hh
@@ -188,6 +188,8 @@ logger::do_log(log_level level, const char* fmt, Args&&... args) {
|
||||
// Pretty-printer for exceptions to be logged, e.g., std::current_exception().
|
||||
namespace std {
|
||||
std::ostream& operator<<(std::ostream&, const std::exception_ptr&);
|
||||
std::ostream& operator<<(std::ostream&, const std::exception&);
|
||||
std::ostream& operator<<(std::ostream&, const std::system_error&);
|
||||
}
|
||||
|
||||
#endif /* LOG_HH_ */
|
||||
|
||||
@@ -41,7 +41,12 @@
|
||||
|
||||
#include "client_state.hh"
|
||||
#include "auth/auth.hh"
|
||||
#include "auth/authorizer.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "validation.hh"
|
||||
#include "db/system_keyspace.hh"
|
||||
#include "db/schema_tables.hh"
|
||||
|
||||
void service::client_state::set_login(::shared_ptr<auth::authenticated_user> user) {
|
||||
if (user == nullptr) {
|
||||
@@ -87,3 +92,103 @@ void service::client_state::merge(const client_state& other) {
|
||||
}
|
||||
_last_timestamp_micros = std::max(_last_timestamp_micros, other._last_timestamp_micros);
|
||||
}
|
||||
|
||||
future<> service::client_state::has_all_keyspaces_access(
|
||||
auth::permission p) const {
|
||||
if (_is_internal) {
|
||||
return make_ready_future();
|
||||
}
|
||||
validate_login();
|
||||
return ensure_has_permission(p, auth::data_resource());
|
||||
}
|
||||
|
||||
future<> service::client_state::has_keyspace_access(const sstring& ks,
|
||||
auth::permission p) const {
|
||||
validation::validate_keyspace(ks);
|
||||
return has_access(ks, p, auth::data_resource(ks));
|
||||
}
|
||||
|
||||
future<> service::client_state::has_column_family_access(const sstring& ks,
|
||||
const sstring& cf, auth::permission p) const {
|
||||
validation::validate_column_family(ks, cf);
|
||||
return has_access(ks, p, auth::data_resource(ks, cf));
|
||||
}
|
||||
|
||||
future<> service::client_state::has_access(const sstring& ks, auth::permission p, auth::data_resource resource) const {
|
||||
if (_is_internal) {
|
||||
return make_ready_future();
|
||||
}
|
||||
|
||||
validate_login();
|
||||
|
||||
// we only care about schema modification.
|
||||
if (auth::permissions::ALTERATIONS.contains(p)) {
|
||||
// prevent system keyspace modification
|
||||
auto name = ks;
|
||||
std::transform(name.begin(), name.end(), name.begin(), ::tolower);
|
||||
if (name == db::system_keyspace::NAME) {
|
||||
throw exceptions::unauthorized_exception(ks + " keyspace is not user-modifiable.");
|
||||
}
|
||||
|
||||
// we want to allow altering AUTH_KS and TRACING_KS.
|
||||
for (auto& n : { auth::auth::AUTH_KS /* TODO: tracing */ }) {
|
||||
if (name == n && !resource.is_keyspace_level() && p != auth::permission::ALTER) {
|
||||
throw exceptions::unauthorized_exception(sprint("Cannot %s %s", auth::permissions::to_string(p), resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p == auth::permission::SELECT && resource.is_column_family_level() && resource.keyspace() == db::system_keyspace::NAME) {
|
||||
for (auto& n : { db::system_keyspace::LOCAL, db::system_keyspace::PEERS }) {
|
||||
if (resource.column_family() == n) {
|
||||
return make_ready_future();
|
||||
}
|
||||
}
|
||||
for (auto& n : db::schema_tables::ALL) {
|
||||
if (resource.column_family() == n) {
|
||||
return make_ready_future();
|
||||
}
|
||||
}
|
||||
}
|
||||
if (auth::permissions::ALTERATIONS.contains(p)) {
|
||||
for (auto& s : { auth::authorizer::get().protected_resources(),
|
||||
auth::authenticator::get().protected_resources() }) {
|
||||
if (s.count(resource)) {
|
||||
throw exceptions::unauthorized_exception(
|
||||
sprint("%s schema is protected",
|
||||
resource));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ensure_has_permission(p, std::move(resource));
|
||||
}
|
||||
|
||||
future<bool> service::client_state::check_has_permission(auth::permission p, auth::data_resource resource) const {
|
||||
std::experimental::optional<auth::data_resource> parent;
|
||||
if (resource.has_parent()) {
|
||||
parent = resource.get_parent();
|
||||
}
|
||||
|
||||
return auth::auth::get_permissions(_user, resource).then([this, p, parent = std::move(parent)](auth::permission_set set) {
|
||||
if (set.contains(p)) {
|
||||
return make_ready_future<bool>(true);
|
||||
}
|
||||
if (parent) {
|
||||
return check_has_permission(p, std::move(*parent));
|
||||
}
|
||||
return make_ready_future<bool>(false);
|
||||
});
|
||||
}
|
||||
|
||||
future<> service::client_state::ensure_has_permission(auth::permission p, auth::data_resource resource) const {
|
||||
return check_has_permission(p, resource).then([this, p, resource](bool ok) {
|
||||
if (!ok) {
|
||||
throw exceptions::unauthorized_exception(sprint("User %s has no %s permission on %s or any of its parents",
|
||||
_user->name(),
|
||||
auth::permissions::to_string(p),
|
||||
resource));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -48,6 +48,7 @@
|
||||
#include "database.hh"
|
||||
#include "auth/authenticated_user.hh"
|
||||
#include "auth/authenticator.hh"
|
||||
#include "auth/permission.hh"
|
||||
|
||||
namespace service {
|
||||
|
||||
@@ -191,73 +192,16 @@ public:
|
||||
*/
|
||||
future<> check_user_exists();
|
||||
|
||||
#if 0
|
||||
public void hasAllKeyspacesAccess(Permission perm) throws UnauthorizedException
|
||||
{
|
||||
if (isInternal)
|
||||
return;
|
||||
validateLogin();
|
||||
ensureHasPermission(perm, DataResource.root());
|
||||
}
|
||||
|
||||
public void hasKeyspaceAccess(String keyspace, Permission perm) throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
hasAccess(keyspace, perm, DataResource.keyspace(keyspace));
|
||||
}
|
||||
|
||||
public void hasColumnFamilyAccess(String keyspace, String columnFamily, Permission perm)
|
||||
throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
ThriftValidation.validateColumnFamily(keyspace, columnFamily);
|
||||
hasAccess(keyspace, perm, DataResource.columnFamily(keyspace, columnFamily));
|
||||
}
|
||||
|
||||
private void hasAccess(String keyspace, Permission perm, DataResource resource)
|
||||
throws UnauthorizedException, InvalidRequestException
|
||||
{
|
||||
validateKeyspace(keyspace);
|
||||
if (isInternal)
|
||||
return;
|
||||
validateLogin();
|
||||
preventSystemKSSchemaModification(keyspace, resource, perm);
|
||||
if ((perm == Permission.SELECT) && READABLE_SYSTEM_RESOURCES.contains(resource))
|
||||
return;
|
||||
if (PROTECTED_AUTH_RESOURCES.contains(resource))
|
||||
if ((perm == Permission.CREATE) || (perm == Permission.ALTER) || (perm == Permission.DROP))
|
||||
throw new UnauthorizedException(String.format("%s schema is protected", resource));
|
||||
ensureHasPermission(perm, resource);
|
||||
}
|
||||
|
||||
public void ensureHasPermission(Permission perm, IResource resource) throws UnauthorizedException
|
||||
{
|
||||
for (IResource r : Resources.chain(resource))
|
||||
if (authorize(r).contains(perm))
|
||||
return;
|
||||
|
||||
throw new UnauthorizedException(String.format("User %s has no %s permission on %s or any of its parents",
|
||||
user.getName(),
|
||||
perm,
|
||||
resource));
|
||||
}
|
||||
|
||||
private void preventSystemKSSchemaModification(String keyspace, DataResource resource, Permission perm) throws UnauthorizedException
|
||||
{
|
||||
// we only care about schema modification.
|
||||
if (!((perm == Permission.ALTER) || (perm == Permission.DROP) || (perm == Permission.CREATE)))
|
||||
return;
|
||||
|
||||
// prevent system keyspace modification
|
||||
if (SystemKeyspace.NAME.equalsIgnoreCase(keyspace))
|
||||
throw new UnauthorizedException(keyspace + " keyspace is not user-modifiable.");
|
||||
|
||||
// we want to allow altering AUTH_KS and TRACING_KS.
|
||||
Set<String> allowAlter = Sets.newHashSet(Auth.AUTH_KS, TraceKeyspace.NAME);
|
||||
if (allowAlter.contains(keyspace.toLowerCase()) && !(resource.isKeyspaceLevel() && (perm == Permission.ALTER)))
|
||||
throw new UnauthorizedException(String.format("Cannot %s %s", perm, resource));
|
||||
}
|
||||
#endif
|
||||
future<> has_all_keyspaces_access(auth::permission) const;
|
||||
future<> has_keyspace_access(const sstring&, auth::permission) const;
|
||||
future<> has_column_family_access(const sstring&, const sstring&, auth::permission) const;
|
||||
|
||||
private:
|
||||
future<> has_access(const sstring&, auth::permission, auth::data_resource) const;
|
||||
future<bool> check_has_permission(auth::permission, auth::data_resource) const;
|
||||
public:
|
||||
future<> ensure_has_permission(auth::permission, auth::data_resource) const;
|
||||
|
||||
void validate_login() const;
|
||||
void ensure_not_anonymous() const throw(exceptions::unauthorized_exception);
|
||||
|
||||
|
||||
@@ -81,7 +81,9 @@ private:
|
||||
service::client_state client_state;
|
||||
|
||||
core_local_state()
|
||||
: client_state(service::client_state::for_external_calls()) {
|
||||
: client_state(service::client_state::for_external_calls())
|
||||
{
|
||||
client_state.set_login(::make_shared<auth::authenticated_user>("cassandra"));
|
||||
}
|
||||
|
||||
future<> stop() {
|
||||
|
||||
7
types.hh
7
types.hh
@@ -321,6 +321,8 @@ public:
|
||||
explicit data_value(std::experimental::optional<bytes>);
|
||||
template <typename NativeType>
|
||||
data_value(std::experimental::optional<NativeType>);
|
||||
template <typename NativeType>
|
||||
data_value(const std::unordered_set<NativeType>&);
|
||||
|
||||
data_value& operator=(const data_value&);
|
||||
data_value& operator=(data_value&&);
|
||||
@@ -1447,6 +1449,11 @@ data_value::data_value(std::experimental::optional<NativeType> v)
|
||||
: data_value(v ? data_value(*v) : data_value::make_null(data_type_for<NativeType>())) {
|
||||
}
|
||||
|
||||
template <typename NativeType>
|
||||
data_value::data_value(const std::unordered_set<NativeType>& v)
|
||||
: data_value(new set_type_impl::native_type(v.begin(), v.end()), set_type_impl::get_instance(data_type_for<NativeType>(), true))
|
||||
{}
|
||||
|
||||
template<>
|
||||
struct appending_hash<data_type> {
|
||||
template<typename Hasher>
|
||||
|
||||
@@ -62,6 +62,10 @@ public:
|
||||
size_t operator()(const std::pair<T1, T2>& p) const {
|
||||
return hash_combine(hash(p.first), hash(p.second));
|
||||
}
|
||||
template<typename T1, typename T2>
|
||||
size_t operator()(const T1& t1, const T2& t2) const {
|
||||
return hash_combine(hash(t1), hash(t2));
|
||||
}
|
||||
template<typename... Args>
|
||||
size_t operator()(const std::tuple<Args...>& v) const;
|
||||
};
|
||||
|
||||
133
utils/loading_cache.hh
Normal file
133
utils/loading_cache.hh
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
* Copyright (C) 2016 ScyllaDB
|
||||
*/
|
||||
|
||||
/*
|
||||
* This file is part of Scylla.
|
||||
*
|
||||
* Scylla is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Scylla is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Scylla. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <seastar/core/timer.hh>
|
||||
|
||||
namespace utils {
|
||||
// Simple variant of the "LoadingCache" used for permissions in origin.
|
||||
|
||||
typedef steady_clock_type loading_cache_clock_type;
|
||||
|
||||
template<typename _Tp>
|
||||
struct timestamped_val {
|
||||
_Tp value;
|
||||
typename loading_cache_clock_type::time_point loaded;
|
||||
};
|
||||
|
||||
template<typename _Key, typename _Tp, typename _Hash = std::hash<_Key>,
|
||||
typename _Pred = std::equal_to<_Key>,
|
||||
typename _Alloc = std::allocator<std::pair<const _Key, timestamped_val<_Tp>> > >
|
||||
class loading_cache {
|
||||
private:
|
||||
typedef timestamped_val<_Tp> ts_value_type;
|
||||
typedef std::unordered_map<_Key, ts_value_type, _Hash, _Pred, _Alloc> map_type;
|
||||
typedef loading_cache<_Key, _Tp, _Hash, _Pred, _Alloc> _MyType;
|
||||
public:
|
||||
typedef _Tp value_type;
|
||||
typedef typename map_type::key_type key_type;
|
||||
typedef typename map_type::allocator_type allocator_type;
|
||||
typedef typename map_type::hasher hasher;
|
||||
typedef typename map_type::key_equal key_equal;
|
||||
|
||||
template<typename Func>
|
||||
loading_cache(size_t max_size, std::chrono::milliseconds expiry,
|
||||
std::chrono::milliseconds refresh, Func && load,
|
||||
const hasher& hf = hasher(), const key_equal& eql =
|
||||
key_equal(), const allocator_type& a =
|
||||
allocator_type())
|
||||
: _map(10, hf, eql, a), _max_size(max_size), _expiry(
|
||||
expiry), _refresh(refresh), _load(
|
||||
std::forward<Func>(load)) {
|
||||
|
||||
auto period = _expiry;
|
||||
|
||||
if (expiry == std::chrono::milliseconds() && _max_size != 0) {
|
||||
period = std::chrono::milliseconds(5000);
|
||||
}
|
||||
if (period != std::chrono::milliseconds()) {
|
||||
_timer.set_callback(std::bind(&_MyType::on_timer, this));
|
||||
_timer.arm_periodic(period);
|
||||
}
|
||||
}
|
||||
|
||||
future<_Tp> get(const _Key & k) {
|
||||
auto i = _map.find(k);
|
||||
|
||||
if (i == _map.end()
|
||||
|| (i->second.loaded + _refresh)
|
||||
< loading_cache_clock_type::now()) {
|
||||
return _load(k).then([this, k](_Tp t) {
|
||||
_map[k] = ts_value_type{t, loading_cache_clock_type::now()};
|
||||
return make_ready_future<_Tp>(std::move(t));
|
||||
});
|
||||
}
|
||||
return make_ready_future<_Tp>(i->second.value);
|
||||
}
|
||||
|
||||
private:
|
||||
void on_timer() {
|
||||
auto i = _map.begin();
|
||||
auto e = _map.end();
|
||||
|
||||
auto now = loading_cache_clock_type::now();
|
||||
|
||||
while (i != e) {
|
||||
if ((i->second.loaded + _expiry) < now) {
|
||||
i = _map.erase(i);
|
||||
continue;
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if (_max_size != 0 && _map.size() > _max_size) {
|
||||
typedef typename map_type::iterator iterator;
|
||||
std::vector<iterator> tmp;
|
||||
for (i = _map.begin(); i != e; ++i) {
|
||||
tmp.emplace_back(i);
|
||||
}
|
||||
std::sort(tmp.begin(), tmp.end(), [](iterator i1, iterator i2) {
|
||||
return i1->second.loaded < i2->second.loaded;
|
||||
});
|
||||
|
||||
for (auto& e : tmp) {
|
||||
_map.erase(e);
|
||||
if (_map.size() < _max_size) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_map<_Key, ts_value_type, _Hash, _Pred, _Alloc> _map;
|
||||
size_t _max_size;
|
||||
std::chrono::milliseconds _expiry;
|
||||
std::chrono::milliseconds _refresh;
|
||||
std::function<future<_Tp>(_Key)> _load;
|
||||
timer<> _timer;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "validation.hh"
|
||||
#include "database.hh"
|
||||
#include "exceptions/exceptions.hh"
|
||||
#include "service/storage_proxy.hh"
|
||||
|
||||
namespace validation {
|
||||
|
||||
@@ -74,15 +75,7 @@ validate_cql_key(schema_ptr schema, const partition_key& key) {
|
||||
*/
|
||||
schema_ptr
|
||||
validate_column_family(database& db, const sstring& keyspace_name, const sstring& cf_name) {
|
||||
if (keyspace_name.empty()) {
|
||||
throw exceptions::invalid_request_exception("Keyspace not set");
|
||||
}
|
||||
|
||||
try {
|
||||
db.find_keyspace(keyspace_name);
|
||||
} catch (...) {
|
||||
throw exceptions::keyspace_not_defined_exception(sprint("Keyspace %s does not exist", keyspace_name));
|
||||
}
|
||||
validate_keyspace(db, keyspace_name);
|
||||
|
||||
if (cf_name.empty()) {
|
||||
throw exceptions::invalid_request_exception("non-empty table is required");
|
||||
@@ -95,4 +88,29 @@ validate_column_family(database& db, const sstring& keyspace_name, const sstring
|
||||
}
|
||||
}
|
||||
|
||||
schema_ptr validate_column_family(const sstring& keyspace_name,
|
||||
const sstring& cf_name) {
|
||||
return validate_column_family(
|
||||
service::get_local_storage_proxy().get_db().local(),
|
||||
keyspace_name, cf_name);
|
||||
}
|
||||
|
||||
void validate_keyspace(database& db, const sstring& keyspace_name) {
|
||||
if (keyspace_name.empty()) {
|
||||
throw exceptions::invalid_request_exception("Keyspace not set");
|
||||
}
|
||||
|
||||
try {
|
||||
db.find_keyspace(keyspace_name);
|
||||
} catch (...) {
|
||||
throw exceptions::keyspace_not_defined_exception(sprint("Keyspace %s does not exist", keyspace_name));
|
||||
}
|
||||
}
|
||||
|
||||
void validate_keyspace(const sstring& keyspace_name) {
|
||||
validate_keyspace(service::get_local_storage_proxy().get_db().local(),
|
||||
keyspace_name);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -51,5 +51,9 @@ constexpr size_t max_key_size = std::numeric_limits<uint16_t>::max();
|
||||
|
||||
void validate_cql_key(schema_ptr schema, const partition_key& key);
|
||||
schema_ptr validate_column_family(database& db, const sstring& keyspace_name, const sstring& cf_name);
|
||||
schema_ptr validate_column_family(const sstring& keyspace_name, const sstring& cf_name);
|
||||
|
||||
void validate_keyspace(database& db, const sstring& keyspace_name);
|
||||
void validate_keyspace(const sstring& keyspace_name);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user