]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw/policy: Allow logging policy evaluation
authorAdam C. Emerson <aemerson@redhat.com>
Tue, 21 Oct 2025 19:07:18 +0000 (15:07 -0400)
committerAdam C. Emerson <aemerson@redhat.com>
Mon, 15 Dec 2025 17:28:50 +0000 (12:28 -0500)
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 <aemerson@redhat.com>
PendingReleaseNotes
src/common/options/rgw.yaml.in
src/rgw/rgw_bucket_logging.cc
src/rgw/rgw_common.cc
src/rgw/rgw_iam_policy.cc
src/rgw/rgw_iam_policy.h
src/rgw/rgw_op.cc
src/rgw/rgw_rest_sts.cc
src/test/rgw/test_rgw_iam_policy.cc

index fd171f73f65308a70880157a6c822f93b5ce08b4..21bd4da5f2205dc8dd3ad418bb4a2fae29b4fd1b 100644 (file)
@@ -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.
index 7b44a9587e012bc00bf6c6f593683fa2748c35fe..eec1b1ed90a7a58becb59efb27d7d172b1979d84 100644 (file)
@@ -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
index bfb76a46c77d8e4adfa1638976e0eec0751a0fc3..65024171060703046f61ae88aebb0405828f7a88 100644 (file)
@@ -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 <<
index 7d102224d872530520a9dd8b6722bf96dc4a2165..633678ea28d35b57c2f6143a8274cfd43fac9337 100644 (file)
@@ -1153,10 +1153,11 @@ Effect eval_or_pass(const DoutPrefixProvider* dpp,
                     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,
@@ -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;
   }
index c6d5c38ac3273471ddf01bedfd401502eaa5bf69..c665f1370ee4e5038742c0f59df03a2e95901f92 100644 (file)
@@ -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 <cstring>
 #include <iostream>
 #include <regex>
 #include <sstream>
-#include <stack>
 #include <utility>
 
 #include <arpa/inet.h>
 
 #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<string, string>::const_iterator,
+        unordered_multimap<string, string>::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 <typename T>
+inline std::ostream&
+operator<<(std::ostream& out, const boost::optional<T>& 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]{{"<Top>", 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<std::string> 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<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 << "Unknown operation: Returning false" << lendl;
     return false;
   }
 }
@@ -1071,96 +1246,6 @@ boost::optional<MaskedIP> 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<typename Iterator>
 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<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 maybeout& eval_log,
+    boost::optional<PolicyPrincipal&> 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<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 maybeout& eval_log,
+    boost::optional<PolicyPrincipal&> 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<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,
+                    maybeout eval_log,
+                    boost::optional<PolicyPrincipal&> 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<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,
+    maybeout 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 << "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
index 72f29dd4acd5c27cec71bcede6e53bae3b7fb364..c1a3a7e801d5029c5115b108479c0e173d68c933 100644 (file)
@@ -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<allCount>;
 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<double> as_number(const std::string& s) {
     std::size_t p = 0;
@@ -483,43 +580,64 @@ struct Condition {
     }
   };
 
-  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 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<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 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<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 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<typename F, typename X>
+  template <typename F, typename X>
   static bool typed_any(F&& f, X& x, const std::string& c,
-                        const std::vector<std::string>& v) {
+                        const std::vector<std::string>& v,
+                        const maybeout& eval_log) {
     auto xc = std::forward<X>(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<typename F, typename X>
   static bool typed_none(F&& f, X& x, const std::string& c,
-                         const std::vector<std::string>& v) {
+                         const std::vector<std::string>& v,
+                         const maybeout& eval_log) {
     auto xc = std::forward<X>(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<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 maybeout& eval_log,
+              boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+
+  Effect eval_principal(
+    const Environment& e,
+    boost::optional<const rgw::auth::Identity&> ida,
+    const maybeout& eval_log,
+    boost::optional<PolicyPrincipal&> 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<std::string> id = boost::none;
@@ -651,13 +792,43 @@ struct Policy {
         bool reject_invalid_principals);
 
   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,
+              maybeout eval_log,
+              boost::optional<PolicyPrincipal&> princ_type  =boost::none) const;
+
+  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 {
+    std::ostringstream ostr;
+    bool copious_logging = false;
+    if (dpp) {
+      copious_logging =
+          (dpp->get_cct()->_conf.get_val<bool>("rgw_copious_policy_logging") &&
+           dpp->get_cct()->_conf->subsys.should_gather(ceph_subsys_rgw, 20));
+    }
 
-  Effect eval_principal(const Environment& e,
-             boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> 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<const rgw::auth::Identity&> ida,
+      maybeout eval_log,
+      boost::optional<PolicyPrincipal&> princ_type = boost::none) const;
+
+  Effect eval_conditions(const Environment& e,
+                         maybeout eval_log) const;
 
   template <typename F>
   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<bool>("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;
+}
 }
 }
index a0a6068ae78a11f9a809b772367ea7acd664dee0..3e5d40f0947b012175095d9a6b922e4436fe9268 100644 (file)
@@ -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)
index e2d16d56988b0b7ef022a76171e48c00ee84e5de..0769597eefdf63c03e741d630dc65f438c5ae614 100644 (file)
@@ -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;
index c67f92582bd959f47d83cf7ac48f4fe554444960..322f428b83906f436ef8e9648a5797253054110e 100644 (file)
@@ -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));
   }
 }