default: 11
services:
- rgw
+
- name: rgw_usage_log_key_transition
type: bool
level: advanced
desc: When not empty, this value is returned as a response header Access-Control-Expose-Headers.
services:
- rgw
+
+- name: rgw_copious_policy_logging
+ type: bool
+ level: advanced
+ desc: Log the details of each policy evaluation in extreme verbosity
+ default: false
+ services:
+ - rgw
+ flags:
+ - runtime
+ with_legacy: true
rgw::IAM::Environment env;
const auto arn_it = env.emplace("aws:SourceArn", std::move(source_bucket_arn));
const auto acct_it = env.emplace("aws:SourceAccount", std::move(source_account));
- if (policy.eval(env, ident, rgw::IAM::s3PutObject, target_resource_arn) != rgw::IAM::Effect::Allow) {
+ if (policy.eval(dpp, env, ident, rgw::IAM::s3PutObject, target_resource_arn) != rgw::IAM::Effect::Allow) {
ldpp_dout(dpp, 1) << "ERROR: logging bucket: '" << target_bucket_id <<
"' must have a bucket policy that allows logging service principal to put objects in the following resource ARN: '" <<
target_resource_arn.to_string() << "' from source bucket ARN: '" << arn_it->second <<
const uint64_t op,
const ARN& resource,
boost::optional<rgw::IAM::PolicyPrincipal&> princ_type=boost::none) {
- if (!policy)
+ if (!policy) {
return Effect::Pass;
- else
- return policy->eval(env, id, op, resource, princ_type);
+ } else {
+ return policy->eval(dpp, env, id, op, resource, princ_type);
+ }
}
Effect eval_identity_or_session_policies(const DoutPrefixProvider* dpp,
// If RestrictPublicBuckets is enabled and the bucket policy allows public access,
// deny the request if the requester is not in the bucket owner account
if (s->public_access_block.RestrictPublicBuckets &&
- bucket_policy && rgw::IAM::is_public(*bucket_policy) &&
+ bucket_policy && rgw::IAM::is_public(dpp, *bucket_policy) &&
!s->identity->is_owner_of(s->bucket_info.owner)) {
ldpp_dout(dpp, 10) << __func__ << ": public policies are blocked by the RestrictPublicBuckets block public access setting" << dendl;
return false;
// If RestrictPublicBuckets is enabled and the bucket policy allows public access,
// deny the request if the requester is not in the bucket owner account
if (ps->public_access_block.RestrictPublicBuckets &&
- bucket_policy && rgw::IAM::is_public(*bucket_policy) &&
+ bucket_policy && rgw::IAM::is_public(dpp, *bucket_policy) &&
!ps->identity->is_owner_of(ps->bucket_info.owner)) {
ldpp_dout(dpp, 10) << __func__ << ": public policies are blocked by the RestrictPublicBuckets block public access setting" << dendl;
return false;
// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
// vim: ts=8 sw=2 sts=2 expandtab ft=cpp
-
#include <cstring>
#include <iostream>
#include <regex>
#include <sstream>
-#include <stack>
#include <utility>
+#include <fmt/ranges.h>
+#include <fmt/std.h>
+
#include <arpa/inet.h>
#include <experimental/iterator>
#include "rapidjson/reader.h"
-#include "include/expected.hpp"
-
#include "rgw_auth.h"
#include "rgw_iam_policy.h"
-namespace {
-constexpr int dout_subsys = ceph_subsys_rgw;
-}
+inline constexpr int dout_subsys = ceph_subsys_rgw;
using std::dec;
using std::hex;
using rgw::auth::Principal;
+using namespace std::literals;
+
+template<typename T>
+struct fmt::formatter<boost::optional<const T&>> : fmt::formatter<T> {
+ template<typename FormatContext>
+ auto format(const boost::optional<const T&>& opt, FormatContext& ctx) const {
+ if (opt) {
+ return fmt::formatter<T>::format(*opt, ctx);
+ }
+ return fmt::format_to(ctx.out(), "--");
+ }
+};
+
namespace rgw {
namespace IAM {
#include "rgw_iam_policy_keywords.frag.cc"
const uint64_t bit;
};
-
-
static const actpair actpairs[] =
{{ "s3:AbortMultipartUpload", s3AbortMultipartUpload },
{ "s3:CreateBucket", s3CreateBucket },
{ "organizations:ListTargetsForPolicy", organizationsListTargetsForPolicy},
};
+namespace {
+const char* condop_string(const TokenID t) {
+ switch (t) {
+ case TokenID::StringEquals:
+ return "StringEquals";
+
+ case TokenID::StringNotEquals:
+ return "StringNotEquals";
+
+ case TokenID::StringEqualsIgnoreCase:
+ return "StringEqualsIgnoreCase";
+
+ case TokenID::StringNotEqualsIgnoreCase:
+ return "StringNotEqualsIgnoreCase";
+
+ case TokenID::StringLike:
+ return "StringLike";
+
+ case TokenID::StringNotLike:
+ return "StringNotLike";
+
+ // Numeric!
+ case TokenID::NumericEquals:
+ return "NumericEquals";
+
+ case TokenID::NumericNotEquals:
+ return "NumericNotEquals";
+
+ case TokenID::NumericLessThan:
+ return "NumericLessThan";
+
+ case TokenID::NumericLessThanEquals:
+ return "NumericLessThanEquals";
+
+ case TokenID::NumericGreaterThan:
+ return "NumericGreaterThan";
+
+ case TokenID::NumericGreaterThanEquals:
+ return "NumericGreaterThanEquals";
+
+ case TokenID::DateEquals:
+ return "DateEquals";
+
+ case TokenID::DateNotEquals:
+ return "DateNotEquals";
+
+ case TokenID::DateLessThan:
+ return "DateLessThan";
+
+ case TokenID::DateLessThanEquals:
+ return "DateLessThanEquals";
+
+ case TokenID::DateGreaterThan:
+ return "DateGreaterThan";
+
+ case TokenID::DateGreaterThanEquals:
+ return "DateGreaterThanEquals";
+
+ case TokenID::Bool:
+ return "Bool";
+
+ case TokenID::BinaryEquals:
+ return "BinaryEquals";
+
+ case TokenID::IpAddress:
+ return "IpAddress";
+
+ case TokenID::NotIpAddress:
+ return "NotIpAddress";
+
+ case TokenID::ArnEquals:
+ return "ArnEquals";
+
+ case TokenID::ArnNotEquals:
+ return "ArnNotEquals";
+
+ case TokenID::ArnLike:
+ return "ArnLike";
+
+ case TokenID::ArnNotLike:
+ return "ArnNotLike";
+
+ case TokenID::Null:
+ return "Null";
+
+ default:
+ return "InvalidConditionOperator";
+ }
+}
+}
+
+template <typename T>
+inline std::ostream&
+operator<<(std::ostream& out, const boost::optional<T>& t)
+{
+ if (!t)
+ out << "--";
+ else
+ out << ' ' << *t;
+ return out;
+}
+
struct PolicyParser;
const Keyword top[1]{{"<Top>", TokenKind::pseudo, TokenID::Top, 0, false,
return match_policy(pattern, input, MATCH_POLICY_ARN);
}
-bool Condition::eval(const Environment& env) const {
+bool Condition::eval(const Environment& env, const LogOut& eval_log) const {
std::vector<std::string> runtime_vals;
auto i = env.find(key);
if (op == TokenID::Null) {
const std::string value = (i == env.end() ? "true" : "false");
- return typed_any(std::equal_to<bool>{}, as_bool, value, vals);
+ eval_log.format("Null check. key `{}` {}.", key,
+ i == env.end() ? "is not present"sv :
+ "is present"sv);
+ return typed_any(std::equal_to<bool>{}, as_bool, value, vals, eval_log);
}
if (i == env.end()) {
if (op == TokenID::ForAllValuesStringEquals ||
op == TokenID::ForAllValuesStringEqualsIgnoreCase ||
op == TokenID::ForAllValuesStringLike) {
+ eval_log.format(
+ "Evaluating `{}` when key `{}` is not present. Vacuously true.",
+ condop_string(op), key);
return true;
} else {
+ eval_log.format("Evaluating `{}` when key `{}` is not present. Returning {}.",
+ condop_string(op), key, ifexists ? "true" : "false");
return ifexists;
}
}
}
}
const auto& s = i->second;
-
- const auto& itr = env.equal_range(key);
-
+ const auto itr = env.equal_range(key);
+ // Printable range because for fmt::format.
+ const std::ranges::subrange prange(itr.first, itr.second);
+ eval_log.format("Evaluating `{}` for `[{}]` against `[{}]`",
+ condop_string(op), fmt::join(prange, ","),
+ fmt::join(isruntime ? runtime_vals : vals, ","));
switch (op) {
// String!
case TokenID::ForAnyValueStringEquals:
case TokenID::StringEquals:
- return multimap_any(std::equal_to<std::string>(), itr, isruntime? runtime_vals : vals);
+ return multimap_any(std::equal_to<std::string>(), itr,
+ isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::StringNotEquals:
- return multimap_none(std::equal_to<std::string>(),
- itr, isruntime? runtime_vals : vals);
+ return multimap_none(std::equal_to<std::string>(), itr,
+ isruntime ? runtime_vals : vals, eval_log);
case TokenID::ForAnyValueStringEqualsIgnoreCase:
case TokenID::StringEqualsIgnoreCase:
- return multimap_any(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+ return multimap_any(ci_equal_to(), itr, isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::StringNotEqualsIgnoreCase:
- return multimap_none(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+ return multimap_none(ci_equal_to(), itr, isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::ForAnyValueStringLike:
case TokenID::StringLike:
- return multimap_any(string_like(), itr, isruntime? runtime_vals : vals);
+ return multimap_any(string_like(), itr, isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::StringNotLike:
- return multimap_none(string_like(), itr, isruntime? runtime_vals : vals);
+ return multimap_none(string_like(), itr, isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::ForAllValuesStringEquals:
- return multimap_all(std::equal_to<std::string>(), itr, isruntime? runtime_vals : vals);
+ return multimap_all(std::equal_to<std::string>(), itr,
+ isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::ForAllValuesStringLike:
- return multimap_all(string_like(), itr, isruntime? runtime_vals : vals);
+ return multimap_all(string_like(), itr, isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::ForAllValuesStringEqualsIgnoreCase:
- return multimap_all(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+ return multimap_all(ci_equal_to(), itr, isruntime ? runtime_vals : vals,
+ eval_log);
// Numeric
case TokenID::NumericEquals:
- return typed_any(std::equal_to<double>(), as_number, s, vals);
+ return typed_any(std::equal_to<double>(), as_number, s, vals, eval_log);
case TokenID::NumericNotEquals:
- return typed_none(std::equal_to<double>(),
- as_number, s, vals);
+ return typed_none(std::equal_to<double>(), as_number, s, vals, eval_log);
case TokenID::NumericLessThan:
- return typed_any(std::less<double>(), as_number, s, vals);
+ return typed_any(std::less<double>(), as_number, s, vals, eval_log);
case TokenID::NumericLessThanEquals:
- return typed_any(std::less_equal<double>(), as_number, s, vals);
+ return typed_any(std::less_equal<double>(), as_number, s, vals, eval_log);
case TokenID::NumericGreaterThan:
- return typed_any(std::greater<double>(), as_number, s, vals);
+ return typed_any(std::greater<double>(), as_number, s, vals, eval_log);
case TokenID::NumericGreaterThanEquals:
- return typed_any(std::greater_equal<double>(), as_number, s,
- vals);
+ return typed_any(std::greater_equal<double>(), as_number, s, vals,
+ eval_log);
// Date!
case TokenID::DateEquals:
- return typed_any(std::equal_to<ceph::real_time>(), as_date, s, vals);
+ return typed_any(std::equal_to<ceph::real_time>(), as_date, s, vals,
+ eval_log);
case TokenID::DateNotEquals:
return typed_none(std::equal_to<ceph::real_time>(),
- as_date, s, vals);
+ as_date, s, vals, eval_log);
case TokenID::DateLessThan:
- return typed_any(std::less<ceph::real_time>(), as_date, s, vals);
+ return typed_any(std::less<ceph::real_time>(), as_date, s, vals, eval_log);
case TokenID::DateLessThanEquals:
- return typed_any(std::less_equal<ceph::real_time>(), as_date, s, vals);
+ return typed_any(std::less_equal<ceph::real_time>(), as_date, s, vals,
+ eval_log);
case TokenID::DateGreaterThan:
- return typed_any(std::greater<ceph::real_time>(), as_date, s, vals);
+ return typed_any(std::greater<ceph::real_time>(), as_date, s, vals,
+ eval_log);
case TokenID::DateGreaterThanEquals:
return typed_any(std::greater_equal<ceph::real_time>(), as_date, s,
- vals);
+ vals, eval_log);
// Bool!
case TokenID::Bool:
- return typed_any(std::equal_to<bool>(), as_bool, s, vals);
+ return typed_any(std::equal_to<bool>(), as_bool, s, vals, eval_log);
// Binary!
case TokenID::BinaryEquals:
return typed_any(std::equal_to<ceph::bufferlist>(), as_binary, s,
- vals);
+ vals, eval_log);
// IP Address!
case TokenID::IpAddress:
- return typed_any(std::equal_to<MaskedIP>(), as_network, s, vals);
+ return typed_any(std::equal_to<MaskedIP>(), as_network, s, vals, eval_log);
case TokenID::NotIpAddress:
return typed_none(std::equal_to<MaskedIP>(),
- as_network, s, vals);
+ as_network, s, vals, eval_log);
// Amazon Resource Names!
// The ArnEquals and ArnLike condition operators behave identically.
case TokenID::ArnEquals:
case TokenID::ArnLike:
- return multimap_any(arn_like, itr, isruntime? runtime_vals : vals);
+ return multimap_any(arn_like, itr, isruntime ? runtime_vals : vals,
+ eval_log);
case TokenID::ArnNotEquals:
case TokenID::ArnNotLike:
- return multimap_none(arn_like, itr, isruntime? runtime_vals : vals);
+ return multimap_none(arn_like, itr, isruntime ? runtime_vals : vals,
+ eval_log);
default:
+ eval_log.format("Unknown operation: Returning false.");
return false;
}
}
return m;
}
-namespace {
-const char* condop_string(const TokenID t) {
- switch (t) {
- case TokenID::StringEquals:
- return "StringEquals";
-
- case TokenID::StringNotEquals:
- return "StringNotEquals";
-
- case TokenID::StringEqualsIgnoreCase:
- return "StringEqualsIgnoreCase";
-
- case TokenID::StringNotEqualsIgnoreCase:
- return "StringNotEqualsIgnoreCase";
-
- case TokenID::StringLike:
- return "StringLike";
-
- case TokenID::StringNotLike:
- return "StringNotLike";
-
- // Numeric!
- case TokenID::NumericEquals:
- return "NumericEquals";
-
- case TokenID::NumericNotEquals:
- return "NumericNotEquals";
-
- case TokenID::NumericLessThan:
- return "NumericLessThan";
-
- case TokenID::NumericLessThanEquals:
- return "NumericLessThanEquals";
-
- case TokenID::NumericGreaterThan:
- return "NumericGreaterThan";
-
- case TokenID::NumericGreaterThanEquals:
- return "NumericGreaterThanEquals";
-
- case TokenID::DateEquals:
- return "DateEquals";
-
- case TokenID::DateNotEquals:
- return "DateNotEquals";
-
- case TokenID::DateLessThan:
- return "DateLessThan";
-
- case TokenID::DateLessThanEquals:
- return "DateLessThanEquals";
-
- case TokenID::DateGreaterThan:
- return "DateGreaterThan";
-
- case TokenID::DateGreaterThanEquals:
- return "DateGreaterThanEquals";
-
- case TokenID::Bool:
- return "Bool";
-
- case TokenID::BinaryEquals:
- return "BinaryEquals";
-
- case TokenID::IpAddress:
- return "case TokenID::IpAddress";
-
- case TokenID::NotIpAddress:
- return "NotIpAddress";
-
- case TokenID::ArnEquals:
- return "ArnEquals";
-
- case TokenID::ArnNotEquals:
- return "ArnNotEquals";
-
- case TokenID::ArnLike:
- return "ArnLike";
-
- case TokenID::ArnNotLike:
- return "ArnNotLike";
-
- case TokenID::Null:
- return "Null";
-
- default:
- return "InvalidConditionOperator";
- }
-}
-
template<typename Iterator>
ostream& print_array(ostream& m, Iterator begin, Iterator end) {
if (begin == end) {
return m;
}
-}
-
ostream& operator <<(ostream& m, const Condition& c) {
m << condop_string(c.op);
if (c.ifexists) {
return m << " }";
}
-Effect Statement::eval(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida,
- uint64_t act, boost::optional<const ARN&> res, boost::optional<PolicyPrincipal&> princ_type) const {
-
- if (eval_principal(e, ida, princ_type) == Effect::Deny) {
+Effect
+Statement::eval(
+ const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ uint64_t act,
+ boost::optional<const ARN&> res,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type) const {
+
+ if (eval_principal(e, ida, eval_log, princ_type) == Effect::Deny) {
+ eval_log.format("Passing.");
return Effect::Pass;
}
if (res && resource.empty() && notresource.empty()) {
+ eval_log.format("A resource was specified but both Resource and NotResource "
+ "were empty. Passing.");
return Effect::Pass;
}
if (!res && (!resource.empty() || !notresource.empty())) {
+ eval_log.format("No resource was specified, but Resource or "
+ "NotResource was non-empty. Passing.");
return Effect::Pass;
}
if (!resource.empty() && res) {
- if (!std::any_of(resource.begin(), resource.end(),
- [&res](const ARN& pattern) {
- return pattern.match(*res);
- })) {
+ eval_log.format("Checking Resource for {}:", res);
+ if (!std::any_of(
+ resource.begin(), resource.end(),
+ [&res, &eval_log](const ARN& pattern) {
+ if (pattern.match(*res)) {
+ eval_log.format("Matched `{}`.", pattern);
+ return true;
+ }
+ return false;
+ })) {
+ eval_log.format("No match in Resource. Passing.");
return Effect::Pass;
}
} else if (!notresource.empty() && res) {
- if (std::any_of(notresource.begin(), notresource.end(),
- [&res](const ARN& pattern) {
- return pattern.match(*res);
+ eval_log.format("Checking NotResource for {}:", res);
+ if (std::any_of(
+ notresource.begin(), notresource.end(),
+ [&res, &eval_log](const ARN& pattern) {
+ if (pattern.match(*res)) {
+ eval_log.format("Matched `{}`.", pattern);
+ return true;
+ }
+ return false;
})) {
+ eval_log.format("Match found in NotResource. Passing.");
return Effect::Pass;
}
}
- if (!(action[act] == 1) || (notaction[act] == 1)) {
+ if (!(action[act] == 1)) {
+ eval_log.format("{} not found in Action. Passing.",
+ action_bit_string(action_t(act)));
return Effect::Pass;
}
- if (std::all_of(conditions.begin(),
- conditions.end(),
- [&e](const Condition& c) { return c.eval(e);})) {
+ if (notaction[act] == 1) {
+ eval_log.format("{} found in NotAction. Passing.",
+ action_bit_string(action_t(act)));
+ return Effect::Pass;
+ }
+
+ eval_log.format("Evaluating conditions:");
+ if (std::all_of(conditions.begin(), conditions.end(),
+ [&e, &eval_log](const Condition& c) {
+ return c.eval(e, eval_log);
+ })) {
+ eval_log.format("Returning {}.", effect);
return effect;
}
+ eval_log.format("Passing.");
return Effect::Pass;
}
});
}
-Effect Statement::eval_principal(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type) const {
+Effect
+Statement::eval_principal(
+ const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type) const {
if (princ_type) {
*princ_type = PolicyPrincipal::Other;
}
+ eval_log.format("Evaluating identity `{}`:", ida);
if (ida) {
if (princ.empty() && noprinc.empty()) {
+ eval_log.format("Principal empty and NotPrincipal empty: Denying.");
return Effect::Deny;
}
- if (ida->get_identity_type() != TYPE_ROLE && !princ.empty() && !is_identity(*ida, princ)) {
+ if (ida->get_identity_type() != TYPE_ROLE &&
+ !princ.empty() && !is_identity(*ida, princ)) {
+ eval_log.format("Identity is not role, Principal is not empty, and "
+ "the identity is not in Principal.");
return Effect::Deny;
}
+ eval_log.format("Checking type of Principal match.");
if (ida->get_identity_type() == TYPE_ROLE && !princ.empty()) {
bool princ_matched = false;
- for (auto p : princ) { // Check each principal to determine the type of the one that has matched
+ // Check each principal to determine the type of the one that has matched
+ for (auto p : princ) {
if (ida->is_identity(p)) {
if (p.is_assumed_role() || p.is_user()) {
- if (princ_type) *princ_type = PolicyPrincipal::Session;
+ if (princ_type) {
+ eval_log.format("Setting principal type to Session.");
+ *princ_type = PolicyPrincipal::Session;
+ }
} else {
- if (princ_type) *princ_type = PolicyPrincipal::Role;
+ if (princ_type) {
+ eval_log.format("Setting principal type to Role.");
+ *princ_type = PolicyPrincipal::Role;
+ }
}
princ_matched = true;
}
}
if (!princ_matched) {
+ eval_log.format("No match in Principal, Denying.");
return Effect::Deny;
}
} else if (!noprinc.empty() && is_identity(*ida, noprinc)) {
+ eval_log.format("Match in NotPrincipal, Denying.");
return Effect::Deny;
}
}
return Effect::Allow;
}
-Effect Statement::eval_conditions(const Environment& e) const {
- if (std::all_of(conditions.begin(),
- conditions.end(),
- [&e](const Condition& c) { return c.eval(e);})) {
- return Effect::Allow;
- }
+Effect Statement::eval_conditions(const Environment& e,
+ const LogOut& eval_log) const {
+ if (std::all_of(
+ conditions.begin(), conditions.end(),
+ [&e, &eval_log](const Condition& c) {
+ eval_log.format("Evaluating condition `{}`:", c);
+ return c.eval(e, eval_log);
+ })) {
+ eval_log.format("Returning Allow.");
+ return Effect::Allow;
+ }
+
+ eval_log.format("Returning Deny.");
return Effect::Deny;
}
-const char* action_bit_string(action_t action) {
+std::string_view action_bit_string(action_t action) {
switch (action) {
case s3GetObject:
return "s3:GetObject";
return "organizations:ListTargetsForPolicy";
case s3All:
+ return "s3:*";
+
case s3objectlambdaAll:
+ return "s3-object-lambda:*";
+
case iamAll:
+ return "iam:*";
+
case stsAll:
+ return "sts:*";
+
case snsAll:
+ return "sns:*";
+
case organizationsAll:
+ return "organizations:*";
+
case allCount:
return "{invalidSentinel}";
}
}
Effect Policy::eval(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida,
- std::uint64_t action, boost::optional<const ARN&> resource,
- boost::optional<PolicyPrincipal&> princ_type) const {
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action,
+ boost::optional<const ARN&> resource,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type) const
+{
auto allowed = false;
+ eval_log.format("Evaluating policy:\n```\n{}\n```\n"
+ "Environment: {}\n"
+ "Principal: {}\n"
+ "Action: {}\n"
+ "Resource: {}",
+ text, e, ida, action_bit_string(action_t(action)),
+ resource);
+
for (auto& s : statements) {
- auto g = s.eval(e, ida, action, resource, princ_type);
+ eval_log.format("Evaluating statement `{}`:", s);
+ auto g = s.eval(e, ida, action, resource, eval_log, princ_type);
if (g == Effect::Deny) {
+ eval_log.format("Denying.");
return g;
} else if (g == Effect::Allow) {
allowed = true;
}
}
+ eval_log.format("{}", allowed ? "Allowing." : "Passing.");
return allowed ? Effect::Allow : Effect::Pass;
}
-Effect Policy::eval_principal(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type) const {
+Effect
+Policy::eval_principal(
+ const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type) const {
auto allowed = false;
for (auto& s : statements) {
- auto g = s.eval_principal(e, ida, princ_type);
+ eval_log.format("Evaluating identity `{}` in statement `{}`:", ida, s);
+ auto g = s.eval_principal(e, ida, eval_log, princ_type);
if (g == Effect::Deny) {
+ eval_log.format("Denying.");
return g;
} else if (g == Effect::Allow) {
allowed = true;
}
}
+ eval_log.format("{}", allowed ? "Allowing." : "Denying.");
return allowed ? Effect::Allow : Effect::Deny;
}
-Effect Policy::eval_conditions(const Environment& e) const {
+Effect Policy::eval_conditions(const Environment& e,
+ const LogOut& eval_log) const {
auto allowed = false;
for (auto& s : statements) {
- auto g = s.eval_conditions(e);
+ eval_log.format("Evaluating conditions in statement `{}`", s);
+ auto g = s.eval_conditions(e, eval_log);
if (g == Effect::Deny) {
+ eval_log.format("Denying.");
return g;
} else if (g == Effect::Allow) {
allowed = true;
}
}
+ eval_log.format("{}", allowed ? "Allowing." : "Denying.");
return allowed ? Effect::Allow : Effect::Deny;
}
struct IsPublicStatement
{
- bool operator() (const Statement &s) const {
+ const LogOut& eval_log;
+
+ IsPublicStatement(const LogOut& eval_log) :
+ eval_log(eval_log)
+ {}
+
+ bool operator()(const Statement& s) const {
if (s.effect == Effect::Allow) {
for (const auto& p : s.princ) {
if (p.is_wildcard()) {
- return s.eval_conditions(iam_all_env) == Effect::Allow;
+ eval_log.format("Evaluating conditions in statement `{}`:", s);
+ return s.eval_conditions(iam_all_env, eval_log) == Effect::Allow;
}
}
}
};
-bool is_public(const Policy& p)
+bool is_public(const Policy& p, const LogOut& eval_log)
{
- return std::any_of(p.statements.begin(), p.statements.end(), IsPublicStatement());
+ return std::any_of(p.statements.begin(), p.statements.end(),
+ IsPublicStatement(eval_log));
}
-
} // namespace IAM
} // namespace rgw
#include <chrono>
#include <cstdint>
#include <iostream>
+#include <sstream>
#include <string>
#include <string_view>
+#include <variant>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/container/flat_map.hpp>
#include <boost/variant.hpp>
#include <fmt/format.h>
+#include <fmt/ostream.h>
#include "common/ceph_time.h"
#include "common/iso_8601.h"
}
}
-const char* action_bit_string(action_t action);
+std::string_view action_bit_string(action_t action);
enum class PolicyPrincipal {
Role,
return (l.addr >> shift) == (r.addr >> shift);
}
+class LogOut {
+ template <class... Ts>
+ struct overloaded : Ts... {
+ using Ts::operator()...;
+ };
+
+ mutable std::variant<std::monostate, std::ostream*, const DoutPrefixProvider*> log;
+ std::string prefix;
+
+public:
+ LogOut() : log(std::monostate{}) {}
+ LogOut(std::nullptr_t) : log(std::monostate{}) {}
+ LogOut(std::ostream& m) : log(&m) {}
+ LogOut(const DoutPrefixProvider* dpp)
+ : log(dpp) {
+ if (!dpp || !dpp->get_cct()->_conf->rgw_copious_policy_logging) {
+ log = std::monostate{};
+ } else {
+ std::ostringstream ostr;
+ dpp->gen_prefix(ostr);
+ prefix = std::move(ostr).str();
+ }
+ }
+
+ operator bool() const { return !std::holds_alternative<std::monostate>(log); };
+ void vformat(fmt::string_view fmt, fmt::format_args args) const {
+ std::visit(
+ overloaded{
+ [](std::monostate) {},
+ [&fmt, &args](std::ostream* o) {
+ assert(o); // We're constructing from a reference so `o` can't be null.
+ fmt::vprint(*o, fmt, args);
+ *o << std::endl;
+ },
+ [&fmt, &args, this](const DoutPrefixProvider* dpp) {
+ assert(dpp); // If we're given null, we switch to monostate
+
+ // The user has explicitly turned on policy logging, so
+ // we don't bother with the conditional logging
+ // business.
+ ceph::logging::StringEntry e(0, ceph_subsys_rgw, prefix);
+ fmt::vformat_to(e.get_inserter(), fmt, args);
+ dpp->get_cct()->_log->submit_entry(std::move(e));
+ return;
+ }
+ }, log);
+ }
+ template <typename... Args>
+ void format(fmt::format_string<Args...> fmt, Args&&... args) const
+ {
+ vformat(fmt, fmt::make_format_args(args...));
+ }
+};
+
+
struct Condition {
TokenID op;
// Originally I was going to use a perfect hash table, but Marcus
Condition() = default;
Condition(TokenID op, const char* s, std::size_t len, bool ifexists)
: op(op), key(s, len), ifexists(ifexists) {}
-
- bool eval(const Environment& e) const;
+ bool eval(const Environment& e, const LogOut& eval_log) const;
static boost::optional<double> as_number(const std::string& s) {
std::size_t p = 0;
}
};
- using unordered_multimap_it_pair = std::pair <std::unordered_multimap<std::string,std::string>::const_iterator, std::unordered_multimap<std::string,std::string>::const_iterator>;
+ using unordered_multimap_it_pair = std::pair<
+ std::unordered_multimap<std::string, std::string>::const_iterator,
+ std::unordered_multimap<std::string, std::string>::const_iterator>;
template<typename F>
- static bool multimap_all(F&& f, const unordered_multimap_it_pair& it,
- const std::vector<std::string>& v) {
+ static bool multimap_all(F&& f,
+ const unordered_multimap_it_pair& it,
+ const std::vector<std::string>& v,
+ const LogOut& eval_log) {
for (auto itr = it.first; itr != it.second; itr++) {
bool matched = false;
+ std::string failmatch;
for (const auto& d : v) {
if (f(itr->second, d)) {
- matched = true;
+ matched = true;
+ } else if (eval_log && failmatch.empty()) {
+ failmatch = fmt::format("({}, {})", itr->second, d);
+ }
+ }
+ if (!matched) {
+ if (failmatch.empty()) {
+ eval_log.format("Values matched against were empty.");
+ } else {
+ eval_log.format("Predicate false for {}", failmatch);
+ }
+ return false;
}
- }
- if (!matched)
- return false;
}
return true;
}
- template<typename F>
- static bool multimap_any(F&& f, const unordered_multimap_it_pair& it,
- const std::vector<std::string>& v) {
+ template <typename F>
+ static bool
+ multimap_any(F&& f,
+ const unordered_multimap_it_pair& it,
+ const std::vector<std::string>& v,
+ const LogOut& eval_log) {
for (auto itr = it.first; itr != it.second; itr++) {
for (const auto& d : v) {
if (f(itr->second, d)) {
- return true;
+ eval_log.format("Predicate true for ({}, {})", itr->second, d);
+ return true;
+ }
}
- }
}
+ eval_log.format("No predicate true. Returning false.");
return false;
}
template<typename F>
static bool multimap_none(F&& f, const unordered_multimap_it_pair& it,
- const std::vector<std::string>& v) {
+ const std::vector<std::string>& v,
+ const LogOut& eval_log) {
for (auto itr = it.first; itr != it.second; itr++) {
for (const auto& d : v) {
if (f(itr->second, d)) {
+ eval_log.format("Predicate true for ({}, {})", itr->second, d);
return false;
}
}
}
+ eval_log.format("No predicate true. Returning true.");
return true;
}
template<typename F, typename X>
static bool typed_any(F&& f, X&& x, const std::string& c,
- const std::vector<std::string>& v) {
- auto xc = std::forward<X>(x)(c);
+ const std::vector<std::string>& v,
+ const LogOut& eval_log) {
+ auto xc = x(c);
if (!xc) {
+ eval_log.format("Failed to convert `{}`. Returning false.", c);
return false;
}
for (const auto& d : v) {
auto xd = x(d);
if (!xd) {
+ eval_log.format("Failed to convert `{}`. Skipping.", d);
continue;
}
if (f(*xc, *xd)) {
+ eval_log.format("Predicate true for ({}, {})", *xc, *xd);
return true;
}
}
+ eval_log.format("No predicate true for `{}`. Returning false.", *xc);
return false;
}
template<typename F, typename X>
static bool typed_none(F&& f, X&& x, const std::string& c,
- const std::vector<std::string>& v) {
- auto xc = std::forward<X>(x)(c);
+ const std::vector<std::string>& v,
+ const LogOut& eval_log) {
+ auto xc = x(c);
if (!xc) {
+ eval_log.format("Failed to convert `{}`. Returning false.", c);
return false;
}
for (const auto& d : v) {
auto xd = x(d);
if (!xd) {
+ eval_log.format("Failed to convert `{}`. Skipping.", d);
continue;
}
if (f(*xc, *xd)) {
+ eval_log.format("Predicate true for ({}, {})", *xc, *xd);
return false;
}
}
+ eval_log.format("No predicate true for `{}`. Returning true.", *xc);
return true;
}
std::vector<Condition> conditions;
Effect eval(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida,
- std::uint64_t action, boost::optional<const ARN&> resource, boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
-
- Effect eval_principal(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
-
- Effect eval_conditions(const Environment& e) const;
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action,
+ boost::optional<const ARN&> resource,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+
+ Effect eval_principal(
+ const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type = boost::none) const;
+
+ Effect
+ eval_conditions(const Environment& e,
+ const LogOut& eval_log) const;
};
std::ostream& operator <<(std::ostream& m, const Statement& s);
std::string text,
bool reject_invalid_principals);
+ Effect eval(std::ostream& out, const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action,
+ boost::optional<const ARN&> resource,
+ boost::optional<PolicyPrincipal&> princ_type = boost::none) const {
+ return eval(e, ida, action, resource, out, princ_type);
+ }
+
Effect eval(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida,
- std::uint64_t action, boost::optional<const ARN&> resource, boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action,
+ boost::optional<const ARN&> resource,
+ boost::optional<PolicyPrincipal&> princ_type = boost::none) const {
+ return eval(e, ida, action, resource, {}, princ_type);
+ }
+
+ Effect eval(const DoutPrefixProvider* dpp,
+ const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action,
+ boost::optional<const ARN&> resource,
+ boost::optional<PolicyPrincipal&> princ_type = boost::none) const {
+ return eval(e, ida, action, resource, dpp, princ_type);
+ }
- Effect eval_principal(const Environment& e,
- boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+ Effect eval_principal(
+ const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type = boost::none) const;
- Effect eval_conditions(const Environment& e) const;
+ Effect eval_conditions(const Environment& e,
+ const LogOut& eval_log) const;
template <typename F>
bool has_conditional(const std::string& conditional, F p) const {
bool has_partial_conditional_value(const std::string& c) const {
return has_conditional_value(c, Condition::ci_starts_with());
}
+
+private:
+
+ Effect eval(const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action,
+ boost::optional<const ARN&> resource,
+ const LogOut& eval_log,
+ boost::optional<PolicyPrincipal&> princ_type =boost::none) const;
+
};
std::ostream& operator <<(std::ostream& m, const Policy& p);
-bool is_public(const Policy& p);
+bool is_public(const Policy& p, const LogOut& eval_log);
+inline bool is_public(const DoutPrefixProvider* dpp, const Policy& p)
+{
+ bool b = false;
+ if (dpp) {
+ bool copious_logging = dpp->get_cct()->_conf->rgw_copious_policy_logging;
+ b = is_public(p, {copious_logging ? dpp : nullptr});
+ } else {
+ b = is_public(p, nullptr);
+ }
+ return b;
}
}
+}
+
+template <> struct fmt::formatter<rgw::IAM::MaskedIP> : ostream_formatter {};
+template <> struct fmt::formatter<rgw::IAM::Condition> : ostream_formatter {};
+template <> struct fmt::formatter<rgw::IAM::Statement> : ostream_formatter {};
+template <> struct fmt::formatter<rgw::IAM::Policy> : ostream_formatter {};
#pragma once
-namespace rgw {
-namespace IAM {
+#include <string_view>
+
+#include <fmt/format.h>
+
+namespace rgw::IAM {
enum class TokenKind {
pseudo, top, statement, cond_op, cond_key, version_key, effect_key,
Pass
};
+inline std::string_view to_string(Effect e)
+{
+ using enum Effect;
+ switch (e) {
+ case Allow:
+ return "Allow";
+ case Pass:
+ return "Pass";
+ case Deny:
+ return "Deny";
+ }
+ return "Unknown Effect";
+}
+
+inline std::ostream& operator <<(std::ostream& m, const Effect& e)
+{
+ return m << to_string(e);
+}
+
enum class Type {
string,
number,
arn,
null
};
-}
-}
+} // namespace rgw::IAM
+
+template<>
+struct fmt::formatter<rgw::IAM::Effect> : formatter<std::string_view> {
+ template<typename FormatContext>
+ auto format(const rgw::IAM::Effect& e, FormatContext& ctx) const {
+ auto s = to_string(e);
+ return formatter<std::string_view>::format(s, ctx);
+ }
+};
s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
rgw::sal::Attrs attrs(s->bucket_attrs);
if (s->public_access_block.BlockPublicPolicy &&
- rgw::IAM::is_public(p)) {
+ rgw::IAM::is_public(this, p)) {
op_ret = -EACCES;
return;
}
void RGWGetBucketPolicyStatus::execute(optional_yield y)
{
- isPublic = (s->iam_policy && rgw::IAM::is_public(*s->iam_policy)) || s->bucket_acl.is_public(this);
+ isPublic = (s->iam_policy && rgw::IAM::is_public(this, *s->iam_policy)) ||
+ s->bucket_acl.is_public(this);
}
int RGWPutBucketPublicAccessBlock::verify_permission(optional_yield y)
const rgw::IAM::Policy p(s->cct, policy_tenant, policy, false);
if (!s->principal_tags.empty()) {
- auto res = p.eval(s->env, *s->auth.identity, rgw::IAM::stsTagSession, boost::none);
+ auto res = p.eval(this, s->env, *s->auth.identity, rgw::IAM::stsTagSession, boost::none);
if (res != rgw::IAM::Effect::Allow) {
ldout(s->cct, 0) << "evaluating policy for stsTagSession returned deny/pass" << dendl;
return -EPERM;
op = rgw::IAM::stsAssumeRole;
}
- auto res = p.eval(s->env, *s->auth.identity, op, boost::none);
+ auto res = p.eval(this, s->env, *s->auth.identity, op, boost::none);
if (res != rgw::IAM::Effect::Allow) {
ldout(s->cct, 0) << "evaluating policy for op: " << op << " returned deny/pass" << dendl;
return -EPERM;
ARN arn1(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
- EXPECT_EQ(p.eval(e, none, s3ListBucket, arn1),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, none, s3ListBucket, arn1), Effect::Allow);
ARN arn2(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
- EXPECT_EQ(p.eval(e, none, s3PutBucketAcl, arn2),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, none, s3PutBucketAcl, arn2), Effect::Pass);
ARN arn3(Partition::aws, Service::s3,
"", arbitrary_tenant, "erroneous_bucket");
- EXPECT_EQ(p.eval(e, none, s3ListBucket, arn3),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, none, s3ListBucket, arn3), Effect::Pass);
}
for (auto i = 0ULL; i < s3All; ++i) {
ARN arn1(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket");
- EXPECT_EQ(p.eval(e, trueacct, i, arn1),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, trueacct, i, arn1), Effect::Allow);
ARN arn2(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket/myobject");
- EXPECT_EQ(p.eval(e, trueacct, i, arn2),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, trueacct, i, arn2), Effect::Allow);
ARN arn3(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket");
- EXPECT_EQ(p.eval(e, notacct, i, arn3),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, notacct, i, arn3), Effect::Pass);
ARN arn4(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket/myobject");
- EXPECT_EQ(p.eval(e, notacct, i, arn4),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, notacct, i, arn4), Effect::Pass);
ARN arn5(Partition::aws, Service::s3,
"", arbitrary_tenant, "notyourbucket");
- EXPECT_EQ(p.eval(e, trueacct, i, arn5),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, trueacct, i, arn5), Effect::Pass);
ARN arn6(Partition::aws, Service::s3,
"", arbitrary_tenant, "notyourbucket/notyourobject");
- EXPECT_EQ(p.eval(e, trueacct, i, arn6),
- Effect::Pass);
-
+ EXPECT_EQ(p.eval(e, trueacct, i, arn6), Effect::Pass);
}
}
ARN arn1(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket");
- EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn1),
- Effect::Allow);
+ EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn1), Effect::Allow);
ARN arn2(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket");
- EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn2),
- Effect::Allow);
+ EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn2), Effect::Allow);
for (auto op = 0ULL; op < s3All; ++op) {
}
ARN arn3(Partition::aws, Service::s3,
"", arbitrary_tenant, "confidential-data");
- EXPECT_EQ(p.eval(em, none, op, arn3),
- Effect::Pass);
+ EXPECT_EQ(p.eval(em, none, op, arn3), Effect::Pass);
ARN arn4(Partition::aws, Service::s3,
"", arbitrary_tenant, "confidential-data");
- EXPECT_EQ(p.eval(tr, none, op, arn4),
- s3allow[op] ? Effect::Allow : Effect::Pass);
+ EXPECT_EQ(p.eval(tr, none, op, arn4), s3allow[op] ? Effect::Allow : Effect::Pass);
ARN arn5(Partition::aws, Service::s3,
"", arbitrary_tenant, "confidential-data");
- EXPECT_EQ(p.eval(fa, none, op, arn5),
- Effect::Pass);
+ EXPECT_EQ(p.eval(fa, none, op, arn5), Effect::Pass);
ARN arn6(Partition::aws, Service::s3,
"", arbitrary_tenant, "confidential-data/moo");
- EXPECT_EQ(p.eval(em, none, op, arn6),
- Effect::Pass);
+ EXPECT_EQ(p.eval(em, none, op, arn6), Effect::Pass);
ARN arn7(Partition::aws, Service::s3,
"", arbitrary_tenant, "confidential-data/moo");
EXPECT_EQ(p.eval(tr, none, op, arn7),
Effect::Pass);
ARN arn9(Partition::aws, Service::s3,
"", arbitrary_tenant, "really-confidential-data");
- EXPECT_EQ(p.eval(em, none, op, arn9),
- Effect::Pass);
+ EXPECT_EQ(p.eval(em, none, op, arn9), Effect::Pass);
ARN arn10(Partition::aws, Service::s3,
"", arbitrary_tenant, "really-confidential-data");
- EXPECT_EQ(p.eval(tr, none, op, arn10),
- Effect::Pass);
+ EXPECT_EQ(p.eval(tr, none, op, arn10), Effect::Pass);
ARN arn11(Partition::aws, Service::s3,
"", arbitrary_tenant, "really-confidential-data");
- EXPECT_EQ(p.eval(fa, none, op, arn11),
- Effect::Pass);
+ EXPECT_EQ(p.eval(fa, none, op, arn11), Effect::Pass);
ARN arn12(Partition::aws, Service::s3,
"", arbitrary_tenant,
"really-confidential-data/moo");
"", arbitrary_tenant,
"really-confidential-data/moo");
EXPECT_EQ(p.eval(fa, none, op, arn14), Effect::Pass);
-
}
}
ARN arn1(Partition::aws, Service::iam,
"", arbitrary_tenant, "role/example_role");
- EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), Effect::Allow);
ARN arn2(Partition::aws, Service::iam,
"", arbitrary_tenant, "role/example_role");
- EXPECT_EQ(p.eval(e, none, iamDeleteRole, arn2),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, none, iamDeleteRole, arn2), Effect::Pass);
}
TEST_F(PolicyTest, Parse5) {
ARN arn1(Partition::aws, Service::iam,
"", arbitrary_tenant, "role/example_role");
- EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), Effect::Allow);
ARN arn2(Partition::aws, Service::iam,
"", arbitrary_tenant, "role/example_role");
- EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2), Effect::Pass);
ARN arn3(Partition::aws, Service::iam,
"", "", "role/example_role");
- EXPECT_EQ(p.eval(e, none, iamCreateRole, arn3),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, none, iamCreateRole, arn3), Effect::Pass);
}
TEST_F(PolicyTest, Parse6) {
ARN arn1(Partition::aws, Service::iam,
"", arbitrary_tenant, "user/A");
- EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), Effect::Allow);
ARN arn2(Partition::aws, Service::iam,
"", arbitrary_tenant, "user/A");
- EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2),
- Effect::Allow);
+ EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2), Effect::Allow);
}
TEST_F(PolicyTest, Parse7) {
ARN arn1(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket/*");
- EXPECT_EQ(p.eval(e, subacct, s3ListBucket, arn1),
- Effect::Allow);
-
+ EXPECT_EQ(p.eval(e, subacct, s3ListBucket, arn1), Effect::Allow);
+
ARN arn2(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket/*");
- EXPECT_EQ(p.eval(e, parentacct, s3ListBucket, arn2),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, parentacct, s3ListBucket, arn2), Effect::Pass);
ARN arn3(Partition::aws, Service::s3,
"", arbitrary_tenant, "mybucket/*");
- EXPECT_EQ(p.eval(e, sub2acct, s3ListBucket, arn3),
- Effect::Pass);
+ EXPECT_EQ(p.eval(e, sub2acct, s3ListBucket, arn3), Effect::Pass);
}
ARN arn1(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
EXPECT_EQ(allowp.eval(e, trueacct, s3ListBucket, arn1),
- Effect::Pass);
+ Effect::Pass);
ARN arn2(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket/myobject");
EXPECT_EQ(fullp.eval(e, trueacct, s3ListBucket, arn2),
- Effect::Pass);
+ Effect::Pass);
ARN arn3(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
EXPECT_EQ(allowp.eval(allowedIP, trueacct, s3ListBucket, arn3),
- Effect::Allow);
+ Effect::Allow);
ARN arn4(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
EXPECT_EQ(allowp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn4),
- Effect::Pass);
+ Effect::Pass);
ARN arn5(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn5),
- Effect::Deny);
+ Effect::Deny);
ARN arn6(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket/myobject");
EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn6),
- Effect::Deny);
+ Effect::Deny);
ARN arn7(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn7),
- Effect::Pass);
+ Effect::Pass);
ARN arn8(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket/myobject");
EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn8),
- Effect::Pass);
+ Effect::Pass);
ARN arn9(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket");
EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn9),
- Effect::Pass);
+ Effect::Pass);
ARN arn10(Partition::aws, Service::s3,
"", arbitrary_tenant, "example_bucket/myobject");
EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn10),
Condition ArnLike{TokenID::ArnLike, key.data(), key.size(), false};
ArnLike.vals.push_back("arn:aws:s3:::bucket");
- EXPECT_FALSE(ArnLike.eval({}));
- EXPECT_TRUE(ArnLike.eval({{key, "arn:aws:s3:::bucket"}}));
- EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::BUCKET"}}));
- EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::user"}}));
+ EXPECT_FALSE(ArnLike.eval({}, nullptr));
+ EXPECT_TRUE(ArnLike.eval({{key, "arn:aws:s3:::bucket"}}, nullptr));
+ EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::BUCKET"}}, nullptr));
+ EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::user"}}, nullptr));
}
{
Condition ArnLike{TokenID::ArnLike, key.data(), key.size(), false};
ArnLike.vals.push_back("arn:aws:s3:::b*");
- EXPECT_FALSE(ArnLike.eval({}));
- EXPECT_TRUE(ArnLike.eval({{key, "arn:aws:s3:::b"}}));
- EXPECT_TRUE(ArnLike.eval({{key, "arn:aws:s3:::bucket"}}));
- EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::BUCKET"}}));
- EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::user"}}));
+ EXPECT_FALSE(ArnLike.eval({}, nullptr));
+ EXPECT_TRUE(ArnLike.eval({{key, "arn:aws:s3:::b"}}, nullptr));
+ EXPECT_TRUE(ArnLike.eval({{key, "arn:aws:s3:::bucket"}}, nullptr));
+ EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::BUCKET"}}, nullptr));
+ EXPECT_FALSE(ArnLike.eval({{key, "arn:aws:s3:::user"}}, nullptr));
}
}
TEST_F(ConditionTest, StringNotEqualsLogic)
{
std::string key = "aws:UserName";
-
+
// Test case: value matches one of multiple condition values
// Should return false because value equals at least one condition value
{
stringNotEquals.vals.push_back("charlie");
// Input "bob" matches second condition value, should return false
- EXPECT_FALSE(stringNotEquals.eval({{key, "bob"}}));
- // Input "alice" matches first condition value, should return false
- EXPECT_FALSE(stringNotEquals.eval({{key, "alice"}}));
+ EXPECT_FALSE(stringNotEquals.eval({{key, "bob"}}, nullptr));
+ // Input "alice" matches first condition value, should return false
+ EXPECT_FALSE(stringNotEquals.eval({{key, "alice"}}, nullptr));
}
-
+
// Test case: value doesn't match any condition values
// Should return true because value differs from all condition values
{
stringNotEquals.vals.push_back("charlie");
// Input "david" doesn't match any condition value, should return true
- EXPECT_TRUE(stringNotEquals.eval({{key, "david"}}));
+ EXPECT_TRUE(stringNotEquals.eval({{key, "david"}}, nullptr));
}
}
numericNotEquals.vals.push_back("30");
// Input "20" matches second condition value, should return false
- EXPECT_FALSE(numericNotEquals.eval({{key, "20"}}));
+ EXPECT_FALSE(numericNotEquals.eval({{key, "20"}}, nullptr));
// Input "10" matches first condition value, should return false
- EXPECT_FALSE(numericNotEquals.eval({{key, "10"}}));
+ EXPECT_FALSE(numericNotEquals.eval({{key, "10"}}, nullptr));
}
-
+
// Test case: value doesn't match any condition values
// Should return true because value differs from all condition values
{
numericNotEquals.vals.push_back("30");
// Input "40" doesn't match any condition value, should return true
- EXPECT_TRUE(numericNotEquals.eval({{key, "40"}}));
+ EXPECT_TRUE(numericNotEquals.eval({{key, "40"}}, nullptr));
}
}
TEST_F(ConditionTest, DateNotEqualsLogic)
{
std::string key = "aws:CurrentTime";
-
+
// Test case: value matches one of multiple condition values
// Should return false because value equals at least one condition value
{
dateNotEquals.vals.push_back("2023-12-01T00:00:00Z");
// Input matches second condition value, should return false
- EXPECT_FALSE(dateNotEquals.eval({{key, "2023-06-01T00:00:00Z"}}));
+ EXPECT_FALSE(dateNotEquals.eval({{key, "2023-06-01T00:00:00Z"}}, nullptr));
}
-
+
// Test case: value doesn't match any condition values
// Should return true because value differs from all condition values
{
dateNotEquals.vals.push_back("2023-12-01T00:00:00Z");
// Input doesn't match any condition value, should return true
- EXPECT_TRUE(dateNotEquals.eval({{key, "2024-01-01T00:00:00Z"}}));
+ EXPECT_TRUE(dateNotEquals.eval({{key, "2024-01-01T00:00:00Z"}}, nullptr));
}
}
TEST_F(ConditionTest, NotIpAddressLogic)
{
std::string key = "aws:SourceIp";
-
+
// Test case: value matches one of multiple condition values
// Should return false because value equals at least one condition value
{
notIpAddress.vals.push_back("172.16.0.1");
// Input matches second condition value, should return false
- EXPECT_FALSE(notIpAddress.eval({{key, "10.0.0.1"}}));
+ EXPECT_FALSE(notIpAddress.eval({{key, "10.0.0.1"}}, nullptr));
// Input matches first condition value, should return false
- EXPECT_FALSE(notIpAddress.eval({{key, "192.168.1.1"}}));
+ EXPECT_FALSE(notIpAddress.eval({{key, "192.168.1.1"}}, nullptr));
}
-
+
// Test case: value doesn't match any condition values
// Should return true because value differs from all condition values
{
notIpAddress.vals.push_back("172.16.0.1");
// Input doesn't match any condition value, should return true
- EXPECT_TRUE(notIpAddress.eval({{key, "8.8.8.8"}}));
+ EXPECT_TRUE(notIpAddress.eval({{key, "8.8.8.8"}}, nullptr));
}
}
TEST_F(ConditionTest, ArnNotEqualsLogic)
{
std::string key = "aws:SourceArn";
-
+
// Test case: value matches one of multiple condition values
// Should return false because value equals at least one condition value
{
arnNotEquals.vals.push_back("arn:aws:s3:::bucket3");
// Input matches second condition value, should return false
- EXPECT_FALSE(arnNotEquals.eval({{key, "arn:aws:s3:::bucket2"}}));
+ EXPECT_FALSE(arnNotEquals.eval({{key, "arn:aws:s3:::bucket2"}}, nullptr));
}
-
+
// Test case: value doesn't match any condition values
// Should return true because value differs from all condition values
{
arnNotEquals.vals.push_back("arn:aws:s3:::bucket3");
// Input doesn't match any condition value, should return true
- EXPECT_TRUE(arnNotEquals.eval({{key, "arn:aws:s3:::other-bucket"}}));
+ EXPECT_TRUE(arnNotEquals.eval({{key, "arn:aws:s3:::other-bucket"}},
+ nullptr));
}
}
TEST_F(ConditionTest, StringNotLikeLogic)
{
std::string key = "s3:prefix";
-
+
// Test case: value matches one of multiple condition patterns
// Should return false because value matches at least one condition pattern
{
stringNotLike.vals.push_back("temp/*");
// Input matches second condition pattern, should return false
- EXPECT_FALSE(stringNotLike.eval({{key, "admin/config.txt"}}));
+ EXPECT_FALSE(stringNotLike.eval({{key, "admin/config.txt"}}, nullptr));
// Input matches first condition pattern, should return false
- EXPECT_FALSE(stringNotLike.eval({{key, "user/profile.jpg"}}));
+ EXPECT_FALSE(stringNotLike.eval({{key, "user/profile.jpg"}}, nullptr));
}
-
+
// Test case: value doesn't match any condition patterns
// Should return true because value differs from all condition patterns
{
stringNotLike.vals.push_back("temp/*");
// Input doesn't match any condition pattern, should return true
- EXPECT_TRUE(stringNotLike.eval({{key, "public/document.pdf"}}));
+ EXPECT_TRUE(stringNotLike.eval({{key, "public/document.pdf"}}, nullptr));
}
}
Condition isNull{TokenID::Null, key.data(), key.size(), false};
isNull.vals.push_back("true");
- EXPECT_TRUE(isNull.eval({}));
- EXPECT_FALSE(isNull.eval({{key, "admin/config.txt"}}));
+ EXPECT_TRUE(isNull.eval({}, nullptr));
+ EXPECT_FALSE(isNull.eval({{key, "admin/config.txt"}}, nullptr));
}
{
Condition notNull{TokenID::Null, key.data(), key.size(), false};
notNull.vals.push_back("false");
- EXPECT_FALSE(notNull.eval({}));
- EXPECT_TRUE(notNull.eval({{key, "admin/config.txt"}}));
+ EXPECT_FALSE(notNull.eval({}, nullptr));
+ EXPECT_TRUE(notNull.eval({{key, "admin/config.txt"}}, nullptr));
}
}
cond.vals.push_back("testrole");
// No roles
- EXPECT_FALSE(cond.eval({}));
+ EXPECT_FALSE(cond.eval({}, nullptr));
// Single matching role
- EXPECT_TRUE(cond.eval({{key, "testrole"}}));
+ EXPECT_TRUE(cond.eval({{key, "testrole"}}, nullptr));
// Single non-matching role
- EXPECT_FALSE(cond.eval({{key, "member"}}));
+ EXPECT_FALSE(cond.eval({{key, "member"}}, nullptr));
//Multiple roles in env,one matches
Environment multi_env;
multi_env.emplace(key, "member");
multi_env.emplace(key, "testrole");
multi_env.emplace(key, "reader");
- EXPECT_TRUE(cond.eval(multi_env));
+ EXPECT_TRUE(cond.eval(multi_env, nullptr));
//Multiple roles in env, no match
Environment no_match_env;
no_match_env.emplace(key, "member");
no_match_env.emplace(key, "reader");
- EXPECT_FALSE(cond.eval(no_match_env));
+ EXPECT_FALSE(cond.eval(no_match_env, nullptr));
// Multiple identical roles (redundancy check)
Environment duplicate_env;
duplicate_env.emplace(key, "testrole");
duplicate_env.emplace(key, "testrole");
- EXPECT_TRUE(cond.eval(duplicate_env));
+ EXPECT_TRUE(cond.eval(duplicate_env, nullptr));
}
TEST_F(ConditionTest, KeyStoneRoleNotStringEquals)
cond.vals.push_back("admin");
// No roles
- EXPECT_FALSE(cond.eval({}));
+ EXPECT_FALSE(cond.eval({}, nullptr));
// Role matches
- EXPECT_FALSE(cond.eval({{key, "admin"}}));
+ EXPECT_FALSE(cond.eval({{key, "admin"}}, nullptr));
// Role doesn't match
- EXPECT_TRUE(cond.eval({{key, "member"}}));
+ EXPECT_TRUE(cond.eval({{key, "member"}}, nullptr));
// Multiple roles in env, one matches -> false
Environment multi_env;
multi_env.emplace(key, "member");
multi_env.emplace(key, "admin");
EXPECT_FALSE(multi_env.count(key) == 0);
- EXPECT_FALSE(cond.eval(multi_env));
+ EXPECT_FALSE(cond.eval(multi_env, nullptr));
// Multiple roles, none match -> true
Environment no_match_env;
no_match_env.emplace(key, "member");
no_match_env.emplace(key, "reader");
- EXPECT_TRUE(cond.eval(no_match_env));
+ EXPECT_TRUE(cond.eval(no_match_env, nullptr));
}
TEST_F(ConditionTest, KeyStoneRolePolicyParsing)
// Eval with matching role in environment
Environment match_env;
match_env.emplace("keystone:role", "testrole");
- EXPECT_TRUE(p->statements[0].conditions[0].eval(match_env));
+ EXPECT_TRUE(p->statements[0].conditions[0].eval(match_env, nullptr));
// Eval with non-matching role
Environment nomatch_env;
nomatch_env.emplace("keystone:role", "member");
- EXPECT_FALSE(p->statements[0].conditions[0].eval(nomatch_env));
+ EXPECT_FALSE(p->statements[0].conditions[0].eval(nomatch_env, nullptr));
// Eval with multiple roles, one matching
Environment multi_env;
multi_env.emplace("keystone:role", "member");
multi_env.emplace("keystone:role", "testrole");
- EXPECT_TRUE(p->statements[0].conditions[0].eval(multi_env));
+ EXPECT_TRUE(p->statements[0].conditions[0].eval(multi_env, nullptr));
}
TEST_F(ConditionTest, KeystoneUserIdStringEquals)
Condition cond{TokenID::StringEquals, key.data(), key.size(), false};
cond.vals.push_back("user-123");
- EXPECT_FALSE(cond.eval({}));
- EXPECT_TRUE(cond.eval({{key, "user-123"}}));
- EXPECT_FALSE(cond.eval({{key, "user-456"}}));
-}
\ No newline at end of file
+ EXPECT_FALSE(cond.eval({}, nullptr));
+ EXPECT_TRUE(cond.eval({{key, "user-123"}}, nullptr));
+ EXPECT_FALSE(cond.eval({{key, "user-456"}}, nullptr));
+}