]> git-server-git.apps.pok.os.sepia.ceph.com Git - ceph.git/commitdiff
rgw/policy: Allow logging policy evaluation
authorAdam C. Emerson <aemerson@redhat.com>
Thu, 25 Jun 2026 20:48:08 +0000 (16:48 -0400)
committerAdam C. Emerson <aemerson@redhat.com>
Tue, 21 Jul 2026 21:55:08 +0000 (17:55 -0400)
When the `rgw_copious_policy_logging` option is set to true, a
detailed trace of policy evaluation is logged.

Assisted-by: Claude Opus 4.6 <noreply@anthropic.com>
(I wanted to see how it performed on rebase conflict resolution.)
Assisted-by: Claude Fable 5 <noreply@anthropic.com>
(I figured I should have it look at the policy evaluation code for
bugs while it's included, and it found a few mistakes in output.)
Signed-off-by: Adam C. Emerson <aemerson@redhat.com>
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_iam_policy_keywords.h
src/rgw/rgw_op.cc
src/rgw/rgw_rest_sts.cc
src/test/rgw/test_rgw_iam_policy.cc

index 921069cfdd0ea61bfd31acb9b4cdc9d2d3f2144e..227a184b1383b393eba53d01e3dcb3b7b54b5ff9 100644 (file)
@@ -4851,6 +4851,7 @@ options:
   default: 11
   services: 
     - rgw
+
 - name: rgw_usage_log_key_transition
   type: bool
   level: advanced
@@ -4891,3 +4892,14 @@ options:
   desc: When not empty, this value is returned as a response header Access-Control-Expose-Headers.
   services:
   - rgw
+
+- name: rgw_copious_policy_logging
+  type: bool
+  level: advanced
+  desc: Log the details of each policy evaluation in extreme verbosity
+  default: false
+  services:
+  - rgw
+  flags:
+  - runtime
+  with_legacy: true
index 597584ce6f204300fd88a92eba4e7c8f30d34aa5..7d9585a5ec805391a42e1b8c0c52cd51b8475b43 100644 (file)
@@ -1102,7 +1102,7 @@ int verify_target_bucket_policy(const DoutPrefixProvider* dpp,
     rgw::IAM::Environment env;
     const auto arn_it = env.emplace("aws:SourceArn", std::move(source_bucket_arn));
     const auto acct_it = env.emplace("aws:SourceAccount", std::move(source_account));
-    if (policy.eval(env, ident, rgw::IAM::s3PutObject, target_resource_arn) != rgw::IAM::Effect::Allow) {
+    if (policy.eval(dpp, env, ident, rgw::IAM::s3PutObject, target_resource_arn) != rgw::IAM::Effect::Allow) {
       ldpp_dout(dpp, 1) << "ERROR: logging bucket: '" << target_bucket_id <<
         "' must have a bucket policy that allows logging service principal to put objects in the following resource ARN: '" <<
         target_resource_arn.to_string() << "' from source bucket ARN: '" << arn_it->second <<
index 88366087ca910c1078c6b3bbf68bd3c3351e630d..f58119686c93896d0904d0dfd555c8f790d08a2f 100644 (file)
@@ -1152,10 +1152,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,
@@ -1376,7 +1377,7 @@ 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
   if (s->public_access_block.RestrictPublicBuckets &&
-      bucket_policy && rgw::IAM::is_public(*bucket_policy) &&
+      bucket_policy && rgw::IAM::is_public(dpp, *bucket_policy) &&
       !s->identity->is_owner_of(s->bucket_info.owner)) {
     ldpp_dout(dpp, 10) << __func__ << ": public policies are blocked by the RestrictPublicBuckets block public access setting" << dendl;
     return false;
@@ -1543,7 +1544,7 @@ 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
   if (ps->public_access_block.RestrictPublicBuckets &&
-      bucket_policy && rgw::IAM::is_public(*bucket_policy) &&
+      bucket_policy && rgw::IAM::is_public(dpp, *bucket_policy) &&
       !ps->identity->is_owner_of(ps->bucket_info.owner)) {
     ldpp_dout(dpp, 10) << __func__ << ": public policies are blocked by the RestrictPublicBuckets block public access setting" << dendl;
     return false;
index e42001c83610cc3f902f6d0c0e08721fc1770089..ac513b85d693fbf2f28c892ef73ee9302651e45c 100644 (file)
@@ -1,29 +1,26 @@
 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:nil -*-
 // vim: ts=8 sw=2 sts=2 expandtab ft=cpp
 
-
 #include <cstring>
 #include <iostream>
 #include <regex>
 #include <sstream>
-#include <stack>
 #include <utility>
 
+#include <fmt/ranges.h>
+#include <fmt/std.h>
+
 #include <arpa/inet.h>
 
 #include <experimental/iterator>
 
 #include "rapidjson/reader.h"
 
-#include "include/expected.hpp"
-
 #include "rgw_auth.h"
 #include "rgw_iam_policy.h"
 
 
-namespace {
-constexpr int dout_subsys = ceph_subsys_rgw;
-}
+inline constexpr int dout_subsys = ceph_subsys_rgw;
 
 using std::dec;
 using std::hex;
@@ -50,6 +47,19 @@ using rapidjson::StringStream;
 
 using rgw::auth::Principal;
 
+using namespace std::literals;
+
+template<typename T>
+struct fmt::formatter<boost::optional<const T&>> : fmt::formatter<T> {
+  template<typename FormatContext>
+  auto format(const boost::optional<const T&>& opt, FormatContext& ctx) const {
+    if (opt) {
+      return fmt::formatter<T>::format(*opt, ctx);
+    }
+    return fmt::format_to(ctx.out(), "--");
+  }
+};
+
 namespace rgw {
 namespace IAM {
 #include "rgw_iam_policy_keywords.frag.cc"
@@ -59,8 +69,6 @@ struct actpair {
   const uint64_t bit;
 };
 
-
-
 static const actpair actpairs[] =
 {{ "s3:AbortMultipartUpload", s3AbortMultipartUpload },
  { "s3:CreateBucket", s3CreateBucket },
@@ -227,6 +235,108 @@ 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 "IpAddress";
+
+  case TokenID::NotIpAddress:
+    return "NotIpAddress";
+
+  case TokenID::ArnEquals:
+    return "ArnEquals";
+
+  case TokenID::ArnNotEquals:
+    return "ArnNotEquals";
+
+  case TokenID::ArnLike:
+    return "ArnLike";
+
+  case TokenID::ArnNotLike:
+    return "ArnNotLike";
+
+  case TokenID::Null:
+    return "Null";
+
+  default:
+    return "InvalidConditionOperator";
+  }
+}
+}
+
+template <typename T>
+inline std::ostream&
+operator<<(std::ostream& out, const boost::optional<T>& t)
+{
+  if (!t)
+    out << "--";
+  else
+    out << ' ' << *t;
+  return out;
+}
+
 struct PolicyParser;
 
 const Keyword top[1]{{"<Top>", TokenKind::pseudo, TokenID::Top, 0, false,
@@ -873,20 +983,28 @@ 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 LogOut& eval_log) const {
   std::vector<std::string> runtime_vals;
   auto i = env.find(key);
   if (op == TokenID::Null) {
     const std::string value = (i == env.end() ? "true" : "false");
-    return typed_any(std::equal_to<bool>{}, as_bool, value, vals);
+    eval_log.format("Null check. key `{}` {}.", key,
+                    i == env.end() ? "is not present"sv :
+                                     "is present"sv);
+    return typed_any(std::equal_to<bool>{}, as_bool, value, vals, eval_log);
   }
 
   if (i == env.end()) {
     if (op == TokenID::ForAllValuesStringEquals ||
         op == TokenID::ForAllValuesStringEqualsIgnoreCase ||
         op == TokenID::ForAllValuesStringLike) {
+      eval_log.format(
+          "Evaluating `{}` when key `{}` is not present. Vacuously true.",
+          condop_string(op), key);
       return true;
     } else {
+      eval_log.format("Evaluating `{}` when key `{}` is not present. Returning {}.",
+                      condop_string(op), key, ifexists ? "true" : "false");
       return ifexists;
     }
   }
@@ -901,113 +1019,131 @@ bool Condition::eval(const Environment& env) const {
     }
   }
   const auto& s = i->second;
-
-  const auto& itr = env.equal_range(key);
-
+  const auto itr = env.equal_range(key);
+  // Printable range because for fmt::format.
+  const std::ranges::subrange prange(itr.first, itr.second);
+  eval_log.format("Evaluating `{}` for `[{}]` against `[{}]`",
+                  condop_string(op), fmt::join(prange, ","),
+                  fmt::join(isruntime ? runtime_vals : vals, ","));
   switch (op) {
     // String!
   case TokenID::ForAnyValueStringEquals:
   case TokenID::StringEquals:
-    return multimap_any(std::equal_to<std::string>(), itr, isruntime? runtime_vals : vals);
+    return multimap_any(std::equal_to<std::string>(), itr,
+                        isruntime ? runtime_vals : vals,
+                        eval_log);
 
   case TokenID::StringNotEquals:
-    return multimap_none(std::equal_to<std::string>(),
-     itr, isruntime? runtime_vals : vals);
+    return multimap_none(std::equal_to<std::string>(), itr,
+                         isruntime ? runtime_vals : vals, eval_log);
 
   case TokenID::ForAnyValueStringEqualsIgnoreCase:
   case TokenID::StringEqualsIgnoreCase:
-    return multimap_any(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+    return multimap_any(ci_equal_to(), itr, isruntime ? runtime_vals : vals,
+                        eval_log);
 
   case TokenID::StringNotEqualsIgnoreCase:
-    return multimap_none(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+    return multimap_none(ci_equal_to(), itr, isruntime ? runtime_vals : vals,
+                         eval_log);
 
   case TokenID::ForAnyValueStringLike:
   case TokenID::StringLike:
-    return multimap_any(string_like(), itr, isruntime? runtime_vals : vals);
+    return multimap_any(string_like(), itr, isruntime ? runtime_vals : vals,
+                        eval_log);
 
   case TokenID::StringNotLike:
-    return multimap_none(string_like(), itr, isruntime? runtime_vals : vals);
+    return multimap_none(string_like(), itr, isruntime ? runtime_vals : vals,
+                         eval_log);
 
   case TokenID::ForAllValuesStringEquals:
-    return multimap_all(std::equal_to<std::string>(), itr, isruntime? runtime_vals : vals);
+    return multimap_all(std::equal_to<std::string>(), itr,
+                        isruntime ? runtime_vals : vals,
+                        eval_log);
 
   case TokenID::ForAllValuesStringLike:
-    return multimap_all(string_like(), itr, isruntime? runtime_vals : vals);
+    return multimap_all(string_like(), itr, isruntime ? runtime_vals : vals,
+                        eval_log);
 
   case TokenID::ForAllValuesStringEqualsIgnoreCase:
-    return multimap_all(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+    return multimap_all(ci_equal_to(), itr, isruntime ? runtime_vals : vals,
+                        eval_log);
 
     // Numeric
   case TokenID::NumericEquals:
-    return typed_any(std::equal_to<double>(), as_number, s, vals);
+    return typed_any(std::equal_to<double>(), as_number, s, vals, eval_log);
 
   case TokenID::NumericNotEquals:
-    return typed_none(std::equal_to<double>(),
-       as_number, s, vals);
+    return typed_none(std::equal_to<double>(), as_number, s, vals, eval_log);
 
 
   case TokenID::NumericLessThan:
-    return typed_any(std::less<double>(), as_number, s, vals);
+    return typed_any(std::less<double>(), as_number, s, vals, eval_log);
 
 
   case TokenID::NumericLessThanEquals:
-    return typed_any(std::less_equal<double>(), as_number, s, vals);
+    return typed_any(std::less_equal<double>(), as_number, s, vals, eval_log);
 
   case TokenID::NumericGreaterThan:
-    return typed_any(std::greater<double>(), as_number, s, vals);
+    return typed_any(std::greater<double>(), as_number, s, vals, eval_log);
 
   case TokenID::NumericGreaterThanEquals:
-    return typed_any(std::greater_equal<double>(), as_number, s,
-       vals);
+    return typed_any(std::greater_equal<double>(), as_number, s, vals,
+                     eval_log);
 
     // Date!
   case TokenID::DateEquals:
-    return typed_any(std::equal_to<ceph::real_time>(), as_date, s, vals);
+    return typed_any(std::equal_to<ceph::real_time>(), as_date, s, vals,
+                     eval_log);
 
   case TokenID::DateNotEquals:
     return typed_none(std::equal_to<ceph::real_time>(),
-       as_date, s, vals);
+                      as_date, s, vals, eval_log);
   case TokenID::DateLessThan:
-    return typed_any(std::less<ceph::real_time>(), as_date, s, vals);
+    return typed_any(std::less<ceph::real_time>(), as_date, s, vals, eval_log);
 
 
   case TokenID::DateLessThanEquals:
-    return typed_any(std::less_equal<ceph::real_time>(), as_date, s, vals);
+    return typed_any(std::less_equal<ceph::real_time>(), as_date, s, vals,
+                     eval_log);
 
   case TokenID::DateGreaterThan:
-    return typed_any(std::greater<ceph::real_time>(), as_date, s, vals);
+    return typed_any(std::greater<ceph::real_time>(), as_date, s, vals,
+                     eval_log);
 
   case TokenID::DateGreaterThanEquals:
     return typed_any(std::greater_equal<ceph::real_time>(), as_date, s,
-                    vals);
+                    vals, eval_log);
 
     // Bool!
   case TokenID::Bool:
-    return typed_any(std::equal_to<bool>(), as_bool, s, vals);
+    return typed_any(std::equal_to<bool>(), as_bool, s, vals, eval_log);
 
     // Binary!
   case TokenID::BinaryEquals:
     return typed_any(std::equal_to<ceph::bufferlist>(), as_binary, s,
-                    vals);
+                    vals, eval_log);
 
     // IP Address!
   case TokenID::IpAddress:
-    return typed_any(std::equal_to<MaskedIP>(), as_network, s, vals);
+    return typed_any(std::equal_to<MaskedIP>(), as_network, s, vals, eval_log);
 
   case TokenID::NotIpAddress:
     return typed_none(std::equal_to<MaskedIP>(),
-      as_network, s, vals);
+                      as_network, s, vals, eval_log);
 
     // Amazon Resource Names!
     // The ArnEquals and ArnLike condition operators behave identically.
   case TokenID::ArnEquals:
   case TokenID::ArnLike:
-    return multimap_any(arn_like, itr, isruntime? runtime_vals : vals);
+    return multimap_any(arn_like, itr, isruntime ? runtime_vals : vals,
+                        eval_log);
   case TokenID::ArnNotEquals:
   case TokenID::ArnNotLike:
-    return multimap_none(arn_like, itr, isruntime? runtime_vals : vals);
+    return multimap_none(arn_like, itr, isruntime ? runtime_vals : vals,
+                         eval_log);
 
   default:
+    eval_log.format("Unknown operation: Returning false.");
     return false;
   }
 }
@@ -1074,96 +1210,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) {
@@ -1184,8 +1230,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) {
@@ -1196,46 +1240,82 @@ 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 LogOut& eval_log,
+    boost::optional<PolicyPrincipal&> princ_type) const {
+
+  if (eval_principal(e, ida, eval_log, princ_type) == Effect::Deny) {
+    eval_log.format("Passing.");
     return Effect::Pass;
   }
 
   if (res && resource.empty() && notresource.empty()) {
+    eval_log.format("A resource was specified but both Resource and NotResource "
+                    "were empty. Passing.");
     return Effect::Pass;
   }
   if (!res && (!resource.empty() || !notresource.empty())) {
+    eval_log.format("No resource was specified, but Resource or "
+                    "NotResource was non-empty. Passing.");
     return Effect::Pass;
   }
   if (!resource.empty() && res) {
-    if (!std::any_of(resource.begin(), resource.end(),
-          [&res](const ARN& pattern) {
-            return pattern.match(*res);
-          })) {
+    eval_log.format("Checking Resource for {}:", res);
+    if (!std::any_of(
+            resource.begin(), resource.end(),
+            [&res, &eval_log](const ARN& pattern) {
+              if (pattern.match(*res)) {
+                eval_log.format("Matched `{}`.", pattern);
+                return true;
+              }
+              return false;
+            })) {
+      eval_log.format("No match in Resource. Passing.");
       return Effect::Pass;
     }
   } else if (!notresource.empty() && res) {
-    if (std::any_of(notresource.begin(), notresource.end(),
-          [&res](const ARN& pattern) {
-            return pattern.match(*res);
+    eval_log.format("Checking NotResource for {}:", res);
+    if (std::any_of(
+          notresource.begin(), notresource.end(),
+          [&res, &eval_log](const ARN& pattern) {
+            if (pattern.match(*res)) {
+              eval_log.format("Matched `{}`.", pattern);
+              return true;
+            }
+            return false;
           })) {
+      eval_log.format("Match found in NotResource. Passing.");
       return Effect::Pass;
     }
   }
 
-  if (!(action[act] == 1) || (notaction[act] == 1)) {
+  if (!(action[act] == 1)) {
+    eval_log.format("{} not found in Action. Passing.",
+                    action_bit_string(action_t(act)));
     return Effect::Pass;
   }
 
-  if (std::all_of(conditions.begin(),
-                 conditions.end(),
-                 [&e](const Condition& c) { return c.eval(e);})) {
+  if (notaction[act] == 1) {
+    eval_log.format("{} found in NotAction. Passing.",
+                    action_bit_string(action_t(act)));
+    return Effect::Pass;
+  }
+
+  eval_log.format("Evaluating conditions:");
+  if (std::all_of(conditions.begin(), conditions.end(),
+                  [&e, &eval_log](const Condition& c) {
+                    return c.eval(e, eval_log);
+                  })) {
+    eval_log.format("Returning {}.", effect);
     return effect;
   }
 
+  eval_log.format("Passing.");
   return Effect::Pass;
 }
 
@@ -1248,50 +1328,76 @@ 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 LogOut& eval_log,
+    boost::optional<PolicyPrincipal&> princ_type) const {
   if (princ_type) {
     *princ_type = PolicyPrincipal::Other;
   }
+  eval_log.format("Evaluating identity `{}`:", ida);
   if (ida) {
     if (princ.empty() && noprinc.empty()) {
+      eval_log.format("Principal empty and NotPrincipal empty: Denying.");
       return Effect::Deny;
     }
-    if (ida->get_identity_type() != TYPE_ROLE && !princ.empty() && !is_identity(*ida, princ)) {
+    if (ida->get_identity_type() != TYPE_ROLE &&
+        !princ.empty() && !is_identity(*ida, princ)) {
+      eval_log.format("Identity is not role, Principal is not empty, and "
+                      "the identity is not in Principal.");
       return Effect::Deny;
     }
+    eval_log.format("Checking type of Principal match.");
     if (ida->get_identity_type() == TYPE_ROLE && !princ.empty()) {
       bool princ_matched = false;
-      for (auto p : princ) { // Check each principal to determine the type of the one that has matched
+      // Check each principal to determine the type of the one that has matched
+      for (auto p : princ) {
         if (ida->is_identity(p)) {
           if (p.is_assumed_role() || p.is_user()) {
-            if (princ_type) *princ_type = PolicyPrincipal::Session;
+            if (princ_type) {
+              eval_log.format("Setting principal type to Session.");
+              *princ_type = PolicyPrincipal::Session;
+            }
           } else {
-            if (princ_type) *princ_type = PolicyPrincipal::Role;
+            if (princ_type) {
+              eval_log.format("Setting principal type to Role.");
+              *princ_type = PolicyPrincipal::Role;
+            }
           }
           princ_matched = true;
         }
       }
       if (!princ_matched) {
+        eval_log.format("No match in Principal, Denying.");
         return Effect::Deny;
       }
     } else if (!noprinc.empty() && is_identity(*ida, noprinc)) {
+      eval_log.format("Match in NotPrincipal, Denying.");
       return Effect::Deny;
     }
   }
   return Effect::Allow;
 }
 
-Effect Statement::eval_conditions(const Environment& e) const {
-  if (std::all_of(conditions.begin(),
-                 conditions.end(),
-                 [&e](const Condition& c) { return c.eval(e);})) {
-    return Effect::Allow;
-  }
+Effect Statement::eval_conditions(const Environment& e,
+                                  const LogOut& eval_log) const {
+  if (std::all_of(
+        conditions.begin(), conditions.end(),
+        [&e, &eval_log](const Condition& c) {
+          eval_log.format("Evaluating condition `{}`:", c);
+          return c.eval(e, eval_log);
+        })) {
+      eval_log.format("Returning Allow.");
+      return Effect::Allow;
+    }
+
+  eval_log.format("Returning Deny.");
   return Effect::Deny;
 }
 
-const char* action_bit_string(action_t action) {
+std::string_view action_bit_string(action_t action) {
   switch (action) {
   case s3GetObject:
     return "s3:GetObject";
@@ -1783,11 +1889,23 @@ const char* action_bit_string(action_t action) {
     return "organizations:ListTargetsForPolicy";
 
   case s3All:
+    return "s3:*";
+
   case s3objectlambdaAll:
+    return "s3-object-lambda:*";
+
   case iamAll:
+    return "iam:*";
+
   case stsAll:
+    return "sts:*";
+
   case snsAll:
+    return "sns:*";
+
   case organizationsAll:
+    return "organizations:*";
+
   case allCount:
     return "{invalidSentinel}";
   }
@@ -1903,45 +2021,70 @@ 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,
+                    const LogOut& eval_log,
+                    boost::optional<PolicyPrincipal&> princ_type) const
+{
   auto allowed = false;
+  eval_log.format("Evaluating policy:\n```\n{}\n```\n"
+                  "Environment: {}\n"
+                  "Principal: {}\n"
+                  "Action: {}\n"
+                  "Resource: {}",
+                  text, e, ida, action_bit_string(action_t(action)),
+                  resource);
+
   for (auto& s : statements) {
-    auto g = s.eval(e, ida, action, resource, princ_type);
+    eval_log.format("Evaluating statement `{}`:", s);
+    auto g = s.eval(e, ida, action, resource, eval_log, princ_type);
     if (g == Effect::Deny) {
+      eval_log.format("Denying.");
       return g;
     } else if (g == Effect::Allow) {
       allowed = true;
     }
   }
+  eval_log.format("{}", allowed ? "Allowing." : "Passing.");
   return allowed ? Effect::Allow : Effect::Pass;
 }
 
-Effect Policy::eval_principal(const Environment& e,
-                   boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type) const {
+Effect
+Policy::eval_principal(
+    const Environment& e,
+    boost::optional<const rgw::auth::Identity&> ida,
+    const LogOut& eval_log,
+    boost::optional<PolicyPrincipal&> princ_type) const {
   auto allowed = false;
   for (auto& s : statements) {
-    auto g = s.eval_principal(e, ida, princ_type);
+    eval_log.format("Evaluating identity `{}` in statement `{}`:", ida, s);
+    auto g = s.eval_principal(e, ida, eval_log, princ_type);
     if (g == Effect::Deny) {
+      eval_log.format("Denying.");
       return g;
     } else if (g == Effect::Allow) {
       allowed = true;
     }
   }
+  eval_log.format("{}", allowed ? "Allowing." : "Denying.");
   return allowed ? Effect::Allow : Effect::Deny;
 }
 
-Effect Policy::eval_conditions(const Environment& e) const {
+Effect Policy::eval_conditions(const Environment& e,
+                               const LogOut& eval_log) const {
   auto allowed = false;
   for (auto& s : statements) {
-    auto g = s.eval_conditions(e);
+    eval_log.format("Evaluating conditions in statement `{}`", s);
+    auto g = s.eval_conditions(e, eval_log);
     if (g == Effect::Deny) {
+      eval_log.format("Denying.");
       return g;
     } else if (g == Effect::Allow) {
       allowed = true;
     }
   }
+  eval_log.format("{}", allowed ? "Allowing." : "Denying.");
   return allowed ? Effect::Allow : Effect::Deny;
 }
 
@@ -1976,11 +2119,18 @@ static const Environment iam_all_env = {
 
 struct IsPublicStatement
 {
-  bool operator() (const Statement &s) const {
+  const LogOut& eval_log;
+
+  IsPublicStatement(const LogOut& eval_log) :
+    eval_log(eval_log)
+  {}
+
+  bool operator()(const Statement& s) const {
     if (s.effect == Effect::Allow) {
       for (const auto& p : s.princ) {
         if (p.is_wildcard()) {
-          return s.eval_conditions(iam_all_env) == Effect::Allow;
+          eval_log.format("Evaluating conditions in statement `{}`:", s);
+          return s.eval_conditions(iam_all_env, eval_log) == Effect::Allow;
         }
       }
     }
@@ -1989,10 +2139,10 @@ struct IsPublicStatement
 };
 
 
-bool is_public(const Policy& p)
+bool is_public(const Policy& p, const LogOut& eval_log)
 {
-  return std::any_of(p.statements.begin(), p.statements.end(), IsPublicStatement());
+  return std::any_of(p.statements.begin(), p.statements.end(),
+                     IsPublicStatement(eval_log));
 }
-
 } // namespace IAM
 } // namespace rgw
index 85ed19f94c7fb2f3af7ff8ac29f1fe2c11753937..313e6438e49242557db6498203eec9393f7f1ad4 100644 (file)
@@ -7,8 +7,10 @@
 #include <chrono>
 #include <cstdint>
 #include <iostream>
+#include <sstream>
 #include <string>
 #include <string_view>
+#include <variant>
 
 #include <boost/algorithm/string/predicate.hpp>
 #include <boost/container/flat_map.hpp>
@@ -18,6 +20,7 @@
 #include <boost/variant.hpp>
 
 #include <fmt/format.h>
+#include <fmt/ostream.h>
 
 #include "common/ceph_time.h"
 #include "common/iso_8601.h"
@@ -339,7 +342,7 @@ inline int op_to_perm(std::uint64_t op) {
 }
 }
 
-const char* action_bit_string(action_t action);
+std::string_view action_bit_string(action_t action);
 
 enum class PolicyPrincipal {
   Role,
@@ -369,6 +372,61 @@ inline bool operator ==(const MaskedIP& l, const MaskedIP& r) {
   return (l.addr >> shift) == (r.addr >> shift);
 }
 
+class LogOut {
+  template <class... Ts>
+  struct overloaded : Ts... {
+    using Ts::operator()...;
+  };
+
+  mutable std::variant<std::monostate, std::ostream*, const DoutPrefixProvider*> log;
+  std::string prefix;
+
+public:
+  LogOut() : log(std::monostate{}) {}
+  LogOut(std::nullptr_t) : log(std::monostate{}) {}
+  LogOut(std::ostream& m) : log(&m) {}
+  LogOut(const DoutPrefixProvider* dpp)
+    : log(dpp) {
+    if (!dpp || !dpp->get_cct()->_conf->rgw_copious_policy_logging) {
+      log = std::monostate{};
+    } else {
+      std::ostringstream ostr;
+      dpp->gen_prefix(ostr);
+      prefix = std::move(ostr).str();
+    }
+  }
+
+  operator bool() const { return !std::holds_alternative<std::monostate>(log); };
+  void vformat(fmt::string_view fmt, fmt::format_args args) const {
+    std::visit(
+        overloaded{
+            [](std::monostate) {},
+            [&fmt, &args](std::ostream* o) {
+              assert(o); // We're constructing from a reference so `o` can't be null.
+              fmt::vprint(*o, fmt, args);
+              *o << std::endl;
+            },
+            [&fmt, &args, this](const DoutPrefixProvider* dpp) {
+              assert(dpp); // If we're given null, we switch to monostate
+
+              // The user has explicitly turned on policy logging, so
+              // we don't bother with the conditional logging
+              // business.
+              ceph::logging::StringEntry e(0, ceph_subsys_rgw, prefix);
+              fmt::vformat_to(e.get_inserter(), fmt, args);
+              dpp->get_cct()->_log->submit_entry(std::move(e));
+              return;
+            }
+            }, log);
+  }
+  template <typename... Args>
+  void format(fmt::format_string<Args...> fmt, Args&&... args) const
+  {
+    vformat(fmt, fmt::make_format_args(args...));
+  }
+};
+
+
 struct Condition {
   TokenID op;
   // Originally I was going to use a perfect hash table, but Marcus
@@ -387,8 +445,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 LogOut& eval_log) const;
 
   static boost::optional<double> as_number(const std::string& s) {
     std::size_t p = 0;
@@ -485,89 +542,120 @@ 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 LogOut& eval_log) {
     for (auto itr = it.first; itr != it.second; itr++) {
       bool matched = false;
+      std::string failmatch;
       for (const auto& d : v) {
         if (f(itr->second, d)) {
-               matched = true;
+          matched = true;
+        } else if (eval_log && failmatch.empty()) {
+          failmatch = fmt::format("({}, {})", itr->second, d);
+        }
+      }
+      if (!matched) {
+        if (failmatch.empty()) {
+          eval_log.format("Values matched against were empty.");
+        } else {
+          eval_log.format("Predicate false for {}", failmatch);
+        }
+        return false;
       }
-     }
-     if (!matched)
-      return false;
     }
     return true;
   }
 
-  template<typename F>
-  static bool multimap_any(F&& f, const unordered_multimap_it_pair& it,
-                           const std::vector<std::string>& v) {
+  template <typename F>
+  static bool
+  multimap_any(F&& f,
+               const unordered_multimap_it_pair& it,
+               const std::vector<std::string>& v,
+               const LogOut& eval_log) {
     for (auto itr = it.first; itr != it.second; itr++) {
       for (const auto& d : v) {
         if (f(itr->second, d)) {
-               return true;
+          eval_log.format("Predicate true for ({}, {})", itr->second, d);
+          return true;
+        }
       }
-     }
     }
+    eval_log.format("No predicate true. Returning false.");
     return false;
   }
 
   template<typename F>
   static bool multimap_none(F&& f, const unordered_multimap_it_pair& it,
-                            const std::vector<std::string>& v) {
+                            const std::vector<std::string>& v,
+                            const LogOut& eval_log) {
     for (auto itr = it.first; itr != it.second; itr++) {
       for (const auto& d : v) {
         if (f(itr->second, d)) {
+          eval_log.format("Predicate true for ({}, {})", itr->second, d);
           return false;
         }
       }
     }
+    eval_log.format("No predicate true. Returning true.");
     return true;
   }
 
   template<typename F, typename X>
   static bool typed_any(F&& f, X&& x, const std::string& c,
-                        const std::vector<std::string>& v) {
-    auto xc = std::forward<X>(x)(c);
+                        const std::vector<std::string>& v,
+                        const LogOut& eval_log) {
+    auto xc = x(c);
     if (!xc) {
+      eval_log.format("Failed to convert `{}`. Returning false.", c);
       return false;
     }
 
     for (const auto& d : v) {
       auto xd = x(d);
       if (!xd) {
+        eval_log.format("Failed to convert `{}`. Skipping.", d);
         continue;
       }
 
       if (f(*xc, *xd)) {
+        eval_log.format("Predicate true for ({}, {})", *xc, *xd);
         return true;
       }
     }
+    eval_log.format("No predicate true for `{}`. Returning false.", *xc);
     return false;
   }
 
   template<typename F, typename X>
   static bool typed_none(F&& f, X&& x, const std::string& c,
-                         const std::vector<std::string>& v) {
-    auto xc = std::forward<X>(x)(c);
+                         const std::vector<std::string>& v,
+                         const LogOut& eval_log) {
+    auto xc = x(c);
     if (!xc) {
+      eval_log.format("Failed to convert `{}`. Returning false.", c);
       return false;
     }
 
     for (const auto& d : v) {
       auto xd = x(d);
       if (!xd) {
+        eval_log.format("Failed to convert `{}`. Skipping.", d);
         continue;
       }
 
       if (f(*xc, *xd)) {
+        eval_log.format("Predicate true for ({}, {})", *xc, *xd);
         return false;
       }
     }
+    eval_log.format("No predicate true for `{}`. Returning true.", *xc);
     return true;
   }
 
@@ -607,13 +695,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 LogOut& eval_log,
+              boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+
+  Effect eval_principal(
+    const Environment& e,
+    boost::optional<const rgw::auth::Identity&> ida,
+    const LogOut& eval_log,
+    boost::optional<PolicyPrincipal&> princ_type = boost::none) const;
+
+  Effect
+  eval_conditions(const Environment& e,
+                  const LogOut& eval_log) const;
 };
 
 std::ostream& operator <<(std::ostream& m, const Statement& s);
@@ -652,14 +748,39 @@ struct Policy {
         std::string text,
         bool reject_invalid_principals);
 
+  Effect eval(std::ostream& out, const Environment& e,
+              boost::optional<const rgw::auth::Identity&> ida,
+              std::uint64_t action,
+              boost::optional<const ARN&> resource,
+              boost::optional<PolicyPrincipal&> princ_type = boost::none) const {
+    return eval(e, ida, action, resource, out, princ_type);
+  }
+
   Effect eval(const Environment& e,
-             boost::optional<const rgw::auth::Identity&> ida,
-             std::uint64_t action, boost::optional<const ARN&> resource, boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+              boost::optional<const rgw::auth::Identity&> ida,
+              std::uint64_t action,
+              boost::optional<const ARN&> resource,
+              boost::optional<PolicyPrincipal&> princ_type = boost::none) const {
+    return eval(e, ida, action, resource, {}, princ_type);
+  }
+
+  Effect eval(const DoutPrefixProvider* dpp,
+              const Environment& e,
+              boost::optional<const rgw::auth::Identity&> ida,
+              std::uint64_t action,
+              boost::optional<const ARN&> resource,
+              boost::optional<PolicyPrincipal&> princ_type = boost::none) const {
+      return eval(e, ida, action, resource, dpp, princ_type);
+  }
 
-  Effect eval_principal(const Environment& e,
-             boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type=boost::none) const;
+  Effect eval_principal(
+      const Environment& e,
+      boost::optional<const rgw::auth::Identity&> ida,
+      const LogOut& eval_log,
+      boost::optional<PolicyPrincipal&> princ_type = boost::none) const;
 
-  Effect eval_conditions(const Environment& e) const;
+  Effect eval_conditions(const Environment& e,
+                         const LogOut& eval_log) const;
 
   template <typename F>
   bool has_conditional(const std::string& conditional, F p) const {
@@ -693,10 +814,36 @@ struct Policy {
   bool has_partial_conditional_value(const std::string& c) const {
     return has_conditional_value(c, Condition::ci_starts_with());
   }
+
+private:
+
+  Effect eval(const Environment& e,
+              boost::optional<const rgw::auth::Identity&> ida,
+              std::uint64_t action,
+              boost::optional<const ARN&> resource,
+              const LogOut& eval_log,
+              boost::optional<PolicyPrincipal&> princ_type  =boost::none) const;
+
 };
 
 std::ostream& operator <<(std::ostream& m, const Policy& p);
-bool is_public(const Policy& p);
+bool is_public(const Policy& p, const LogOut& eval_log);
 
+inline bool is_public(const DoutPrefixProvider* dpp, const Policy& p)
+{
+  bool b = false;
+  if (dpp) {
+    bool copious_logging = dpp->get_cct()->_conf->rgw_copious_policy_logging;
+    b = is_public(p, {copious_logging ? dpp : nullptr});
+  } else {
+    b = is_public(p, nullptr);
+  }
+  return b;
 }
 }
+}
+
+template <> struct fmt::formatter<rgw::IAM::MaskedIP> : ostream_formatter {};
+template <> struct fmt::formatter<rgw::IAM::Condition> : ostream_formatter {};
+template <> struct fmt::formatter<rgw::IAM::Statement> : ostream_formatter {};
+template <> struct fmt::formatter<rgw::IAM::Policy> : ostream_formatter {};
index dc04bff0bba45ee01aab69f009efd825a5057fbb..ada69a64e2fff6dd0bbed9ed8334209f76946d57 100644 (file)
@@ -3,8 +3,11 @@
 
 #pragma once
 
-namespace rgw {
-namespace IAM {
+#include <string_view>
+
+#include <fmt/format.h>
+
+namespace rgw::IAM {
 
 enum class TokenKind {
   pseudo, top, statement, cond_op, cond_key, version_key, effect_key,
@@ -127,6 +130,25 @@ enum class Effect {
   Pass
 };
 
+inline std::string_view to_string(Effect e)
+{
+  using enum Effect;
+  switch (e) {
+  case Allow:
+    return "Allow";
+  case Pass:
+    return "Pass";
+  case Deny:
+    return "Deny";
+  }
+  return "Unknown Effect";
+}
+
+inline std::ostream& operator <<(std::ostream& m, const Effect& e)
+{
+  return m << to_string(e);
+}
+
 enum class Type {
   string,
   number,
@@ -137,5 +159,13 @@ enum class Type {
   arn,
   null
 };
-}
-}
+} // namespace rgw::IAM
+
+template<>
+struct fmt::formatter<rgw::IAM::Effect> : formatter<std::string_view> {
+  template<typename FormatContext>
+  auto format(const rgw::IAM::Effect& e, FormatContext& ctx) const {
+    auto s = to_string(e);
+    return formatter<std::string_view>::format(s, ctx);
+  }
+};
index 9ff104919753782c9bd9e79fe7b0f10e3dc9f297..207637417a257e6ea2293b15740e9088963ca637 100644 (file)
@@ -9359,7 +9359,7 @@ void RGWPutBucketPolicy::execute(optional_yield y)
       s->cct->_conf.get_val<bool>("rgw_policy_reject_invalid_principals"));
     rgw::sal::Attrs attrs(s->bucket_attrs);
     if (s->public_access_block.BlockPublicPolicy &&
-        rgw::IAM::is_public(p)) {
+        rgw::IAM::is_public(this, p)) {
       op_ret = -EACCES;
       return;
     }
@@ -9906,7 +9906,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 676312d75dbc4a6aaadb3df3dff5c2ea3f35c4e2..d9b3f7ff952ecb238fceb4ab0b837a888eb20b16 100644 (file)
@@ -857,7 +857,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;
@@ -870,7 +870,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 2ccdc577089a3e46a74efbf7aca60baad10908ca..af0d6aab8c91b81419fb76e7d32ce95eb8091f33 100644 (file)
@@ -271,18 +271,15 @@ TEST_F(PolicyTest, Eval1) {
 
   ARN arn1(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "example_bucket");
-  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn1),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn1), Effect::Allow);
 
   ARN arn2(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "example_bucket");
-  EXPECT_EQ(p.eval(e, none, s3PutBucketAcl, arn2),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, none, s3PutBucketAcl, arn2), Effect::Pass);
 
   ARN arn3(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "erroneous_bucket");
-  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn3),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn3), Effect::Pass);
 
 }
 
@@ -341,29 +338,22 @@ 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),
-             Effect::Allow);
+    EXPECT_EQ(p.eval(e, trueacct, i, arn1), Effect::Allow);
     ARN arn2(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "mybucket/myobject");
-    EXPECT_EQ(p.eval(e, trueacct, i, arn2),
-             Effect::Allow);
+    EXPECT_EQ(p.eval(e, trueacct, i, arn2), Effect::Allow);
     ARN arn3(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "mybucket");
-    EXPECT_EQ(p.eval(e, notacct, i, arn3),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(e, notacct, i, arn3), Effect::Pass);
     ARN arn4(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "mybucket/myobject");
-    EXPECT_EQ(p.eval(e, notacct, i, arn4),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(e, notacct, i, arn4), Effect::Pass);
     ARN arn5(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "notyourbucket");
-    EXPECT_EQ(p.eval(e, trueacct, i, arn5),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(e, trueacct, i, arn5), Effect::Pass);
     ARN arn6(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "notyourbucket/notyourobject");
-    EXPECT_EQ(p.eval(e, trueacct, i, arn6),
-             Effect::Pass);
-
+    EXPECT_EQ(p.eval(e, trueacct, i, arn6), Effect::Pass);
   }
 }
 
@@ -535,13 +525,11 @@ TEST_F(PolicyTest, Eval3) {
 
   ARN arn1(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "mybucket");
-  EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn1),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn1), Effect::Allow);
 
   ARN arn2(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "mybucket");
-  EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn2),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy, arn2), Effect::Allow);
 
 
   for (auto op = 0ULL; op < s3All; ++op) {
@@ -550,20 +538,16 @@ TEST_F(PolicyTest, Eval3) {
     }
     ARN arn3(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "confidential-data");
-    EXPECT_EQ(p.eval(em, none, op, arn3),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(em, none, op, arn3), Effect::Pass);
     ARN arn4(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "confidential-data");
-    EXPECT_EQ(p.eval(tr, none, op, arn4),
-             s3allow[op] ? Effect::Allow : Effect::Pass);
+    EXPECT_EQ(p.eval(tr, none, op, arn4), s3allow[op] ? Effect::Allow : Effect::Pass);
     ARN arn5(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "confidential-data");
-    EXPECT_EQ(p.eval(fa, none, op, arn5),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(fa, none, op, arn5), Effect::Pass);
     ARN arn6(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "confidential-data/moo");
-    EXPECT_EQ(p.eval(em, none, op, arn6),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(em, none, op, arn6), Effect::Pass);
     ARN arn7(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "confidential-data/moo");
     EXPECT_EQ(p.eval(tr, none, op, arn7),
@@ -574,16 +558,13 @@ TEST_F(PolicyTest, Eval3) {
              Effect::Pass);
     ARN arn9(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "really-confidential-data");
-    EXPECT_EQ(p.eval(em, none, op, arn9),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(em, none, op, arn9), Effect::Pass);
     ARN arn10(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "really-confidential-data");
-    EXPECT_EQ(p.eval(tr, none, op, arn10),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(tr, none, op, arn10), Effect::Pass);
     ARN arn11(Partition::aws, Service::s3,
                         "", arbitrary_tenant, "really-confidential-data");
-    EXPECT_EQ(p.eval(fa, none, op, arn11),
-             Effect::Pass);
+    EXPECT_EQ(p.eval(fa, none, op, arn11), Effect::Pass);
     ARN arn12(Partition::aws, Service::s3,
                         "", arbitrary_tenant,
                         "really-confidential-data/moo");
@@ -596,7 +577,6 @@ TEST_F(PolicyTest, Eval3) {
                         "", arbitrary_tenant,
                         "really-confidential-data/moo");
     EXPECT_EQ(p.eval(fa, none, op, arn14), Effect::Pass);
-
   }
 }
 
@@ -636,13 +616,11 @@ TEST_F(PolicyTest, Eval4) {
 
   ARN arn1(Partition::aws, Service::iam,
                       "", arbitrary_tenant, "role/example_role");
-  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), Effect::Allow);
 
   ARN arn2(Partition::aws, Service::iam,
                       "", arbitrary_tenant, "role/example_role");
-  EXPECT_EQ(p.eval(e, none, iamDeleteRole, arn2),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, none, iamDeleteRole, arn2), Effect::Pass);
 }
 
 TEST_F(PolicyTest, Parse5) {
@@ -681,18 +659,15 @@ TEST_F(PolicyTest, Eval5) {
 
   ARN arn1(Partition::aws, Service::iam,
                       "", arbitrary_tenant, "role/example_role");
-  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), Effect::Allow);
 
   ARN arn2(Partition::aws, Service::iam,
                       "", arbitrary_tenant, "role/example_role");
-  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2), Effect::Pass);
 
   ARN arn3(Partition::aws, Service::iam,
                       "", "", "role/example_role");
-  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn3),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn3), Effect::Pass);
 }
 
 TEST_F(PolicyTest, Parse6) {
@@ -731,13 +706,11 @@ TEST_F(PolicyTest, Eval6) {
 
   ARN arn1(Partition::aws, Service::iam,
                       "", arbitrary_tenant, "user/A");
-  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(e, none, iamCreateRole, arn1), Effect::Allow);
 
   ARN arn2(Partition::aws, Service::iam,
                       "", arbitrary_tenant, "user/A");
-  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2),
-           Effect::Allow);
+  EXPECT_EQ(p.eval(e, none, s3ListBucket, arn2), Effect::Allow);
 }
 
 TEST_F(PolicyTest, Parse7) {
@@ -786,18 +759,15 @@ TEST_F(PolicyTest, Eval7) {
 
   ARN arn1(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "mybucket/*");
-  EXPECT_EQ(p.eval(e, subacct, s3ListBucket, arn1),
-           Effect::Allow);
-  
+  EXPECT_EQ(p.eval(e, subacct, s3ListBucket, arn1), Effect::Allow);
+
   ARN arn2(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "mybucket/*");
-  EXPECT_EQ(p.eval(e, parentacct, s3ListBucket, arn2),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, parentacct, s3ListBucket, arn2), Effect::Pass);
 
   ARN arn3(Partition::aws, Service::s3,
                       "", arbitrary_tenant, "mybucket/*");
-  EXPECT_EQ(p.eval(e, sub2acct, s3ListBucket, arn3),
-           Effect::Pass);
+  EXPECT_EQ(p.eval(e, sub2acct, s3ListBucket, arn3), Effect::Pass);
 }
 
 
@@ -1283,43 +1253,43 @@ TEST_F(IPPolicyTest, EvalIPAddress) {
   ARN arn1(Partition::aws, Service::s3,
                            "", arbitrary_tenant, "example_bucket");
   EXPECT_EQ(allowp.eval(e, trueacct, s3ListBucket, arn1),
-           Effect::Pass);
+            Effect::Pass);
   ARN arn2(Partition::aws, Service::s3,
       "", arbitrary_tenant, "example_bucket/myobject");
   EXPECT_EQ(fullp.eval(e, trueacct, s3ListBucket, arn2),
-           Effect::Pass);
+            Effect::Pass);
 
   ARN arn3(Partition::aws, Service::s3,
                            "", arbitrary_tenant, "example_bucket");
   EXPECT_EQ(allowp.eval(allowedIP, trueacct, s3ListBucket, arn3),
-           Effect::Allow);
+            Effect::Allow);
   ARN arn4(Partition::aws, Service::s3,
                            "", arbitrary_tenant, "example_bucket");
   EXPECT_EQ(allowp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn4),
-           Effect::Pass);
+            Effect::Pass);
 
   ARN arn5(Partition::aws, Service::s3,
                           "", arbitrary_tenant, "example_bucket");
   EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn5),
-           Effect::Deny);
+            Effect::Deny);
   ARN arn6(Partition::aws, Service::s3,
                           "", arbitrary_tenant, "example_bucket/myobject");
   EXPECT_EQ(denyp.eval(allowedIP, trueacct, s3ListBucket, arn6),
-           Effect::Deny);
+            Effect::Deny);
 
   ARN arn7(Partition::aws, Service::s3,
                           "", arbitrary_tenant, "example_bucket");
   EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn7),
-           Effect::Pass);
+            Effect::Pass);
   ARN arn8(Partition::aws, Service::s3,
                           "", arbitrary_tenant, "example_bucket/myobject");
   EXPECT_EQ(denyp.eval(blocklistedIP, trueacct, s3ListBucket, arn8),
-           Effect::Pass);
+            Effect::Pass);
 
   ARN arn9(Partition::aws, Service::s3,
                           "", arbitrary_tenant, "example_bucket");
   EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn9),
-           Effect::Pass);
+            Effect::Pass);
   ARN arn10(Partition::aws, Service::s3,
                           "", arbitrary_tenant, "example_bucket/myobject");
   EXPECT_EQ(denyp.eval(blocklistedIPv6, trueacct, s3ListBucket, arn10),
@@ -1555,20 +1525,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));
   }
 }
 
@@ -1589,7 +1559,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
   {
@@ -1599,11 +1569,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
   {
@@ -1613,7 +1583,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));
   }
 }
 
@@ -1630,11 +1600,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
   {
@@ -1644,14 +1614,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
   {
@@ -1661,9 +1631,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
   {
@@ -1673,14 +1643,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
   {
@@ -1690,11 +1660,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
   {
@@ -1704,14 +1674,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
   {
@@ -1721,9 +1691,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
   {
@@ -1733,14 +1703,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
   {
@@ -1750,11 +1721,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
   {
@@ -1764,7 +1735,7 @@ 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));
   }
 }
 
@@ -1777,8 +1748,8 @@ TEST_F(ConditionTest, Null)
     Condition isNull{TokenID::Null, key.data(), key.size(), false};
     isNull.vals.push_back("true");
 
-    EXPECT_TRUE(isNull.eval({}));
-    EXPECT_FALSE(isNull.eval({{key, "admin/config.txt"}}));
+    EXPECT_TRUE(isNull.eval({}, nullptr));
+    EXPECT_FALSE(isNull.eval({{key, "admin/config.txt"}}, nullptr));
   }
 
   {
@@ -1786,8 +1757,8 @@ TEST_F(ConditionTest, Null)
     Condition notNull{TokenID::Null, key.data(), key.size(), false};
     notNull.vals.push_back("false");
 
-    EXPECT_FALSE(notNull.eval({}));
-    EXPECT_TRUE(notNull.eval({{key, "admin/config.txt"}}));
+    EXPECT_FALSE(notNull.eval({}, nullptr));
+    EXPECT_TRUE(notNull.eval({{key, "admin/config.txt"}}, nullptr));
   }
 }
 
@@ -1799,32 +1770,32 @@ TEST_F(ConditionTest, KeyStoneRoleStringEquals)
   cond.vals.push_back("testrole");
 
   // No roles
-  EXPECT_FALSE(cond.eval({}));
+  EXPECT_FALSE(cond.eval({}, nullptr));
 
   // Single matching role
-  EXPECT_TRUE(cond.eval({{key, "testrole"}}));
+  EXPECT_TRUE(cond.eval({{key, "testrole"}}, nullptr));
 
   // Single non-matching role
-  EXPECT_FALSE(cond.eval({{key, "member"}}));
+  EXPECT_FALSE(cond.eval({{key, "member"}}, nullptr));
 
   //Multiple roles in env,one matches
   Environment multi_env;
   multi_env.emplace(key, "member");
   multi_env.emplace(key, "testrole");
   multi_env.emplace(key, "reader");
-  EXPECT_TRUE(cond.eval(multi_env));
+  EXPECT_TRUE(cond.eval(multi_env, nullptr));
 
   //Multiple roles in env, no match
   Environment no_match_env;
   no_match_env.emplace(key, "member");
   no_match_env.emplace(key, "reader");
-  EXPECT_FALSE(cond.eval(no_match_env));
+  EXPECT_FALSE(cond.eval(no_match_env, nullptr));
 
   // Multiple identical roles (redundancy check)
   Environment duplicate_env;
   duplicate_env.emplace(key, "testrole");
   duplicate_env.emplace(key, "testrole");
-  EXPECT_TRUE(cond.eval(duplicate_env));
+  EXPECT_TRUE(cond.eval(duplicate_env, nullptr));
 }
 
 TEST_F(ConditionTest, KeyStoneRoleNotStringEquals)
@@ -1835,26 +1806,26 @@ TEST_F(ConditionTest, KeyStoneRoleNotStringEquals)
   cond.vals.push_back("admin");
 
   // No roles
-  EXPECT_FALSE(cond.eval({}));
+  EXPECT_FALSE(cond.eval({}, nullptr));
 
   // Role matches
-  EXPECT_FALSE(cond.eval({{key, "admin"}}));
+  EXPECT_FALSE(cond.eval({{key, "admin"}}, nullptr));
 
   // Role doesn't match
-  EXPECT_TRUE(cond.eval({{key, "member"}}));
+  EXPECT_TRUE(cond.eval({{key, "member"}}, nullptr));
 
   // Multiple roles in env, one matches -> false
   Environment multi_env;
   multi_env.emplace(key, "member");
   multi_env.emplace(key, "admin");
   EXPECT_FALSE(multi_env.count(key) == 0);
-  EXPECT_FALSE(cond.eval(multi_env));
+  EXPECT_FALSE(cond.eval(multi_env, nullptr));
 
   // Multiple roles, none match -> true
   Environment no_match_env;
   no_match_env.emplace(key, "member");
   no_match_env.emplace(key, "reader");
-  EXPECT_TRUE(cond.eval(no_match_env));
+  EXPECT_TRUE(cond.eval(no_match_env, nullptr));
 }
 
 TEST_F(ConditionTest, KeyStoneRolePolicyParsing)
@@ -1893,18 +1864,18 @@ TEST_F(ConditionTest, KeyStoneRolePolicyParsing)
   // Eval with matching role in environment
   Environment match_env;
   match_env.emplace("keystone:role", "testrole");
-  EXPECT_TRUE(p->statements[0].conditions[0].eval(match_env));
+  EXPECT_TRUE(p->statements[0].conditions[0].eval(match_env, nullptr));
 
   // Eval with non-matching role
   Environment nomatch_env;
   nomatch_env.emplace("keystone:role", "member");
-  EXPECT_FALSE(p->statements[0].conditions[0].eval(nomatch_env));
+  EXPECT_FALSE(p->statements[0].conditions[0].eval(nomatch_env, nullptr));
 
   // Eval with multiple roles, one matching
   Environment multi_env;
   multi_env.emplace("keystone:role", "member");
   multi_env.emplace("keystone:role", "testrole");
-  EXPECT_TRUE(p->statements[0].conditions[0].eval(multi_env));
+  EXPECT_TRUE(p->statements[0].conditions[0].eval(multi_env, nullptr));
 }
 
 TEST_F(ConditionTest, KeystoneUserIdStringEquals)
@@ -1913,7 +1884,7 @@ TEST_F(ConditionTest, KeystoneUserIdStringEquals)
   Condition cond{TokenID::StringEquals, key.data(), key.size(), false};
   cond.vals.push_back("user-123");
 
-  EXPECT_FALSE(cond.eval({}));
-  EXPECT_TRUE(cond.eval({{key, "user-123"}}));
-  EXPECT_FALSE(cond.eval({{key, "user-456"}}));
-}
\ No newline at end of file
+  EXPECT_FALSE(cond.eval({}, nullptr));
+  EXPECT_TRUE(cond.eval({{key, "user-123"}}, nullptr));
+  EXPECT_FALSE(cond.eval({{key, "user-456"}}, nullptr));
+}