Files
scylladb/tests/auth_test.cc
Jesse Haber-Kucharsky 741d215516 auth: Switch to roles from users
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).
2018-02-14 14:15:57 -05:00

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);
}