From: Adam C. Emerson Date: Tue, 21 Oct 2025 19:07:18 +0000 (-0400) Subject: rgw/policy: Allow logging policy evaluation X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=6df13ba002260c8a3d8a6a9b36559f683b40d1ce;p=ceph-ci.git rgw/policy: Allow logging policy evaluation When the `rgw_copious_policy_logging` variable is set to true *and* rgw logging is set to 20, a detailed trace of policy evaluation is logged. Signed-off-by: Adam C. Emerson --- diff --git a/PendingReleaseNotes b/PendingReleaseNotes index fd171f73f65..21bd4da5f22 100644 --- a/PendingReleaseNotes +++ b/PendingReleaseNotes @@ -7,6 +7,10 @@ If the default provider is required when also using custom providers, it must be explicitly loaded in the configuration file or code (see https://github.com/openssl/openssl/blob/master/README-PROVIDERS.md). +* RGW: When the `rgw_copious_policy_logging` variable is set to true + and rgw logging is set to 20, a detailed trace of policy + evaluation is logged. + * DASHBOARD: Removed the older landing page which was deprecated in Quincy. Admins can no longer enable the older, deprecated landing page layout by adjusting FEATURE_TOGGLE_DASHBOARD. diff --git a/src/common/options/rgw.yaml.in b/src/common/options/rgw.yaml.in index 7b44a9587e0..eec1b1ed90a 100644 --- a/src/common/options/rgw.yaml.in +++ b/src/common/options/rgw.yaml.in @@ -4556,6 +4556,7 @@ options: default: 11 services: - rgw + - name: rgw_usage_log_key_transition type: bool level: advanced @@ -4572,3 +4573,12 @@ options: see_also: - rgw_enable_usage_log with_legacy: true + +- name: rgw_copious_policy_logging + type: bool + level: advanced + desc: Log the details of each policy evaluation in extreme verbosity + default: false + services: + - rgw + with_legacy: false diff --git a/src/rgw/rgw_bucket_logging.cc b/src/rgw/rgw_bucket_logging.cc index bfb76a46c77..65024171060 100644 --- a/src/rgw/rgw_bucket_logging.cc +++ b/src/rgw/rgw_bucket_logging.cc @@ -991,7 +991,7 @@ int verify_target_bucket_policy(const DoutPrefixProvider* dpp, const auto source_account = to_string(s->bucket_owner.id); s->env.emplace("aws:SourceArn", source_bucket_arn); s->env.emplace("aws:SourceAccount", source_account); - if (policy.eval(s->env, ident, rgw::IAM::s3PutObject, target_resource_arn) != rgw::IAM::Effect::Allow) { + if (policy.eval(dpp, s->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: '" << source_bucket_arn << diff --git a/src/rgw/rgw_common.cc b/src/rgw/rgw_common.cc index 7d102224d87..633678ea28d 100644 --- a/src/rgw/rgw_common.cc +++ b/src/rgw/rgw_common.cc @@ -1153,10 +1153,11 @@ Effect eval_or_pass(const DoutPrefixProvider* dpp, const uint64_t op, const ARN& resource, boost::optional 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, @@ -1377,7 +1378,9 @@ bool verify_bucket_permission(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 const bool restrict_public_buckets = s->bucket_access_conf && s->bucket_access_conf->restrict_public_buckets(); - if (restrict_public_buckets && bucket_policy && rgw::IAM::is_public(*bucket_policy) && !s->identity->is_owner_of(s->bucket_info.owner)) { + if (restrict_public_buckets && 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; } @@ -1544,7 +1547,9 @@ bool verify_object_permission(const DoutPrefixProvider* dpp, struct perm_state_b // If RestrictPublicBuckets is enabled and the bucket policy allows public access, // deny the request if the requester is not in the bucket owner account const bool restrict_public_buckets = ps->bucket_access_conf && ps->bucket_access_conf->restrict_public_buckets(); - if (restrict_public_buckets && bucket_policy && rgw::IAM::is_public(*bucket_policy) && !ps->identity->is_owner_of(ps->bucket_info.owner)) { + if (restrict_public_buckets && 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; } diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc index c6d5c38ac32..c665f1370ee 100644 --- a/src/rgw/rgw_iam_policy.cc +++ b/src/rgw/rgw_iam_policy.cc @@ -1,12 +1,10 @@ // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*- // vim: ts=8 sw=2 sts=2 expandtab ft=cpp - #include #include #include #include -#include #include #include @@ -15,15 +13,11 @@ #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; @@ -50,6 +44,25 @@ using rapidjson::StringStream; using rgw::auth::Principal; +namespace std { +inline std::ostream& +operator<<( + std::ostream& out, + const pair< + unordered_multimap::const_iterator, + unordered_multimap::const_iterator> it) +{ + out << "["; + for (auto itr = it.first; itr != it.second; itr++) { + if (itr != it.first) { + out << ", "; + } + out << *itr; + } + return out << "]"; +} +} // namespace std + namespace rgw { namespace IAM { #include "rgw_iam_policy_keywords.frag.cc" @@ -225,6 +238,139 @@ static const actpair actpairs[] = { "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 "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 +inline std::ostream& +operator<<(std::ostream& out, const boost::optional& t) +{ + if (!t) + out << "--"; + else + out << ' ' << *t; + return out; +} + +inline std::ostream& +operator<<(std::ostream& out, const Effect& e) +{ + switch (e) { + case Effect::Allow: + return out << "Allow"; + case Effect::Pass: + return out << "Pass"; + case Effect::Deny: + return out << "Deny"; + } + return out << "Unknown Effect"; +} + +void +maybeout::lendl_() const +{ + *this << lendl; +} + +void +maybeout::bracket::close() +{ + if (m && !closer.empty()) { + *m.m << closer; + m.lendl_(); + closer = std::string_view{}; + } +} + + struct PolicyParser; const Keyword top[1]{{"", TokenKind::pseudo, TokenID::Top, 0, false, @@ -871,19 +1017,31 @@ static bool arn_like(const std::string& input, const std::string& pattern) return match_policy(pattern, input, MATCH_POLICY_ARN); } -bool Condition::eval(const Environment& env) const { +bool Condition::eval(const Environment& env, const maybeout& eval_log) const { + eval_log << "Evaluating condition " << *this << lendl; std::vector runtime_vals; auto i = env.find(key); if (op == TokenID::Null) { - return i == env.end() ? true : false; + auto ret = i == env.end() ? true : false; + eval_log << "Null check. key `" << key + << (ret ? + "`. Is not present, returning true." : + "`. Is present, returning false.") << lendl; + return ret; } if (i == env.end()) { if (op == TokenID::ForAllValuesStringEquals || op == TokenID::ForAllValuesStringEqualsIgnoreCase || op == TokenID::ForAllValuesStringLike) { + eval_log + << "Evaluating " << condop_string(op) << " When key " + << " is not present. Vacuously true." << lendl; return true; } else { + eval_log << "Evaluating " << condop_string(op) << " When key `" << key + << "` is not present. Returning " + << (ifexists ? "true" : "false") << lendl; return ifexists; } } @@ -901,110 +1059,127 @@ bool Condition::eval(const Environment& env) const { const auto& itr = env.equal_range(key); + eval_log << "Evaluating " << condop_string(op) << "for " << itr << " against " + << (isruntime ? runtime_vals : vals) << lendl; switch (op) { // String! case TokenID::ForAnyValueStringEquals: case TokenID::StringEquals: - return multimap_any(std::equal_to(), itr, isruntime? runtime_vals : vals); + return multimap_any(std::equal_to(), itr, + isruntime ? runtime_vals : vals, + eval_log); case TokenID::StringNotEquals: - return multimap_none(std::equal_to(), - itr, isruntime? runtime_vals : vals); + return multimap_none(std::equal_to(), 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(), itr, isruntime? runtime_vals : vals); + return multimap_all(std::equal_to(), 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(), as_number, s, vals); + return typed_any(std::equal_to(), as_number, s, vals, eval_log); case TokenID::NumericNotEquals: - return typed_none(std::equal_to(), - as_number, s, vals); + return typed_none(std::equal_to(), as_number, s, vals, eval_log); case TokenID::NumericLessThan: - return typed_any(std::less(), as_number, s, vals); + return typed_any(std::less(), as_number, s, vals, eval_log); case TokenID::NumericLessThanEquals: - return typed_any(std::less_equal(), as_number, s, vals); + return typed_any(std::less_equal(), as_number, s, vals, eval_log); case TokenID::NumericGreaterThan: - return typed_any(std::greater(), as_number, s, vals); + return typed_any(std::greater(), as_number, s, vals, eval_log); case TokenID::NumericGreaterThanEquals: - return typed_any(std::greater_equal(), as_number, s, - vals); + return typed_any(std::greater_equal(), as_number, s, vals, + eval_log); // Date! case TokenID::DateEquals: - return typed_any(std::equal_to(), as_date, s, vals); + return typed_any(std::equal_to(), as_date, s, vals, + eval_log); case TokenID::DateNotEquals: return typed_none(std::equal_to(), - as_date, s, vals); + as_date, s, vals, eval_log); case TokenID::DateLessThan: - return typed_any(std::less(), as_date, s, vals); + return typed_any(std::less(), as_date, s, vals, eval_log); case TokenID::DateLessThanEquals: - return typed_any(std::less_equal(), as_date, s, vals); + return typed_any(std::less_equal(), as_date, s, vals, + eval_log); case TokenID::DateGreaterThan: - return typed_any(std::greater(), as_date, s, vals); + return typed_any(std::greater(), as_date, s, vals, + eval_log); case TokenID::DateGreaterThanEquals: return typed_any(std::greater_equal(), as_date, s, - vals); + vals, eval_log); // Bool! case TokenID::Bool: - return typed_any(std::equal_to(), as_bool, s, vals); + return typed_any(std::equal_to(), as_bool, s, vals, eval_log); // Binary! case TokenID::BinaryEquals: return typed_any(std::equal_to(), as_binary, s, - vals); + vals, eval_log); // IP Address! case TokenID::IpAddress: - return typed_any(std::equal_to(), as_network, s, vals); + return typed_any(std::equal_to(), as_network, s, vals, eval_log); case TokenID::NotIpAddress: return typed_none(std::equal_to(), - 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 << "Unknown operation: Returning false" << lendl; return false; } } @@ -1071,96 +1246,6 @@ boost::optional Condition::as_network(const string& s) { 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 ostream& print_array(ostream& m, Iterator begin, Iterator end) { if (begin == end) { @@ -1181,8 +1266,6 @@ ostream& print_dict(ostream& m, Iterator begin, Iterator end) { return m; } -} - ostream& operator <<(ostream& m, const Condition& c) { m << condop_string(c.op); if (c.ifexists) { @@ -1193,46 +1276,89 @@ ostream& operator <<(ostream& m, const Condition& c) { return m << " }"; } -Effect Statement::eval(const Environment& e, - boost::optional ida, - uint64_t act, boost::optional res, boost::optional princ_type) const { - - if (eval_principal(e, ida, princ_type) == Effect::Deny) { +Effect +Statement::eval( + const Environment& e, + boost::optional ida, + uint64_t act, + boost::optional res, + const maybeout& eval_log, + boost::optional princ_type) const { + + if (eval_principal(e, ida, eval_log, princ_type) == Effect::Deny) { + eval_log << "Passing." << lendl; return Effect::Pass; } if (res && resource.empty() && notresource.empty()) { + eval_log << "A resource was specified but both Resource and NotResource " + "were empty. Passing." << lendl; return Effect::Pass; } if (!res && (!resource.empty() || !notresource.empty())) { + eval_log << "No resource was specified yet neither " + "Resource nor NotResource were empty. Passing." << lendl; 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 << "Checking Resource for " << res << lendl; + if (!std::any_of( + resource.begin(), resource.end(), + [&res, &eval_log](const ARN& pattern) { + if (pattern.match(*res)) { + eval_log << "Matched " << pattern << lendl; + return true; + } + return false; + })) { + eval_log << "No match in Resource. Passing." << lendl; 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 << "Checking NotResource for " << res << lendl; + if (std::any_of( + notresource.begin(), notresource.end(), + [&res, &eval_log](const ARN& pattern) { + if (pattern.match(*res)) { + eval_log << "Matched " << pattern << lendl; + return true; + } + return false; })) { + eval_log << "Match found in NotResource. Passing." << lendl; return Effect::Pass; } } - if (!(action[act] == 1) || (notaction[act] == 1)) { + if (!(action[act] == 1)) { + eval_log << action_bit_string(action_t(act)) + << "not found in Action. Passing." << lendl; + return Effect::Pass; + } + + if (notaction[act] == 1) { + eval_log << action_bit_string(action_t(act)) + << "found in NotAction. Passing." << lendl; return Effect::Pass; } - if (std::all_of(conditions.begin(), - conditions.end(), - [&e](const Condition& c) { return c.eval(e);})) { - return effect; + { + eval_log << "Evaluating conditions:" << lendl; + auto b = eval_log.open("{", "}"); + auto indent = eval_log.indent(); + if (std::all_of( + conditions.begin(), conditions.end(), + [&e, &indent](const Condition& c) { + indent << "Evaluating condition: " << c << lendl; + return c.eval(e, indent); + })) { + b.close(); + eval_log << "Returning " << effect; + return effect; + } } + eval_log << "Passing." << lendl; return Effect::Pass; } @@ -1245,26 +1371,43 @@ static bool is_identity(const auth::Identity& ida, }); } -Effect Statement::eval_principal(const Environment& e, - boost::optional ida, boost::optional princ_type) const { +Effect +Statement::eval_principal( + const Environment& e, + boost::optional ida, + const maybeout& eval_log, + boost::optional princ_type) const { if (princ_type) { *princ_type = PolicyPrincipal::Other; } + eval_log << "Evaluating identity: " << ida << lendl; if (ida) { if (princ.empty() && noprinc.empty()) { + eval_log << "Principal empty and NotPrincipal empty: Denying." << lendl; 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 << "Identity is not role, Principal is not empty, and " + "the identity is not in Principal." << lendl; return Effect::Deny; } + eval_log << "Checking type of Principal match." << lendl; 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 << "Setting principal type to Session" << lendl; + *princ_type = PolicyPrincipal::Session; + } } else { - if (princ_type) *princ_type = PolicyPrincipal::Role; + if (princ_type) { + eval_log << "Setting principal type to Session" << lendl; + *princ_type = PolicyPrincipal::Role; + } } princ_matched = true; } @@ -1279,12 +1422,19 @@ Effect Statement::eval_principal(const Environment& e, 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 maybeout& eval_log) const { + if (std::all_of( + conditions.begin(), conditions.end(), + [&e, &eval_log](const Condition& c) { + eval_log << "Evaluating condition: " << c << lendl; + return c.eval(e, eval_log); + })) { + eval_log << "Returning Allow." << lendl; + return Effect::Allow; + } + + eval_log << "Returning Deny." << lendl; return Effect::Deny; } @@ -1894,45 +2044,76 @@ Policy::Policy(CephContext* cct, const string* tenant, } Effect Policy::eval(const Environment& e, - boost::optional ida, - std::uint64_t action, boost::optional resource, - boost::optional princ_type) const { + boost::optional ida, + std::uint64_t action, + boost::optional resource, + maybeout eval_log, + boost::optional princ_type) const +{ auto allowed = false; - for (auto& s : statements) { - auto g = s.eval(e, ida, action, resource, princ_type); - if (g == Effect::Deny) { - return g; - } else if (g == Effect::Allow) { - allowed = true; + eval_log + << "Evaluating policy: ```" << lendl << text << lendl << "'''" << lendl + << "Environment: " << e << lendl + << "Principal: " << ida << lendl + << "Action: " << action_bit_string(action_t(action)) << lendl + << "Resource: " << resource << lendl; + + { + for (auto& s : statements) { + eval_log << "Evaluating statement: " << s << lendl; + auto b = eval_log.open("{", "}"); + auto indent = eval_log.indent(); + auto g = s.eval(e, ida, action, resource, indent, princ_type); + b.close(); + if (g == Effect::Deny) { + eval_log << "Denying.\n"; + return g; + } else if (g == Effect::Allow) { + allowed = true; + } } } + eval_log << (allowed ? "Allowing.\n" : "Passing\n"); return allowed ? Effect::Allow : Effect::Pass; } -Effect Policy::eval_principal(const Environment& e, - boost::optional ida, boost::optional princ_type) const { +Effect +Policy::eval_principal( + const Environment& e, + boost::optional ida, + maybeout eval_log, + boost::optional princ_type) const { auto allowed = false; for (auto& s : statements) { - auto g = s.eval_principal(e, ida, princ_type); + eval_log << "Evaluating identity " << ida << " in statement " << s << lendl; + auto g = s.eval_principal(e, ida, eval_log.indent(), princ_type); if (g == Effect::Deny) { + eval_log << "Denying." << lendl; return g; } else if (g == Effect::Allow) { allowed = true; } } + eval_log << (allowed ? "Allowing." : "Denying.") << lendl; return allowed ? Effect::Allow : Effect::Deny; } -Effect Policy::eval_conditions(const Environment& e) const { +Effect Policy::eval_conditions(const Environment& e, + maybeout eval_log) const { auto allowed = false; for (auto& s : statements) { - auto g = s.eval_conditions(e); + eval_log << "Evaluating conditions in statement:" << lendl; + auto b = eval_log.open("{", "}"); + auto indent = eval_log.indent(); + auto g = s.eval_conditions(e, indent); if (g == Effect::Deny) { + eval_log << "Denying." << lendl; return g; } else if (g == Effect::Allow) { allowed = true; } } + eval_log << (allowed ? "Allowing." : "Denying.") << lendl; return allowed ? Effect::Allow : Effect::Deny; } @@ -1967,11 +2148,20 @@ static const Environment iam_all_env = { struct IsPublicStatement { - bool operator() (const Statement &s) const { + const maybeout& eval_log; + + IsPublicStatement(const maybeout& 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 << "Evaluating conditions in statement:" << lendl; + auto b = eval_log.open("{", "}"); + auto indent = eval_log.indent(); + return s.eval_conditions(iam_all_env, indent) == Effect::Allow; } } } @@ -1980,9 +2170,10 @@ struct IsPublicStatement }; -bool is_public(const Policy& p) +bool is_public(const Policy& p, maybeout 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 diff --git a/src/rgw/rgw_iam_policy.h b/src/rgw/rgw_iam_policy.h index 72f29dd4acd..c1a3a7e801d 100644 --- a/src/rgw/rgw_iam_policy.h +++ b/src/rgw/rgw_iam_policy.h @@ -217,6 +217,104 @@ enum action_t { allCount }; +struct maybeout { + mutable std::ostream* m; + std::string prefix; + + maybeout(std::ostream* m, + std::string&& prefix_ = "") : + m(m) + { + if (m) { + prefix = std::move(prefix_); + lendl_(); + } + } + + ~maybeout() + { + if (m) { + m->flush(); + } + } + maybeout(const maybeout&) = default; + maybeout& operator=(const maybeout&) = default; + maybeout(maybeout&&) = delete; + maybeout& operator=(maybeout&&) = delete; + + maybeout& + operator=(std::nullptr_t) + { + m = nullptr; + prefix.clear(); + return *this; + } + + maybeout + indent(std::string&& prefix2 = " ", + std::string_view closer_ = "") const + { + if (m) { + return maybeout(m, (prefix + std::move(prefix2))); + } else { + return nullptr; + } + } + + void lendl_() const; + + struct bracket { + const maybeout& m; + std::string_view closer; + + bracket(const maybeout& m, std::string_view closer) : + m(m), closer(closer) + {} + + void close(); + + ~bracket() { close(); } + }; + + bracket + open(std::string_view opener, std::string_view closer_) const + { + if (m) { + *m << opener; + lendl_(); + return { *this, closer_ }; + } + return { *this, "" }; + } + operator bool() const { return !!m; } + + const maybeout& + operator<<(const maybeout& (*func)(const maybeout&)) const + { + return func(*this); + } +}; + +const maybeout& +operator<<(const maybeout& m, const auto& datum) +{ + if (m) { + *(m.m) << datum; + } + return m; +} + +inline const maybeout& lendl(const maybeout& m) +{ + if (m.m) { + m.m->put(m.m->widen('\n')); + *m.m << m.prefix; + } + return m; +} + + + using Action_t = std::bitset; using NotAction_t = Action_t; @@ -385,8 +483,7 @@ struct Condition { 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 maybeout& eval_log) const; static boost::optional as_number(const std::string& s) { std::size_t p = 0; @@ -483,43 +580,64 @@ struct Condition { } }; - using unordered_multimap_it_pair = std::pair ::const_iterator, std::unordered_multimap::const_iterator>; + using unordered_multimap_it_pair = std::pair< + std::unordered_multimap::const_iterator, + std::unordered_multimap::const_iterator>; template - static bool multimap_all(F&& f, const unordered_multimap_it_pair& it, - const std::vector& v) { + static bool multimap_all(F&& f, + const unordered_multimap_it_pair& it, + const std::vector& v, + const maybeout& 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 << "Values matched against were empty." << lendl; + } else { + eval_log << "Predicate false for " << failmatch << lendl; + } + return false; } - } - if (!matched) - return false; } return true; } - template - static bool multimap_any(F&& f, const unordered_multimap_it_pair& it, - const std::vector& v) { + template + static bool + multimap_any(F&& f, + const unordered_multimap_it_pair& it, + const std::vector& v, + const maybeout 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 << "Predicate true for (" << itr->second + << ", " << d << ")" << lendl; + return true; + } } - } } return false; } template static bool multimap_none(F&& f, const unordered_multimap_it_pair& it, - const std::vector& v) { + const std::vector& v, + const maybeout eval_log) { for (auto itr = it.first; itr != it.second; itr++) { for (const auto& d : v) { if (f(itr->second, d)) { + eval_log << "Predicate true for (" << itr->second << ", " + << d << ")" << lendl; return false; } } @@ -527,21 +645,28 @@ struct Condition { return true; } - template + template static bool typed_any(F&& f, X& x, const std::string& c, - const std::vector& v) { + const std::vector& v, + const maybeout& eval_log) { auto xc = std::forward(x)(c); if (!xc) { + eval_log << "Failed to convert `" << c << "`. Returning false." + << lendl; return false; } for (const auto& d : v) { auto xd = x(d); if (!xd) { + eval_log << "Failed to convert `" << d << "`. Skipping." + << lendl; continue; } if (f(*xc, *xd)) { + eval_log << "Predicate true for (" << *xc << ", " << *xd << ")" + << lendl; return true; } } @@ -550,19 +675,26 @@ struct Condition { template static bool typed_none(F&& f, X& x, const std::string& c, - const std::vector& v) { + const std::vector& v, + const maybeout& eval_log) { auto xc = std::forward(x)(c); if (!xc) { + eval_log << "Failed to convert `" << c << "`. Returning false." + << lendl; return false; } for (const auto& d : v) { auto xd = x(d); if (!xd) { + eval_log << "Failed to convert `" << d << "`. Skipping." + << lendl; continue; } if (f(*xc, *xd)) { + eval_log << "Predicate true for (" << *xc << ", " << *xd << ")" + << lendl; return false; } } @@ -605,13 +737,21 @@ struct Statement { std::vector conditions; Effect eval(const Environment& e, - boost::optional ida, - std::uint64_t action, boost::optional resource, boost::optional princ_type=boost::none) const; - - Effect eval_principal(const Environment& e, - boost::optional ida, boost::optional princ_type=boost::none) const; - - Effect eval_conditions(const Environment& e) const; + boost::optional ida, + std::uint64_t action, + boost::optional resource, + const maybeout& eval_log, + boost::optional princ_type=boost::none) const; + + Effect eval_principal( + const Environment& e, + boost::optional ida, + const maybeout& eval_log, + boost::optional princ_type = boost::none) const; + + Effect + eval_conditions(const Environment& e, + const maybeout& eval_log) const; }; std::ostream& operator <<(std::ostream& m, const Statement& s); @@ -635,6 +775,7 @@ struct PolicyParseException : public std::exception { }; struct Policy { + bool copious_logging; std::string text; Version version = Version::v2008_10_17; boost::optional id = boost::none; @@ -651,13 +792,43 @@ struct Policy { bool reject_invalid_principals); Effect eval(const Environment& e, - boost::optional ida, - std::uint64_t action, boost::optional resource, boost::optional princ_type=boost::none) const; + boost::optional ida, + std::uint64_t action, + boost::optional resource, + maybeout eval_log, + boost::optional princ_type =boost::none) const; + + Effect + eval(const DoutPrefixProvider* dpp, + const Environment& e, + boost::optional ida, + std::uint64_t action, + boost::optional resource, + boost::optional princ_type = boost::none) const { + std::ostringstream ostr; + bool copious_logging = false; + if (dpp) { + copious_logging = + (dpp->get_cct()->_conf.get_val("rgw_copious_policy_logging") && + dpp->get_cct()->_conf->subsys.should_gather(ceph_subsys_rgw, 20)); + } - Effect eval_principal(const Environment& e, - boost::optional ida, boost::optional princ_type=boost::none) const; + auto eff = eval(e, ida, action, resource, + {copious_logging ? &ostr : nullptr}, princ_type); + if (dpp) { + ldpp_dout(dpp, 20) << ostr.str() << dendl; + } + return eff; + } - Effect eval_conditions(const Environment& e) const; + Effect eval_principal( + const Environment& e, + boost::optional ida, + maybeout eval_log, + boost::optional princ_type = boost::none) const; + + Effect eval_conditions(const Environment& e, + maybeout eval_log) const; template bool has_conditional(const std::string& conditional, F p) const { @@ -694,7 +865,23 @@ struct Policy { }; std::ostream& operator <<(std::ostream& m, const Policy& p); -bool is_public(const Policy& p); +bool is_public(const Policy& p, maybeout eval_log); + +inline bool is_public(const DoutPrefixProvider* dpp, const Policy& p) +{ + std::ostringstream ostr; + bool copious_logging = false; + if (dpp) { + copious_logging = + (dpp->get_cct()->_conf.get_val("rgw_copious_policy_logging") && + dpp->get_cct()->_conf->subsys.should_gather(ceph_subsys_rgw, 20)); + } + auto b = is_public(p, {copious_logging ? &ostr : nullptr}); + if (dpp) { + ldpp_dout(dpp, 20) << ostr.str() << dendl; + } + return b; +} } } diff --git a/src/rgw/rgw_op.cc b/src/rgw/rgw_op.cc index a0a6068ae78..3e5d40f0947 100644 --- a/src/rgw/rgw_op.cc +++ b/src/rgw/rgw_op.cc @@ -9031,7 +9031,7 @@ void RGWPutBucketPolicy::execute(optional_yield y) rgw::sal::Attrs attrs(s->bucket_attrs); if (s->bucket_access_conf && s->bucket_access_conf->block_public_policy() && - rgw::IAM::is_public(p)) { + rgw::IAM::is_public(this, p)) { op_ret = -EACCES; return; } @@ -9578,7 +9578,8 @@ int RGWGetBucketPolicyStatus::verify_permission(optional_yield y) 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) diff --git a/src/rgw/rgw_rest_sts.cc b/src/rgw/rgw_rest_sts.cc index e2d16d56988..0769597eefd 100644 --- a/src/rgw/rgw_rest_sts.cc +++ b/src/rgw/rgw_rest_sts.cc @@ -849,7 +849,7 @@ int RGWREST_STS::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; @@ -862,7 +862,7 @@ int RGWREST_STS::verify_permission(optional_yield y) 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; diff --git a/src/test/rgw/test_rgw_iam_policy.cc b/src/test/rgw/test_rgw_iam_policy.cc index c67f92582bd..322f428b839 100644 --- a/src/test/rgw/test_rgw_iam_policy.cc +++ b/src/test/rgw/test_rgw_iam_policy.cc @@ -270,17 +270,17 @@ TEST_F(PolicyTest, Eval1) { ARN arn1(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(p.eval(e, none, s3ListBucket, arn1), + EXPECT_EQ(p.eval(e, none, s3ListBucket, arn1, nullptr), Effect::Allow); ARN arn2(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(p.eval(e, none, s3PutBucketAcl, arn2), + EXPECT_EQ(p.eval(e, none, s3PutBucketAcl, arn2, nullptr), Effect::Pass); ARN arn3(Partition::aws, Service::s3, "", arbitrary_tenant, "erroneous_bucket"); - EXPECT_EQ(p.eval(e, none, s3ListBucket, arn3), + EXPECT_EQ(p.eval(e, none, s3ListBucket, arn3, nullptr), Effect::Pass); } @@ -340,27 +340,27 @@ TEST_F(PolicyTest, Eval2) { for (auto i = 0ULL; i < s3All; ++i) { ARN arn1(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket"); - EXPECT_EQ(p.eval(e, trueacct, i, arn1), + EXPECT_EQ(p.eval(e, trueacct, i, arn1, nullptr), Effect::Allow); ARN arn2(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket/myobject"); - EXPECT_EQ(p.eval(e, trueacct, i, arn2), + EXPECT_EQ(p.eval(e, trueacct, i, arn2, nullptr), Effect::Allow); ARN arn3(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket"); - EXPECT_EQ(p.eval(e, notacct, i, arn3), + EXPECT_EQ(p.eval(e, notacct, i, arn3, nullptr), Effect::Pass); ARN arn4(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket/myobject"); - EXPECT_EQ(p.eval(e, notacct, i, arn4), + EXPECT_EQ(p.eval(e, notacct, i, arn4, nullptr), Effect::Pass); ARN arn5(Partition::aws, Service::s3, "", arbitrary_tenant, "notyourbucket"); - EXPECT_EQ(p.eval(e, trueacct, i, arn5), + EXPECT_EQ(p.eval(e, trueacct, i, arn5, nullptr), Effect::Pass); ARN arn6(Partition::aws, Service::s3, "", arbitrary_tenant, "notyourbucket/notyourobject"); - EXPECT_EQ(p.eval(e, trueacct, i, arn6), + EXPECT_EQ(p.eval(e, trueacct, i, arn6, nullptr), Effect::Pass); } @@ -532,12 +532,12 @@ TEST_F(PolicyTest, Eval3) { ARN arn1(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket"); - EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn1), + EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn1, nullptr), Effect::Allow); ARN arn2(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket"); - EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn2), + EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn2, nullptr), Effect::Allow); @@ -547,52 +547,52 @@ TEST_F(PolicyTest, Eval3) { } ARN arn3(Partition::aws, Service::s3, "", arbitrary_tenant, "confidential-data"); - EXPECT_EQ(p.eval(em, none, op, arn3), + EXPECT_EQ(p.eval(em, none, op, arn3, nullptr), Effect::Pass); ARN arn4(Partition::aws, Service::s3, "", arbitrary_tenant, "confidential-data"); - EXPECT_EQ(p.eval(tr, none, op, arn4), + EXPECT_EQ(p.eval(tr, none, op, arn4, nullptr), s3allow[op] ? Effect::Allow : Effect::Pass); ARN arn5(Partition::aws, Service::s3, "", arbitrary_tenant, "confidential-data"); - EXPECT_EQ(p.eval(fa, none, op, arn5), + EXPECT_EQ(p.eval(fa, none, op, arn5, nullptr), Effect::Pass); ARN arn6(Partition::aws, Service::s3, "", arbitrary_tenant, "confidential-data/moo"); - EXPECT_EQ(p.eval(em, none, op, arn6), + EXPECT_EQ(p.eval(em, none, op, arn6, nullptr), Effect::Pass); ARN arn7(Partition::aws, Service::s3, "", arbitrary_tenant, "confidential-data/moo"); - EXPECT_EQ(p.eval(tr, none, op, arn7), + EXPECT_EQ(p.eval(tr, none, op, arn7, nullptr), s3allow[op] ? Effect::Allow : Effect::Pass); ARN arn8(Partition::aws, Service::s3, "", arbitrary_tenant, "confidential-data/moo"); - EXPECT_EQ(p.eval(fa, none, op, arn8), + EXPECT_EQ(p.eval(fa, none, op, arn8, nullptr), Effect::Pass); ARN arn9(Partition::aws, Service::s3, "", arbitrary_tenant, "really-confidential-data"); - EXPECT_EQ(p.eval(em, none, op, arn9), + EXPECT_EQ(p.eval(em, none, op, arn9, nullptr), Effect::Pass); ARN arn10(Partition::aws, Service::s3, "", arbitrary_tenant, "really-confidential-data"); - EXPECT_EQ(p.eval(tr, none, op, arn10), + EXPECT_EQ(p.eval(tr, none, op, arn10, nullptr), Effect::Pass); ARN arn11(Partition::aws, Service::s3, "", arbitrary_tenant, "really-confidential-data"); - EXPECT_EQ(p.eval(fa, none, op, arn11), + EXPECT_EQ(p.eval(fa, none, op, arn11, nullptr), Effect::Pass); ARN arn12(Partition::aws, Service::s3, "", arbitrary_tenant, "really-confidential-data/moo"); - EXPECT_EQ(p.eval(em, none, op, arn12), Effect::Pass); + EXPECT_EQ(p.eval(em, none, op, arn12, nullptr), Effect::Pass); ARN arn13(Partition::aws, Service::s3, "", arbitrary_tenant, "really-confidential-data/moo"); - EXPECT_EQ(p.eval(tr, none, op, arn13), Effect::Pass); + EXPECT_EQ(p.eval(tr, none, op, arn13, nullptr), Effect::Pass); ARN arn14(Partition::aws, Service::s3, "", arbitrary_tenant, "really-confidential-data/moo"); - EXPECT_EQ(p.eval(fa, none, op, arn14), Effect::Pass); + EXPECT_EQ(p.eval(fa, none, op, arn14, nullptr), Effect::Pass); } } @@ -633,12 +633,12 @@ TEST_F(PolicyTest, Eval4) { ARN arn1(Partition::aws, Service::iam, "", arbitrary_tenant, "role/example_role"); - EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), + EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1, nullptr), Effect::Allow); ARN arn2(Partition::aws, Service::iam, "", arbitrary_tenant, "role/example_role"); - EXPECT_EQ(p.eval(e, none, iamDeleteRole, arn2), + EXPECT_EQ(p.eval(e, none, iamDeleteRole, arn2, nullptr), Effect::Pass); } @@ -678,17 +678,17 @@ TEST_F(PolicyTest, Eval5) { ARN arn1(Partition::aws, Service::iam, "", arbitrary_tenant, "role/example_role"); - EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), + EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1, nullptr), Effect::Allow); ARN arn2(Partition::aws, Service::iam, "", arbitrary_tenant, "role/example_role"); - EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2), + EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2, nullptr), Effect::Pass); ARN arn3(Partition::aws, Service::iam, "", "", "role/example_role"); - EXPECT_EQ(p.eval(e, none, iamCreateRole, arn3), + EXPECT_EQ(p.eval(e, none, iamCreateRole, arn3, nullptr), Effect::Pass); } @@ -728,12 +728,12 @@ TEST_F(PolicyTest, Eval6) { ARN arn1(Partition::aws, Service::iam, "", arbitrary_tenant, "user/A"); - EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), + EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1, nullptr), Effect::Allow); ARN arn2(Partition::aws, Service::iam, "", arbitrary_tenant, "user/A"); - EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2), + EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2, nullptr), Effect::Allow); } @@ -783,17 +783,17 @@ TEST_F(PolicyTest, Eval7) { ARN arn1(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket/*"); - EXPECT_EQ(p.eval(e, subacct, s3ListBucket, arn1), + EXPECT_EQ(p.eval(e, subacct, s3ListBucket, arn1, nullptr), Effect::Allow); - + ARN arn2(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket/*"); - EXPECT_EQ(p.eval(e, parentacct, s3ListBucket, arn2), + EXPECT_EQ(p.eval(e, parentacct, s3ListBucket, arn2, nullptr), Effect::Pass); ARN arn3(Partition::aws, Service::s3, "", arbitrary_tenant, "mybucket/*"); - EXPECT_EQ(p.eval(e, sub2acct, s3ListBucket, arn3), + EXPECT_EQ(p.eval(e, sub2acct, s3ListBucket, arn3, nullptr), Effect::Pass); } @@ -1278,91 +1278,91 @@ TEST_F(IPPolicyTest, EvalIPAddress) { // Without an IP address in the environment then evaluation will always pass ARN arn1(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(allowp.eval(e, trueacct, s3ListBucket, arn1), + EXPECT_EQ(allowp.eval(e, trueacct, s3ListBucket, arn1, nullptr), Effect::Pass); ARN arn2(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(fullp.eval(e, trueacct, s3ListBucket, arn2), + EXPECT_EQ(fullp.eval(e, trueacct, s3ListBucket, arn2, nullptr), Effect::Pass); ARN arn3(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(allowp.eval(allowedIP, trueacct, s3ListBucket, arn3), + EXPECT_EQ(allowp.eval(allowedIP, trueacct, s3ListBucket, arn3, nullptr), Effect::Allow); ARN arn4(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(allowp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn4), + EXPECT_EQ(allowp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn4, nullptr), Effect::Pass); ARN arn5(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn5), + EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn5, nullptr), Effect::Deny); ARN arn6(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn6), + EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn6, nullptr), Effect::Deny); ARN arn7(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn7), + EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn7, nullptr), Effect::Pass); ARN arn8(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn8), + EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn8, nullptr), Effect::Pass); ARN arn9(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn9), + EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn9, nullptr), Effect::Pass); ARN arn10(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn10), + EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn10, nullptr), Effect::Pass); ARN arn11(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(denyp.eval(allowedIPv6, trueacct, s3ListBucket, arn11), + EXPECT_EQ(denyp.eval(allowedIPv6, trueacct, s3ListBucket, arn11, nullptr), Effect::Deny); ARN arn12(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(denyp.eval(allowedIPv6, trueacct, s3ListBucket, arn12), + EXPECT_EQ(denyp.eval(allowedIPv6, trueacct, s3ListBucket, arn12, nullptr), Effect::Deny); ARN arn13(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(fullp.eval(allowedIP, trueacct, s3ListBucket, arn13), + EXPECT_EQ(fullp.eval(allowedIP, trueacct, s3ListBucket, arn13, nullptr), Effect::Allow); ARN arn14(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(fullp.eval(allowedIP, trueacct, s3ListBucket, arn14), + EXPECT_EQ(fullp.eval(allowedIP, trueacct, s3ListBucket, arn14, nullptr), Effect::Allow); ARN arn15(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(fullp.eval(blocklistedIP, trueacct, s3ListBucket, arn15), + EXPECT_EQ(fullp.eval(blocklistedIP, trueacct, s3ListBucket, arn15, nullptr), Effect::Pass); ARN arn16(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(fullp.eval(blocklistedIP, trueacct, s3ListBucket, arn16), + EXPECT_EQ(fullp.eval(blocklistedIP, trueacct, s3ListBucket, arn16, nullptr), Effect::Pass); ARN arn17(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(fullp.eval(allowedIPv6, trueacct, s3ListBucket, arn17), + EXPECT_EQ(fullp.eval(allowedIPv6, trueacct, s3ListBucket, arn17, nullptr), Effect::Allow); ARN arn18(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(fullp.eval(allowedIPv6, trueacct, s3ListBucket, arn18), + EXPECT_EQ(fullp.eval(allowedIPv6, trueacct, s3ListBucket, arn18, nullptr), Effect::Allow); ARN arn19(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket"); - EXPECT_EQ(fullp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn19), + EXPECT_EQ(fullp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn19, nullptr), Effect::Pass); ARN arn20(Partition::aws, Service::s3, "", arbitrary_tenant, "example_bucket/myobject"); - EXPECT_EQ(fullp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn20), + EXPECT_EQ(fullp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn20, nullptr), Effect::Pass); } @@ -1551,20 +1551,20 @@ TEST(Condition, ArnLike) 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)); } } @@ -1585,7 +1585,7 @@ protected: 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 { @@ -1595,11 +1595,11 @@ TEST_F(ConditionTest, StringNotEqualsLogic) 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 { @@ -1609,7 +1609,7 @@ TEST_F(ConditionTest, StringNotEqualsLogic) 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)); } } @@ -1626,11 +1626,11 @@ TEST_F(ConditionTest, NumericNotEqualsLogic) 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 { @@ -1640,14 +1640,14 @@ TEST_F(ConditionTest, NumericNotEqualsLogic) 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 { @@ -1657,9 +1657,9 @@ TEST_F(ConditionTest, DateNotEqualsLogic) 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 { @@ -1669,14 +1669,14 @@ TEST_F(ConditionTest, DateNotEqualsLogic) 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 { @@ -1686,11 +1686,11 @@ TEST_F(ConditionTest, NotIpAddressLogic) 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 { @@ -1700,14 +1700,14 @@ TEST_F(ConditionTest, NotIpAddressLogic) 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 { @@ -1717,9 +1717,9 @@ TEST_F(ConditionTest, ArnNotEqualsLogic) 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 { @@ -1729,14 +1729,15 @@ TEST_F(ConditionTest, ArnNotEqualsLogic) 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 { @@ -1746,11 +1747,11 @@ TEST_F(ConditionTest, StringNotLikeLogic) 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 { @@ -1760,6 +1761,6 @@ TEST_F(ConditionTest, StringNotLikeLogic) 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)); } }