BuildRequires: /usr/share/selinux/devel/policyhelp
%endif
BuildRequires: bc
+BuildRequires: gperf
BuildRequires: cmake
BuildRequires: cryptsetup
BuildRequires: fuse-devel
Alfredo Deza <adeza@redhat.com>
Build-Depends: bc,
btrfs-tools,
+ gperf,
cmake,
cpio,
cryptsetup-bin | cryptsetup,
if [ x`uname`x = xFreeBSDx ]; then
$SUDO pkg install -yq \
devel/git \
+ devel/gperf \
devel/gmake \
devel/cmake \
devel/yasm \
add_subdirectory(json_spirit)
include_directories("${CMAKE_SOURCE_DIR}/src/xxHash")
+include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/src/rapidjson/include")
set(GMOCK_INCLUDE_DIRS
"${CMAKE_SOURCE_DIR}/src/googletest/googletest/include/gmock")
"${CMAKE_BINARY_DIR}/src/include/civetweb"
COMMENT "keep civetweb.h up-to-date")
+function(gperf_generate input output)
+ add_custom_command(
+ OUTPUT ${output}
+ COMMAND gperf ${input} > ${output}
+ DEPENDS ${input}
+ COMMENT "Generate ${output}"
+ )
+endfunction()
+
set(rgw_a_srcs
rgw_acl.cc
rgw_acl_s3.cc
rgw_xml_enc.cc
rgw_torrent.cc
rgw_crypt.cc
- rgw_crypt_sanitize.cc)
+ rgw_crypt_sanitize.cc
+ rgw_iam_policy.cc)
+
+gperf_generate(${CMAKE_SOURCE_DIR}/src/rgw/rgw_iam_policy_keywords.gperf
+ rgw_iam_policy_keywords.frag.cc)
+set_source_files_properties(rgw_iam_policy.cc PROPERTIES
+ OBJECT_DEPENDS ${CMAKE_BINARY_DIR}/src/rgw/rgw_iam_policy_keywords.frag.cc
+ COMPILE_FLAGS -I${CMAKE_BINARY_DIR}/src/rgw)
+
if (WITH_RADOSGW_FCGI_FRONTEND)
list(APPEND rgw_a_srcs rgw_fcgi.cc)
target_include_directories(rgw_a PUBLIC
"../Beast/include"
- ${FCGI_INCLUDE_DIR})
+ ${FCGI_INCLUDE_DIR}
+ "../rapidjson/include"
+ )
target_compile_definitions(rgw_a PUBLIC BOOST_COROUTINES_NO_DEPRECATION_WARNING)
target_link_libraries(rgw_a librados cls_lock_client cls_rgw_client cls_refcount_client
+#include <iostream>
+#include <sstream>
+#include <string>
+
#include "rgw_basic_types.h"
#include "common/ceph_json.h"
+using std::string;
+using std::stringstream;
+
void decode_json_obj(rgw_user& val, JSONObj *obj)
{
string s = obj->get_data();
string s = val.to_str();
f->dump_string(name, s);
}
+
+namespace rgw {
+namespace auth {
+ostream& operator <<(ostream& m, const Principal& p) {
+ if (p.is_wildcard()) {
+ return m << "*";
+ }
+
+ m << "arn:aws:iam:" << p.get_tenant() << ":";
+ if (p.is_tenant()) {
+ return m << "root";
+ }
+ return m << (p.is_user() ? "user/" : "role/") << p.get_id();
+}
+string to_string(const Principal& p) {
+ stringstream s;
+ s << p;
+ return s.str();
+}
+}
+}
return (t < o.t) || ((t == o.t) && (u < o.u));
}
};
+
+std::ostream& operator <<(std::ostream& m, const Principal& p);
+std::string to_string(const Principal& p);
}
}
string substr_pattern = pattern.substr(last_pos_pattern, cur_pos_pattern);
int res;
- if (flag & MATCH_POLICY_ACTION || flag & MATCH_POLICY_ARN) {
+ if (substr_pattern == "*") {
+ res = 1;
+ } else if (flag & MATCH_POLICY_ACTION || flag & MATCH_POLICY_ARN) {
res = match_internal(substr_pattern, substr_input, &matchignorecase);
} else {
res = match_internal(substr_pattern, substr_input, &matchcase);
if (cur_pos_pattern == string::npos && cur_pos_input == string::npos)
return 1;
else if ((cur_pos_pattern == string::npos && cur_pos_input != string::npos) ||
- (cur_pos_pattern != string::npos && cur_pos_input == string::npos))
+ (cur_pos_pattern != string::npos && cur_pos_input == string::npos))
return 0;
last_pos_pattern = cur_pos_pattern + 1;
namespace s3 {
class RGWGetPolicyV2Extractor;
}
+ class Completer;
}
namespace io {
class BasicClient;
}
}
+
struct req_info {
RGWEnv *env;
RGWHTTPArgs args;
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+
+#include <cstring>
+#include <regex>
+#include <sstream>
+#include <stack>
+#include <utility>
+
+#include "rapidjson/reader.h"
+
+#include "rgw_auth.h"
+#include "rgw_iam_policy.h"
+
+namespace {
+constexpr int dout_subsys = ceph_subsys_rgw;
+}
+
+using std::bitset;
+using std::find;
+using std::int64_t;
+using std::move;
+using std::pair;
+using std::regex;
+using std::regex_match;
+using std::size_t;
+using std::smatch;
+using std::string;
+using std::stringstream;
+using std::ostream;
+using std::uint16_t;
+using std::uint64_t;
+using std::unordered_map;
+
+using boost::container::flat_set;
+using boost::none;
+using boost::optional;
+
+using rapidjson::BaseReaderHandler;
+using rapidjson::UTF8;
+using rapidjson::SizeType;
+using rapidjson::Reader;
+using rapidjson::kParseCommentsFlag;
+using rapidjson::kParseNumbersAsStringsFlag;
+using rapidjson::StringStream;
+using rapidjson::ParseResult;
+
+using rgw::auth::Principal;
+
+namespace rgw {
+namespace IAM {
+#include "rgw_iam_policy_keywords.frag.cc"
+
+struct actpair {
+ const char* name;
+ const uint64_t bit;
+};
+
+namespace {
+optional<Partition> to_partition(const smatch::value_type& p,
+ bool wildcards) {
+ if (p == "aws") {
+ return Partition::aws;
+ } else if (p == "aws-cn") {
+ return Partition::aws_cn;
+ } else if (p == "aws-us-gov") {
+ return Partition::aws_us_gov;
+ } else if (p == "*" && wildcards) {
+ return Partition::wildcard;
+ } else {
+ return none;
+ }
+
+ ceph_abort();
+}
+
+optional<Service> to_service(const smatch::value_type& s,
+ bool wildcards) {
+ static const unordered_map<string, Service> services = {
+ { "acm", Service::acm },
+ { "apigateway", Service::apigateway },
+ { "appstream", Service::appstream },
+ { "artifact", Service::artifact },
+ { "autoscaling", Service::autoscaling },
+ { "aws-marketplace", Service::aws_marketplace },
+ { "aws-marketplace-management",
+ Service::aws_marketplace_management },
+ { "aws-portal", Service::aws_portal },
+ { "cloudformation", Service::cloudformation },
+ { "cloudfront", Service::cloudfront },
+ { "cloudhsm", Service::cloudhsm },
+ { "cloudsearch", Service::cloudsearch },
+ { "cloudtrail", Service::cloudtrail },
+ { "cloudwatch", Service::cloudwatch },
+ { "codebuild", Service::codebuild },
+ { "codecommit", Service::codecommit },
+ { "codedeploy", Service::codedeploy },
+ { "codepipeline", Service::codepipeline },
+ { "cognito-identity", Service::cognito_identity },
+ { "cognito-idp", Service::cognito_idp },
+ { "cognito-sync", Service::cognito_sync },
+ { "config", Service::config },
+ { "datapipeline", Service::datapipeline },
+ { "devicefarm", Service::devicefarm },
+ { "directconnect", Service::directconnect },
+ { "dms", Service::dms },
+ { "ds", Service::ds },
+ { "dynamodb", Service::dynamodb },
+ { "ec2", Service::ec2 },
+ { "ecr", Service::ecr },
+ { "ecs", Service::ecs },
+ { "elasticache", Service::elasticache },
+ { "elasticbeanstalk", Service::elasticbeanstalk },
+ { "elasticfilesystem", Service::elasticfilesystem },
+ { "elasticloadbalancing", Service::elasticloadbalancing },
+ { "elasticmapreduce", Service::elasticmapreduce },
+ { "elastictranscoder", Service::elastictranscoder },
+ { "es", Service::es },
+ { "events", Service::events },
+ { "firehose", Service::firehose },
+ { "gamelift", Service::gamelift },
+ { "glacier", Service::glacier },
+ { "health", Service::health },
+ { "iam", Service::iam },
+ { "importexport", Service::importexport },
+ { "inspector", Service::inspector },
+ { "iot", Service::iot },
+ { "kinesis", Service::kinesis },
+ { "kinesisanalytics", Service::kinesisanalytics },
+ { "kms", Service::kms },
+ { "lambda", Service::lambda },
+ { "lightsail", Service::lightsail },
+ { "logs", Service::logs },
+ { "machinelearning", Service::machinelearning },
+ { "mobileanalytics", Service::mobileanalytics },
+ { "mobilehub", Service::mobilehub },
+ { "opsworks", Service::opsworks },
+ { "opsworks-cm", Service::opsworks_cm },
+ { "polly", Service::polly },
+ { "rds", Service::rds },
+ { "redshift", Service::redshift },
+ { "route53", Service::route53 },
+ { "route53domains", Service::route53domains },
+ { "s3", Service::s3 },
+ { "sdb", Service::sdb },
+ { "servicecatalog", Service::servicecatalog },
+ { "ses", Service::ses },
+ { "sns", Service::sns },
+ { "sqs", Service::sqs },
+ { "ssm", Service::ssm },
+ { "states", Service::states },
+ { "storagegateway", Service::storagegateway },
+ { "sts", Service::sts },
+ { "support", Service::support },
+ { "swf", Service::swf },
+ { "trustedadvisor", Service::trustedadvisor },
+ { "waf", Service::waf },
+ { "workmail", Service::workmail },
+ { "workspaces", Service::workspaces }};
+
+ if (wildcards && s == "*") {
+ return Service::wildcard;
+ }
+
+ auto i = services.find(s);
+ if (i == services.end()) {
+ return none;
+ } else {
+ return i->second;
+ }
+}
+}
+
+ARN::ARN(const rgw_obj& o)
+ : partition(Partition::aws),
+ service(Service::s3),
+ region(),
+ account(o.bucket.tenant),
+ resource(o.bucket.name)
+{
+ resource.push_back('/');
+ resource.append(o.key.name);
+}
+
+ARN::ARN(const rgw_bucket& b)
+ : partition(Partition::aws),
+ service(Service::s3),
+ region(),
+ account(b.tenant),
+ resource(b.name) { }
+
+ARN::ARN(const rgw_bucket& b, const string& o)
+ : partition(Partition::aws),
+ service(Service::s3),
+ region(),
+ account(b.tenant),
+ resource(b.name) {
+ resource.push_back('/');
+ resource.append(o);
+}
+
+optional<ARN> ARN::parse(const string& s, bool wildcards) {
+ static const regex rx_wild("arn:([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)",
+ std::regex_constants::ECMAScript |
+ std::regex_constants::optimize);
+ static const regex rx_no_wild(
+ "arn:([^:*]*):([^:*]*):([^:*]*):([^:*]*):([^:*]*)",
+ std::regex_constants::ECMAScript |
+ std::regex_constants::optimize);
+
+ smatch match;
+
+ if ((s == "*") && wildcards) {
+ return ARN(Partition::wildcard, Service::wildcard, "*", "*", "*");
+ } else if (regex_match(s, match, wildcards ? rx_wild : rx_no_wild)) {
+ ceph_assert(match.size() == 6);
+
+ ARN a;
+ {
+ auto p = to_partition(match[1], wildcards);
+ if (!p)
+ return none;
+
+ a.partition = *p;
+ }
+ {
+ auto s = to_service(match[2], wildcards);
+ if (!s) {
+ return none;
+ }
+ a.service = *s;
+ }
+
+ a.region = match[3];
+ a.account = match[4];
+ a.resource = match[5];
+
+ return a;
+ }
+ return none;
+}
+
+string ARN::to_string() const {
+ string s;
+
+ if (partition == Partition::aws) {
+ s.append("aws:");
+ } else if (partition == Partition::aws_cn) {
+ s.append("aws-cn:");
+ } else if (partition == Partition::aws_us_gov) {
+ s.append("aws-us-gov:");
+ } else {
+ s.append("*:");
+ }
+
+ static const unordered_map<Service, string> services = {
+ { Service::acm, "acm" },
+ { Service::apigateway, "apigateway" },
+ { Service::appstream, "appstream" },
+ { Service::artifact, "artifact" },
+ { Service::autoscaling, "autoscaling" },
+ { Service::aws_marketplace, "aws-marketplace" },
+ { Service::aws_marketplace_management, "aws-marketplace-management" },
+ { Service::aws_portal, "aws-portal" },
+ { Service::cloudformation, "cloudformation" },
+ { Service::cloudfront, "cloudfront" },
+ { Service::cloudhsm, "cloudhsm" },
+ { Service::cloudsearch, "cloudsearch" },
+ { Service::cloudtrail, "cloudtrail" },
+ { Service::cloudwatch, "cloudwatch" },
+ { Service::codebuild, "codebuild" },
+ { Service::codecommit, "codecommit" },
+ { Service::codedeploy, "codedeploy" },
+ { Service::codepipeline, "codepipeline" },
+ { Service::cognito_identity, "cognito-identity" },
+ { Service::cognito_idp, "cognito-idp" },
+ { Service::cognito_sync, "cognito-sync" },
+ { Service::config, "config" },
+ { Service::datapipeline, "datapipeline" },
+ { Service::devicefarm, "devicefarm" },
+ { Service::directconnect, "directconnect" },
+ { Service::dms, "dms" },
+ { Service::ds, "ds" },
+ { Service::dynamodb, "dynamodb" },
+ { Service::ec2, "ec2" },
+ { Service::ecr, "ecr" },
+ { Service::ecs, "ecs" },
+ { Service::elasticache, "elasticache" },
+ { Service::elasticbeanstalk, "elasticbeanstalk" },
+ { Service::elasticfilesystem, "elasticfilesystem" },
+ { Service::elasticloadbalancing, "elasticloadbalancing" },
+ { Service::elasticmapreduce, "elasticmapreduce" },
+ { Service::elastictranscoder, "elastictranscoder" },
+ { Service::es, "es" },
+ { Service::events, "events" },
+ { Service::firehose, "firehose" },
+ { Service::gamelift, "gamelift" },
+ { Service::glacier, "glacier" },
+ { Service::health, "health" },
+ { Service::iam, "iam" },
+ { Service::importexport, "importexport" },
+ { Service::inspector, "inspector" },
+ { Service::iot, "iot" },
+ { Service::kinesis, "kinesis" },
+ { Service::kinesisanalytics, "kinesisanalytics" },
+ { Service::kms, "kms" },
+ { Service::lambda, "lambda" },
+ { Service::lightsail, "lightsail" },
+ { Service::logs, "logs" },
+ { Service::machinelearning, "machinelearning" },
+ { Service::mobileanalytics, "mobileanalytics" },
+ { Service::mobilehub, "mobilehub" },
+ { Service::opsworks, "opsworks" },
+ { Service::opsworks_cm, "opsworks-cm" },
+ { Service::polly, "polly" },
+ { Service::rds, "rds" },
+ { Service::redshift, "redshift" },
+ { Service::route53, "route53" },
+ { Service::route53domains, "route53domains" },
+ { Service::s3, "s3" },
+ { Service::sdb, "sdb" },
+ { Service::servicecatalog, "servicecatalog" },
+ { Service::ses, "ses" },
+ { Service::sns, "sns" },
+ { Service::sqs, "sqs" },
+ { Service::ssm, "ssm" },
+ { Service::states, "states" },
+ { Service::storagegateway, "storagegateway" },
+ { Service::sts, "sts" },
+ { Service::support, "support" },
+ { Service::swf, "swf" },
+ { Service::trustedadvisor, "trustedadvisor" },
+ { Service::waf, "waf" },
+ { Service::workmail, "workmail" },
+ { Service::workspaces, "workspaces" }};
+
+ auto i = services.find(service);
+ if (i != services.end()) {
+ s.append(i->second);
+ } else {
+ s.push_back('*');
+ }
+ s.push_back(':');
+
+ s.append(region);
+ s.push_back(':');
+
+ s.append(account);
+ s.push_back(':');
+
+ s.append(resource);
+
+ return s;
+}
+
+bool operator ==(const ARN& l, const ARN& r) {
+ return ((l.partition == r.partition) &&
+ (l.service == r.service) &&
+ (l.region == r.region) &&
+ (l.account == r.account) &&
+ (l.resource == r.resource));
+}
+bool operator <(const ARN& l, const ARN& r) {
+ return ((l.partition < r.partition) ||
+ (l.service < r.service) ||
+ (l.region < r.region) ||
+ (l.account < r.account) ||
+ (l.resource < r.resource));
+}
+
+// The candidate is not allowed to have wildcards. The only way to
+// do that sanely would be to use unification rather than matching.
+bool ARN::match(const ARN& candidate) const {
+ if ((candidate.partition == Partition::wildcard) ||
+ (partition != candidate.partition && partition
+ != Partition::wildcard)) {
+ return false;
+ }
+
+ if ((candidate.service == Service::wildcard) ||
+ (service != candidate.service && service != Service::wildcard)) {
+ return false;
+ }
+
+ if (!::match(region, candidate.region, MATCH_POLICY_ARN)) {
+ return false;
+ }
+
+ if (!::match(account, candidate.account, MATCH_POLICY_ARN)) {
+ return false;
+ }
+
+ if (!::match(resource, candidate.resource, MATCH_POLICY_ARN)) {
+ return false;
+ }
+
+ return true;
+}
+
+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:DeleteReplicationConfiguration", s3DeleteReplicationConfiguration },
+ { "s3:GetAccelerateConfiguration", s3GetAccelerateConfiguration },
+ { "s3:GetBucketAcl", s3GetBucketAcl },
+ { "s3:GetBucketCORS", s3GetBucketCORS },
+ { "s3:GetBucketLocation", s3GetBucketLocation },
+ { "s3:GetBucketLogging", s3GetBucketLogging },
+ { "s3:GetBucketNotification", s3GetBucketNotification },
+ { "s3:GetBucketPolicy", s3GetBucketPolicy },
+ { "s3:GetBucketRequestPayment", s3GetBucketRequestPayment },
+ { "s3:GetBucketTagging", s3GetBucketTagging },
+ { "s3:GetBucketVersioning", s3GetBucketVersioning },
+ { "s3:GetBucketWebsite", s3GetBucketWebsite },
+ { "s3:GetLifecycleConfiguration", s3GetLifecycleConfiguration },
+ { "s3:GetObjectAcl", s3GetObjectAcl },
+ { "s3:GetObject", s3GetObject },
+ { "s3:GetObjectTorrent", s3GetObjectTorrent },
+ { "s3:GetObjectVersionAcl", s3GetObjectVersionAcl },
+ { "s3:GetObjectVersion", s3GetObjectVersion },
+ { "s3:GetObjectVersionTorrent", s3GetObjectVersionTorrent },
+ { "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:PutBucketLogging", s3PutBucketLogging },
+ { "s3:PutBucketNotification", s3PutBucketNotification },
+ { "s3:PutBucketPolicy", s3PutBucketPolicy },
+ { "s3:PutBucketRequestPayment", s3PutBucketRequestPayment },
+ { "s3:PutBucketTagging", s3PutBucketTagging },
+ { "s3:PutBucketVersioning", s3PutBucketVersioning },
+ { "s3:PutBucketWebsite", s3PutBucketWebsite },
+ { "s3:PutLifecycleConfiguration", s3PutLifecycleConfiguration },
+ { "s3:PutObjectAcl", s3PutObjectAcl },
+ { "s3:PutObject", s3PutObject },
+ { "s3:PutObjectVersionAcl", s3PutObjectVersionAcl },
+ { "s3:PutReplicationConfiguration", s3PutReplicationConfiguration },
+ { "s3:RestoreObject", s3RestoreObject }};
+
+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;
+
+ void reset();
+
+ 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;
+ }
+ 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 seen = 0;
+
+ 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);
+ }
+ void set(std::initializer_list<TokenID> l) {
+ for (auto in : l) {
+ seen |= dex(in);
+ }
+ }
+ void reset(TokenID in) {
+ seen &= ~dex(in);
+ }
+ void reset(std::initializer_list<TokenID> l) {
+ for (auto in : l) {
+ seen &= ~dex(in);
+ }
+ }
+
+ PolicyParser(CephContext* cct, const string& tenant, Policy& policy)
+ : cct(cct), tenant(tenant), policy(policy) {}
+ 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()) {
+ return false;
+ }
+
+ return s.back().obj_end();
+ }
+ bool Key(const char* str, SizeType length, bool copy) {
+ if (s.empty()) {
+ return false;
+ }
+
+ return s.back().key(str, length);
+ }
+
+ bool String(const char* str, SizeType length, bool copy) {
+ if (s.empty()) {
+ return false;
+ }
+
+ return s.back().do_string(cct, str, length);
+ }
+ bool RawNumber(const char* str, SizeType length, bool copy) {
+ if (s.empty()) {
+ return false;
+ }
+
+ return s.back().number(str, length);
+ }
+ bool StartArray() {
+ if (s.empty()) {
+ 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++.
+//
+bool ParseState::obj_end() {
+ if (objecting) {
+ objecting = false;
+ if (!arraying) {
+ pp->s.pop_back();
+ } else {
+ reset();
+ }
+ return true;
+ }
+ return false;
+}
+
+bool ParseState::key(const char* s, size_t l) {
+ auto k = pp->tokens.lookup(s, l);
+
+ if (!k) {
+ if (w->kind == TokenKind::cond_op) {
+ auto& t = pp->policy.statements.back();
+ pp->s.emplace_back(pp, cond_key);
+ t.conditions.emplace_back(w->id, s, l);
+ return true;
+ } else {
+ 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);
+ return true;
+ }
+ return false;
+}
+
+// I should just rewrite a few helper functions to use iterators,
+// which will make all of this ever so much nicer.
+static optional<Principal> parse_principal(CephContext* cct, TokenID t,
+ string&& s) {
+ // Wildcard!
+ if ((t == TokenID::AWS) && (s == "*")) {
+ return Principal::wildcard();
+
+ // Do nothing for now.
+ } else if (t == TokenID::CanonicalUser) {
+
+ // AWS ARNs
+ } else if (t == TokenID::AWS) {
+ auto a = ARN::parse(s);
+ if (!a) {
+ 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 (a->resource == "root") {
+ return Principal::tenant(std::move(a->account));
+ }
+
+ static const regex rx("([^/]*)/(.*)",
+ std::regex_constants::ECMAScript |
+ std::regex_constants::optimize);
+ smatch match;
+ if (regex_match(a->resource, match, rx)) {
+ ceph_assert(match.size() == 2);
+
+ 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]);
+ }
+ }
+ }
+
+ ldout(cct, 0) << "Supplied principal is discarded: " << s << dendl;
+ 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;
+ Statement* t = p.statements.empty() ? nullptr : &(p.statements.back());
+
+ // Top level!
+ if ((w->id == TokenID::Version) && k &&
+ k->kind == TokenKind::version_key) {
+ p.version = static_cast<Version>(k->specific);
+ } 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) &&
+ k->kind == TokenKind::effect_key) {
+ t->effect = static_cast<Effect>(k->specific);
+ } 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)) {
+ for (auto& p : actpairs) {
+ if (match({s, l}, p.name, MATCH_POLICY_ACTION)) {
+ (w->id == TokenID::Action ? t->action : t->notaction) |= p.bit;
+ }
+ }
+ } else if (w->id == TokenID::Resource || w->id == TokenID::NotResource) {
+ auto a = ARN::parse({s, l}, true);
+ // You can't specify resources for someone ELSE'S account.
+ if (a && (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
+ ldout(cct, 0) << "Supplied resource is discarded: " << string(s, l)
+ << dendl;
+ } else if (w->kind == TokenKind::cond_key) {
+ auto& t = pp->policy.statements.back();
+ t.conditions.back().vals.emplace_back(s, l);
+
+ // Principals
+
+ } else if (w->kind == TokenKind::princ_type) {
+ ceph_assert(pp->s.size() > 1);
+ auto& pri = pp->s[pp->s.size() - 2].w->id == TokenID::Principal ?
+ t->princ : t->noprinc;
+
+ auto o = parse_principal(pp->cct, w->id, string(s, l));
+ if (o)
+ pri.emplace(std::move(*o));
+
+ // Failure
+
+ } else {
+ return false;
+ }
+
+ if (!arraying) {
+ pp->s.pop_back();
+ }
+
+ 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);
+
+ // Failure
+
+ } else {
+ return false;
+ }
+
+ if (!arraying) {
+ pp->s.pop_back();
+ }
+
+ return true;
+}
+
+void ParseState::reset() {
+ pp->reset({TokenID::Sid, TokenID::Effect, TokenID::Principal,
+ TokenID::NotPrincipal, TokenID::Action, TokenID::NotAction,
+ TokenID::Resource, TokenID::NotResource, TokenID::Condition});
+}
+
+bool ParseState::obj_start() {
+ if (w->objectable && !objecting) {
+ objecting = true;
+ if (w->id == TokenID::Statement) {
+ pp->policy.statements.push_back({});
+ }
+
+ return true;
+ }
+
+ return false;
+}
+
+
+bool ParseState::array_end() {
+ if (arraying && !objecting) {
+ pp->s.pop_back();
+ return true;
+ }
+
+ 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 = 15; i >= 0; --i) {
+ uint8_t b = 0;
+ for (int j = 7; j >= 0; --j) {
+ b |= (ip.addr[(i * 8) + j] << j);
+ }
+ m << hex << b;
+ 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 << b;
+ if (i != 0) {
+ m << ".";
+ }
+ }
+ }
+ m << "/" << ip.prefix;
+ // It would explain a lot
+ return m;
+}
+
+string to_string(const MaskedIP& m) {
+ stringstream ss;
+ ss << m;
+ return ss.str();
+}
+
+bool Condition::eval(const Environment& env) const {
+ auto i = env.find(key);
+ if (op == TokenID::Null) {
+ return i == env.end() ? true : false;
+ }
+
+ if (i == env.end()) {
+ return false;
+ }
+ const auto& s = i->second;
+
+ switch (op) {
+ // String!
+ case TokenID::StringEquals:
+ return orrible(std::equal_to<std::string>(), s, vals);
+
+ case TokenID::StringNotEquals:
+ return orrible(std::not2(std::equal_to<std::string>()),
+ s, vals);
+
+ case TokenID::StringEqualsIgnoreCase:
+ return orrible(ci_equal_to(), s, vals);
+
+ case TokenID::StringNotEqualsIgnoreCase:
+ return orrible(std::not2(ci_equal_to()), s, vals);
+
+ // Implement actual StringLike with wildcarding later
+ case TokenID::StringLike:
+ return orrible(std::equal_to<std::string>(), s, vals);
+ case TokenID::StringNotLike:
+ return orrible(std::not2(std::equal_to<std::string>()),
+ s, vals);
+
+ // Numeric
+ case TokenID::NumericEquals:
+ return shortible(std::equal_to<double>(), as_number, s, vals);
+
+ case TokenID::NumericNotEquals:
+ return shortible(std::not2(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::not2(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:
+ return shortible(std::not2(std::equal_to<MaskedIP>()), as_network, s,
+ vals);
+
+#if 0
+ // Amazon Resource Names! (Does S3 need this?)
+ TokenID::ArnEquals, TokenID::ArnNotEquals, TokenID::ArnLike,
+ TokenID::ArnNotLike,
+#endif
+
+ default:
+ return false;
+ }
+}
+
+optional<MaskedIP> Condition::as_network(const string& s) {
+ MaskedIP m;
+ if (s.empty()) {
+ return none;
+ }
+
+ m.v6 = s.find(':');
+ 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 none;
+ }
+ }
+
+ string t;
+ auto p = &s;
+
+ if (slash != string::npos) {
+ t.assign(s, 0, slash);
+ p = &t;
+ }
+
+ if (m.v6) {
+ struct sockaddr_in6 a;
+ if (inet_pton(AF_INET6, p->c_str(), static_cast<void*>(&a)) != 1) {
+ return none;
+ }
+
+ m.addr |= Address(a.sin6_addr.s6_addr[0]) << 0;
+ m.addr |= Address(a.sin6_addr.s6_addr[1]) << 8;
+ m.addr |= Address(a.sin6_addr.s6_addr[2]) << 16;
+ m.addr |= Address(a.sin6_addr.s6_addr[3]) << 24;
+ m.addr |= Address(a.sin6_addr.s6_addr[4]) << 32;
+ m.addr |= Address(a.sin6_addr.s6_addr[5]) << 40;
+ m.addr |= Address(a.sin6_addr.s6_addr[6]) << 48;
+ m.addr |= Address(a.sin6_addr.s6_addr[7]) << 56;
+ m.addr |= Address(a.sin6_addr.s6_addr[8]) << 64;
+ m.addr |= Address(a.sin6_addr.s6_addr[9]) << 72;
+ m.addr |= Address(a.sin6_addr.s6_addr[10]) << 80;
+ m.addr |= Address(a.sin6_addr.s6_addr[11]) << 88;
+ m.addr |= Address(a.sin6_addr.s6_addr[12]) << 96;
+ m.addr |= Address(a.sin6_addr.s6_addr[13]) << 104;
+ m.addr |= Address(a.sin6_addr.s6_addr[14]) << 112;
+ m.addr |= Address(a.sin6_addr.s6_addr[15]) << 120;
+ } else {
+ struct sockaddr_in a;
+ if (inet_pton(AF_INET, p->c_str(), static_cast<void*>(&a)) != 1) {
+ return none;
+ }
+ m.addr = ntohl(a.sin_addr.s_addr);
+ }
+
+ return none;
+}
+
+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 {
+ auto beforelast = end - 1;
+ m << "[ ";
+ for (auto i = begin; i != end; ++i) {
+ m << *i;
+ if (i != beforelast) {
+ m << ", ";
+ } else {
+ m << " ";
+ }
+ }
+ }
+ m << "]";
+ return m;
+}
+}
+
+ostream& operator <<(ostream& m, const Condition& c) {
+ m << "{ " << condop_string(c.op) << ": { " << c.key;
+ if (c.ifexists) {
+ m << "IfExists";
+ }
+ print_array(m, c.vals.cbegin(), c.vals.cend());
+ return m << "}";
+}
+
+string to_string(const Condition& c) {
+ stringstream ss;
+ ss << c;
+ return ss.str();
+}
+
+Effect Statement::eval(const Environment& e,
+ optional<const rgw::auth::Identity&> ida,
+ uint64_t act, const ARN& res) const {
+ if (ida && (!ida->is_identity(princ) || ida->is_identity(noprinc))) {
+ return Effect::Pass;
+ }
+
+
+ if (!std::any_of(resource.begin(), resource.end(),
+ [&res](const ARN& pattern) {
+ return pattern.match(res);
+ }) ||
+ (std::any_of(notresource.begin(), notresource.end(),
+ [&res](const ARN& pattern) {
+ return pattern.match(res);
+ }))) {
+ return Effect::Pass;
+ }
+
+ if (!(action & act) || (notaction & act)) {
+ return Effect::Pass;
+ }
+
+ if (std::all_of(conditions.begin(),
+ conditions.end(),
+ [&e](const Condition& c) { return c.eval(e);})) {
+ return effect;
+ }
+
+ return Effect::Pass;
+}
+
+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 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";
+ }
+ return "s3Invalid";
+}
+
+ostream& print_actions(ostream& m, const uint64_t a) {
+ bool begun = false;
+ m << "[ ";
+ for (auto i = 0U; i < s3Count; ++i) {
+ if (a & (1 << i)) {
+ if (begun) {
+ m << ", ";
+ } else {
+ begun = true;
+ }
+ m << action_bit_string(1 << 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_array(m, s.princ.cbegin(), s.princ.cend());
+ m << ", ";
+ }
+ if (!s.noprinc.empty()) {
+ m << "NotPrincipal: ";
+ print_array(m, s.noprinc.cbegin(), s.noprinc.cend());
+ m << ", ";
+ }
+
+ m << "Effect: " <<
+ (s.effect == Effect::Allow ?
+ (const char*) "Allow" :
+ (const char*) "Deny");
+
+ if (s.action || s.notaction || !s.resource.empty() ||
+ !s.notresource.empty() || !s.conditions.empty()) {
+ m << ", ";
+ }
+
+ if (s.action) {
+ m << "Action: ";
+ print_actions(m, s.action);
+
+ if (s.notaction || !s.resource.empty() ||
+ !s.notresource.empty() || !s.conditions.empty()) {
+ m << ", ";
+ }
+ }
+
+ if (s.notaction) {
+ 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_array(m, s.conditions.cbegin(), s.conditions.cend());
+ }
+
+ return m << " }";
+}
+
+string to_string(const Statement& s) {
+ stringstream m;
+ m << s;
+ return m.str();
+}
+
+Policy::Policy(CephContext* cct, const string& tenant,
+ const bufferlist& _text)
+ : text(_text.to_str()) {
+ StringStream ss(text.data());
+ PolicyParser pp(cct, tenant, *this);
+ auto pr = Reader{}.Parse<kParseNumbersAsStringsFlag |
+ kParseCommentsFlag>(ss, pp);
+ if (!pr) {
+ throw PolicyParseException(std::move(pr));
+ }
+}
+
+Effect Policy::eval(const Environment& e,
+ optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action, const ARN& resource) const {
+ auto allowed = false;
+ for (auto& s : statements) {
+ auto g = s.eval(e, ida, action, resource);
+ if (g == Effect::Deny) {
+ return g;
+ } else if (g == Effect::Allow) {
+ allowed = true;
+ }
+ }
+ return allowed ? Effect::Allow : Effect::Pass;
+}
+
+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 << " }";
+}
+
+string to_string(const Policy& p) {
+ stringstream s;
+ s << p;
+ return s.str();
+}
+
+}
+}
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RGW_IAM_POLICY_H
+#define CEPH_RGW_IAM_POLICY_H
+
+#include <bitset>
+#include <chrono>
+#include <cstdint>
+#include <iostream>
+#include <string>
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/container/flat_map.hpp>
+#include <boost/container/flat_set.hpp>
+#include <boost/optional.hpp>
+#include <boost/thread/shared_mutex.hpp>
+#include <boost/utility/string_ref.hpp>
+#include <boost/variant.hpp>
+
+#include "common/ceph_time.h"
+#include "common/iso_8601.h"
+
+#include "rapidjson/error/error.h"
+#include "rapidjson/error/en.h"
+
+#include "fnmatch.h"
+
+#include "rgw_basic_types.h"
+#include "rgw_iam_policy_keywords.h"
+
+#include "include/assert.h" // razzin' frazzin' ...grrr.
+
+class RGWRados;
+namespace rgw {
+namespace auth {
+class Identity;
+}
+}
+struct rgw_obj;
+struct rgw_bucket;
+
+namespace rgw {
+namespace IAM {
+static constexpr std::uint64_t s3None = 0;
+static constexpr std::uint64_t s3GetObject = 1ULL << 0;
+static constexpr std::uint64_t s3GetObjectVersion = 1ULL << 1;
+static constexpr std::uint64_t s3PutObject = 1ULL << 2;
+static constexpr std::uint64_t s3GetObjectAcl = 1ULL << 3;
+static constexpr std::uint64_t s3GetObjectVersionAcl = 1ULL << 4;
+static constexpr std::uint64_t s3PutObjectAcl = 1ULL << 5;
+static constexpr std::uint64_t s3PutObjectVersionAcl = 1ULL << 6;
+static constexpr std::uint64_t s3DeleteObject = 1ULL << 7;
+static constexpr std::uint64_t s3DeleteObjectVersion = 1ULL << 8;
+static constexpr std::uint64_t s3ListMultipartUploadParts = 1ULL << 9;
+static constexpr std::uint64_t s3AbortMultipartUpload = 1ULL << 10;
+static constexpr std::uint64_t s3GetObjectTorrent = 1ULL << 11;
+static constexpr std::uint64_t s3GetObjectVersionTorrent = 1ULL << 12;
+static constexpr std::uint64_t s3RestoreObject = 1ULL << 13;
+static constexpr std::uint64_t s3CreateBucket = 1ULL << 14;
+static constexpr std::uint64_t s3DeleteBucket = 1ULL << 15;
+static constexpr std::uint64_t s3ListBucket = 1ULL << 16;
+static constexpr std::uint64_t s3ListBucketVersions = 1ULL << 17;
+static constexpr std::uint64_t s3ListAllMyBuckets = 1ULL << 18;
+static constexpr std::uint64_t s3ListBucketMultiPartUploads = 1ULL << 19;
+static constexpr std::uint64_t s3GetAccelerateConfiguration = 1ULL << 20;
+static constexpr std::uint64_t s3PutAccelerateConfiguration = 1ULL << 21;
+static constexpr std::uint64_t s3GetBucketAcl = 1ULL << 22;
+static constexpr std::uint64_t s3PutBucketAcl = 1ULL << 23;
+static constexpr std::uint64_t s3GetBucketCORS = 1ULL << 24;
+static constexpr std::uint64_t s3PutBucketCORS = 1ULL << 25;
+static constexpr std::uint64_t s3GetBucketVersioning = 1ULL << 26;
+static constexpr std::uint64_t s3PutBucketVersioning = 1ULL << 27;
+static constexpr std::uint64_t s3GetBucketRequestPayment = 1ULL << 28;
+static constexpr std::uint64_t s3PutBucketRequestPayment = 1ULL << 29;
+static constexpr std::uint64_t s3GetBucketLocation = 1ULL << 30;
+static constexpr std::uint64_t s3GetBucketPolicy = 1ULL << 31;
+static constexpr std::uint64_t s3DeleteBucketPolicy = 1ULL << 32;
+static constexpr std::uint64_t s3PutBucketPolicy = 1ULL << 33;
+static constexpr std::uint64_t s3GetBucketNotification = 1ULL << 34;
+static constexpr std::uint64_t s3PutBucketNotification = 1ULL << 35;
+static constexpr std::uint64_t s3GetBucketLogging = 1ULL << 36;
+static constexpr std::uint64_t s3PutBucketLogging = 1ULL << 37;
+static constexpr std::uint64_t s3GetBucketTagging = 1ULL << 38;
+static constexpr std::uint64_t s3PutBucketTagging = 1ULL << 39;
+static constexpr std::uint64_t s3GetBucketWebsite = 1ULL << 40;
+static constexpr std::uint64_t s3PutBucketWebsite = 1ULL << 41;
+static constexpr std::uint64_t s3DeleteBucketWebsite = 1ULL << 42;
+static constexpr std::uint64_t s3GetLifecycleConfiguration = 1ULL << 43;
+static constexpr std::uint64_t s3PutLifecycleConfiguration = 1ULL << 44;
+static constexpr std::uint64_t s3PutReplicationConfiguration = 1ULL << 45;
+static constexpr std::uint64_t s3GetReplicationConfiguration = 1ULL << 46;
+static constexpr std::uint64_t s3DeleteReplicationConfiguration = 1ULL << 47;
+static constexpr std::uint64_t s3Count = 48;
+static constexpr std::uint64_t s3All = (1ULL << s3Count) - 1;
+
+namespace {
+inline int op_to_perm(std::uint64_t op) {
+ switch (op) {
+ case s3GetObject:
+ case s3GetObjectTorrent:
+ case s3GetObjectVersion:
+ case s3GetObjectVersionTorrent:
+ case s3ListAllMyBuckets:
+ case s3ListBucket:
+ case s3ListBucketMultiPartUploads:
+ case s3ListBucketVersions:
+ case s3ListMultipartUploadParts:
+ return RGW_PERM_READ;
+
+ case s3AbortMultipartUpload:
+ case s3CreateBucket:
+ case s3DeleteBucket:
+ case s3DeleteObject:
+ case s3DeleteObjectVersion:
+ case s3PutObject:
+ case s3RestoreObject:
+ return RGW_PERM_WRITE;
+
+ case s3GetAccelerateConfiguration:
+ case s3GetBucketAcl:
+ case s3GetBucketCORS:
+ case s3GetBucketLocation:
+ case s3GetBucketLogging:
+ case s3GetBucketNotification:
+ case s3GetBucketPolicy:
+ case s3GetBucketRequestPayment:
+ case s3GetBucketTagging:
+ case s3GetBucketVersioning:
+ case s3GetBucketWebsite:
+ case s3GetLifecycleConfiguration:
+ case s3GetObjectAcl:
+ case s3GetObjectVersionAcl:
+ case s3GetReplicationConfiguration:
+ return RGW_PERM_READ_ACP;
+
+ case s3DeleteBucketPolicy:
+ case s3DeleteBucketWebsite:
+ case s3DeleteReplicationConfiguration:
+ case s3PutAccelerateConfiguration:
+ case s3PutBucketAcl:
+ case s3PutBucketCORS:
+ case s3PutBucketLogging:
+ case s3PutBucketNotification:
+ case s3PutBucketPolicy:
+ case s3PutBucketRequestPayment:
+ case s3PutBucketTagging:
+ case s3PutBucketVersioning:
+ case s3PutBucketWebsite:
+ case s3PutLifecycleConfiguration:
+ case s3PutObjectAcl:
+ case s3PutObjectVersionAcl:
+ case s3PutReplicationConfiguration:
+ return RGW_PERM_WRITE_ACP;
+
+ case s3All:
+ return RGW_PERM_FULL_CONTROL;
+ }
+ return RGW_PERM_INVALID;
+}
+}
+
+using Environment = boost::container::flat_map<std::string, std::string>;
+
+enum struct Partition {
+ aws, aws_cn, aws_us_gov, wildcard
+ // If we wanted our own ARNs for principal type unique to us
+ // (maybe to integrate better with Swift) or for anything else we
+ // provide that doesn't map onto S3, we could add an 'rgw'
+ // partition type.
+};
+
+enum struct Service {
+ apigateway, appstream, artifact, autoscaling, aws_portal, acm,
+ cloudformation, cloudfront, cloudhsm, cloudsearch, cloudtrail,
+ cloudwatch, events, logs, codebuild, codecommit, codedeploy,
+ codepipeline, cognito_idp, cognito_identity, cognito_sync,
+ config, datapipeline, dms, devicefarm, directconnect,
+ ds, dynamodb, ec2, ecr, ecs, ssm, elasticbeanstalk, elasticfilesystem,
+ elasticloadbalancing, elasticmapreduce, elastictranscoder, elasticache,
+ es, gamelift, glacier, health, iam, importexport, inspector, iot,
+ kms, kinesisanalytics, firehose, kinesis, lambda, lightsail,
+ machinelearning, aws_marketplace, aws_marketplace_management,
+ mobileanalytics, mobilehub, opsworks, opsworks_cm, polly,
+ redshift, rds, route53, route53domains, sts, servicecatalog,
+ ses, sns, sqs, s3, swf, sdb, states, storagegateway, support,
+ trustedadvisor, waf, workmail, workspaces, wildcard
+};
+
+struct ARN {
+ Partition partition;
+ Service service;
+ std::string region;
+ // Once we refity tenant, we should probably use that instead of a
+ // string.
+ std::string account;
+ std::string resource;
+
+ ARN()
+ : partition(Partition::wildcard), service(Service::wildcard) {}
+ ARN(Partition partition, Service service, std::string region,
+ std::string account, std::string resource)
+ : partition(partition), service(service), region(std::move(region)),
+ account(std::move(account)), resource(std::move(resource)) {}
+ ARN(const rgw_obj& o);
+ ARN(const rgw_bucket& b);
+ ARN(const rgw_bucket& b, const std::string& o);
+
+ static boost::optional<ARN> parse(const std::string& s,
+ bool wildcard = false);
+ std::string to_string() const;
+
+ // `this` is the pattern
+ bool match(const ARN& candidate) const;
+};
+
+inline std::string to_string(const ARN& a) {
+ return a.to_string();
+}
+
+inline std::ostream& operator <<(std::ostream& m, const ARN& a) {
+ return m << to_string(a);
+}
+
+bool operator ==(const ARN& l, const ARN& r);
+bool operator <(const ARN& l, const ARN& r);
+
+using Address = std::bitset<128>;
+struct MaskedIP {
+ bool v6;
+ Address addr;
+ // Since we're mapping IPv6 to IPv4 addresses, we may want to
+ // consider making the prefix always be in terms of a v6 address
+ // and just use the v6 bit to rewrite it as a v4 prefix for
+ // output.
+ unsigned int prefix;
+};
+
+std::ostream& operator <<(std::ostream& m, const MaskedIP& ip);
+string to_string(const MaskedIP& m);
+
+inline bool operator ==(const MaskedIP& l, const MaskedIP& r) {
+ auto shift = std::max((l.v6 ? 128 : 32) - l.prefix,
+ (r.v6 ? 128 : 32) - r.prefix);
+ ceph_assert(shift > 0);
+ return (l.addr >> shift) == (r.addr >> shift);
+}
+
+struct Condition {
+ TokenID op;
+ // Originally I was going to use a perfect hash table, but Marcus
+ // says keys are to be added at run-time not compile time.
+
+ // In future development, use symbol internment.
+ std::string key;
+ bool ifexists = false;
+ // Much to my annoyance there is no actual way to do this in a
+ // typed way that is compatible with AWS. I know this because I've
+ // seen examples where the same value is used as a string in one
+ // context and a date in another.
+ std::vector<std::string> vals;
+
+ Condition() = default;
+ Condition(TokenID op, const char* s, std::size_t len) : op(op) {
+ static constexpr char ifexistr[] = "IfExists";
+ auto l = static_cast<const char*>(memmem(static_cast<const void*>(s), len,
+ static_cast<const void*>(ifexistr),
+ sizeof(ifexistr) -1));
+ if (l && ((l + sizeof(ifexistr) - 1 == (s + len)))) {
+ ifexists = true;
+ key.assign(s, static_cast<const char*>(l) - s);
+ } else {
+ key.assign(s, len);
+ }
+ }
+
+ bool eval(const Environment& e) const;
+
+ static boost::optional<double> as_number(const std::string& s) {
+ std::size_t p = 0;
+
+ try {
+ double d = std::stod(s, &p);
+ if (p < s.length()) {
+ return boost::none;
+ }
+
+ return d;
+ } catch (const std::logic_error& e) {
+ return boost::none;
+ }
+ }
+
+ static boost::optional<ceph::real_time> as_date(const std::string& s) {
+ std::size_t p = 0;
+
+ try {
+ double d = std::stod(s, &p);
+ if (p == s.length()) {
+ return ceph::real_time(
+ std::chrono::seconds(static_cast<uint64_t>(d)) +
+ std::chrono::nanoseconds(
+ static_cast<uint64_t>((d - static_cast<uint64_t>(d))
+ * 1000000000)));
+ }
+
+ return from_iso_8601(boost::string_ref(s), false);
+ } catch (const std::logic_error& e) {
+ return boost::none;
+ }
+ }
+
+ static boost::optional<bool> as_bool(const std::string& s) {
+ std::size_t p = 0;
+
+ if (s.empty() || boost::iequals(s, "false")) {
+ return false;
+ }
+
+ try {
+ double d = std::stod(s, &p);
+ if (p == s.length()) {
+ return !((d == +0.0) || (d = -0.0) || std::isnan(d));
+ }
+ } catch (const std::logic_error& e) {
+ // Fallthrough
+ }
+
+ return true;
+ }
+
+ static boost::optional<ceph::bufferlist> as_binary(const std::string& s) {
+ // In a just world
+ ceph::bufferlist base64;
+ // I could populate a bufferlist
+ base64.push_back(buffer::create_static(
+ s.length(),
+ const_cast<char*>(s.data()))); // Yuck
+ // From a base64 encoded std::string.
+ ceph::bufferlist bin;
+
+ try {
+ base64.decode_base64(bin);
+ } catch (const ceph::buffer::malformed_input& e) {
+ return boost::none;
+ }
+ return bin;
+ }
+
+ static boost::optional<MaskedIP> as_network(const std::string& s);
+
+
+ struct ci_equal_to : public std::binary_function<const std::string,
+ const std::string,
+ bool> {
+ bool operator ()(const std::string& s1,
+ const std::string& s2) const {
+ return boost::iequals(s1, s2);
+ }
+ };
+
+
+ template<typename F>
+ static bool orrible(F&& f, const std::string& c,
+ const std::vector<std::string>& v) {
+ for (const auto& d : v) {
+ if (std::forward<F>(f)(c, d)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ template<typename F, typename X>
+ static bool shortible(F&& f, X& x, const std::string& c,
+ const std::vector<std::string>& v) {
+ auto xc = std::forward<X>(x)(c);
+ if (!xc) {
+ return false;
+ }
+
+ for (const auto& d : v) {
+ auto xd = std::forward<X>(x)(d);
+ if (!xd) {
+ continue;
+ }
+
+ if (std::forward<F>(f)(*xc, *xd)) {
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+std::ostream& operator <<(std::ostream& m, const Condition& c);
+
+std::string to_string(const Condition& c);
+
+struct Statement {
+ boost::optional<std::string> sid = boost::none;
+
+ boost::container::flat_set<rgw::auth::Principal> princ;
+ boost::container::flat_set<rgw::auth::Principal> noprinc;
+
+ // Every statement MUST provide an effect. I just initialize it to
+ // deny as defensive programming.
+ Effect effect = Effect::Deny;
+
+ std::uint64_t action = 0;
+ std::uint64_t notaction = 0;
+
+ boost::container::flat_set<ARN> resource;
+ boost::container::flat_set<ARN> notresource;
+
+ std::vector<Condition> conditions;
+
+ Effect eval(const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action, const ARN& resource) const;
+};
+
+std::ostream& operator <<(ostream& m, const Statement& s);
+std::string to_string(const Statement& s);
+
+struct PolicyParseException : public std::exception {
+ rapidjson::ParseResult pr;
+
+ PolicyParseException(rapidjson::ParseResult&& pr)
+ : pr(pr) { }
+ const char* what() const noexcept override {
+ return rapidjson::GetParseError_En(pr.Code());
+ }
+};
+
+struct Policy {
+ std::string text;
+ Version version = Version::v2008_10_17;
+ boost::optional<std::string> id = boost::none;
+
+ std::vector<Statement> statements;
+
+ Policy(CephContext* cct, const std::string& tenant,
+ const bufferlist& text);
+
+ Effect eval(const Environment& e,
+ boost::optional<const rgw::auth::Identity&> ida,
+ std::uint64_t action, const ARN& resource) const;
+};
+
+std::ostream& operator <<(ostream& m, const Policy& p);
+std::string to_string(const Policy& p);
+}
+}
+
+namespace std {
+template<>
+struct hash<::rgw::IAM::Service> {
+ size_t operator()(const ::rgw::IAM::Service& s) const noexcept {
+ // Invoke a default-constructed hash object for int.
+ return hash<int>()(static_cast<int>(s));
+ }
+};
+}
+
+#endif
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+%language=C++
+%define class-name keyword_hash
+%define lookup-function-name lookup
+%struct-type
+struct Keyword {
+ const char* name;
+ TokenKind kind;
+ TokenID id;
+ uint64_t specific;
+ bool arrayable;
+ bool objectable;
+};
+%%
+# Top-level
+#
+Version, TokenKind::top, TokenID::Version, 0, false, false
+Id, TokenKind::top, TokenID::Id, 0, false, false
+Statement, TokenKind::top, TokenID::Statement, 0, true, true
+#
+# Statement level
+#
+Sid, TokenKind::statement, TokenID::Sid, 0, false, false
+Effect, TokenKind::statement, TokenID::Effect, 0, false, false
+Principal, TokenKind::statement, TokenID::Principal, 0, false, true
+NotPrincipal, TokenKind::statement, TokenID::NotPrincipal, 0, true, true
+Action, TokenKind::statement, TokenID::Action, 0, true, false
+NotAction, TokenKind::statement, TokenID::NotAction, 0, true, false
+Resource, TokenKind::statement, TokenID::Resource, 0, true, false
+NotResource, TokenKind::statement, TokenID::NotResource, 0, true, false
+Condition, TokenKind::statement, TokenID::Condition, 0, true, true
+#
+# Condition operators
+#
+# String
+StringEquals, TokenKind::cond_op, TokenID::StringEquals, (uint64_t) Type::string, true, true
+StringNotEquals, TokenKind::cond_op, TokenID::StringNotEquals, (uint64_t) Type::string, true, true
+StringEqualsIgnoreCase, TokenKind::cond_op, TokenID::StringEqualsIgnoreCase, (uint64_t) Type::string, true, true
+StringNotEqualsIgnoreCase, TokenKind::cond_op, TokenID::StringNotEqualsIgnoreCase, (uint64_t) Type::string, true, true
+StringLike, TokenKind::cond_op, TokenID::StringLike, (uint64_t) Type::string, true, true,
+StringNotLike, TokenKind::cond_op, TokenID::StringNotLike, (uint64_t) Type::string, true, true
+# Numeric
+NumericEquals, TokenKind::cond_op, TokenID::NumericEquals, (uint64_t) Type::number, true, true
+NumericNotEquals, TokenKind::cond_op, TokenID::NumericNotEquals, (uint64_t) Type::number, true, true
+NumericLessThan, TokenKind::cond_op, TokenID::NumericLessThan, (uint64_t) Type::number, true, true
+NumericLessThanEquals, TokenKind::cond_op, TokenID::NumericLessThanEquals, (uint64_t) Type::number, true, true
+NumericGreaterThan, TokenKind::cond_op, TokenID::NumericGreaterThan, (uint64_t) Type::number, true, true
+NumericGreaterThanEquals, TokenKind::cond_op, TokenID::NumericGreaterThanEquals, (uint64_t) Type::number, true, true
+# Date
+DateEquals, TokenKind::cond_op, TokenID::DateEquals, (uint64_t) Type::date, true, true
+DateNotEquals, TokenKind::cond_op, TokenID::DateNotEquals, (uint64_t) Type::date, true, true
+DateLessThan, TokenKind::cond_op, TokenID::DateLessThan, (uint64_t) Type::date, true, true
+DateLessThanEquals, TokenKind::cond_op, TokenID::DateLessThanEquals, (uint64_t) Type::date, true, true
+DateGreaterThan, TokenKind::cond_op, TokenID::DateGreaterThan, (uint64_t) Type::date, true, true
+DateGreaterThanEquals, TokenKind::cond_op, TokenID::DateGreaterThanEquals, (uint64_t) Type::date, true, true
+# Bool
+Bool, TokenKind::cond_op, TokenID::Bool, (uint64_t) Type::boolean, true, true
+# Binary
+BinaryEquals, TokenKind::cond_op, TokenID::BinaryEquals, (uint64_t) Type::binary, true, true
+# IP Address
+IpAddress, TokenKind::cond_op, TokenID::IpAddress, (uint64_t) Type::ipaddr, true, true
+NotIpAddress, TokenKind::cond_op, TokenID::NotIpAddress, (uint64_t) Type::ipaddr, true, true
+# Amazon Resource Names
+ArnEquals, TokenKind::cond_op, TokenID::ArnEquals, (uint64_t) Type::arn, true, true
+ArnNotEquals, TokenKind::cond_op, TokenID::ArnNotEquals, (uint64_t) Type::arn, true, true
+ArnLike, TokenKind::cond_op, TokenID::ArnLike, (uint64_t) Type::arn, true, true
+ArnNotLike, TokenKind::cond_op, TokenID::ArnNotLike, (uint64_t) Type::arn, true, true
+# Null
+Null, TokenKind::cond_op, TokenID::Null, (uint64_t) Type::null, true, true
+#
+# Condition keys
+#
+# AWS
+#aws:CurrentTime, TokenKind::cond_key, TokenID::awsCurrentTime, (uint64_t) Type::date, true, false
+#aws:EpochTime, TokenKind::cond_key, TokenID::awsEpochTime, (uint64_t) Type::date, true, false
+#aws:TokenIssueTime, TokenKind::cond_key, TokenID::awsTokenIssueTime, (uint64_t) Type::date, true, false
+#aws:MultiFactorAuthPresent, TokenKind::cond_key, TokenID::awsMultiFactorAuthPresent, (uint64_t) Type::boolean, true, false
+#aws:MultiFactorAuthAge, TokenKind::cond_key, TokenID::awsMultiFactorAuthAge, (uint64_t) Type::number, true, false
+#aws:PrincipalType, TokenKind::cond_key, TokenID::awsPrincipalType, (uint64_t) Type::string, true, false
+#aws:Referer, TokenKind::cond_key, TokenID::awsReferer, (uint64_t) Type::string, true, false
+#aws:SecureTransport, TokenKind::cond_key, TokenID::awsSecureTransport, (uint64_t) Type::boolean, true, false
+#aws:SourceArn, TokenKind::cond_key, TokenID::awsSourceArn, (uint64_t) Type::arn, true, false
+#aws:SourceIp, TokenKind::cond_key, TokenID::awsSourceIp, (uint64_t) Type::ipaddr, true, false
+#aws:SourceVpc, TokenKind::cond_key, TokenID::awsSourceVpc, (uint64_t) Type::string, true, false
+#aws:SourceVpce, TokenKind::cond_key, TokenID::awsSourceVpce, (uint64_t) Type::string, true, false
+#aws:UserAgent, TokenKind::cond_key, TokenID::awsUserAgent, (uint64_t) Type::string, true, false
+#aws:userid, TokenKind::cond_key, TokenID::awsuserid, (uint64_t) Type::string, true, false
+#aws:username, TokenKind::cond_key, TokenID::awsusername, (uint64_t) Type::string, true, false
+# S3
+#s3:x-amz-acl, TokenKind::cond_key, TokenID::s3x_amz_acl, (uint64_t) Type::string, true, false
+#s3:x-amz-grant-read, TokenKind::cond_key, TokenID::s3x_amz_grant_permission, (uint64_t) Type::boolean, true, false
+#s3:x-amz-grant-write, TokenKind::cond_key, TokenID::s3x_amz_grant_permission, (uint64_t) Type::boolean, true, false
+#s3:x-amz-grant-read-acp, TokenKind::cond_key, TokenID::s3x_amz_grant_permission, (uint64_t) Type::boolean, true, false
+#s3:x-amz-grant-write-acp, TokenKind::cond_key, TokenID::s3x_amz_grant_permission, (uint64_t) Type::boolean, true, false
+#s3:x-amz-grant-full-control, TokenKind::cond_key, TokenID::s3x_amz_grant_permission, (uint64_t) Type::boolean, true, false
+#s3:x-amz-copy-source, TokenKind::cond_key, TokenID::s3x_amz_copy_source, (uint64_t) Type::string, true, false
+#s3:x-amz-server-side-encryption, TokenKind::cond_key, TokenID::s3x_amz_server_side_encryption, (uint64_t) Type::boolean, true, false
+#s3:x-amz-server-side-encryption-aws-kms-key-id, TokenKind::cond_key, TokenID::s3x_amz_server_side_encryption_aws_kms_key_id, (uint64_t) Type::arn, true, false
+#s3:x-amz-metadata-directive, TokenKind::cond_key, TokenID::s3x_amz_metadata_directive, (uint64_t) Type::string, true, false
+#s3:x-amz-storage-class, TokenKind::cond_key, TokenID::s3x_amz_storage_class, (uint64_t) Type::string, true, false
+#s3:VersionId, TokenKind::cond_key, TokenID::s3VersionId, (uint64_t) Type::string, true, false
+#s3:LocationConstraint, TokenKind::cond_key, TokenID::s3LocationConstraint, (uint64_t) Type::string, true, false
+#s3:prefix, TokenKind::cond_key, TokenID::s3prefix, (uint64_t) Type::string, true, false
+#s3:delimiter, TokenKind::cond_key, TokenID::s3delimiter, (uint64_t) Type::string, true, false
+#s3:max-keys, TokenKind::cond_key, TokenID::s3max_keys, (uint64_t) Type::number, true, false
+#s3:signatureversion, TokenKind::cond_key, TokenID::s3signatureversion, (uint64_t) Type::string, true, false
+#s3:authType, TokenKind::cond_key, TokenID::s3authType, (uint64_t) Type::string, true, false
+#s3:signatureAge, TokenKind::cond_key, TokenID::s3signatureAge, (uint64_t) Type::number, true, false
+#s3:x-amz-content-sha256, TokenKind::cond_key, TokenID::s3x_amz_content_sha256, (uint64_t) Type::string, true, false
+#
+# Version Keywords
+#
+2008-10-17, TokenKind::version_key, TokenID::v2008_10_17, (uint64_t) Version::v2008_10_17, false, false
+2012-10-17, TokenKind::version_key, TokenID::v2012_10_17, (uint64_t) Version::v2012_10_17, false, false
+#
+# Effect Keywords
+#
+Allow, TokenKind::effect_key, TokenID::Allow, (uint64_t) Effect::Allow, false, false
+Deny, TokenKind::effect_key, TokenID::Deny, (uint64_t) Effect::Deny, false, false
+#
+# Principal types
+#
+AWS, TokenKind::princ_type, TokenID::AWS, 0, true, false
+Federated, TokenKind::princ_type, TokenID::Federated, 0, true, false
+Service, TokenKind::princ_type, TokenID::Service, 0, true, false
+CanonicalUser, TokenKind::princ_type, TokenID::CanonicalUser, 0, true, false
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#ifndef CEPH_RGW_POLICY_S3V2_KEYWORDS_H
+#define CEPH_RGW_POLICY_S3V2_KEYWORDS_H
+
+namespace rgw {
+namespace IAM {
+
+enum class TokenKind {
+ pseudo, top, statement, cond_op, cond_key, version_key, effect_key,
+ princ_type
+};
+
+enum class TokenID {
+ /// Pseudo-token
+ Top,
+
+ /// Top-level tokens
+ Version, Id, Statement,
+
+ /// Statement level tokens
+ Sid, Effect, Principal, NotPrincipal, Action, NotAction,
+ Resource, NotResource, Condition,
+
+ /// Condition Operators!
+ /// Any of these, except Null, can have an IfExists variant.
+
+ // String!
+ StringEquals, StringNotEquals, StringEqualsIgnoreCase,
+ StringNotEqualsIgnoreCase, StringLike, StringNotLike,
+
+ // Numeric!
+ NumericEquals, NumericNotEquals, NumericLessThan, NumericLessThanEquals,
+ NumericGreaterThan, NumericGreaterThanEquals,
+
+ // Date!
+ DateEquals, DateNotEquals, DateLessThan, DateLessThanEquals,
+ DateGreaterThan, DateGreaterThanEquals,
+
+ // Bool!
+ Bool,
+
+ // Binary!
+ BinaryEquals,
+
+ // IP Address!
+ IpAddress, NotIpAddress,
+
+ // Amazon Resource Names! (Does S3 need this?)
+ ArnEquals, ArnNotEquals, ArnLike, ArnNotLike,
+
+ // Null!
+ Null,
+
+#if 0 // Keys are done at runtime now
+
+ /// Condition Keys!
+ awsCurrentTime,
+ awsEpochTime,
+ awsTokenIssueTime,
+ awsMultiFactorAuthPresent,
+ awsMultiFactorAuthAge,
+ awsPrincipalType,
+ awsReferer,
+ awsSecureTransport,
+ awsSourceArn,
+ awsSourceIp,
+ awsSourceVpc,
+ awsSourceVpce,
+ awsUserAgent,
+ awsuserid,
+ awsusername,
+ s3x_amz_acl,
+ s3x_amz_grant_permission,
+ s3x_amz_copy_source,
+ s3x_amz_server_side_encryption,
+ s3x_amz_server_side_encryption_aws_kms_key_id,
+ s3x_amz_metadata_directive,
+ s3x_amz_storage_class,
+ s3VersionId,
+ s3LocationConstraint,
+ s3prefix,
+ s3delimiter,
+ s3max_keys,
+ s3signatureversion,
+ s3authType,
+ s3signatureAge,
+ s3x_amz_content_sha256,
+#else
+ CondKey,
+#endif
+
+ ///
+ /// Versions!
+ ///
+ v2008_10_17,
+ v2012_10_17,
+
+ ///
+ /// Effects!
+ ///
+ Allow,
+ Deny,
+
+ /// Principal Types!
+ AWS,
+ Federated,
+ Service,
+ CanonicalUser
+};
+
+
+enum class Version {
+ v2008_10_17,
+ v2012_10_17
+};
+
+
+enum class Effect {
+ Allow,
+ Deny,
+ Pass
+};
+
+enum class Type {
+ string,
+ number,
+ date,
+ boolean,
+ binary,
+ ipaddr,
+ arn,
+ null
+};
+}
+}
+
+#endif // CEPH_RGW_POLICY_S3V2_KEYWORDS_H
${UNITTEST_LIBS}
${CRYPTO_LIBS}
)
-set_target_properties(unittest_rgw_crypto PROPERTIES COMPILE_FLAGS
- ${UNITTEST_CXX_FLAGS})
+set_target_properties(unittest_rgw_crypto PROPERTIES COMPILE_FLAGS$ {UNITTEST_CXX_FLAGS})
+
+# ceph_test_rgw_iam_policy
+set(test_rgw_iam_policy_srcs test_rgw_iam_policy.cc)
+add_executable(ceph_test_rgw_iam_policy
+ ${test_rgw_iam_policy_srcs}
+ )
+target_link_libraries(ceph_test_rgw_iam_policy
+ rgw_a
+ cls_rgw_client
+ cls_lock_client
+ cls_refcount_client
+ cls_log_client
+ cls_statelog_client
+ cls_version_client
+ cls_replica_log_client
+ cls_user_client
+ librados
+ global
+ ${CURL_LIBRARIES}
+ ${EXPAT_LIBRARIES}
+ ${CMAKE_DL_LIBS}
+ ${UNITTEST_LIBS}
+ ${CRYPTO_LIBS}
+ )
+set_target_properties(ceph_test_rgw_iam_policy PROPERTIES COMPILE_FLAGS ${UNITTEST_CXX_FLAGS})
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2015 Red Hat
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include <string>
+
+#include <boost/intrusive_ptr.hpp>
+#include <boost/optional.hpp>
+
+#include <gtest/gtest.h>
+
+#include "common/code_environment.h"
+#include "common/ceph_context.h"
+#include "global/global_init.h"
+#include "rgw/rgw_auth.h"
+#include "rgw/rgw_iam_policy.h"
+
+
+using std::string;
+using std::vector;
+
+using boost::container::flat_set;
+using boost::intrusive_ptr;
+using boost::make_optional;
+using boost::none;
+using boost::optional;
+
+using rgw::auth::Identity;
+using rgw::auth::Principal;
+
+using rgw::IAM::ARN;
+using rgw::IAM::Effect;
+using rgw::IAM::Environment;
+using rgw::IAM::Partition;
+using rgw::IAM::Policy;
+using rgw::IAM::s3All;
+using rgw::IAM::s3Count;
+using rgw::IAM::s3GetAccelerateConfiguration;
+using rgw::IAM::s3GetBucketAcl;
+using rgw::IAM::s3GetBucketCORS;
+using rgw::IAM::s3GetBucketLocation;
+using rgw::IAM::s3GetBucketLogging;
+using rgw::IAM::s3GetBucketNotification;
+using rgw::IAM::s3GetBucketPolicy;
+using rgw::IAM::s3GetBucketRequestPayment;
+using rgw::IAM::s3GetBucketTagging;
+using rgw::IAM::s3GetBucketVersioning;
+using rgw::IAM::s3GetBucketWebsite;
+using rgw::IAM::s3GetLifecycleConfiguration;
+using rgw::IAM::s3GetObject;
+using rgw::IAM::s3GetObjectAcl;
+using rgw::IAM::s3GetObjectVersionAcl;
+using rgw::IAM::s3GetObjectTorrent;
+using rgw::IAM::s3GetObjectVersion;
+using rgw::IAM::s3GetObjectVersionTorrent;
+using rgw::IAM::s3GetReplicationConfiguration;
+using rgw::IAM::s3ListAllMyBuckets;
+using rgw::IAM::s3ListBucket;
+using rgw::IAM::s3ListBucket;
+using rgw::IAM::s3ListBucketMultiPartUploads;
+using rgw::IAM::s3ListBucketVersions;
+using rgw::IAM::s3ListMultipartUploadParts;
+using rgw::IAM::s3None;
+using rgw::IAM::s3PutBucketAcl;
+using rgw::IAM::s3PutBucketPolicy;
+using rgw::IAM::Service;
+using rgw::IAM::TokenID;
+using rgw::IAM::Version;
+
+class FakeIdentity : public Identity {
+ const Principal id;
+public:
+
+ FakeIdentity(Principal&& id) : id(std::move(id)) {}
+ uint32_t get_perms_from_aclspec(const aclspec_t& aclspec) const override {
+ abort();
+ return 0;
+ };
+
+ bool is_admin_of(const rgw_user& uid) const override {
+ abort();
+ return false;
+ }
+
+ bool is_owner_of(const rgw_user& uid) const override {
+ abort();
+ return false;
+ }
+
+ virtual uint32_t get_perm_mask() const override {
+ abort();
+ return 0;
+ }
+
+ void to_str(std::ostream& out) const override {
+ abort();
+ }
+
+ bool is_identity(const flat_set<Principal>& ids) const override {
+ return ids.find(id) != ids.end();
+ }
+};
+
+class PolicyTest : public ::testing::Test {
+protected:
+ intrusive_ptr<CephContext> cct;
+ static const string arbitrary_tenant;
+ static string example1;
+ static string example2;
+ static string example3;
+public:
+ PolicyTest() {
+ cct = new CephContext(CEPH_ENTITY_TYPE_CLIENT);
+ }
+};
+
+TEST_F(PolicyTest, Parse1) {
+ optional<Policy> p;
+
+ ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(example1)));
+ ASSERT_TRUE(p);
+
+ EXPECT_EQ(p->text, example1);
+ EXPECT_EQ(p->version, Version::v2012_10_17);
+ EXPECT_FALSE(p->id);
+ EXPECT_FALSE(p->statements[0].sid);
+ EXPECT_FALSE(p->statements.empty());
+ EXPECT_EQ(p->statements.size(), 1U);
+ EXPECT_TRUE(p->statements[0].princ.empty());
+ EXPECT_TRUE(p->statements[0].noprinc.empty());
+ EXPECT_EQ(p->statements[0].effect, Effect::Allow);
+ EXPECT_EQ(p->statements[0].action, s3ListBucket);
+ EXPECT_EQ(p->statements[0].notaction, s3None);
+ ASSERT_FALSE(p->statements[0].resource.empty());
+ ASSERT_EQ(p->statements[0].resource.size(), 1U);
+ EXPECT_EQ(p->statements[0].resource.begin()->partition, Partition::aws);
+ EXPECT_EQ(p->statements[0].resource.begin()->service, Service::s3);
+ EXPECT_TRUE(p->statements[0].resource.begin()->region.empty());
+ EXPECT_EQ(p->statements[0].resource.begin()->account, arbitrary_tenant);
+ EXPECT_EQ(p->statements[0].resource.begin()->resource, "example_bucket");
+ EXPECT_TRUE(p->statements[0].notresource.empty());
+ EXPECT_TRUE(p->statements[0].conditions.empty());
+}
+
+TEST_F(PolicyTest, Eval1) {
+ auto p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(example1));
+ Environment e;
+
+ EXPECT_EQ(p.eval(e, none, s3ListBucket,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "example_bucket")),
+ Effect::Allow);
+
+ EXPECT_EQ(p.eval(e, none, s3PutBucketAcl,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "example_bucket")),
+ Effect::Pass);
+
+ EXPECT_EQ(p.eval(e, none, s3ListBucket,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "erroneous_bucket")),
+ Effect::Pass);
+
+}
+
+TEST_F(PolicyTest, Parse2) {
+ optional<Policy> p;
+
+ ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(example2)));
+ ASSERT_TRUE(p);
+
+ EXPECT_EQ(p->text, example2);
+ EXPECT_EQ(p->version, Version::v2012_10_17);
+ EXPECT_EQ(*p->id, "S3-Account-Permissions");
+ ASSERT_FALSE(p->statements.empty());
+ EXPECT_EQ(p->statements.size(), 1U);
+ EXPECT_EQ(*p->statements[0].sid, "1");
+ EXPECT_FALSE(p->statements[0].princ.empty());
+ EXPECT_EQ(p->statements[0].princ.size(), 1U);
+ EXPECT_EQ(*p->statements[0].princ.begin(),
+ Principal::tenant("ACCOUNT-ID-WITHOUT-HYPHENS"));
+ EXPECT_TRUE(p->statements[0].noprinc.empty());
+ EXPECT_EQ(p->statements[0].effect, Effect::Allow);
+ EXPECT_EQ(p->statements[0].action, s3All);
+ EXPECT_EQ(p->statements[0].notaction, s3None);
+ ASSERT_FALSE(p->statements[0].resource.empty());
+ ASSERT_EQ(p->statements[0].resource.size(), 2U);
+ EXPECT_EQ(p->statements[0].resource.begin()->partition, Partition::aws);
+ EXPECT_EQ(p->statements[0].resource.begin()->service, Service::s3);
+ EXPECT_TRUE(p->statements[0].resource.begin()->region.empty());
+ EXPECT_EQ(p->statements[0].resource.begin()->account, arbitrary_tenant);
+ EXPECT_EQ(p->statements[0].resource.begin()->resource, "mybucket");
+ EXPECT_EQ((p->statements[0].resource.begin() + 1)->partition,
+ Partition::aws);
+ EXPECT_EQ((p->statements[0].resource.begin() + 1)->service,
+ Service::s3);
+ EXPECT_TRUE((p->statements[0].resource.begin() + 1)->region.empty());
+ EXPECT_EQ((p->statements[0].resource.begin() + 1)->account,
+ arbitrary_tenant);
+ EXPECT_EQ((p->statements[0].resource.begin() + 1)->resource, "mybucket/*");
+ EXPECT_TRUE(p->statements[0].notresource.empty());
+ EXPECT_TRUE(p->statements[0].conditions.empty());
+}
+
+TEST_F(PolicyTest, Eval2) {
+ auto p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(example2));
+ Environment e;
+
+ auto trueacct = FakeIdentity(
+ Principal::tenant("ACCOUNT-ID-WITHOUT-HYPHENS"));
+
+ auto notacct = FakeIdentity(
+ Principal::tenant("some-other-account"));
+ for (auto i = 0ULL; i < s3Count; ++i) {
+ EXPECT_EQ(p.eval(e, trueacct, 1ULL << i,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "mybucket")),
+ Effect::Allow);
+ EXPECT_EQ(p.eval(e, trueacct, 1ULL << i,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "mybucket/myobject")),
+ Effect::Allow);
+
+ EXPECT_EQ(p.eval(e, notacct, 1ULL << i,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "mybucket")),
+ Effect::Pass);
+ EXPECT_EQ(p.eval(e, notacct, 1ULL << i,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "mybucket/myobject")),
+ Effect::Pass);
+
+ EXPECT_EQ(p.eval(e, trueacct, 1ULL << i,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "notyourbucket")),
+ Effect::Pass);
+ EXPECT_EQ(p.eval(e, trueacct, 1ULL << i,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "notyourbucket/notyourobject")),
+ Effect::Pass);
+
+ }
+}
+
+TEST_F(PolicyTest, Parse3) {
+ optional<Policy> p;
+
+ ASSERT_NO_THROW(p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(example3)));
+ ASSERT_TRUE(p);
+
+ EXPECT_EQ(p->text, example3);
+ EXPECT_EQ(p->version, Version::v2012_10_17);
+ EXPECT_FALSE(p->id);
+ ASSERT_FALSE(p->statements.empty());
+ EXPECT_EQ(p->statements.size(), 3U);
+
+ EXPECT_EQ(*p->statements[0].sid, "FirstStatement");
+ EXPECT_TRUE(p->statements[0].princ.empty());
+ EXPECT_TRUE(p->statements[0].noprinc.empty());
+ EXPECT_EQ(p->statements[0].effect, Effect::Allow);
+ EXPECT_EQ(p->statements[0].action, s3PutBucketPolicy);
+ EXPECT_EQ(p->statements[0].notaction, s3None);
+ ASSERT_FALSE(p->statements[0].resource.empty());
+ ASSERT_EQ(p->statements[0].resource.size(), 1U);
+ EXPECT_EQ(p->statements[0].resource.begin()->partition, Partition::wildcard);
+ EXPECT_EQ(p->statements[0].resource.begin()->service, Service::wildcard);
+ EXPECT_EQ(p->statements[0].resource.begin()->region, "*");
+ EXPECT_EQ(p->statements[0].resource.begin()->account, arbitrary_tenant);
+ EXPECT_EQ(p->statements[0].resource.begin()->resource, "*");
+ EXPECT_TRUE(p->statements[0].notresource.empty());
+ EXPECT_TRUE(p->statements[0].conditions.empty());
+
+ EXPECT_EQ(*p->statements[1].sid, "SecondStatement");
+ EXPECT_TRUE(p->statements[1].princ.empty());
+ EXPECT_TRUE(p->statements[1].noprinc.empty());
+ EXPECT_EQ(p->statements[1].effect, Effect::Allow);
+ EXPECT_EQ(p->statements[1].action, s3ListAllMyBuckets);
+ EXPECT_EQ(p->statements[1].notaction, s3None);
+ ASSERT_FALSE(p->statements[1].resource.empty());
+ ASSERT_EQ(p->statements[1].resource.size(), 1U);
+ EXPECT_EQ(p->statements[1].resource.begin()->partition, Partition::wildcard);
+ EXPECT_EQ(p->statements[1].resource.begin()->service, Service::wildcard);
+ EXPECT_EQ(p->statements[1].resource.begin()->region, "*");
+ EXPECT_EQ(p->statements[1].resource.begin()->account, arbitrary_tenant);
+ EXPECT_EQ(p->statements[1].resource.begin()->resource, "*");
+ EXPECT_TRUE(p->statements[1].notresource.empty());
+ EXPECT_TRUE(p->statements[1].conditions.empty());
+
+ EXPECT_EQ(*p->statements[2].sid, "ThirdStatement");
+ EXPECT_TRUE(p->statements[2].princ.empty());
+ EXPECT_TRUE(p->statements[2].noprinc.empty());
+ EXPECT_EQ(p->statements[2].effect, Effect::Allow);
+ EXPECT_EQ(p->statements[2].action, (s3ListMultipartUploadParts |
+ s3ListBucket | s3ListBucketVersions |
+ s3ListAllMyBuckets |
+ s3ListBucketMultiPartUploads |
+ s3GetObject | s3GetObjectVersion |
+ s3GetObjectAcl | s3GetObjectVersionAcl |
+ s3GetObjectTorrent |
+ s3GetObjectVersionTorrent |
+ s3GetAccelerateConfiguration |
+ s3GetBucketAcl | s3GetBucketCORS |
+ s3GetBucketVersioning |
+ s3GetBucketRequestPayment |
+ s3GetBucketLocation |
+ s3GetBucketPolicy |
+ s3GetBucketNotification |
+ s3GetBucketLogging |
+ s3GetBucketTagging |
+ s3GetBucketWebsite |
+ s3GetLifecycleConfiguration |
+ s3GetReplicationConfiguration));
+ EXPECT_EQ(p->statements[2].notaction, s3None);
+ ASSERT_FALSE(p->statements[2].resource.empty());
+ ASSERT_EQ(p->statements[2].resource.size(), 2U);
+ EXPECT_EQ(p->statements[2].resource.begin()->partition, Partition::aws);
+ EXPECT_EQ(p->statements[2].resource.begin()->service, Service::s3);
+ EXPECT_TRUE(p->statements[2].resource.begin()->region.empty());
+ EXPECT_EQ(p->statements[2].resource.begin()->account, arbitrary_tenant);
+ EXPECT_EQ(p->statements[2].resource.begin()->resource, "confidential-data");
+ EXPECT_EQ((p->statements[2].resource.begin() + 1)->partition,
+ Partition::aws);
+ EXPECT_EQ((p->statements[2].resource.begin() + 1)->service, Service::s3);
+ EXPECT_TRUE((p->statements[2].resource.begin() + 1)->region.empty());
+ EXPECT_EQ((p->statements[2].resource.begin() + 1)->account,
+ arbitrary_tenant);
+ EXPECT_EQ((p->statements[2].resource.begin() + 1)->resource,
+ "confidential-data/*");
+ EXPECT_TRUE(p->statements[2].notresource.empty());
+ ASSERT_FALSE(p->statements[2].conditions.empty());
+ ASSERT_EQ(p->statements[2].conditions.size(), 1U);
+ EXPECT_EQ(p->statements[2].conditions[0].op, TokenID::Bool);
+ EXPECT_EQ(p->statements[2].conditions[0].key, "aws:MultiFactorAuthPresent");
+ EXPECT_FALSE(p->statements[2].conditions[0].ifexists);
+ ASSERT_FALSE(p->statements[2].conditions[0].vals.empty());
+ EXPECT_EQ(p->statements[2].conditions[0].vals.size(), 1U);
+ EXPECT_EQ(p->statements[2].conditions[0].vals[0], "true");
+}
+
+TEST_F(PolicyTest, Eval3) {
+ auto p = Policy(cct.get(), arbitrary_tenant,
+ bufferlist::static_from_string(example3));
+ Environment em;
+ Environment tr = { { "aws:MultiFactorAuthPresent", "true" } };
+ Environment fa = { { "aws:MultiFactorAuthPresent", "false" } };
+
+ auto s3allow = (s3ListMultipartUploadParts | s3ListBucket |
+ s3ListBucketVersions | s3ListAllMyBuckets |
+ s3ListBucketMultiPartUploads | s3GetObject |
+ s3GetObjectVersion | s3GetObjectAcl | s3GetObjectVersionAcl |
+ s3GetObjectTorrent | s3GetObjectVersionTorrent |
+ s3GetAccelerateConfiguration | s3GetBucketAcl |
+ s3GetBucketCORS | s3GetBucketVersioning |
+ s3GetBucketRequestPayment | s3GetBucketLocation |
+ s3GetBucketPolicy | s3GetBucketNotification |
+ s3GetBucketLogging | s3GetBucketTagging |
+ s3GetBucketWebsite | s3GetLifecycleConfiguration |
+ s3GetReplicationConfiguration);
+
+ EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "mybucket")),
+ Effect::Allow);
+
+ EXPECT_EQ(p.eval(em, none, s3PutBucketPolicy,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "mybucket")),
+ Effect::Allow);
+
+
+ for (auto i = 0ULL; i < s3Count; ++i) {
+ auto op = 1ULL << i;
+ if ((op == s3ListAllMyBuckets) || (op == s3PutBucketPolicy)) {
+ continue;
+ }
+
+ EXPECT_EQ(p.eval(em, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "confidential-data")),
+ Effect::Pass);
+ EXPECT_EQ(p.eval(tr, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "confidential-data")),
+ op & s3allow ? Effect::Allow : Effect::Pass);
+ EXPECT_EQ(p.eval(fa, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "confidential-data")),
+ Effect::Pass);
+
+ EXPECT_EQ(p.eval(em, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "confidential-data/moo")),
+ Effect::Pass);
+ EXPECT_EQ(p.eval(tr, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "confidential-data/moo")),
+ op & s3allow ? Effect::Allow : Effect::Pass);
+ EXPECT_EQ(p.eval(fa, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "confidential-data/moo")),
+ Effect::Pass);
+
+ EXPECT_EQ(p.eval(em, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "really-confidential-data")),
+ Effect::Pass);
+ EXPECT_EQ(p.eval(tr, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "really-confidential-data")),
+ Effect::Pass);
+ EXPECT_EQ(p.eval(fa, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant, "really-confidential-data")),
+ Effect::Pass);
+
+ EXPECT_EQ(p.eval(em, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant,
+ "really-confidential-data/moo")), Effect::Pass);
+ EXPECT_EQ(p.eval(tr, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant,
+ "really-confidential-data/moo")), Effect::Pass);
+ EXPECT_EQ(p.eval(fa, none, op,
+ ARN(Partition::aws, Service::s3,
+ "", arbitrary_tenant,
+ "really-confidential-data/moo")), Effect::Pass);
+
+ }
+}
+
+const string PolicyTest::arbitrary_tenant = "arbitrary_tenant";
+string PolicyTest::example1 = R"(
+{
+ "Version": "2012-10-17",
+ "Statement": {
+ "Effect": "Allow",
+ "Action": "s3:ListBucket",
+ "Resource": "arn:aws:s3:::example_bucket"
+ }
+}
+)";
+
+string PolicyTest::example2 = R"(
+{
+ "Version": "2012-10-17",
+ "Id": "S3-Account-Permissions",
+ "Statement": [{
+ "Sid": "1",
+ "Effect": "Allow",
+ "Principal": {"AWS": ["arn:aws:iam::ACCOUNT-ID-WITHOUT-HYPHENS:root"]},
+ "Action": "s3:*",
+ "Resource": [
+ "arn:aws:s3:::mybucket",
+ "arn:aws:s3:::mybucket/*"
+ ]
+ }]
+}
+)";
+
+string PolicyTest::example3 = R"(
+{
+ "Version": "2012-10-17",
+ "Statement": [
+ {
+ "Sid": "FirstStatement",
+ "Effect": "Allow",
+ "Action": ["s3:PutBucketPolicy"],
+ "Resource": "*"
+ },
+ {
+ "Sid": "SecondStatement",
+ "Effect": "Allow",
+ "Action": "s3:ListAllMyBuckets",
+ "Resource": "*"
+ },
+ {
+ "Sid": "ThirdStatement",
+ "Effect": "Allow",
+ "Action": [
+ "s3:List*",
+ "s3:Get*"
+ ],
+ "Resource": [
+ "arn:aws:s3:::confidential-data",
+ "arn:aws:s3:::confidential-data/*"
+ ],
+ "Condition": {"Bool": {"aws:MultiFactorAuthPresent": "true"}}
+ }
+ ]
+}
+)";