}
+static inline std::pair<bool, rgw::auth::Engine::result_t>
+strategy_handle_rejected(rgw::auth::Engine::result_t&& engine_result,
+ const rgw::auth::Strategy::Control policy,
+ rgw::auth::Engine::result_t&& strategy_result)
+{
+ using Control = rgw::auth::Strategy::Control;
+ switch (policy) {
+ case Control::REQUISITE:
+ /* Don't try next. */
+ return std::make_pair(false, std::move(engine_result));
+
+ case Control::SUFFICIENT:
+ /* Don't try next. */
+ return std::make_pair(false, std::move(engine_result));
+
+ case Control::FALLBACK:
+ /* Don't try next. */
+ return std::make_pair(false, std::move(strategy_result));
+
+ default:
+ /* Huh, memory corruption? */
+ abort();
+ }
+}
+
+static inline std::pair<bool, rgw::auth::Engine::result_t>
+strategy_handle_denied(rgw::auth::Engine::result_t&& engine_result,
+ const rgw::auth::Strategy::Control policy,
+ rgw::auth::Engine::result_t&& strategy_result)
+{
+ using Control = rgw::auth::Strategy::Control;
+ switch (policy) {
+ case Control::REQUISITE:
+ /* Don't try next. */
+ return std::make_pair(false, std::move(engine_result));
+
+ case Control::SUFFICIENT:
+ /* Just try next. */
+ return std::make_pair(true, std::move(engine_result));
+
+ case Control::FALLBACK:
+ return std::make_pair(true, std::move(strategy_result));
+
+ default:
+ /* Huh, memory corruption? */
+ abort();
+ }
+}
+
+static inline std::pair<bool, rgw::auth::Engine::result_t>
+strategy_handle_granted(rgw::auth::Engine::result_t&& engine_result,
+ const rgw::auth::Strategy::Control policy,
+ rgw::auth::Engine::result_t&& strategy_result)
+{
+ using Control = rgw::auth::Strategy::Control;
+ switch (policy) {
+ case Control::REQUISITE:
+ /* Try next. */
+ return std::make_pair(true, std::move(engine_result));
+
+ case Control::SUFFICIENT:
+ /* Don't try next. */
+ return std::make_pair(false, std::move(engine_result));
+
+ case Control::FALLBACK:
+ /* Don't try next. */
+ return std::make_pair(false, std::move(engine_result));
+
+ default:
+ /* Huh, memory corruption? */
+ abort();
+ }
+}
+
rgw::auth::Engine::result_t
rgw::auth::Strategy::authenticate(const req_state* const s) const
{
- int previous_error = 0;
+ result_t strategy_result = result_t::deny();
+
for (const stack_item_t& kv : auth_stack) {
const rgw::auth::Engine& engine = kv.first;
const auto& policy = kv.second;
- rgw::auth::Engine::result_t res;
+ result_t engine_result = result_t::deny();
try {
- res = engine.authenticate(s);
+ engine_result = engine.authenticate(s);
} catch (const int err) {
- previous_error = err;
+ engine_result = result_t::deny(err);
}
- const auto& applier = res.first;
- if (! applier) {
- /* The current auth engine denied authenticate the request returning
- * a null rgw::auth::Applier. As it has been included into strategy
- * as an obligatory one, we quite immediately. */
- switch (policy) {
- case Control::REQUISITE:
- goto auth_fail;
- case Control::SUFFICIENT:
- /* Just try next. */
- continue;
- case Control::FALLBACK:
- throw previous_error;
- default:
- /* Huh, memory corruption? */
- abort();
- }
- } else {
- /* Success. */
- return std::move(res);
+ bool try_next = true;
+ switch (engine_result.get_status()) {
+ case result_t::Status::REJECTED:
+ std::tie(try_next, strategy_result) = \
+ strategy_handle_rejected(std::move(engine_result), policy,
+ std::move(strategy_result));
+ break;
+ case result_t::Status::DENIED:
+ std::tie(try_next, strategy_result) = \
+ strategy_handle_denied(std::move(engine_result), policy,
+ std::move(strategy_result));
+ break;
+ case result_t::Status::GRANTED:
+ std::tie(try_next, strategy_result) = \
+ strategy_handle_granted(std::move(engine_result), policy,
+ std::move(strategy_result));
+ break;
+ default:
+ abort();
+ }
+
+ if (! try_next) {
+ break;
}
}
-auth_fail:
- /* Returning nullptr as the rgw::auth::Applier means access denied. */
- return Engine::result_t(nullptr, nullptr);
+ return strategy_result;
}
void
rgw::auth::AnonymousEngine::authenticate(const req_state* const s) const
{
if (! is_applicable()) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
} else {
RGWUserInfo user_info;
rgw_get_anon_user(user_info);
// FIXME: over 80 columns
auto apl = apl_factory->create_apl_local(cct, s, user_info,
rgw::auth::LocalApplier::NO_SUBUSER);
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
}
};
+/* Interface class for completing the two-step authentication process.
+ * Completer provides the second step - the complete() method that should
+ * be called after Engine::authenticate() but before *committing* results
+ * of an RGWOp (or sending a response in the case of non-mutating ops).
+ *
+ * The motivation driving the interface is to address those authentication
+ * schemas that require message integrity verification *without* in-memory
+ * data buffering. Typical examples are AWS Auth v4 and the auth mechanism
+ * of browser uploads facilities both in S3 and Swift APIs (see RGWPostObj).
+ * The workflow of request from the authentication point-of-view does look
+ * like following one:
+ * A. authenticate (Engine::authenticate),
+ * B. authorize (see RGWOp::verify_permissions),
+ * C. execute-prepare (init potential data modifications),
+ * D. authenticate-complete - (Completer::complete),
+ * E. execute-commit - commit the modifications from point C. */
+class Completer {
+public:
+ typedef std::unique_ptr<Completer> cmplptr_t;
+
+ virtual ~Completer() = default;
+
+ /* Complete the authentication process. Return boolean indicating whether
+ * the completion succeeded. On error throws rgw::auth::Exception storing
+ * the reason. */
+ virtual bool complete() = 0;
+};
+
+
/* Interface class for authentication backends (auth engines) in RadosGW.
*
* An engine is supposed only to authenticate (not authorize!) requests
- * basing on their req_state and provide an upper layer with:
+ * basing on their req_state and - if access has been granted - provide
+ * an upper layer with:
* - rgw::auth::IdentityApplier to commit all changes to the request state as
* well as to the RADOS store (creating an account, synchronizing
* user-related information with external databases and so on).
* of the request. Typical use case is verifying message integrity
* in AWS Auth v4 and browser uploads (RGWPostObj).
*
- * The authentication process consists two steps:
- * - Engine::authenticate() supposed to be called before *initiating*
+ * Both of them are supposed to be wrapped in Engine::AuthResult.
+ *
+ * The authentication process consists of two steps:
+ * - Engine::authenticate() which should be called before *initiating*
* any modifications to RADOS store that are related to an operation
* a client wants to perform (RGWOp::execute).
* - Completer::complete() supposed to be called, if completer has been
* returned, after the authenticate() step but before *committing*
* those modifications or sending a response (RGWOp::complete).
*
- * An engine outlives both Applier and Completer. It's
- * intended to live since RadosGW's initialization and handle multiple
- * requests till a reconfiguration.
+ * An engine outlives both Applier and Completer. It's intended to live
+ * since RadosGW's initialization and handle multiple requests till
+ * a reconfiguration.
*
* Auth engine MUST NOT make any changes to req_state nor RADOS store.
* This is solely an Applier's responsibility!
public:
virtual ~Engine() = default;
- using result_t = std::pair<std::unique_ptr<class IdentityApplier>,
- std::unique_ptr<class Completer>>;
+ class AuthResult {
+ struct rejection_mark_t {};
+ bool is_rejected = false;
+ int reason = 0;
+
+ std::pair<IdentityApplier::aplptr_t, Completer::cmplptr_t> result_pair;
+
+ AuthResult(const int reason)
+ : reason(reason) {
+ }
+
+ AuthResult(rejection_mark_t&&, const int reason)
+ : is_rejected(true),
+ reason(reason) {
+ }
+
+ /* Allow only the reasonable combintations - returning just Completer
+ * without accompanying IdentityApplier is strictly prohibited! */
+ AuthResult(IdentityApplier::aplptr_t&& applier)
+ : result_pair(std::move(applier), nullptr) {
+ }
+
+ AuthResult(IdentityApplier::aplptr_t&& applier,
+ Completer::cmplptr_t&& completer)
+ : result_pair(std::move(applier), std::move(completer)) {
+ }
+
+ public:
+ enum class Status {
+ /* Engine doesn't grant the access but also doesn't reject it. */
+ DENIED,
+
+ /* Engine successfully authenticated requester. */
+ GRANTED,
+
+ /* Engine strictly indicates that a request should be rejected
+ * without trying any further engine. */
+ REJECTED
+ };
+
+ Status get_status() const {
+ if (is_rejected) {
+ return Status::REJECTED;
+ } else if (! result_pair.first) {
+ return Status::DENIED;
+ } else {
+ return Status::GRANTED;
+ }
+ }
+
+ int get_reason() const {
+ return reason;
+ }
+
+ IdentityApplier::aplptr_t get_applier() {
+ return std::move(result_pair.first);
+ }
+
+ Completer::cmplptr_t get_completer() {
+ return std::move(result_pair.second);
+ }
+
+ static AuthResult reject(const int reason = -EACCES) {
+ return AuthResult(rejection_mark_t(), reason);
+ }
+
+ static AuthResult deny(const int reason = -EACCES) {
+ return AuthResult(reason);
+ }
+
+ static AuthResult grant(IdentityApplier::aplptr_t&& applier) {
+ return AuthResult(std::move(applier));
+ }
+
+ static AuthResult grant(IdentityApplier::aplptr_t&& applier,
+ Completer::cmplptr_t&& completer) {
+ return AuthResult(std::move(applier), std::move(completer));
+ }
+ };
+
+ using result_t = AuthResult;
/* Get name of the auth engine. */
virtual const char* get_name() const noexcept = 0;
};
-/* Interface class for completing the two-step authentication process.
- * Completer provides the second step - the complete() method that should
- * be called after Engine::authenticate() but before *committing* results
- * of an RGWOp (or sending a response in the case of non-mutating ops).
- *
- * The motivation driving the interface is to address those authentication
- * schemas that require message integrity verification *without* in-memory
- * data buffering. Typical examples are AWS Auth v4 and the auth mechanism
- * of browser uploads facilities both in S3 and Swift APIs (see RGWPostObj).
- * The workflow of request from the authentication point-of-view does look
- * like following one:
- * A. authenticate (Engine::authenticate),
- * B. authorize (see RGWOp::verify_permissions),
- * C. execute-prepare (init potential data modifications),
- * D. authenticate-complete - (Completer::complete),
- * E. execute-commit - commit the modifications from point C. */
-class Completer {
-public:
- typedef std::unique_ptr<Completer> cmplptr_t;
-
- virtual ~Completer() = default;
-
- /* Complete the authentication process. Return boolean indicating whether
- * the completion succeeded. On error throws rgw::auth::Exception storing
- * the reason. */
- virtual bool complete() = 0;
-};
-
-
/* Interface for extracting a token basing from data carried by req_state. */
class TokenExtractor {
public:
* The names and semantic has been borrowed mostly from libpam. */
enum class Control {
/* Failure of an engine injected with the REQUISITE specifier aborts
- * the whole authentication process immediately. No other engine will
- * be tried. */
+ * the strategy's authentication process immediately. No other engine
+ * will be tried. */
REQUISITE,
/* Success of an engine injected with the SUFFICIENT specifier ends
- * the whole authentication process successfully. However, failure
- * doesn't abort it - there will be fall-back to following engine
- * it the one that failed wasn't the last. */
+ * strategy's authentication process successfully. However, denying
+ * doesn't abort it -- there will be fall-back to following engine
+ * if the one that failed wasn't the last one. */
SUFFICIENT,
/* Like SUFFICIENT with the exception that on failure the reason code
<< dendl;
auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(t),
get_creds_info(t, roles.admin));
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
/* Retrieve token. */
ldout(cct, 0) << "got expired token: " << t.get_project_name()
<< ":" << t.get_user_name()
<< " expired: " << t.get_expires() << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
/* Check for necessary roles. */
token_cache.add(token_id, t);
auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(t),
get_creds_info(t, roles.admin));
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
}
ldout(cct, 0) << "user does not hold a matching role; required roles: "
<< g_conf->rgw_keystone_accepted_roles << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
if (ret < 0) {
ldout(cct, 2) << "s3 keystone: token validation ERROR: "
<< token_body_bl.c_str() << dendl;
- throw -EPERM;
+ throw ret;
}
/* if the supplied signature is wrong, we will get 401 from Keystone */
ldout(cct, 0) << "got expired token: " << t.get_project_name()
<< ":" << t.get_user_name()
<< " expired: " << t.get_expires() << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
/* check if we have a valid role */
ldout(cct, 5) << "s3 keystone: user does not hold a matching role;"
" required roles: "
<< cct->_conf->rgw_keystone_accepted_roles << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
} else {
/* everything seems fine, continue with this user */
ldout(cct, 5) << "s3 keystone: validated token: " << t.get_project_name()
auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(t),
get_creds_info(t, accepted_roles.admin));
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
}
const rgw::auth::s3::AWSv2AuthStrategy strategy(g_ceph_context, store, &extr);
try {
auto result = strategy.authenticate(s);
- auto& applier = result.first;
- if (! applier) {
+ if (result.get_status() != decltype(result)::Status::GRANTED) {
return -EACCES;
}
try {
+ auto applier = result.get_applier();
+
applier->load_acct_info(*s->user);
s->perm_mask = applier->get_perm_mask();
applier->modify_request_state(s);
static const rgw::auth::s3::AWSv2AuthStrategy strategy(g_ceph_context, store);
try {
auto result = strategy.authenticate(s);
- auto& applier = result.first;
- if (! applier) {
- return -EPERM;
+ if (result.get_status() != decltype(result)::Status::GRANTED) {
+ ldout(s->cct, 5) << "Failed the S3 auth strategy, reason="
+ << result.get_reason() << dendl;
+ return result.get_reason();
}
try {
+ auto applier = result.get_applier();
+
applier->load_acct_info(*s->user);
s->perm_mask = applier->get_perm_mask();
applier->modify_request_state(s);
}
if (! base64_token.valid()) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
//TODO: Uncomment, when we have a migration plan in place.
}*/
if (ldh->auth(base64_token.id, base64_token.key) != 0) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
auto apl = apl_factory->create_apl_remote(cct, s, get_acl_strategy(),
get_creds_info(base64_token));
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
const std::string& string_to_sign,
const req_state* const s) const
{
- if (access_key_id.empty() || signature.empty()) {
- ldout(cct, 5) << "access_key_id or signature is empty" << dendl;
- throw -EINVAL;
- }
-
/* get the user info */
RGWUserInfo user_info;
if (rgw_get_user_info_by_access_key(store, access_key_id, user_info) < 0) {
ldout(cct, 5) << "error reading user info, uid=" << access_key_id
<< " can't authenticate" << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny(-ERR_INVALID_ACCESS_KEY);
}
//TODO: Uncomment, when we have a migration plan in place.
/*else {
const auto iter = user_info.access_keys.find(access_key_id);
if (iter == std::end(user_info.access_keys)) {
ldout(cct, 0) << "ERROR: access key not encoded in user info" << dendl;
- throw -EPERM;
+ return result_t::deny(-EPERM);
}
const RGWAccessKey& k = iter->second;
std::string digest;
int ret = rgw_get_s3_header_digest(string_to_sign, k.key, digest);
if (ret < 0) {
- throw -EPERM;
+ return result_t::deny(-EPERM);
}
ldout(cct, 15) << "string_to_sign=" << string_to_sign << dendl;
ldout(cct, 15) << "compare=" << signature.compare(digest) << dendl;
if (signature != digest) {
- throw -ERR_SIGNATURE_NO_MATCH;
+ return result_t::deny(-ERR_SIGNATURE_NO_MATCH);
}
auto apl = apl_factory->create_apl_local(cct, s, user_info, k.subuser);
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
extractor.get_auth_data(s);
if (access_key_id.empty() || signature.empty()) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny(-EINVAL);
} else {
return authenticate(access_key_id, signature, string_to_sign, s);
}
int RGWHandler_REST_SWIFT::authorize()
{
- rgw::auth::IdentityApplier::aplptr_t applier;
- rgw::auth::Completer::cmplptr_t completer;
- std::tie(applier, completer) = auth_strategy.authenticate(s);
-
try {
- if (! applier) {
+ auto result = auth_strategy.authenticate(s);
+
+ if (result.get_status() != decltype(result)::Status::GRANTED) {
/* Access denied is acknowledged by returning a std::unique_ptr with
* nullptr inside. */
ldout(s->cct, 5) << "auth engine refused to authenicate" << dendl;
}
try {
+ rgw::auth::IdentityApplier::aplptr_t applier = result.get_applier();
+
/* Account used by a given RGWOp is decoupled from identity employed
* in the authorization phase (RGWOp::verify_permissions). */
applier->load_acct_info(*s->user);
applier->modify_request_state(s);
s->auth.identity = std::move(applier);
- s->auth.completer = std::move(completer);
+ s->auth.completer = std::move(result.get_completer());
return 0;
} catch (int err) {
TempURLEngine::authenticate(const req_state* const s) const
{
if (! is_applicable(s)) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
const string& temp_url_sig = s->info.args.get("temp_url_sig");
const string& temp_url_expires = s->info.args.get("temp_url_expires");
if (temp_url_sig.empty() || temp_url_expires.empty()) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
RGWUserInfo owner_info;
get_owner_info(s, owner_info);
} catch (...) {
ldout(cct, 5) << "cannot get user_info of account's owner" << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::reject();
}
if (owner_info.temp_url_keys.empty()) {
ldout(cct, 5) << "user does not have temp url key set, aborting" << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::reject();
}
if (is_expired(temp_url_expires)) {
ldout(cct, 5) << "temp url link expired" << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::reject();
}
/* We need to verify two paths because of compliance with Swift, Tempest
if (sig_helper.is_equal_to(temp_url_sig)) {
auto apl = apl_factory->create_apl_turl(cct, s, owner_info);
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
} else {
ldout(s->cct, 5) << "temp url signature mismatch: " << local_sig
<< " != " << temp_url_sig << dendl;
}
}
- return std::make_pair(nullptr, nullptr);
+ return result_t::reject();
}
const req_state* const s) const
{
if (! is_applicable(token)) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
std::string auth_url = g_conf->rgw_swift_auth_url;
",", swift_groups);
if (0 == swift_groups.size()) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
} else {
swift_user = std::move(swift_groups[0]);
}
} catch (std::out_of_range) {
/* The X-Auth-Groups header isn't present in the response. */
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
if (swift_user.empty()) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
ldout(cct, 10) << "swift user=" << swift_user << dendl;
auto apl = apl_factory->create_apl_local(cct, s, tmp_uinfo,
extract_swift_subuser(swift_user));
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
static int build_token(const string& swift_user,
const req_state* const s) const
{
if (! is_applicable(token)) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
/* Effective token string is the part after the prefix. */
ldout(cct, 0) << "NOTICE: old timed out token was used now=" << now
<< " token.expiration=" << expiration
<< dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
RGWUserInfo user_info;
const auto siter = user_info.swift_keys.find(swift_user);
if (siter == std::end(user_info.swift_keys)) {
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
const auto swift_key = siter->second;
<< " tok_bl.length()=" << tok_bl.length()
<< " local_tok_bl.length()=" << local_tok_bl.length()
<< dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
if (memcmp(local_tok_bl.c_str(), tok_bl.c_str(),
local_tok_bl.length(), buf);
ldout(cct, 0) << "NOTICE: tokens mismatch tok=" << buf << dendl;
- return std::make_pair(nullptr, nullptr);
+ return result_t::deny();
}
auto apl = apl_factory->create_apl_local(cct, s, user_info,
extract_swift_subuser(swift_user));
- return std::make_pair(std::move(apl), nullptr);
+ return result_t::grant(std::move(apl));
}
} /* namespace swift */