mirror of
https://github.com/scylladb/scylladb.git
synced 2026-06-01 20:46:56 +00:00
This is a large change, but it's a necessary evil. This change brings us to a minimally-functional implementation of roles. There are many additional changes that are necessary, including refined grammar, bug fixes, code hygiene, and internal code structure changes. In the interest of keeping this patch somewhat read-able, those changes will come in subsequent patches. Until that time, roles are still marked "unimplemented". IMPORTANT: This code does not include any mechanism for transitioning a cluster from user-based access-control to role-based access control. All existing access-control metadata will be ignored (though not deleted). Specific changes: - All user-specific CQL statements now delegate to their roles equivalent. The statements are effectively the same, but CREATE USER will include LOGIN automatically. Also, LIST USERS only lists roles with LOGIN. - A call to LIST PERMISSIONS will now also list permissions of roles that have been granted to the caller, in addition to permissions which have been granted directly. - Much of the logic of creating, altering, and deleting roles has been moved to `auth::service`, since these operations require cooperation between the authenticator, authorizer, and role-manager. - LIST USERS actually works as expected now (fixes #2968).
202 lines
7.4 KiB
C++
202 lines
7.4 KiB
C++
/*
|
|
* 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/>.
|
|
*/
|
|
|
|
|
|
#include <boost/range/irange.hpp>
|
|
#include <boost/range/adaptors.hpp>
|
|
#include <boost/range/algorithm.hpp>
|
|
#include <boost/test/unit_test.hpp>
|
|
#include <stdint.h>
|
|
|
|
#include <seastar/core/future-util.hh>
|
|
#include <seastar/core/shared_ptr.hh>
|
|
#include <seastar/core/thread.hh>
|
|
|
|
#include "tests/test-utils.hh"
|
|
#include "tests/cql_test_env.hh"
|
|
#include "tests/cql_assertions.hh"
|
|
|
|
#include "auth/allow_all_authenticator.hh"
|
|
#include "auth/authenticator.hh"
|
|
#include "auth/password_authenticator.hh"
|
|
#include "auth/service.hh"
|
|
#include "auth/authenticated_user.hh"
|
|
#include "auth/resource.hh"
|
|
|
|
#include "db/config.hh"
|
|
#include "cql3/query_processor.hh"
|
|
|
|
SEASTAR_TEST_CASE(test_default_authenticator) {
|
|
return do_with_cql_env([](cql_test_env& env) {
|
|
auto& a = env.local_auth_service().underlying_authenticator();
|
|
BOOST_REQUIRE_EQUAL(a.require_authentication(), false);
|
|
BOOST_REQUIRE_EQUAL(a.qualified_java_name(), auth::allow_all_authenticator_name());
|
|
return make_ready_future();
|
|
});
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_password_authenticator_attributes) {
|
|
db::config cfg;
|
|
cfg.authenticator(auth::password_authenticator_name());
|
|
|
|
return do_with_cql_env([](cql_test_env& env) {
|
|
auto& a = env.local_auth_service().underlying_authenticator();
|
|
BOOST_REQUIRE_EQUAL(a.require_authentication(), true);
|
|
BOOST_REQUIRE_EQUAL(a.qualified_java_name(), auth::password_authenticator_name());
|
|
return make_ready_future();
|
|
}, cfg);
|
|
}
|
|
|
|
SEASTAR_TEST_CASE(test_password_authenticator_operations) {
|
|
db::config cfg;
|
|
cfg.authenticator(auth::password_authenticator_name());
|
|
|
|
/**
|
|
* Not using seastar::async due to apparent ASan bug.
|
|
* Enjoy the slightly less readable code.
|
|
*/
|
|
return do_with_cql_env([](cql_test_env& env) {
|
|
sstring username("fisk");
|
|
sstring password("notter");
|
|
|
|
using namespace auth;
|
|
using user_ptr = ::shared_ptr<authenticated_user>;
|
|
|
|
auto USERNAME_KEY = authenticator::USERNAME_KEY;
|
|
auto PASSWORD_KEY = authenticator::PASSWORD_KEY;
|
|
|
|
auto& a = env.local_auth_service().underlying_authenticator();
|
|
|
|
// check non-existing user
|
|
return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([&a](future<user_ptr>&& f) {
|
|
try {
|
|
f.get();
|
|
BOOST_FAIL("should not reach");
|
|
} catch (exceptions::authentication_exception&) {
|
|
// ok
|
|
}
|
|
}).then([=, &a] {
|
|
authentication_options options;
|
|
options.password = password;
|
|
|
|
return a.create(username, std::move(options)).then([=, &a] {
|
|
return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then([=](user_ptr user) {
|
|
BOOST_REQUIRE_EQUAL(user->name(), username);
|
|
BOOST_REQUIRE_EQUAL(user->is_anonymous(), false);
|
|
});
|
|
});
|
|
}).then([=, &a] {
|
|
// check wrong password
|
|
return a.authenticate( { {USERNAME_KEY, username}, {PASSWORD_KEY, "hejkotte"}}).then_wrapped([](future<user_ptr>&& f) {
|
|
try {
|
|
f.get();
|
|
BOOST_FAIL("should not reach");
|
|
} catch (exceptions::authentication_exception&) {
|
|
// ok
|
|
}
|
|
});
|
|
}).then([=, &a] {
|
|
// sasl
|
|
auto sasl = a.new_sasl_challenge();
|
|
|
|
BOOST_REQUIRE_EQUAL(sasl->is_complete(), false);
|
|
|
|
bytes b;
|
|
int8_t i = 0;
|
|
b.append(&i, 1);
|
|
b.insert(b.end(), username.begin(), username.end());
|
|
b.append(&i, 1);
|
|
b.insert(b.end(), password.begin(), password.end());
|
|
|
|
sasl->evaluate_response(b);
|
|
BOOST_REQUIRE_EQUAL(sasl->is_complete(), true);
|
|
|
|
return sasl->get_authenticated_user().then([=](user_ptr user) {
|
|
BOOST_REQUIRE_EQUAL(user->name(), username);
|
|
BOOST_REQUIRE_EQUAL(user->is_anonymous(), false);
|
|
});
|
|
}).then([=, &a] {
|
|
// check deleted user
|
|
return a.drop(username).then([=, &a] {
|
|
return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([](future<user_ptr>&& f) {
|
|
try {
|
|
f.get();
|
|
BOOST_FAIL("should not reach");
|
|
} catch (exceptions::authentication_exception&) {
|
|
// ok
|
|
}
|
|
});
|
|
});
|
|
});
|
|
}, cfg);
|
|
}
|
|
|
|
|
|
SEASTAR_TEST_CASE(test_cassandra_hash) {
|
|
db::config cfg;
|
|
cfg.authenticator(auth::password_authenticator_name());
|
|
|
|
return do_with_cql_env([](cql_test_env& env) {
|
|
/**
|
|
* Try to check password against hash from origin.
|
|
* Allow for specific failure if glibc cannot handle the
|
|
* hash algo (i.e. blowfish).
|
|
*/
|
|
|
|
sstring username("fisk");
|
|
sstring password("cassandra");
|
|
sstring salted_hash("$2a$10$8cz4EZ5v8f/aTZFkNEQafe.z66ZvjOonOpHCApwx0ksWp3aKf.Roq");
|
|
|
|
// This is extremely whitebox. We'll just go right ahead and know
|
|
// what the tables etc are called. Oy wei...
|
|
auto f = env.local_qp().process("INSERT into system_auth.roles (role, salted_hash) values (?, ?)", db::consistency_level::ONE,
|
|
{ username, salted_hash }).discard_result();
|
|
|
|
return f.then([=, &env] {
|
|
auto& a = env.local_auth_service().underlying_authenticator();
|
|
|
|
auto USERNAME_KEY = auth::authenticator::USERNAME_KEY;
|
|
auto PASSWORD_KEY = auth::authenticator::PASSWORD_KEY;
|
|
using user_ptr = ::shared_ptr<auth::authenticated_user>;
|
|
|
|
// try to verify our user with a cassandra-originated salted_hash
|
|
return a.authenticate({ { USERNAME_KEY, username }, { PASSWORD_KEY, password } }).then_wrapped([](future<user_ptr> f) {
|
|
try {
|
|
f.get();
|
|
} catch (exceptions::authentication_exception& e) {
|
|
try {
|
|
std::rethrow_if_nested(e);
|
|
BOOST_FAIL(std::string("Unexcepted exception ") + e.what());
|
|
} catch (std::system_error & e) {
|
|
bool is_einval = e.code().category() == std::system_category() && e.code().value() == EINVAL;
|
|
BOOST_WARN_MESSAGE(is_einval, "Could not verify cassandra password hash due to glibc limitation");
|
|
if (!is_einval) {
|
|
BOOST_FAIL(std::string("Unexcepted system error ") + e.what());
|
|
}
|
|
}
|
|
}
|
|
});
|
|
});
|
|
}, cfg);
|
|
}
|
|
|
|
|