summaryrefslogtreecommitdiffstats
path: root/src/rgw/rgw_iam_policy.cc
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-21 11:54:28 +0000
commite6918187568dbd01842d8d1d2c808ce16a894239 (patch)
tree64f88b554b444a49f656b6c656111a145cbbaa28 /src/rgw/rgw_iam_policy.cc
parentInitial commit. (diff)
downloadceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz
ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/rgw/rgw_iam_policy.cc')
-rw-r--r--src/rgw/rgw_iam_policy.cc1663
1 files changed, 1663 insertions, 0 deletions
diff --git a/src/rgw/rgw_iam_policy.cc b/src/rgw/rgw_iam_policy.cc
new file mode 100644
index 000000000..35aeb15fc
--- /dev/null
+++ b/src/rgw/rgw_iam_policy.cc
@@ -0,0 +1,1663 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab ft=cpp
+
+
+#include <cstring>
+#include <iostream>
+#include <regex>
+#include <sstream>
+#include <stack>
+#include <utility>
+
+#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;
+}
+
+using std::dec;
+using std::hex;
+using std::int64_t;
+using std::size_t;
+using std::string;
+using std::stringstream;
+using std::ostream;
+using std::uint16_t;
+using std::uint64_t;
+
+using boost::container::flat_set;
+using std::regex;
+using std::regex_match;
+using std::smatch;
+
+using rapidjson::BaseReaderHandler;
+using rapidjson::UTF8;
+using rapidjson::SizeType;
+using rapidjson::Reader;
+using rapidjson::kParseCommentsFlag;
+using rapidjson::kParseNumbersAsStringsFlag;
+using rapidjson::StringStream;
+
+using rgw::auth::Principal;
+
+namespace rgw {
+namespace IAM {
+#include "rgw_iam_policy_keywords.frag.cc"
+
+struct actpair {
+ const char* name;
+ const uint64_t bit;
+};
+
+
+
+static const actpair actpairs[] =
+{{ "s3:AbortMultipartUpload", s3AbortMultipartUpload },
+ { "s3:CreateBucket", s3CreateBucket },
+ { "s3:DeleteBucketPolicy", s3DeleteBucketPolicy },
+ { "s3:DeleteBucket", s3DeleteBucket },
+ { "s3:DeleteBucketWebsite", s3DeleteBucketWebsite },
+ { "s3:DeleteObject", s3DeleteObject },
+ { "s3:DeleteObjectVersion", s3DeleteObjectVersion },
+ { "s3:DeleteObjectTagging", s3DeleteObjectTagging },
+ { "s3:DeleteObjectVersionTagging", s3DeleteObjectVersionTagging },
+ { "s3:DeleteBucketPublicAccessBlock", s3DeleteBucketPublicAccessBlock},
+ { "s3:DeletePublicAccessBlock", s3DeletePublicAccessBlock},
+ { "s3:DeleteReplicationConfiguration", s3DeleteReplicationConfiguration },
+ { "s3:GetAccelerateConfiguration", s3GetAccelerateConfiguration },
+ { "s3:GetBucketAcl", s3GetBucketAcl },
+ { "s3:GetBucketCORS", s3GetBucketCORS },
+ { "s3:GetBucketEncryption", s3GetBucketEncryption },
+ { "s3:GetBucketLocation", s3GetBucketLocation },
+ { "s3:GetBucketLogging", s3GetBucketLogging },
+ { "s3:GetBucketNotification", s3GetBucketNotification },
+ { "s3:GetBucketPolicy", s3GetBucketPolicy },
+ { "s3:GetBucketPolicyStatus", s3GetBucketPolicyStatus },
+ { "s3:GetBucketPublicAccessBlock", s3GetBucketPublicAccessBlock },
+ { "s3:GetBucketRequestPayment", s3GetBucketRequestPayment },
+ { "s3:GetBucketTagging", s3GetBucketTagging },
+ { "s3:GetBucketVersioning", s3GetBucketVersioning },
+ { "s3:GetBucketWebsite", s3GetBucketWebsite },
+ { "s3:GetLifecycleConfiguration", s3GetLifecycleConfiguration },
+ { "s3:GetBucketObjectLockConfiguration", s3GetBucketObjectLockConfiguration },
+ { "s3:GetPublicAccessBlock", s3GetPublicAccessBlock },
+ { "s3:GetObjectAcl", s3GetObjectAcl },
+ { "s3:GetObject", s3GetObject },
+ { "s3:GetObjectTorrent", s3GetObjectTorrent },
+ { "s3:GetObjectVersionAcl", s3GetObjectVersionAcl },
+ { "s3:GetObjectVersion", s3GetObjectVersion },
+ { "s3:GetObjectVersionTorrent", s3GetObjectVersionTorrent },
+ { "s3:GetObjectTagging", s3GetObjectTagging },
+ { "s3:GetObjectVersionTagging", s3GetObjectVersionTagging},
+ { "s3:GetObjectRetention", s3GetObjectRetention},
+ { "s3:GetObjectLegalHold", s3GetObjectLegalHold},
+ { "s3:GetReplicationConfiguration", s3GetReplicationConfiguration },
+ { "s3:ListAllMyBuckets", s3ListAllMyBuckets },
+ { "s3:ListBucketMultipartUploads", s3ListBucketMultipartUploads },
+ { "s3:ListBucket", s3ListBucket },
+ { "s3:ListBucketVersions", s3ListBucketVersions },
+ { "s3:ListMultipartUploadParts", s3ListMultipartUploadParts },
+ { "s3:PutAccelerateConfiguration", s3PutAccelerateConfiguration },
+ { "s3:PutBucketAcl", s3PutBucketAcl },
+ { "s3:PutBucketCORS", s3PutBucketCORS },
+ { "s3:PutBucketEncryption", s3PutBucketEncryption },
+ { "s3:PutBucketLogging", s3PutBucketLogging },
+ { "s3:PutBucketNotification", s3PutBucketNotification },
+ { "s3:PutBucketPolicy", s3PutBucketPolicy },
+ { "s3:PutBucketRequestPayment", s3PutBucketRequestPayment },
+ { "s3:PutBucketTagging", s3PutBucketTagging },
+ { "s3:PutBucketVersioning", s3PutBucketVersioning },
+ { "s3:PutBucketWebsite", s3PutBucketWebsite },
+ { "s3:PutLifecycleConfiguration", s3PutLifecycleConfiguration },
+ { "s3:PutBucketObjectLockConfiguration", s3PutBucketObjectLockConfiguration },
+ { "s3:PutObjectAcl", s3PutObjectAcl },
+ { "s3:PutObject", s3PutObject },
+ { "s3:PutObjectVersionAcl", s3PutObjectVersionAcl },
+ { "s3:PutObjectTagging", s3PutObjectTagging },
+ { "s3:PutObjectVersionTagging", s3PutObjectVersionTagging },
+ { "s3:PutObjectRetention", s3PutObjectRetention },
+ { "s3:PutObjectLegalHold", s3PutObjectLegalHold },
+ { "s3:BypassGovernanceRetention", s3BypassGovernanceRetention },
+ { "s3:PutBucketPublicAccessBlock", s3PutBucketPublicAccessBlock },
+ { "s3:PutPublicAccessBlock", s3PutPublicAccessBlock },
+ { "s3:PutReplicationConfiguration", s3PutReplicationConfiguration },
+ { "s3:RestoreObject", s3RestoreObject },
+ { "iam:PutUserPolicy", iamPutUserPolicy },
+ { "iam:GetUserPolicy", iamGetUserPolicy },
+ { "iam:DeleteUserPolicy", iamDeleteUserPolicy },
+ { "iam:ListUserPolicies", iamListUserPolicies },
+ { "iam:CreateRole", iamCreateRole},
+ { "iam:DeleteRole", iamDeleteRole},
+ { "iam:GetRole", iamGetRole},
+ { "iam:ModifyRoleTrustPolicy", iamModifyRoleTrustPolicy},
+ { "iam:ListRoles", iamListRoles},
+ { "iam:PutRolePolicy", iamPutRolePolicy},
+ { "iam:GetRolePolicy", iamGetRolePolicy},
+ { "iam:ListRolePolicies", iamListRolePolicies},
+ { "iam:DeleteRolePolicy", iamDeleteRolePolicy},
+ { "iam:CreateOIDCProvider", iamCreateOIDCProvider},
+ { "iam:DeleteOIDCProvider", iamDeleteOIDCProvider},
+ { "iam:GetOIDCProvider", iamGetOIDCProvider},
+ { "iam:ListOIDCProviders", iamListOIDCProviders},
+ { "iam:TagRole", iamTagRole},
+ { "iam:ListRoleTags", iamListRoleTags},
+ { "iam:UntagRole", iamUntagRole},
+ { "iam:UpdateRole", iamUpdateRole},
+ { "sts:AssumeRole", stsAssumeRole},
+ { "sts:AssumeRoleWithWebIdentity", stsAssumeRoleWithWebIdentity},
+ { "sts:GetSessionToken", stsGetSessionToken},
+ { "sts:TagSession", stsTagSession},
+};
+
+struct PolicyParser;
+
+const Keyword top[1]{{"<Top>", TokenKind::pseudo, TokenID::Top, 0, false,
+ false}};
+const Keyword cond_key[1]{{"<Condition Key>", TokenKind::cond_key,
+ TokenID::CondKey, 0, true, false}};
+
+struct ParseState {
+ PolicyParser* pp;
+ const Keyword* w;
+
+ bool arraying = false;
+ bool objecting = false;
+ bool cond_ifexists = false;
+
+ void reset();
+
+ void annotate(std::string&& a);
+
+ boost::optional<Principal> parse_principal(string&& s, string* errmsg);
+
+ ParseState(PolicyParser* pp, const Keyword* w)
+ : pp(pp), w(w) {}
+
+ bool obj_start();
+
+ bool obj_end();
+
+ bool array_start() {
+ if (w->arrayable && !arraying) {
+ arraying = true;
+ return true;
+ }
+ annotate(fmt::format("`{}` does not take array.",
+ w->name));
+ return false;
+ }
+
+ bool array_end();
+
+ bool key(const char* s, size_t l);
+ bool do_string(CephContext* cct, const char* s, size_t l);
+ bool number(const char* str, size_t l);
+};
+
+// If this confuses you, look up the Curiously Recurring Template Pattern
+struct PolicyParser : public BaseReaderHandler<UTF8<>, PolicyParser> {
+ keyword_hash tokens;
+ std::vector<ParseState> s;
+ CephContext* cct;
+ const string& tenant;
+ Policy& policy;
+ uint32_t v = 0;
+
+ const bool reject_invalid_principals;
+
+ uint32_t seen = 0;
+
+ std::string annotation{"No error?"};
+
+ uint32_t dex(TokenID in) const {
+ switch (in) {
+ case TokenID::Version:
+ return 0x1;
+ case TokenID::Id:
+ return 0x2;
+ case TokenID::Statement:
+ return 0x4;
+ case TokenID::Sid:
+ return 0x8;
+ case TokenID::Effect:
+ return 0x10;
+ case TokenID::Principal:
+ return 0x20;
+ case TokenID::NotPrincipal:
+ return 0x40;
+ case TokenID::Action:
+ return 0x80;
+ case TokenID::NotAction:
+ return 0x100;
+ case TokenID::Resource:
+ return 0x200;
+ case TokenID::NotResource:
+ return 0x400;
+ case TokenID::Condition:
+ return 0x800;
+ case TokenID::AWS:
+ return 0x1000;
+ case TokenID::Federated:
+ return 0x2000;
+ case TokenID::Service:
+ return 0x4000;
+ case TokenID::CanonicalUser:
+ return 0x8000;
+ default:
+ ceph_abort();
+ }
+ }
+ bool test(TokenID in) {
+ return seen & dex(in);
+ }
+ void set(TokenID in) {
+ seen |= dex(in);
+ if (dex(in) & (dex(TokenID::Sid) | dex(TokenID::Effect) |
+ dex(TokenID::Principal) | dex(TokenID::NotPrincipal) |
+ dex(TokenID::Action) | dex(TokenID::NotAction) |
+ dex(TokenID::Resource) | dex(TokenID::NotResource) |
+ dex(TokenID::Condition) | dex(TokenID::AWS) |
+ dex(TokenID::Federated) | dex(TokenID::Service) |
+ dex(TokenID::CanonicalUser))) {
+ v |= dex(in);
+ }
+ }
+ void set(std::initializer_list<TokenID> l) {
+ for (auto in : l) {
+ seen |= dex(in);
+ if (dex(in) & (dex(TokenID::Sid) | dex(TokenID::Effect) |
+ dex(TokenID::Principal) | dex(TokenID::NotPrincipal) |
+ dex(TokenID::Action) | dex(TokenID::NotAction) |
+ dex(TokenID::Resource) | dex(TokenID::NotResource) |
+ dex(TokenID::Condition) | dex(TokenID::AWS) |
+ dex(TokenID::Federated) | dex(TokenID::Service) |
+ dex(TokenID::CanonicalUser))) {
+ v |= dex(in);
+ }
+ }
+ }
+ void reset(TokenID in) {
+ seen &= ~dex(in);
+ if (dex(in) & (dex(TokenID::Sid) | dex(TokenID::Effect) |
+ dex(TokenID::Principal) | dex(TokenID::NotPrincipal) |
+ dex(TokenID::Action) | dex(TokenID::NotAction) |
+ dex(TokenID::Resource) | dex(TokenID::NotResource) |
+ dex(TokenID::Condition) | dex(TokenID::AWS) |
+ dex(TokenID::Federated) | dex(TokenID::Service) |
+ dex(TokenID::CanonicalUser))) {
+ v &= ~dex(in);
+ }
+ }
+ void reset(std::initializer_list<TokenID> l) {
+ for (auto in : l) {
+ seen &= ~dex(in);
+ if (dex(in) & (dex(TokenID::Sid) | dex(TokenID::Effect) |
+ dex(TokenID::Principal) | dex(TokenID::NotPrincipal) |
+ dex(TokenID::Action) | dex(TokenID::NotAction) |
+ dex(TokenID::Resource) | dex(TokenID::NotResource) |
+ dex(TokenID::Condition) | dex(TokenID::AWS) |
+ dex(TokenID::Federated) | dex(TokenID::Service) |
+ dex(TokenID::CanonicalUser))) {
+ v &= ~dex(in);
+ }
+ }
+ }
+ void reset(uint32_t& v) {
+ seen &= ~v;
+ v = 0;
+ }
+
+ PolicyParser(CephContext* cct, const string& tenant, Policy& policy,
+ bool reject_invalid_principals)
+ : cct(cct), tenant(tenant), policy(policy),
+ reject_invalid_principals(reject_invalid_principals) {}
+ PolicyParser(const PolicyParser& policy) = delete;
+
+ bool StartObject() {
+ if (s.empty()) {
+ s.push_back({this, top});
+ s.back().objecting = true;
+ return true;
+ }
+
+ return s.back().obj_start();
+ }
+ bool EndObject(SizeType memberCount) {
+ if (s.empty()) {
+ annotation = "Attempt to end unopened object at top level.";
+ return false;
+ }
+ return s.back().obj_end();
+ }
+ bool Key(const char* str, SizeType length, bool copy) {
+ if (s.empty()) {
+ annotation = "Key not allowed at top level.";
+ return false;
+ }
+ return s.back().key(str, length);
+ }
+
+ bool String(const char* str, SizeType length, bool copy) {
+ if (s.empty()) {
+ annotation = "String not allowed at top level.";
+ return false;
+ }
+ return s.back().do_string(cct, str, length);
+ }
+ bool RawNumber(const char* str, SizeType length, bool copy) {
+ if (s.empty()) {
+ annotation = "Number not allowed at top level.";
+ return false;
+ }
+
+ return s.back().number(str, length);
+ }
+ bool StartArray() {
+ if (s.empty()) {
+ annotation = "Array not allowed at top level.";
+ return false;
+ }
+
+ return s.back().array_start();
+ }
+ bool EndArray(SizeType) {
+ if (s.empty()) {
+ return false;
+ }
+
+ return s.back().array_end();
+ }
+
+ bool Default() {
+ return false;
+ }
+};
+
+
+// I really despise this misfeature of C++.
+//
+void ParseState::annotate(std::string&& a) {
+ pp->annotation = std::move(a);
+}
+
+bool ParseState::obj_end() {
+ if (objecting) {
+ objecting = false;
+ if (!arraying) {
+ pp->s.pop_back();
+ } else {
+ reset();
+ }
+ return true;
+ }
+ annotate(
+ fmt::format("Attempt to end unopened object on keyword `{}`.",
+ w->name));
+ return false;
+}
+
+bool ParseState::key(const char* s, size_t l) {
+ auto token_len = l;
+ bool ifexists = false;
+ if (w->id == TokenID::Condition && w->kind == TokenKind::statement) {
+ static constexpr char IfExists[] = "IfExists";
+ if (boost::algorithm::ends_with(std::string_view{s, l}, IfExists)) {
+ ifexists = true;
+ token_len -= sizeof(IfExists)-1;
+ }
+ }
+ auto k = pp->tokens.lookup(s, token_len);
+
+ if (!k) {
+ if (w->kind == TokenKind::cond_op) {
+ auto id = w->id;
+ auto& t = pp->policy.statements.back();
+ auto c_ife = cond_ifexists;
+ pp->s.emplace_back(pp, cond_key);
+ t.conditions.emplace_back(id, s, l, c_ife);
+ return true;
+ } else {
+ annotate(fmt::format("Unknown key `{}`.", std::string_view{s, token_len}));
+ return false;
+ }
+ }
+
+ // If the token we're going with belongs within the condition at the
+ // top of the stack and we haven't already encountered it, push it
+ // on the stack
+ // Top
+ if ((((w->id == TokenID::Top) && (k->kind == TokenKind::top)) ||
+ // Statement
+ ((w->id == TokenID::Statement) && (k->kind == TokenKind::statement)) ||
+
+ /// Principal
+ ((w->id == TokenID::Principal || w->id == TokenID::NotPrincipal) &&
+ (k->kind == TokenKind::princ_type))) &&
+
+ // Check that it hasn't been encountered. Note that this
+ // conjoins with the run of disjunctions above.
+ !pp->test(k->id)) {
+ pp->set(k->id);
+ pp->s.emplace_back(pp, k);
+ return true;
+ } else if ((w->id == TokenID::Condition) &&
+ (k->kind == TokenKind::cond_op)) {
+ pp->s.emplace_back(pp, k);
+ pp->s.back().cond_ifexists = ifexists;
+ return true;
+ }
+ annotate(fmt::format("Token `{}` is not allowed in the context of `{}`.",
+ k->name, w->name));
+ return false;
+}
+
+// I should just rewrite a few helper functions to use iterators,
+// which will make all of this ever so much nicer.
+boost::optional<Principal> ParseState::parse_principal(string&& s,
+ string* errmsg) {
+ if ((w->id == TokenID::AWS) && (s == "*")) {
+ // Wildcard!
+ return Principal::wildcard();
+ } else if (w->id == TokenID::CanonicalUser) {
+ // Do nothing for now.
+ if (errmsg)
+ *errmsg = "RGW does not support canonical users.";
+ return boost::none;
+ } else if (w->id == TokenID::AWS || w->id == TokenID::Federated) {
+ // AWS and Federated ARNs
+ if (auto a = ARN::parse(s)) {
+ if (a->resource == "root") {
+ return Principal::tenant(std::move(a->account));
+ }
+
+ static const char rx_str[] = "([^/]*)/(.*)";
+ static const regex rx(rx_str, sizeof(rx_str) - 1,
+ std::regex_constants::ECMAScript |
+ std::regex_constants::optimize);
+ smatch match;
+ if (regex_match(a->resource, match, rx) && match.size() == 3) {
+ if (match[1] == "user") {
+ return Principal::user(std::move(a->account),
+ match[2]);
+ }
+
+ if (match[1] == "role") {
+ return Principal::role(std::move(a->account),
+ match[2]);
+ }
+
+ if (match[1] == "oidc-provider") {
+ return Principal::oidc_provider(std::move(match[2]));
+ }
+ if (match[1] == "assumed-role") {
+ return Principal::assumed_role(std::move(a->account), match[2]);
+ }
+ }
+ } else if (std::none_of(s.begin(), s.end(),
+ [](const char& c) {
+ return (c == ':') || (c == '/');
+ })) {
+ // Since tenants are simply prefixes, there's no really good
+ // way to see if one exists or not. So we return the thing and
+ // let them try to match against it.
+ return Principal::tenant(std::move(s));
+ }
+ if (errmsg)
+ *errmsg =
+ fmt::format(
+ "`{}` is not a supported AWS or Federated ARN. Supported ARNs are "
+ "forms like: "
+ "`arn:aws:iam::tenant:root` or a bare tenant name for a tenant, "
+ "`arn:aws:iam::tenant:role/role-name` for a role, "
+ "`arn:aws:sts::tenant:assumed-role/role-name/role-session-name` "
+ "for an assumed role, "
+ "`arn:aws:iam::tenant:user/user-name` for a user, "
+ "`arn:aws:iam::tenant:oidc-provider/idp-url` for OIDC.", s);
+ }
+
+ if (errmsg)
+ *errmsg = fmt::format("RGW does not support principals of type `{}`.",
+ w->name);
+ return boost::none;
+}
+
+bool ParseState::do_string(CephContext* cct, const char* s, size_t l) {
+ auto k = pp->tokens.lookup(s, l);
+ Policy& p = pp->policy;
+ bool is_action = false;
+ bool is_validaction = false;
+ Statement* t = p.statements.empty() ? nullptr : &(p.statements.back());
+
+ // Top level!
+ if (w->id == TokenID::Version) {
+ if (k && k->kind == TokenKind::version_key) {
+ p.version = static_cast<Version>(k->specific);
+ } else {
+ annotate(
+ fmt::format("`{}` is not a valid version. Valid versions are "
+ "`2008-10-17` and `2012-10-17`.",
+ std::string_view{s, l}));
+
+ return false;
+ }
+ } else if (w->id == TokenID::Id) {
+ p.id = string(s, l);
+
+ // Statement
+
+ } else if (w->id == TokenID::Sid) {
+ t->sid.emplace(s, l);
+ } else if (w->id == TokenID::Effect) {
+ if (k && k->kind == TokenKind::effect_key) {
+ t->effect = static_cast<Effect>(k->specific);
+ } else {
+ annotate(fmt::format("`{}` is not a valid effect.",
+ std::string_view{s, l}));
+ return false;
+ }
+ } else if (w->id == TokenID::Principal && s && *s == '*') {
+ t->princ.emplace(Principal::wildcard());
+ } else if (w->id == TokenID::NotPrincipal && s && *s == '*') {
+ t->noprinc.emplace(Principal::wildcard());
+ } else if ((w->id == TokenID::Action) ||
+ (w->id == TokenID::NotAction)) {
+ is_action = true;
+ if (*s == '*') {
+ is_validaction = true;
+ (w->id == TokenID::Action ?
+ t->action = allValue : t->notaction = allValue);
+ } else {
+ for (auto& p : actpairs) {
+ if (match_policy({s, l}, p.name, MATCH_POLICY_ACTION)) {
+ is_validaction = true;
+ (w->id == TokenID::Action ? t->action[p.bit] = 1 : t->notaction[p.bit] = 1);
+ }
+ if ((t->action & s3AllValue) == s3AllValue) {
+ t->action[s3All] = 1;
+ }
+ if ((t->notaction & s3AllValue) == s3AllValue) {
+ t->notaction[s3All] = 1;
+ }
+ if ((t->action & iamAllValue) == iamAllValue) {
+ t->action[iamAll] = 1;
+ }
+ if ((t->notaction & iamAllValue) == iamAllValue) {
+ t->notaction[iamAll] = 1;
+ }
+ if ((t->action & stsAllValue) == stsAllValue) {
+ t->action[stsAll] = 1;
+ }
+ if ((t->notaction & stsAllValue) == stsAllValue) {
+ t->notaction[stsAll] = 1;
+ }
+ }
+ }
+ } else if (w->id == TokenID::Resource || w->id == TokenID::NotResource) {
+ auto a = ARN::parse({s, l}, true);
+ if (!a) {
+ annotate(
+ fmt::format("`{}` is not a valid ARN. Resource ARNs should have a "
+ "format like `arn:aws:s3::tenant:resource' or "
+ "`arn:aws:s3:::resource`.",
+ std::string_view{s, l}));
+ return false;
+ }
+ // You can't specify resources for someone ELSE'S account.
+ if (a->account.empty() || a->account == pp->tenant ||
+ a->account == "*") {
+ if (a->account.empty() || a->account == "*")
+ a->account = pp->tenant;
+ (w->id == TokenID::Resource ? t->resource : t->notresource)
+ .emplace(std::move(*a));
+ } else {
+ annotate(fmt::format("Policy owned by tenant `{}` cannot grant access to "
+ "resource owned by tenant `{}`.",
+ pp->tenant, a->account));
+ return false;
+ }
+ } else if (w->kind == TokenKind::cond_key) {
+ auto& t = pp->policy.statements.back();
+ if (l > 0 && *s == '$') {
+ if (l >= 2 && *(s+1) == '{') {
+ if (l > 0 && *(s+l-1) == '}') {
+ t.conditions.back().isruntime = true;
+ } else {
+ annotate(fmt::format("Invalid interpolation `{}`.",
+ std::string_view{s, l}));
+ return false;
+ }
+ } else {
+ annotate(fmt::format("Invalid interpolation `{}`.",
+ std::string_view{s, l}));
+ return false;
+ }
+ }
+ t.conditions.back().vals.emplace_back(s, l);
+
+ // Principals
+
+ } else if (w->kind == TokenKind::princ_type) {
+ if (pp->s.size() <= 1) {
+ annotate(fmt::format("Principle isn't allowed at top level."));
+ return false;
+ }
+ auto& pri = pp->s[pp->s.size() - 2].w->id == TokenID::Principal ?
+ t->princ : t->noprinc;
+
+ string errmsg;
+ if (auto o = parse_principal({s, l}, &errmsg)) {
+ pri.emplace(std::move(*o));
+ } else if (pp->reject_invalid_principals) {
+ annotate(std::move(errmsg));
+ return false;
+ } else {
+ ldout(cct, 0) << "Ignored principle `" << std::string_view{s, l} << "`: "
+ << errmsg << dendl;
+ }
+ } else {
+ // Failure
+ annotate(fmt::format("`{}` is not valid in the context of `{}`.",
+ std::string_view{s, l}, w->name));
+ return false;
+ }
+
+ if (!arraying) {
+ pp->s.pop_back();
+ }
+
+ if (is_action && !is_validaction) {
+ annotate(fmt::format("`{}` is not a valid action.",
+ std::string_view{s, l}));
+ return false;
+ }
+
+ return true;
+}
+
+bool ParseState::number(const char* s, size_t l) {
+ // Top level!
+ if (w->kind == TokenKind::cond_key) {
+ auto& t = pp->policy.statements.back();
+ t.conditions.back().vals.emplace_back(s, l);
+ } else {
+ // Failure
+ annotate("Numbers are not allowed outside condition arguments.");
+ return false;
+ }
+
+ if (!arraying) {
+ pp->s.pop_back();
+ }
+
+ return true;
+}
+
+void ParseState::reset() {
+ pp->reset(pp->v);
+}
+
+bool ParseState::obj_start() {
+ if (w->objectable && !objecting) {
+ objecting = true;
+ if (w->id == TokenID::Statement) {
+ pp->policy.statements.emplace_back();
+ }
+
+ return true;
+ }
+
+ annotate(fmt::format("The {} keyword cannot introduce an object.",
+ w->name));
+
+ return false;
+}
+
+
+bool ParseState::array_end() {
+ if (arraying && !objecting) {
+ pp->s.pop_back();
+ return true;
+ }
+
+ annotate("Attempt to close unopened array.");
+ return false;
+}
+
+ostream& operator <<(ostream& m, const MaskedIP& ip) {
+ // I have a theory about why std::bitset is the way it is.
+ if (ip.v6) {
+ for (int i = 7; i >= 0; --i) {
+ uint16_t hextet = 0;
+ for (int j = 15; j >= 0; --j) {
+ hextet |= (ip.addr[(i * 16) + j] << j);
+ }
+ m << hex << (unsigned int) hextet;
+ if (i != 0) {
+ m << ":";
+ }
+ }
+ } else {
+ // It involves Satan.
+ for (int i = 3; i >= 0; --i) {
+ uint8_t b = 0;
+ for (int j = 7; j >= 0; --j) {
+ b |= (ip.addr[(i * 8) + j] << j);
+ }
+ m << (unsigned int) b;
+ if (i != 0) {
+ m << ".";
+ }
+ }
+ }
+ m << "/" << dec << ip.prefix;
+ // It would explain a lot
+ return m;
+}
+
+bool Condition::eval(const Environment& env) const {
+ std::vector<std::string> runtime_vals;
+ auto i = env.find(key);
+ if (op == TokenID::Null) {
+ return i == env.end() ? true : false;
+ }
+
+ if (i == env.end()) {
+ if (op == TokenID::ForAllValuesStringEquals ||
+ op == TokenID::ForAllValuesStringEqualsIgnoreCase ||
+ op == TokenID::ForAllValuesStringLike) {
+ return true;
+ } else {
+ return ifexists;
+ }
+ }
+
+ if (isruntime) {
+ string k = vals.back();
+ k.erase(0,2); //erase $, {
+ k.erase(k.length() - 1, 1); //erase }
+ const auto& it = env.equal_range(k);
+ for (auto itr = it.first; itr != it.second; itr++) {
+ runtime_vals.emplace_back(itr->second);
+ }
+ }
+ const auto& s = i->second;
+
+ const auto& itr = env.equal_range(key);
+
+ switch (op) {
+ // String!
+ case TokenID::ForAnyValueStringEquals:
+ case TokenID::StringEquals:
+ return orrible(std::equal_to<std::string>(), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::StringNotEquals:
+ return orrible(std::not_fn(std::equal_to<std::string>()),
+ itr, isruntime? runtime_vals : vals);
+
+ case TokenID::ForAnyValueStringEqualsIgnoreCase:
+ case TokenID::StringEqualsIgnoreCase:
+ return orrible(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::StringNotEqualsIgnoreCase:
+ return orrible(std::not_fn(ci_equal_to()), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::ForAnyValueStringLike:
+ case TokenID::StringLike:
+ return orrible(string_like(), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::StringNotLike:
+ return orrible(std::not_fn(string_like()), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::ForAllValuesStringEquals:
+ return andible(std::equal_to<std::string>(), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::ForAllValuesStringLike:
+ return andible(string_like(), itr, isruntime? runtime_vals : vals);
+
+ case TokenID::ForAllValuesStringEqualsIgnoreCase:
+ return andible(ci_equal_to(), itr, isruntime? runtime_vals : vals);
+
+ // Numeric
+ case TokenID::NumericEquals:
+ return shortible(std::equal_to<double>(), as_number, s, vals);
+
+ case TokenID::NumericNotEquals:
+ return shortible(std::not_fn(std::equal_to<double>()),
+ as_number, s, vals);
+
+
+ case TokenID::NumericLessThan:
+ return shortible(std::less<double>(), as_number, s, vals);
+
+
+ case TokenID::NumericLessThanEquals:
+ return shortible(std::less_equal<double>(), as_number, s, vals);
+
+ case TokenID::NumericGreaterThan:
+ return shortible(std::greater<double>(), as_number, s, vals);
+
+ case TokenID::NumericGreaterThanEquals:
+ return shortible(std::greater_equal<double>(), as_number, s, vals);
+
+ // Date!
+ case TokenID::DateEquals:
+ return shortible(std::equal_to<ceph::real_time>(), as_date, s, vals);
+
+ case TokenID::DateNotEquals:
+ return shortible(std::not_fn(std::equal_to<ceph::real_time>()),
+ as_date, s, vals);
+
+ case TokenID::DateLessThan:
+ return shortible(std::less<ceph::real_time>(), as_date, s, vals);
+
+
+ case TokenID::DateLessThanEquals:
+ return shortible(std::less_equal<ceph::real_time>(), as_date, s, vals);
+
+ case TokenID::DateGreaterThan:
+ return shortible(std::greater<ceph::real_time>(), as_date, s, vals);
+
+ case TokenID::DateGreaterThanEquals:
+ return shortible(std::greater_equal<ceph::real_time>(), as_date, s,
+ vals);
+
+ // Bool!
+ case TokenID::Bool:
+ return shortible(std::equal_to<bool>(), as_bool, s, vals);
+
+ // Binary!
+ case TokenID::BinaryEquals:
+ return shortible(std::equal_to<ceph::bufferlist>(), as_binary, s,
+ vals);
+
+ // IP Address!
+ case TokenID::IpAddress:
+ return shortible(std::equal_to<MaskedIP>(), as_network, s, vals);
+
+ case TokenID::NotIpAddress:
+ {
+ auto xc = as_network(s);
+ if (!xc) {
+ return false;
+ }
+
+ for (const string& d : vals) {
+ auto xd = as_network(d);
+ if (!xd) {
+ continue;
+ }
+
+ if (xc == xd) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+#if 0
+ // Amazon Resource Names! (Does S3 need this?)
+ TokenID::ArnEquals, TokenID::ArnNotEquals, TokenID::ArnLike,
+ TokenID::ArnNotLike,
+#endif
+
+ default:
+ return false;
+ }
+}
+
+boost::optional<MaskedIP> Condition::as_network(const string& s) {
+ MaskedIP m;
+ if (s.empty()) {
+ return boost::none;
+ }
+
+ m.v6 = (s.find(':') == string::npos) ? false : true;
+
+ auto slash = s.find('/');
+ if (slash == string::npos) {
+ m.prefix = m.v6 ? 128 : 32;
+ } else {
+ char* end = 0;
+ m.prefix = strtoul(s.data() + slash + 1, &end, 10);
+ if (*end != 0 || (m.v6 && m.prefix > 128) ||
+ (!m.v6 && m.prefix > 32)) {
+ return boost::none;
+ }
+ }
+
+ string t;
+ auto p = &s;
+
+ if (slash != string::npos) {
+ t.assign(s, 0, slash);
+ p = &t;
+ }
+
+ if (m.v6) {
+ struct in6_addr a;
+ if (inet_pton(AF_INET6, p->c_str(), static_cast<void*>(&a)) != 1) {
+ return boost::none;
+ }
+
+ m.addr |= Address(a.s6_addr[15]) << 0;
+ m.addr |= Address(a.s6_addr[14]) << 8;
+ m.addr |= Address(a.s6_addr[13]) << 16;
+ m.addr |= Address(a.s6_addr[12]) << 24;
+ m.addr |= Address(a.s6_addr[11]) << 32;
+ m.addr |= Address(a.s6_addr[10]) << 40;
+ m.addr |= Address(a.s6_addr[9]) << 48;
+ m.addr |= Address(a.s6_addr[8]) << 56;
+ m.addr |= Address(a.s6_addr[7]) << 64;
+ m.addr |= Address(a.s6_addr[6]) << 72;
+ m.addr |= Address(a.s6_addr[5]) << 80;
+ m.addr |= Address(a.s6_addr[4]) << 88;
+ m.addr |= Address(a.s6_addr[3]) << 96;
+ m.addr |= Address(a.s6_addr[2]) << 104;
+ m.addr |= Address(a.s6_addr[1]) << 112;
+ m.addr |= Address(a.s6_addr[0]) << 120;
+ } else {
+ struct in_addr a;
+ if (inet_pton(AF_INET, p->c_str(), static_cast<void*>(&a)) != 1) {
+ return boost::none;
+ }
+
+ m.addr = ntohl(a.s_addr);
+ }
+
+ 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) {
+ m << "[]";
+ } else {
+ m << "[ ";
+ std::copy(begin, end, std::experimental::make_ostream_joiner(m, ", "));
+ m << " ]";
+ }
+ return m;
+}
+
+template<typename Iterator>
+ostream& print_dict(ostream& m, Iterator begin, Iterator end) {
+ m << "{ ";
+ std::copy(begin, end, std::experimental::make_ostream_joiner(m, ", "));
+ m << " }";
+ return m;
+}
+
+}
+
+ostream& operator <<(ostream& m, const Condition& c) {
+ m << condop_string(c.op);
+ if (c.ifexists) {
+ m << "IfExists";
+ }
+ m << ": { " << c.key;
+ print_array(m, c.vals.cbegin(), c.vals.cend());
+ 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) {
+ return Effect::Pass;
+ }
+
+ if (res && resource.empty() && notresource.empty()) {
+ return Effect::Pass;
+ }
+ if (!res && (!resource.empty() || !notresource.empty())) {
+ return Effect::Pass;
+ }
+ if (!resource.empty() && res) {
+ if (!std::any_of(resource.begin(), resource.end(),
+ [&res](const ARN& pattern) {
+ return pattern.match(*res);
+ })) {
+ return Effect::Pass;
+ }
+ } else if (!notresource.empty() && res) {
+ if (std::any_of(notresource.begin(), notresource.end(),
+ [&res](const ARN& pattern) {
+ return pattern.match(*res);
+ })) {
+ return Effect::Pass;
+ }
+ }
+
+ if (!(action[act] == 1) || (notaction[act] == 1)) {
+ return Effect::Pass;
+ }
+
+ if (std::all_of(conditions.begin(),
+ conditions.end(),
+ [&e](const Condition& c) { return c.eval(e);})) {
+ return effect;
+ }
+
+ return Effect::Pass;
+}
+
+Effect Statement::eval_principal(const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida, boost::optional<PolicyPrincipal&> princ_type) const {
+ if (princ_type) {
+ *princ_type = PolicyPrincipal::Other;
+ }
+ if (ida) {
+ if (princ.empty() && noprinc.empty()) {
+ return Effect::Deny;
+ }
+ if (ida->get_identity_type() != TYPE_ROLE && !princ.empty() && !ida->is_identity(princ)) {
+ return Effect::Deny;
+ }
+ 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
+ boost::container::flat_set<Principal> id;
+ id.insert(p);
+ if (ida->is_identity(id)) {
+ if (p.is_assumed_role() || p.is_user()) {
+ if (princ_type) *princ_type = PolicyPrincipal::Session;
+ } else {
+ if (princ_type) *princ_type = PolicyPrincipal::Role;
+ }
+ princ_matched = true;
+ }
+ }
+ if (!princ_matched) {
+ return Effect::Deny;
+ }
+ } else if (!noprinc.empty() && ida->is_identity(noprinc)) {
+ 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;
+ }
+ return Effect::Deny;
+}
+
+namespace {
+const char* action_bit_string(uint64_t action) {
+ switch (action) {
+ case s3GetObject:
+ return "s3:GetObject";
+
+ case s3GetObjectVersion:
+ return "s3:GetObjectVersion";
+
+ case s3PutObject:
+ return "s3:PutObject";
+
+ case s3GetObjectAcl:
+ return "s3:GetObjectAcl";
+
+ case s3GetObjectVersionAcl:
+ return "s3:GetObjectVersionAcl";
+
+ case s3PutObjectAcl:
+ return "s3:PutObjectAcl";
+
+ case s3PutObjectVersionAcl:
+ return "s3:PutObjectVersionAcl";
+
+ case s3DeleteObject:
+ return "s3:DeleteObject";
+
+ case s3DeleteObjectVersion:
+ return "s3:DeleteObjectVersion";
+
+ case s3ListMultipartUploadParts:
+ return "s3:ListMultipartUploadParts";
+
+ case s3AbortMultipartUpload:
+ return "s3:AbortMultipartUpload";
+
+ case s3GetObjectTorrent:
+ return "s3:GetObjectTorrent";
+
+ case s3GetObjectVersionTorrent:
+ return "s3:GetObjectVersionTorrent";
+
+ case s3RestoreObject:
+ return "s3:RestoreObject";
+
+ case s3CreateBucket:
+ return "s3:CreateBucket";
+
+ case s3DeleteBucket:
+ return "s3:DeleteBucket";
+
+ case s3ListBucket:
+ return "s3:ListBucket";
+
+ case s3ListBucketVersions:
+ return "s3:ListBucketVersions";
+ case s3ListAllMyBuckets:
+ return "s3:ListAllMyBuckets";
+
+ case s3ListBucketMultipartUploads:
+ return "s3:ListBucketMultipartUploads";
+
+ case s3GetAccelerateConfiguration:
+ return "s3:GetAccelerateConfiguration";
+
+ case s3PutAccelerateConfiguration:
+ return "s3:PutAccelerateConfiguration";
+
+ case s3GetBucketAcl:
+ return "s3:GetBucketAcl";
+
+ case s3PutBucketAcl:
+ return "s3:PutBucketAcl";
+
+ case s3GetBucketCORS:
+ return "s3:GetBucketCORS";
+
+ case s3PutBucketCORS:
+ return "s3:PutBucketCORS";
+
+ case s3GetBucketEncryption:
+ return "s3:GetBucketEncryption";
+
+ case s3PutBucketEncryption:
+ return "s3:PutBucketEncryption";
+
+ case s3GetBucketVersioning:
+ return "s3:GetBucketVersioning";
+
+ case s3PutBucketVersioning:
+ return "s3:PutBucketVersioning";
+
+ case s3GetBucketRequestPayment:
+ return "s3:GetBucketRequestPayment";
+
+ case s3PutBucketRequestPayment:
+ return "s3:PutBucketRequestPayment";
+
+ case s3GetBucketLocation:
+ return "s3:GetBucketLocation";
+
+ case s3GetBucketPolicy:
+ return "s3:GetBucketPolicy";
+
+ case s3DeleteBucketPolicy:
+ return "s3:DeleteBucketPolicy";
+
+ case s3PutBucketPolicy:
+ return "s3:PutBucketPolicy";
+
+ case s3GetBucketNotification:
+ return "s3:GetBucketNotification";
+
+ case s3PutBucketNotification:
+ return "s3:PutBucketNotification";
+
+ case s3GetBucketLogging:
+ return "s3:GetBucketLogging";
+
+ case s3PutBucketLogging:
+ return "s3:PutBucketLogging";
+
+ case s3GetBucketTagging:
+ return "s3:GetBucketTagging";
+
+ case s3PutBucketTagging:
+ return "s3:PutBucketTagging";
+
+ case s3GetBucketWebsite:
+ return "s3:GetBucketWebsite";
+
+ case s3PutBucketWebsite:
+ return "s3:PutBucketWebsite";
+
+ case s3DeleteBucketWebsite:
+ return "s3:DeleteBucketWebsite";
+
+ case s3GetLifecycleConfiguration:
+ return "s3:GetLifecycleConfiguration";
+
+ case s3PutLifecycleConfiguration:
+ return "s3:PutLifecycleConfiguration";
+
+ case s3PutReplicationConfiguration:
+ return "s3:PutReplicationConfiguration";
+
+ case s3GetReplicationConfiguration:
+ return "s3:GetReplicationConfiguration";
+
+ case s3DeleteReplicationConfiguration:
+ return "s3:DeleteReplicationConfiguration";
+
+ case s3PutObjectTagging:
+ return "s3:PutObjectTagging";
+
+ case s3PutObjectVersionTagging:
+ return "s3:PutObjectVersionTagging";
+
+ case s3GetObjectTagging:
+ return "s3:GetObjectTagging";
+
+ case s3GetObjectVersionTagging:
+ return "s3:GetObjectVersionTagging";
+
+ case s3DeleteObjectTagging:
+ return "s3:DeleteObjectTagging";
+
+ case s3DeleteObjectVersionTagging:
+ return "s3:DeleteObjectVersionTagging";
+
+ case s3PutBucketObjectLockConfiguration:
+ return "s3:PutBucketObjectLockConfiguration";
+
+ case s3GetBucketObjectLockConfiguration:
+ return "s3:GetBucketObjectLockConfiguration";
+
+ case s3PutObjectRetention:
+ return "s3:PutObjectRetention";
+
+ case s3GetObjectRetention:
+ return "s3:GetObjectRetention";
+
+ case s3PutObjectLegalHold:
+ return "s3:PutObjectLegalHold";
+
+ case s3GetObjectLegalHold:
+ return "s3:GetObjectLegalHold";
+
+ case s3BypassGovernanceRetention:
+ return "s3:BypassGovernanceRetention";
+
+ case iamPutUserPolicy:
+ return "iam:PutUserPolicy";
+
+ case iamGetUserPolicy:
+ return "iam:GetUserPolicy";
+
+ case iamListUserPolicies:
+ return "iam:ListUserPolicies";
+
+ case iamDeleteUserPolicy:
+ return "iam:DeleteUserPolicy";
+
+ case iamCreateRole:
+ return "iam:CreateRole";
+
+ case iamDeleteRole:
+ return "iam:DeleteRole";
+
+ case iamGetRole:
+ return "iam:GetRole";
+
+ case iamModifyRoleTrustPolicy:
+ return "iam:ModifyRoleTrustPolicy";
+
+ case iamListRoles:
+ return "iam:ListRoles";
+
+ case iamPutRolePolicy:
+ return "iam:PutRolePolicy";
+
+ case iamGetRolePolicy:
+ return "iam:GetRolePolicy";
+
+ case iamListRolePolicies:
+ return "iam:ListRolePolicies";
+
+ case iamDeleteRolePolicy:
+ return "iam:DeleteRolePolicy";
+
+ case iamCreateOIDCProvider:
+ return "iam:CreateOIDCProvider";
+
+ case iamDeleteOIDCProvider:
+ return "iam:DeleteOIDCProvider";
+
+ case iamGetOIDCProvider:
+ return "iam:GetOIDCProvider";
+
+ case iamListOIDCProviders:
+ return "iam:ListOIDCProviders";
+
+ case iamTagRole:
+ return "iam:TagRole";
+
+ case iamListRoleTags:
+ return "iam:ListRoleTags";
+
+ case iamUntagRole:
+ return "iam:UntagRole";
+
+ case iamUpdateRole:
+ return "iam:UpdateRole";
+
+ case stsAssumeRole:
+ return "sts:AssumeRole";
+
+ case stsAssumeRoleWithWebIdentity:
+ return "sts:AssumeRoleWithWebIdentity";
+
+ case stsGetSessionToken:
+ return "sts:GetSessionToken";
+
+ case stsTagSession:
+ return "sts:TagSession";
+ }
+ return "s3Invalid";
+}
+
+ostream& print_actions(ostream& m, const Action_t a) {
+ bool begun = false;
+ m << "[ ";
+ for (auto i = 0U; i < allCount; ++i) {
+ if (a[i] == 1) {
+ if (begun) {
+ m << ", ";
+ } else {
+ begun = true;
+ }
+ m << action_bit_string(i);
+ }
+ }
+ if (begun) {
+ m << " ]";
+ } else {
+ m << "]";
+ }
+ return m;
+}
+}
+
+ostream& operator <<(ostream& m, const Statement& s) {
+ m << "{ ";
+ if (s.sid) {
+ m << "Sid: " << *s.sid << ", ";
+ }
+ if (!s.princ.empty()) {
+ m << "Principal: ";
+ print_dict(m, s.princ.cbegin(), s.princ.cend());
+ m << ", ";
+ }
+ if (!s.noprinc.empty()) {
+ m << "NotPrincipal: ";
+ print_dict(m, s.noprinc.cbegin(), s.noprinc.cend());
+ m << ", ";
+ }
+
+ m << "Effect: " <<
+ (s.effect == Effect::Allow ?
+ (const char*) "Allow" :
+ (const char*) "Deny");
+
+ if (s.action.any() || s.notaction.any() || !s.resource.empty() ||
+ !s.notresource.empty() || !s.conditions.empty()) {
+ m << ", ";
+ }
+
+ if (s.action.any()) {
+ m << "Action: ";
+ print_actions(m, s.action);
+
+ if (s.notaction.any() || !s.resource.empty() ||
+ !s.notresource.empty() || !s.conditions.empty()) {
+ m << ", ";
+ }
+ }
+
+ if (s.notaction.any()) {
+ m << "NotAction: ";
+ print_actions(m, s.notaction);
+
+ if (!s.resource.empty() || !s.notresource.empty() ||
+ !s.conditions.empty()) {
+ m << ", ";
+ }
+ }
+
+ if (!s.resource.empty()) {
+ m << "Resource: ";
+ print_array(m, s.resource.cbegin(), s.resource.cend());
+
+ if (!s.notresource.empty() || !s.conditions.empty()) {
+ m << ", ";
+ }
+ }
+
+ if (!s.notresource.empty()) {
+ m << "NotResource: ";
+ print_array(m, s.notresource.cbegin(), s.notresource.cend());
+
+ if (!s.conditions.empty()) {
+ m << ", ";
+ }
+ }
+
+ if (!s.conditions.empty()) {
+ m << "Condition: ";
+ print_dict(m, s.conditions.cbegin(), s.conditions.cend());
+ }
+
+ return m << " }";
+}
+
+Policy::Policy(CephContext* cct, const string& tenant,
+ const bufferlist& _text,
+ bool reject_invalid_principals)
+ : text(_text.to_str()) {
+ StringStream ss(text.data());
+ PolicyParser pp(cct, tenant, *this, reject_invalid_principals);
+ auto pr = Reader{}.Parse<kParseNumbersAsStringsFlag |
+ kParseCommentsFlag>(ss, pp);
+ if (!pr) {
+ throw PolicyParseException(pr, pp.annotation);
+ }
+}
+
+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 {
+ 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;
+ }
+ }
+ 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 {
+ auto allowed = false;
+ for (auto& s : statements) {
+ auto g = s.eval_principal(e, ida, princ_type);
+ if (g == Effect::Deny) {
+ return g;
+ } else if (g == Effect::Allow) {
+ allowed = true;
+ }
+ }
+ return allowed ? Effect::Allow : Effect::Deny;
+}
+
+Effect Policy::eval_conditions(const Environment& e) const {
+ auto allowed = false;
+ for (auto& s : statements) {
+ auto g = s.eval_conditions(e);
+ if (g == Effect::Deny) {
+ return g;
+ } else if (g == Effect::Allow) {
+ allowed = true;
+ }
+ }
+ return allowed ? Effect::Allow : Effect::Deny;
+}
+
+ostream& operator <<(ostream& m, const Policy& p) {
+ m << "{ Version: "
+ << (p.version == Version::v2008_10_17 ? "2008-10-17" : "2012-10-17");
+
+ if (p.id || !p.statements.empty()) {
+ m << ", ";
+ }
+
+ if (p.id) {
+ m << "Id: " << *p.id;
+ if (!p.statements.empty()) {
+ m << ", ";
+ }
+ }
+
+ if (!p.statements.empty()) {
+ m << "Statements: ";
+ print_array(m, p.statements.cbegin(), p.statements.cend());
+ m << ", ";
+ }
+ return m << " }";
+}
+
+static const Environment iam_all_env = {
+ {"aws:SourceIp","1.1.1.1"},
+ {"aws:UserId","anonymous"},
+ {"s3:x-amz-server-side-encryption-aws-kms-key-id","secret"}
+};
+
+struct IsPublicStatement
+{
+ 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;
+ }
+ }
+ // no princ should not contain fixed values
+ return std::none_of(s.noprinc.begin(), s.noprinc.end(), [](const rgw::auth::Principal& p) {
+ return p.is_wildcard();
+ });
+ }
+ return false;
+ }
+};
+
+
+bool is_public(const Policy& p)
+{
+ return std::any_of(p.statements.begin(), p.statements.end(), IsPublicStatement());
+}
+
+} // namespace IAM
+} // namespace rgw