]> git.proxmox.com Git - ceph.git/blob - ceph/src/rgw/rgw_iam_policy.cc
843dca7a2de09cdb1dbd979e98351627234d5619
[ceph.git] / ceph / src / rgw / rgw_iam_policy.cc
1 // -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
2 // vim: ts=8 sw=2 smarttab
3
4
5 #include <cstring>
6 #include <sstream>
7 #include <stack>
8 #include <utility>
9
10 #include <boost/regex.hpp>
11
12 #include "rapidjson/reader.h"
13
14 #include "rgw_auth.h"
15 #include "rgw_iam_policy.h"
16
17 namespace {
18 constexpr int dout_subsys = ceph_subsys_rgw;
19 }
20
21 using std::bitset;
22 using std::find;
23 using std::int64_t;
24 using std::move;
25 using std::pair;
26 using std::size_t;
27 using std::string;
28 using std::stringstream;
29 using std::ostream;
30 using std::uint16_t;
31 using std::uint64_t;
32 using std::unordered_map;
33
34 using boost::container::flat_set;
35 using boost::none;
36 using boost::optional;
37 using boost::regex;
38 using boost::regex_constants::ECMAScript;
39 using boost::regex_constants::optimize;
40 using boost::regex_match;
41 using boost::smatch;
42
43 using rapidjson::BaseReaderHandler;
44 using rapidjson::UTF8;
45 using rapidjson::SizeType;
46 using rapidjson::Reader;
47 using rapidjson::kParseCommentsFlag;
48 using rapidjson::kParseNumbersAsStringsFlag;
49 using rapidjson::StringStream;
50 using rapidjson::ParseResult;
51
52 using rgw::auth::Principal;
53
54 namespace rgw {
55 namespace IAM {
56 #include "rgw_iam_policy_keywords.frag.cc"
57
58 struct actpair {
59 const char* name;
60 const uint64_t bit;
61 };
62
63 namespace {
64 optional<Partition> to_partition(const smatch::value_type& p,
65 bool wildcards) {
66 if (p == "aws") {
67 return Partition::aws;
68 } else if (p == "aws-cn") {
69 return Partition::aws_cn;
70 } else if (p == "aws-us-gov") {
71 return Partition::aws_us_gov;
72 } else if (p == "*" && wildcards) {
73 return Partition::wildcard;
74 } else {
75 return none;
76 }
77
78 ceph_abort();
79 }
80
81 optional<Service> to_service(const smatch::value_type& s,
82 bool wildcards) {
83 static const unordered_map<string, Service> services = {
84 { "acm", Service::acm },
85 { "apigateway", Service::apigateway },
86 { "appstream", Service::appstream },
87 { "artifact", Service::artifact },
88 { "autoscaling", Service::autoscaling },
89 { "aws-marketplace", Service::aws_marketplace },
90 { "aws-marketplace-management",
91 Service::aws_marketplace_management },
92 { "aws-portal", Service::aws_portal },
93 { "cloudformation", Service::cloudformation },
94 { "cloudfront", Service::cloudfront },
95 { "cloudhsm", Service::cloudhsm },
96 { "cloudsearch", Service::cloudsearch },
97 { "cloudtrail", Service::cloudtrail },
98 { "cloudwatch", Service::cloudwatch },
99 { "codebuild", Service::codebuild },
100 { "codecommit", Service::codecommit },
101 { "codedeploy", Service::codedeploy },
102 { "codepipeline", Service::codepipeline },
103 { "cognito-identity", Service::cognito_identity },
104 { "cognito-idp", Service::cognito_idp },
105 { "cognito-sync", Service::cognito_sync },
106 { "config", Service::config },
107 { "datapipeline", Service::datapipeline },
108 { "devicefarm", Service::devicefarm },
109 { "directconnect", Service::directconnect },
110 { "dms", Service::dms },
111 { "ds", Service::ds },
112 { "dynamodb", Service::dynamodb },
113 { "ec2", Service::ec2 },
114 { "ecr", Service::ecr },
115 { "ecs", Service::ecs },
116 { "elasticache", Service::elasticache },
117 { "elasticbeanstalk", Service::elasticbeanstalk },
118 { "elasticfilesystem", Service::elasticfilesystem },
119 { "elasticloadbalancing", Service::elasticloadbalancing },
120 { "elasticmapreduce", Service::elasticmapreduce },
121 { "elastictranscoder", Service::elastictranscoder },
122 { "es", Service::es },
123 { "events", Service::events },
124 { "firehose", Service::firehose },
125 { "gamelift", Service::gamelift },
126 { "glacier", Service::glacier },
127 { "health", Service::health },
128 { "iam", Service::iam },
129 { "importexport", Service::importexport },
130 { "inspector", Service::inspector },
131 { "iot", Service::iot },
132 { "kinesis", Service::kinesis },
133 { "kinesisanalytics", Service::kinesisanalytics },
134 { "kms", Service::kms },
135 { "lambda", Service::lambda },
136 { "lightsail", Service::lightsail },
137 { "logs", Service::logs },
138 { "machinelearning", Service::machinelearning },
139 { "mobileanalytics", Service::mobileanalytics },
140 { "mobilehub", Service::mobilehub },
141 { "opsworks", Service::opsworks },
142 { "opsworks-cm", Service::opsworks_cm },
143 { "polly", Service::polly },
144 { "rds", Service::rds },
145 { "redshift", Service::redshift },
146 { "route53", Service::route53 },
147 { "route53domains", Service::route53domains },
148 { "s3", Service::s3 },
149 { "sdb", Service::sdb },
150 { "servicecatalog", Service::servicecatalog },
151 { "ses", Service::ses },
152 { "sns", Service::sns },
153 { "sqs", Service::sqs },
154 { "ssm", Service::ssm },
155 { "states", Service::states },
156 { "storagegateway", Service::storagegateway },
157 { "sts", Service::sts },
158 { "support", Service::support },
159 { "swf", Service::swf },
160 { "trustedadvisor", Service::trustedadvisor },
161 { "waf", Service::waf },
162 { "workmail", Service::workmail },
163 { "workspaces", Service::workspaces }};
164
165 if (wildcards && s == "*") {
166 return Service::wildcard;
167 }
168
169 auto i = services.find(s);
170 if (i == services.end()) {
171 return none;
172 } else {
173 return i->second;
174 }
175 }
176 }
177
178 ARN::ARN(const rgw_obj& o)
179 : partition(Partition::aws),
180 service(Service::s3),
181 region(),
182 account(o.bucket.tenant),
183 resource(o.bucket.name)
184 {
185 resource.push_back('/');
186 resource.append(o.key.name);
187 }
188
189 ARN::ARN(const rgw_bucket& b)
190 : partition(Partition::aws),
191 service(Service::s3),
192 region(),
193 account(b.tenant),
194 resource(b.name) { }
195
196 ARN::ARN(const rgw_bucket& b, const string& o)
197 : partition(Partition::aws),
198 service(Service::s3),
199 region(),
200 account(b.tenant),
201 resource(b.name) {
202 resource.push_back('/');
203 resource.append(o);
204 }
205
206 optional<ARN> ARN::parse(const string& s, bool wildcards) {
207 static const char str_wild[] = "arn:([^:]*):([^:]*):([^:]*):([^:]*):([^:]*)";
208 static const regex rx_wild(str_wild,
209 sizeof(str_wild) - 1,
210 ECMAScript | optimize);
211 static const char str_no_wild[]
212 = "arn:([^:*]*):([^:*]*):([^:*]*):([^:*]*):([^:*]*)";
213 static const regex rx_no_wild(str_no_wild,
214 sizeof(str_no_wild) - 1,
215 ECMAScript | optimize);
216
217 smatch match;
218
219 if ((s == "*") && wildcards) {
220 return ARN(Partition::wildcard, Service::wildcard, "*", "*", "*");
221 } else if (regex_match(s, match, wildcards ? rx_wild : rx_no_wild)) {
222 ceph_assert(match.size() == 6);
223
224 ARN a;
225 {
226 auto p = to_partition(match[1], wildcards);
227 if (!p)
228 return none;
229
230 a.partition = *p;
231 }
232 {
233 auto s = to_service(match[2], wildcards);
234 if (!s) {
235 return none;
236 }
237 a.service = *s;
238 }
239
240 a.region = match[3];
241 a.account = match[4];
242 a.resource = match[5];
243
244 return a;
245 }
246 return none;
247 }
248
249 string ARN::to_string() const {
250 string s;
251
252 if (partition == Partition::aws) {
253 s.append("aws:");
254 } else if (partition == Partition::aws_cn) {
255 s.append("aws-cn:");
256 } else if (partition == Partition::aws_us_gov) {
257 s.append("aws-us-gov:");
258 } else {
259 s.append("*:");
260 }
261
262 static const unordered_map<Service, string> services = {
263 { Service::acm, "acm" },
264 { Service::apigateway, "apigateway" },
265 { Service::appstream, "appstream" },
266 { Service::artifact, "artifact" },
267 { Service::autoscaling, "autoscaling" },
268 { Service::aws_marketplace, "aws-marketplace" },
269 { Service::aws_marketplace_management, "aws-marketplace-management" },
270 { Service::aws_portal, "aws-portal" },
271 { Service::cloudformation, "cloudformation" },
272 { Service::cloudfront, "cloudfront" },
273 { Service::cloudhsm, "cloudhsm" },
274 { Service::cloudsearch, "cloudsearch" },
275 { Service::cloudtrail, "cloudtrail" },
276 { Service::cloudwatch, "cloudwatch" },
277 { Service::codebuild, "codebuild" },
278 { Service::codecommit, "codecommit" },
279 { Service::codedeploy, "codedeploy" },
280 { Service::codepipeline, "codepipeline" },
281 { Service::cognito_identity, "cognito-identity" },
282 { Service::cognito_idp, "cognito-idp" },
283 { Service::cognito_sync, "cognito-sync" },
284 { Service::config, "config" },
285 { Service::datapipeline, "datapipeline" },
286 { Service::devicefarm, "devicefarm" },
287 { Service::directconnect, "directconnect" },
288 { Service::dms, "dms" },
289 { Service::ds, "ds" },
290 { Service::dynamodb, "dynamodb" },
291 { Service::ec2, "ec2" },
292 { Service::ecr, "ecr" },
293 { Service::ecs, "ecs" },
294 { Service::elasticache, "elasticache" },
295 { Service::elasticbeanstalk, "elasticbeanstalk" },
296 { Service::elasticfilesystem, "elasticfilesystem" },
297 { Service::elasticloadbalancing, "elasticloadbalancing" },
298 { Service::elasticmapreduce, "elasticmapreduce" },
299 { Service::elastictranscoder, "elastictranscoder" },
300 { Service::es, "es" },
301 { Service::events, "events" },
302 { Service::firehose, "firehose" },
303 { Service::gamelift, "gamelift" },
304 { Service::glacier, "glacier" },
305 { Service::health, "health" },
306 { Service::iam, "iam" },
307 { Service::importexport, "importexport" },
308 { Service::inspector, "inspector" },
309 { Service::iot, "iot" },
310 { Service::kinesis, "kinesis" },
311 { Service::kinesisanalytics, "kinesisanalytics" },
312 { Service::kms, "kms" },
313 { Service::lambda, "lambda" },
314 { Service::lightsail, "lightsail" },
315 { Service::logs, "logs" },
316 { Service::machinelearning, "machinelearning" },
317 { Service::mobileanalytics, "mobileanalytics" },
318 { Service::mobilehub, "mobilehub" },
319 { Service::opsworks, "opsworks" },
320 { Service::opsworks_cm, "opsworks-cm" },
321 { Service::polly, "polly" },
322 { Service::rds, "rds" },
323 { Service::redshift, "redshift" },
324 { Service::route53, "route53" },
325 { Service::route53domains, "route53domains" },
326 { Service::s3, "s3" },
327 { Service::sdb, "sdb" },
328 { Service::servicecatalog, "servicecatalog" },
329 { Service::ses, "ses" },
330 { Service::sns, "sns" },
331 { Service::sqs, "sqs" },
332 { Service::ssm, "ssm" },
333 { Service::states, "states" },
334 { Service::storagegateway, "storagegateway" },
335 { Service::sts, "sts" },
336 { Service::support, "support" },
337 { Service::swf, "swf" },
338 { Service::trustedadvisor, "trustedadvisor" },
339 { Service::waf, "waf" },
340 { Service::workmail, "workmail" },
341 { Service::workspaces, "workspaces" }};
342
343 auto i = services.find(service);
344 if (i != services.end()) {
345 s.append(i->second);
346 } else {
347 s.push_back('*');
348 }
349 s.push_back(':');
350
351 s.append(region);
352 s.push_back(':');
353
354 s.append(account);
355 s.push_back(':');
356
357 s.append(resource);
358
359 return s;
360 }
361
362 bool operator ==(const ARN& l, const ARN& r) {
363 return ((l.partition == r.partition) &&
364 (l.service == r.service) &&
365 (l.region == r.region) &&
366 (l.account == r.account) &&
367 (l.resource == r.resource));
368 }
369 bool operator <(const ARN& l, const ARN& r) {
370 return ((l.partition < r.partition) ||
371 (l.service < r.service) ||
372 (l.region < r.region) ||
373 (l.account < r.account) ||
374 (l.resource < r.resource));
375 }
376
377 // The candidate is not allowed to have wildcards. The only way to
378 // do that sanely would be to use unification rather than matching.
379 bool ARN::match(const ARN& candidate) const {
380 if ((candidate.partition == Partition::wildcard) ||
381 (partition != candidate.partition && partition
382 != Partition::wildcard)) {
383 return false;
384 }
385
386 if ((candidate.service == Service::wildcard) ||
387 (service != candidate.service && service != Service::wildcard)) {
388 return false;
389 }
390
391 if (!::match(region, candidate.region, MATCH_POLICY_ARN)) {
392 return false;
393 }
394
395 if (!::match(account, candidate.account, MATCH_POLICY_ARN)) {
396 return false;
397 }
398
399 if (!::match(resource, candidate.resource, MATCH_POLICY_ARN)) {
400 return false;
401 }
402
403 return true;
404 }
405
406 static const actpair actpairs[] =
407 {{ "s3:AbortMultipartUpload", s3AbortMultipartUpload },
408 { "s3:CreateBucket", s3CreateBucket },
409 { "s3:DeleteBucketPolicy", s3DeleteBucketPolicy },
410 { "s3:DeleteBucket", s3DeleteBucket },
411 { "s3:DeleteBucketWebsite", s3DeleteBucketWebsite },
412 { "s3:DeleteObject", s3DeleteObject },
413 { "s3:DeleteObjectVersion", s3DeleteObjectVersion },
414 { "s3:DeleteReplicationConfiguration", s3DeleteReplicationConfiguration },
415 { "s3:GetAccelerateConfiguration", s3GetAccelerateConfiguration },
416 { "s3:GetBucketAcl", s3GetBucketAcl },
417 { "s3:GetBucketCORS", s3GetBucketCORS },
418 { "s3:GetBucketLocation", s3GetBucketLocation },
419 { "s3:GetBucketLogging", s3GetBucketLogging },
420 { "s3:GetBucketNotification", s3GetBucketNotification },
421 { "s3:GetBucketPolicy", s3GetBucketPolicy },
422 { "s3:GetBucketRequestPayment", s3GetBucketRequestPayment },
423 { "s3:GetBucketTagging", s3GetBucketTagging },
424 { "s3:GetBucketVersioning", s3GetBucketVersioning },
425 { "s3:GetBucketWebsite", s3GetBucketWebsite },
426 { "s3:GetLifecycleConfiguration", s3GetLifecycleConfiguration },
427 { "s3:GetObjectAcl", s3GetObjectAcl },
428 { "s3:GetObject", s3GetObject },
429 { "s3:GetObjectTorrent", s3GetObjectTorrent },
430 { "s3:GetObjectVersionAcl", s3GetObjectVersionAcl },
431 { "s3:GetObjectVersion", s3GetObjectVersion },
432 { "s3:GetObjectVersionTorrent", s3GetObjectVersionTorrent },
433 { "s3:GetReplicationConfiguration", s3GetReplicationConfiguration },
434 { "s3:ListAllMyBuckets", s3ListAllMyBuckets },
435 { "s3:ListBucketMultiPartUploads", s3ListBucketMultiPartUploads },
436 { "s3:ListBucket", s3ListBucket },
437 { "s3:ListBucketVersions", s3ListBucketVersions },
438 { "s3:ListMultipartUploadParts", s3ListMultipartUploadParts },
439 { "s3:PutAccelerateConfiguration", s3PutAccelerateConfiguration },
440 { "s3:PutBucketAcl", s3PutBucketAcl },
441 { "s3:PutBucketCORS", s3PutBucketCORS },
442 { "s3:PutBucketLogging", s3PutBucketLogging },
443 { "s3:PutBucketNotification", s3PutBucketNotification },
444 { "s3:PutBucketPolicy", s3PutBucketPolicy },
445 { "s3:PutBucketRequestPayment", s3PutBucketRequestPayment },
446 { "s3:PutBucketTagging", s3PutBucketTagging },
447 { "s3:PutBucketVersioning", s3PutBucketVersioning },
448 { "s3:PutBucketWebsite", s3PutBucketWebsite },
449 { "s3:PutLifecycleConfiguration", s3PutLifecycleConfiguration },
450 { "s3:PutObjectAcl", s3PutObjectAcl },
451 { "s3:PutObject", s3PutObject },
452 { "s3:PutObjectVersionAcl", s3PutObjectVersionAcl },
453 { "s3:PutReplicationConfiguration", s3PutReplicationConfiguration },
454 { "s3:RestoreObject", s3RestoreObject }};
455
456 struct PolicyParser;
457
458 const Keyword top[1]{"<Top>", TokenKind::pseudo, TokenID::Top, 0, false,
459 false};
460 const Keyword cond_key[1]{"<Condition Key>", TokenKind::cond_key,
461 TokenID::CondKey, 0, true, false};
462
463 struct ParseState {
464 PolicyParser* pp;
465 const Keyword* w;
466
467 bool arraying = false;
468 bool objecting = false;
469
470 void reset();
471
472 ParseState(PolicyParser* pp, const Keyword* w)
473 : pp(pp), w(w) {}
474
475 bool obj_start();
476
477 bool obj_end();
478
479 bool array_start() {
480 if (w->arrayable && !arraying) {
481 arraying = true;
482 return true;
483 }
484 return false;
485 }
486
487 bool array_end();
488
489 bool key(const char* s, size_t l);
490 bool do_string(CephContext* cct, const char* s, size_t l);
491 bool number(const char* str, size_t l);
492 };
493
494 // If this confuses you, look up the Curiously Recurring Template Pattern
495 struct PolicyParser : public BaseReaderHandler<UTF8<>, PolicyParser> {
496 keyword_hash tokens;
497 std::vector<ParseState> s;
498 CephContext* cct;
499 const string& tenant;
500 Policy& policy;
501
502 uint32_t seen = 0;
503
504 uint32_t dex(TokenID in) const {
505 switch (in) {
506 case TokenID::Version:
507 return 0x1;
508 case TokenID::Id:
509 return 0x2;
510 case TokenID::Statement:
511 return 0x4;
512 case TokenID::Sid:
513 return 0x8;
514 case TokenID::Effect:
515 return 0x10;
516 case TokenID::Principal:
517 return 0x20;
518 case TokenID::NotPrincipal:
519 return 0x40;
520 case TokenID::Action:
521 return 0x80;
522 case TokenID::NotAction:
523 return 0x100;
524 case TokenID::Resource:
525 return 0x200;
526 case TokenID::NotResource:
527 return 0x400;
528 case TokenID::Condition:
529 return 0x800;
530 case TokenID::AWS:
531 return 0x1000;
532 case TokenID::Federated:
533 return 0x2000;
534 case TokenID::Service:
535 return 0x4000;
536 case TokenID::CanonicalUser:
537 return 0x8000;
538 default:
539 ceph_abort();
540 }
541 }
542 bool test(TokenID in) {
543 return seen & dex(in);
544 }
545 void set(TokenID in) {
546 seen |= dex(in);
547 }
548 void set(std::initializer_list<TokenID> l) {
549 for (auto in : l) {
550 seen |= dex(in);
551 }
552 }
553 void reset(TokenID in) {
554 seen &= ~dex(in);
555 }
556 void reset(std::initializer_list<TokenID> l) {
557 for (auto in : l) {
558 seen &= ~dex(in);
559 }
560 }
561
562 PolicyParser(CephContext* cct, const string& tenant, Policy& policy)
563 : cct(cct), tenant(tenant), policy(policy) {}
564 PolicyParser(const PolicyParser& policy) = delete;
565
566 bool StartObject() {
567 if (s.empty()) {
568 s.push_back({this, top});
569 s.back().objecting = true;
570 return true;
571 }
572
573 return s.back().obj_start();
574 }
575 bool EndObject(SizeType memberCount) {
576 if (s.empty()) {
577 return false;
578 }
579
580 return s.back().obj_end();
581 }
582 bool Key(const char* str, SizeType length, bool copy) {
583 if (s.empty()) {
584 return false;
585 }
586
587 return s.back().key(str, length);
588 }
589
590 bool String(const char* str, SizeType length, bool copy) {
591 if (s.empty()) {
592 return false;
593 }
594
595 return s.back().do_string(cct, str, length);
596 }
597 bool RawNumber(const char* str, SizeType length, bool copy) {
598 if (s.empty()) {
599 return false;
600 }
601
602 return s.back().number(str, length);
603 }
604 bool StartArray() {
605 if (s.empty()) {
606 return false;
607 }
608
609 return s.back().array_start();
610 }
611 bool EndArray(SizeType) {
612 if (s.empty()) {
613 return false;
614 }
615
616 return s.back().array_end();
617 }
618
619 bool Default() {
620 return false;
621 }
622 };
623
624
625 // I really despise this misfeature of C++.
626 //
627 bool ParseState::obj_end() {
628 if (objecting) {
629 objecting = false;
630 if (!arraying) {
631 pp->s.pop_back();
632 } else {
633 reset();
634 }
635 return true;
636 }
637 return false;
638 }
639
640 bool ParseState::key(const char* s, size_t l) {
641 auto k = pp->tokens.lookup(s, l);
642
643 if (!k) {
644 if (w->kind == TokenKind::cond_op) {
645 auto& t = pp->policy.statements.back();
646 pp->s.emplace_back(pp, cond_key);
647 t.conditions.emplace_back(w->id, s, l);
648 return true;
649 } else {
650 return false;
651 }
652 }
653
654 // If the token we're going with belongs within the condition at the
655 // top of the stack and we haven't already encountered it, push it
656 // on the stack
657
658 // Top
659 if ((((w->id == TokenID::Top) && (k->kind == TokenKind::top)) ||
660 // Statement
661 ((w->id == TokenID::Statement) && (k->kind == TokenKind::statement)) ||
662
663 /// Principal
664 ((w->id == TokenID::Principal || w->id == TokenID::NotPrincipal) &&
665 (k->kind == TokenKind::princ_type))) &&
666
667 // Check that it hasn't been encountered. Note that this
668 // conjoins with the run of disjunctions above.
669 !pp->test(k->id)) {
670 pp->set(k->id);
671 pp->s.emplace_back(pp, k);
672 return true;
673 } else if ((w->id == TokenID::Condition) &&
674 (k->kind == TokenKind::cond_op)) {
675 pp->s.emplace_back(pp, k);
676 return true;
677 }
678 return false;
679 }
680
681 // I should just rewrite a few helper functions to use iterators,
682 // which will make all of this ever so much nicer.
683 static optional<Principal> parse_principal(CephContext* cct, TokenID t,
684 string&& s) {
685 // Wildcard!
686 if ((t == TokenID::AWS) && (s == "*")) {
687 return Principal::wildcard();
688
689 // Do nothing for now.
690 } else if (t == TokenID::CanonicalUser) {
691
692 // AWS ARNs
693 } else if (t == TokenID::AWS) {
694 auto a = ARN::parse(s);
695 if (!a) {
696 if (std::none_of(s.begin(), s.end(),
697 [](const char& c) {
698 return (c == ':') || (c == '/');
699 })) {
700 // Since tenants are simply prefixes, there's no really good
701 // way to see if one exists or not. So we return the thing and
702 // let them try to match against it.
703 return Principal::tenant(std::move(s));
704 }
705 }
706
707 if (a->resource == "root") {
708 return Principal::tenant(std::move(a->account));
709 }
710
711 static const char rx_str[] = "([^/]*)/(.*)";
712 static const regex rx(rx_str, sizeof(rx_str) - 1,
713 ECMAScript | optimize);
714 smatch match;
715 if (regex_match(a->resource, match, rx)) {
716 ceph_assert(match.size() == 2);
717
718 if (match[1] == "user") {
719 return Principal::user(std::move(a->account),
720 match[2]);
721 }
722
723 if (match[1] == "role") {
724 return Principal::role(std::move(a->account),
725 match[2]);
726 }
727 }
728 }
729
730 ldout(cct, 0) << "Supplied principal is discarded: " << s << dendl;
731 return boost::none;
732 }
733
734 bool ParseState::do_string(CephContext* cct, const char* s, size_t l) {
735 auto k = pp->tokens.lookup(s, l);
736 Policy& p = pp->policy;
737 Statement* t = p.statements.empty() ? nullptr : &(p.statements.back());
738
739 // Top level!
740 if ((w->id == TokenID::Version) && k &&
741 k->kind == TokenKind::version_key) {
742 p.version = static_cast<Version>(k->specific);
743 } else if (w->id == TokenID::Id) {
744 p.id = string(s, l);
745
746 // Statement
747
748 } else if (w->id == TokenID::Sid) {
749 t->sid.emplace(s, l);
750 } else if ((w->id == TokenID::Effect) &&
751 k->kind == TokenKind::effect_key) {
752 t->effect = static_cast<Effect>(k->specific);
753 } else if (w->id == TokenID::Principal && s && *s == '*') {
754 t->princ.emplace(Principal::wildcard());
755 } else if (w->id == TokenID::NotPrincipal && s && *s == '*') {
756 t->noprinc.emplace(Principal::wildcard());
757 } else if ((w->id == TokenID::Action) ||
758 (w->id == TokenID::NotAction)) {
759 for (auto& p : actpairs) {
760 if (match({s, l}, p.name, MATCH_POLICY_ACTION)) {
761 (w->id == TokenID::Action ? t->action : t->notaction) |= p.bit;
762 }
763 }
764 } else if (w->id == TokenID::Resource || w->id == TokenID::NotResource) {
765 auto a = ARN::parse({s, l}, true);
766 // You can't specify resources for someone ELSE'S account.
767 if (a && (a->account.empty() || a->account == pp->tenant ||
768 a->account == "*")) {
769 if (a->account.empty() || a->account == "*")
770 a->account = pp->tenant;
771 (w->id == TokenID::Resource ? t->resource : t->notresource)
772 .emplace(std::move(*a));
773 }
774 else
775 ldout(cct, 0) << "Supplied resource is discarded: " << string(s, l)
776 << dendl;
777 } else if (w->kind == TokenKind::cond_key) {
778 auto& t = pp->policy.statements.back();
779 t.conditions.back().vals.emplace_back(s, l);
780
781 // Principals
782
783 } else if (w->kind == TokenKind::princ_type) {
784 ceph_assert(pp->s.size() > 1);
785 auto& pri = pp->s[pp->s.size() - 2].w->id == TokenID::Principal ?
786 t->princ : t->noprinc;
787
788 auto o = parse_principal(pp->cct, w->id, string(s, l));
789 if (o)
790 pri.emplace(std::move(*o));
791
792 // Failure
793
794 } else {
795 return false;
796 }
797
798 if (!arraying) {
799 pp->s.pop_back();
800 }
801
802 return true;
803 }
804
805 bool ParseState::number(const char* s, size_t l) {
806 // Top level!
807 if (w->kind == TokenKind::cond_key) {
808 auto& t = pp->policy.statements.back();
809 t.conditions.back().vals.emplace_back(s, l);
810
811 // Failure
812
813 } else {
814 return false;
815 }
816
817 if (!arraying) {
818 pp->s.pop_back();
819 }
820
821 return true;
822 }
823
824 void ParseState::reset() {
825 pp->reset({TokenID::Sid, TokenID::Effect, TokenID::Principal,
826 TokenID::NotPrincipal, TokenID::Action, TokenID::NotAction,
827 TokenID::Resource, TokenID::NotResource, TokenID::Condition});
828 }
829
830 bool ParseState::obj_start() {
831 if (w->objectable && !objecting) {
832 objecting = true;
833 if (w->id == TokenID::Statement) {
834 pp->policy.statements.push_back({});
835 }
836
837 return true;
838 }
839
840 return false;
841 }
842
843
844 bool ParseState::array_end() {
845 if (arraying && !objecting) {
846 pp->s.pop_back();
847 return true;
848 }
849
850 return false;
851 }
852
853 ostream& operator <<(ostream& m, const MaskedIP& ip) {
854 // I have a theory about why std::bitset is the way it is.
855 if (ip.v6) {
856 for (int i = 15; i >= 0; --i) {
857 uint8_t b = 0;
858 for (int j = 7; j >= 0; --j) {
859 b |= (ip.addr[(i * 8) + j] << j);
860 }
861 m << hex << b;
862 if (i != 0) {
863 m << "::";
864 }
865 }
866 } else {
867 // It involves Satan.
868 for (int i = 3; i >= 0; --i) {
869 uint8_t b = 0;
870 for (int j = 7; j >= 0; --j) {
871 b |= (ip.addr[(i * 8) + j] << j);
872 }
873 m << b;
874 if (i != 0) {
875 m << ".";
876 }
877 }
878 }
879 m << "/" << ip.prefix;
880 // It would explain a lot
881 return m;
882 }
883
884 string to_string(const MaskedIP& m) {
885 stringstream ss;
886 ss << m;
887 return ss.str();
888 }
889
890 bool Condition::eval(const Environment& env) const {
891 auto i = env.find(key);
892 if (op == TokenID::Null) {
893 return i == env.end() ? true : false;
894 }
895
896 if (i == env.end()) {
897 return false;
898 }
899 const auto& s = i->second;
900
901 switch (op) {
902 // String!
903 case TokenID::StringEquals:
904 return orrible(std::equal_to<std::string>(), s, vals);
905
906 case TokenID::StringNotEquals:
907 return orrible(std::not2(std::equal_to<std::string>()),
908 s, vals);
909
910 case TokenID::StringEqualsIgnoreCase:
911 return orrible(ci_equal_to(), s, vals);
912
913 case TokenID::StringNotEqualsIgnoreCase:
914 return orrible(std::not2(ci_equal_to()), s, vals);
915
916 // Implement actual StringLike with wildcarding later
917 case TokenID::StringLike:
918 return orrible(std::equal_to<std::string>(), s, vals);
919 case TokenID::StringNotLike:
920 return orrible(std::not2(std::equal_to<std::string>()),
921 s, vals);
922
923 // Numeric
924 case TokenID::NumericEquals:
925 return shortible(std::equal_to<double>(), as_number, s, vals);
926
927 case TokenID::NumericNotEquals:
928 return shortible(std::not2(std::equal_to<double>()),
929 as_number, s, vals);
930
931
932 case TokenID::NumericLessThan:
933 return shortible(std::less<double>(), as_number, s, vals);
934
935
936 case TokenID::NumericLessThanEquals:
937 return shortible(std::less_equal<double>(), as_number, s, vals);
938
939 case TokenID::NumericGreaterThan:
940 return shortible(std::greater<double>(), as_number, s, vals);
941
942 case TokenID::NumericGreaterThanEquals:
943 return shortible(std::greater_equal<double>(), as_number, s, vals);
944
945 // Date!
946 case TokenID::DateEquals:
947 return shortible(std::equal_to<ceph::real_time>(), as_date, s, vals);
948
949 case TokenID::DateNotEquals:
950 return shortible(std::not2(std::equal_to<ceph::real_time>()),
951 as_date, s, vals);
952
953 case TokenID::DateLessThan:
954 return shortible(std::less<ceph::real_time>(), as_date, s, vals);
955
956
957 case TokenID::DateLessThanEquals:
958 return shortible(std::less_equal<ceph::real_time>(), as_date, s, vals);
959
960 case TokenID::DateGreaterThan:
961 return shortible(std::greater<ceph::real_time>(), as_date, s, vals);
962
963 case TokenID::DateGreaterThanEquals:
964 return shortible(std::greater_equal<ceph::real_time>(), as_date, s,
965 vals);
966
967 // Bool!
968 case TokenID::Bool:
969 return shortible(std::equal_to<bool>(), as_bool, s, vals);
970
971 // Binary!
972 case TokenID::BinaryEquals:
973 return shortible(std::equal_to<ceph::bufferlist>(), as_binary, s,
974 vals);
975
976 // IP Address!
977 case TokenID::IpAddress:
978 return shortible(std::equal_to<MaskedIP>(), as_network, s, vals);
979
980 case TokenID::NotIpAddress:
981 return shortible(std::not2(std::equal_to<MaskedIP>()), as_network, s,
982 vals);
983
984 #if 0
985 // Amazon Resource Names! (Does S3 need this?)
986 TokenID::ArnEquals, TokenID::ArnNotEquals, TokenID::ArnLike,
987 TokenID::ArnNotLike,
988 #endif
989
990 default:
991 return false;
992 }
993 }
994
995 optional<MaskedIP> Condition::as_network(const string& s) {
996 MaskedIP m;
997 if (s.empty()) {
998 return none;
999 }
1000
1001 m.v6 = s.find(':');
1002 auto slash = s.find('/');
1003 if (slash == string::npos) {
1004 m.prefix = m.v6 ? 128 : 32;
1005 } else {
1006 char* end = 0;
1007 m.prefix = strtoul(s.data() + slash + 1, &end, 10);
1008 if (*end != 0 || (m.v6 && m.prefix > 128) ||
1009 (!m.v6 && m.prefix > 32)) {
1010 return none;
1011 }
1012 }
1013
1014 string t;
1015 auto p = &s;
1016
1017 if (slash != string::npos) {
1018 t.assign(s, 0, slash);
1019 p = &t;
1020 }
1021
1022 if (m.v6) {
1023 struct sockaddr_in6 a;
1024 if (inet_pton(AF_INET6, p->c_str(), static_cast<void*>(&a)) != 1) {
1025 return none;
1026 }
1027
1028 m.addr |= Address(a.sin6_addr.s6_addr[0]) << 0;
1029 m.addr |= Address(a.sin6_addr.s6_addr[1]) << 8;
1030 m.addr |= Address(a.sin6_addr.s6_addr[2]) << 16;
1031 m.addr |= Address(a.sin6_addr.s6_addr[3]) << 24;
1032 m.addr |= Address(a.sin6_addr.s6_addr[4]) << 32;
1033 m.addr |= Address(a.sin6_addr.s6_addr[5]) << 40;
1034 m.addr |= Address(a.sin6_addr.s6_addr[6]) << 48;
1035 m.addr |= Address(a.sin6_addr.s6_addr[7]) << 56;
1036 m.addr |= Address(a.sin6_addr.s6_addr[8]) << 64;
1037 m.addr |= Address(a.sin6_addr.s6_addr[9]) << 72;
1038 m.addr |= Address(a.sin6_addr.s6_addr[10]) << 80;
1039 m.addr |= Address(a.sin6_addr.s6_addr[11]) << 88;
1040 m.addr |= Address(a.sin6_addr.s6_addr[12]) << 96;
1041 m.addr |= Address(a.sin6_addr.s6_addr[13]) << 104;
1042 m.addr |= Address(a.sin6_addr.s6_addr[14]) << 112;
1043 m.addr |= Address(a.sin6_addr.s6_addr[15]) << 120;
1044 } else {
1045 struct sockaddr_in a;
1046 if (inet_pton(AF_INET, p->c_str(), static_cast<void*>(&a)) != 1) {
1047 return none;
1048 }
1049 m.addr = ntohl(a.sin_addr.s_addr);
1050 }
1051
1052 return none;
1053 }
1054
1055 namespace {
1056 const char* condop_string(const TokenID t) {
1057 switch (t) {
1058 case TokenID::StringEquals:
1059 return "StringEquals";
1060
1061 case TokenID::StringNotEquals:
1062 return "StringNotEquals";
1063
1064 case TokenID::StringEqualsIgnoreCase:
1065 return "StringEqualsIgnoreCase";
1066
1067 case TokenID::StringNotEqualsIgnoreCase:
1068 return "StringNotEqualsIgnoreCase";
1069
1070 case TokenID::StringLike:
1071 return "StringLike";
1072
1073 case TokenID::StringNotLike:
1074 return "StringNotLike";
1075
1076 // Numeric!
1077 case TokenID::NumericEquals:
1078 return "NumericEquals";
1079
1080 case TokenID::NumericNotEquals:
1081 return "NumericNotEquals";
1082
1083 case TokenID::NumericLessThan:
1084 return "NumericLessThan";
1085
1086 case TokenID::NumericLessThanEquals:
1087 return "NumericLessThanEquals";
1088
1089 case TokenID::NumericGreaterThan:
1090 return "NumericGreaterThan";
1091
1092 case TokenID::NumericGreaterThanEquals:
1093 return "NumericGreaterThanEquals";
1094
1095 case TokenID::DateEquals:
1096 return "DateEquals";
1097
1098 case TokenID::DateNotEquals:
1099 return "DateNotEquals";
1100
1101 case TokenID::DateLessThan:
1102 return "DateLessThan";
1103
1104 case TokenID::DateLessThanEquals:
1105 return "DateLessThanEquals";
1106
1107 case TokenID::DateGreaterThan:
1108 return "DateGreaterThan";
1109
1110 case TokenID::DateGreaterThanEquals:
1111 return "DateGreaterThanEquals";
1112
1113 case TokenID::Bool:
1114 return "Bool";
1115
1116 case TokenID::BinaryEquals:
1117 return "BinaryEquals";
1118
1119 case TokenID::IpAddress:
1120 return "case TokenID::IpAddress";
1121
1122 case TokenID::NotIpAddress:
1123 return "NotIpAddress";
1124
1125 case TokenID::ArnEquals:
1126 return "ArnEquals";
1127
1128 case TokenID::ArnNotEquals:
1129 return "ArnNotEquals";
1130
1131 case TokenID::ArnLike:
1132 return "ArnLike";
1133
1134 case TokenID::ArnNotLike:
1135 return "ArnNotLike";
1136
1137 case TokenID::Null:
1138 return "Null";
1139
1140 default:
1141 return "InvalidConditionOperator";
1142 }
1143 }
1144
1145 template<typename Iterator>
1146 ostream& print_array(ostream& m, Iterator begin, Iterator end) {
1147 if (begin == end) {
1148 m << "[";
1149 } else {
1150 auto beforelast = end - 1;
1151 m << "[ ";
1152 for (auto i = begin; i != end; ++i) {
1153 m << *i;
1154 if (i != beforelast) {
1155 m << ", ";
1156 } else {
1157 m << " ";
1158 }
1159 }
1160 }
1161 m << "]";
1162 return m;
1163 }
1164 }
1165
1166 ostream& operator <<(ostream& m, const Condition& c) {
1167 m << "{ " << condop_string(c.op);
1168 if (c.ifexists) {
1169 m << "IfExists";
1170 }
1171 m << ": { " << c.key;
1172 print_array(m, c.vals.cbegin(), c.vals.cend());
1173 return m << "}";
1174 }
1175
1176 string to_string(const Condition& c) {
1177 stringstream ss;
1178 ss << c;
1179 return ss.str();
1180 }
1181
1182 Effect Statement::eval(const Environment& e,
1183 optional<const rgw::auth::Identity&> ida,
1184 uint64_t act, const ARN& res) const {
1185 if (ida && (!ida->is_identity(princ) || ida->is_identity(noprinc))) {
1186 return Effect::Pass;
1187 }
1188
1189
1190 if (!std::any_of(resource.begin(), resource.end(),
1191 [&res](const ARN& pattern) {
1192 return pattern.match(res);
1193 }) ||
1194 (std::any_of(notresource.begin(), notresource.end(),
1195 [&res](const ARN& pattern) {
1196 return pattern.match(res);
1197 }))) {
1198 return Effect::Pass;
1199 }
1200
1201 if (!(action & act) || (notaction & act)) {
1202 return Effect::Pass;
1203 }
1204
1205 if (std::all_of(conditions.begin(),
1206 conditions.end(),
1207 [&e](const Condition& c) { return c.eval(e);})) {
1208 return effect;
1209 }
1210
1211 return Effect::Pass;
1212 }
1213
1214 namespace {
1215 const char* action_bit_string(uint64_t action) {
1216 switch (action) {
1217 case s3GetObject:
1218 return "s3:GetObject";
1219
1220 case s3GetObjectVersion:
1221 return "s3:GetObjectVersion";
1222
1223 case s3PutObject:
1224 return "s3:PutObject";
1225
1226 case s3GetObjectAcl:
1227 return "s3:GetObjectAcl";
1228
1229 case s3GetObjectVersionAcl:
1230 return "s3:GetObjectVersionAcl";
1231
1232 case s3PutObjectAcl:
1233 return "s3:PutObjectAcl";
1234
1235 case s3PutObjectVersionAcl:
1236 return "s3:PutObjectVersionAcl";
1237
1238 case s3DeleteObject:
1239 return "s3:DeleteObject";
1240
1241 case s3DeleteObjectVersion:
1242 return "s3:DeleteObjectVersion";
1243
1244 case s3ListMultipartUploadParts:
1245 return "s3:ListMultipartUploadParts";
1246
1247 case s3AbortMultipartUpload:
1248 return "s3:AbortMultipartUpload";
1249
1250 case s3GetObjectTorrent:
1251 return "s3:GetObjectTorrent";
1252
1253 case s3GetObjectVersionTorrent:
1254 return "s3:GetObjectVersionTorrent";
1255
1256 case s3RestoreObject:
1257 return "s3:RestoreObject";
1258
1259 case s3CreateBucket:
1260 return "s3:CreateBucket";
1261
1262 case s3DeleteBucket:
1263 return "s3:DeleteBucket";
1264
1265 case s3ListBucket:
1266 return "s3:ListBucket";
1267
1268 case s3ListBucketVersions:
1269 return "s3:ListBucketVersions";
1270 case s3ListAllMyBuckets:
1271 return "s3:ListAllMyBuckets";
1272
1273 case s3ListBucketMultiPartUploads:
1274 return "s3:ListBucketMultiPartUploads";
1275
1276 case s3GetAccelerateConfiguration:
1277 return "s3:GetAccelerateConfiguration";
1278
1279 case s3PutAccelerateConfiguration:
1280 return "s3:PutAccelerateConfiguration";
1281
1282 case s3GetBucketAcl:
1283 return "s3:GetBucketAcl";
1284
1285 case s3PutBucketAcl:
1286 return "s3:PutBucketAcl";
1287
1288 case s3GetBucketCORS:
1289 return "s3:GetBucketCORS";
1290
1291 case s3PutBucketCORS:
1292 return "s3:PutBucketCORS";
1293
1294 case s3GetBucketVersioning:
1295 return "s3:GetBucketVersioning";
1296
1297 case s3PutBucketVersioning:
1298 return "s3:PutBucketVersioning";
1299
1300 case s3GetBucketRequestPayment:
1301 return "s3:GetBucketRequestPayment";
1302
1303 case s3PutBucketRequestPayment:
1304 return "s3:PutBucketRequestPayment";
1305
1306 case s3GetBucketLocation:
1307 return "s3:GetBucketLocation";
1308
1309 case s3GetBucketPolicy:
1310 return "s3:GetBucketPolicy";
1311
1312 case s3DeleteBucketPolicy:
1313 return "s3:DeleteBucketPolicy";
1314
1315 case s3PutBucketPolicy:
1316 return "s3:PutBucketPolicy";
1317
1318 case s3GetBucketNotification:
1319 return "s3:GetBucketNotification";
1320
1321 case s3PutBucketNotification:
1322 return "s3:PutBucketNotification";
1323
1324 case s3GetBucketLogging:
1325 return "s3:GetBucketLogging";
1326
1327 case s3PutBucketLogging:
1328 return "s3:PutBucketLogging";
1329
1330 case s3GetBucketTagging:
1331 return "s3:GetBucketTagging";
1332
1333 case s3PutBucketTagging:
1334 return "s3:PutBucketTagging";
1335
1336 case s3GetBucketWebsite:
1337 return "s3:GetBucketWebsite";
1338
1339 case s3PutBucketWebsite:
1340 return "s3:PutBucketWebsite";
1341
1342 case s3DeleteBucketWebsite:
1343 return "s3:DeleteBucketWebsite";
1344
1345 case s3GetLifecycleConfiguration:
1346 return "s3:GetLifecycleConfiguration";
1347
1348 case s3PutLifecycleConfiguration:
1349 return "s3:PutLifecycleConfiguration";
1350
1351 case s3PutReplicationConfiguration:
1352 return "s3:PutReplicationConfiguration";
1353
1354 case s3GetReplicationConfiguration:
1355 return "s3:GetReplicationConfiguration";
1356
1357 case s3DeleteReplicationConfiguration:
1358 return "s3:DeleteReplicationConfiguration";
1359 }
1360 return "s3Invalid";
1361 }
1362
1363 ostream& print_actions(ostream& m, const uint64_t a) {
1364 bool begun = false;
1365 m << "[ ";
1366 for (auto i = 0U; i < s3Count; ++i) {
1367 if (a & (1 << i)) {
1368 if (begun) {
1369 m << ", ";
1370 } else {
1371 begun = true;
1372 }
1373 m << action_bit_string(1 << i);
1374 }
1375 }
1376 if (begun) {
1377 m << " ]";
1378 } else {
1379 m << "]";
1380 }
1381 return m;
1382 }
1383 }
1384
1385 ostream& operator <<(ostream& m, const Statement& s) {
1386 m << "{ ";
1387 if (s.sid) {
1388 m << "Sid: " << *s.sid << ", ";
1389 }
1390 if (!s.princ.empty()) {
1391 m << "Principal: ";
1392 print_array(m, s.princ.cbegin(), s.princ.cend());
1393 m << ", ";
1394 }
1395 if (!s.noprinc.empty()) {
1396 m << "NotPrincipal: ";
1397 print_array(m, s.noprinc.cbegin(), s.noprinc.cend());
1398 m << ", ";
1399 }
1400
1401 m << "Effect: " <<
1402 (s.effect == Effect::Allow ?
1403 (const char*) "Allow" :
1404 (const char*) "Deny");
1405
1406 if (s.action || s.notaction || !s.resource.empty() ||
1407 !s.notresource.empty() || !s.conditions.empty()) {
1408 m << ", ";
1409 }
1410
1411 if (s.action) {
1412 m << "Action: ";
1413 print_actions(m, s.action);
1414
1415 if (s.notaction || !s.resource.empty() ||
1416 !s.notresource.empty() || !s.conditions.empty()) {
1417 m << ", ";
1418 }
1419 }
1420
1421 if (s.notaction) {
1422 m << "NotAction: ";
1423 print_actions(m, s.notaction);
1424
1425 if (!s.resource.empty() || !s.notresource.empty() ||
1426 !s.conditions.empty()) {
1427 m << ", ";
1428 }
1429 }
1430
1431 if (!s.resource.empty()) {
1432 m << "Resource: ";
1433 print_array(m, s.resource.cbegin(), s.resource.cend());
1434
1435 if (!s.notresource.empty() || !s.conditions.empty()) {
1436 m << ", ";
1437 }
1438 }
1439
1440 if (!s.notresource.empty()) {
1441 m << "NotResource: ";
1442 print_array(m, s.notresource.cbegin(), s.notresource.cend());
1443
1444 if (!s.conditions.empty()) {
1445 m << ", ";
1446 }
1447 }
1448
1449 if (!s.conditions.empty()) {
1450 m << "Condition: ";
1451 print_array(m, s.conditions.cbegin(), s.conditions.cend());
1452 }
1453
1454 return m << " }";
1455 }
1456
1457 string to_string(const Statement& s) {
1458 stringstream m;
1459 m << s;
1460 return m.str();
1461 }
1462
1463 Policy::Policy(CephContext* cct, const string& tenant,
1464 const bufferlist& _text)
1465 : text(_text.to_str()) {
1466 StringStream ss(text.data());
1467 PolicyParser pp(cct, tenant, *this);
1468 auto pr = Reader{}.Parse<kParseNumbersAsStringsFlag |
1469 kParseCommentsFlag>(ss, pp);
1470 if (!pr) {
1471 throw PolicyParseException(std::move(pr));
1472 }
1473 }
1474
1475 Effect Policy::eval(const Environment& e,
1476 optional<const rgw::auth::Identity&> ida,
1477 std::uint64_t action, const ARN& resource) const {
1478 auto allowed = false;
1479 for (auto& s : statements) {
1480 auto g = s.eval(e, ida, action, resource);
1481 if (g == Effect::Deny) {
1482 return g;
1483 } else if (g == Effect::Allow) {
1484 allowed = true;
1485 }
1486 }
1487 return allowed ? Effect::Allow : Effect::Pass;
1488 }
1489
1490 ostream& operator <<(ostream& m, const Policy& p) {
1491 m << "{ Version: "
1492 << (p.version == Version::v2008_10_17 ? "2008-10-17" : "2012-10-17");
1493
1494 if (p.id || !p.statements.empty()) {
1495 m << ", ";
1496 }
1497
1498 if (p.id) {
1499 m << "Id: " << *p.id;
1500 if (!p.statements.empty()) {
1501 m << ", ";
1502 }
1503 }
1504
1505 if (!p.statements.empty()) {
1506 m << "Statements: ";
1507 print_array(m, p.statements.cbegin(), p.statements.cend());
1508 m << ", ";
1509 }
1510 return m << " }";
1511 }
1512
1513 string to_string(const Policy& p) {
1514 stringstream s;
1515 s << p;
1516 return s.str();
1517 }
1518
1519 }
1520 }