1use {
2 crate::{display_json, from_str_json, AspenError, Context, Decision, StatementList},
3 derive_builder::Builder,
4 serde::{
5 de,
6 de::{Deserializer, MapAccess, Visitor},
7 ser::{SerializeMap, Serializer},
8 Deserialize, Serialize,
9 },
10 std::{
11 fmt::{Display, Formatter, Result as FmtResult},
12 str::FromStr,
13 },
14};
15
16#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
18pub enum PolicyVersion {
19 None,
22
23 V2008_10_17,
25
26 V2012_10_17,
28}
29
30impl PolicyVersion {
31 #[inline]
33 pub fn is_none(&self) -> bool {
34 matches!(self, Self::None)
35 }
36
37 #[inline]
39 pub fn is_some(&self) -> bool {
40 !self.is_none()
41 }
42}
43
44impl Default for PolicyVersion {
45 fn default() -> Self {
46 Self::None
47 }
48}
49
50impl Display for PolicyVersion {
51 fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
52 match self {
53 Self::None => Ok(()),
54 Self::V2008_10_17 => f.write_str("2008-10-17"),
55 Self::V2012_10_17 => f.write_str("2012-10-17"),
56 }
57 }
58}
59
60impl<'de> Deserialize<'de> for PolicyVersion {
61 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
62 let value = String::deserialize(deserializer)?;
63 match PolicyVersion::from_str(&value) {
64 Ok(v) => Ok(v),
65 Err(e) => Err(serde::de::Error::custom(e)),
66 }
67 }
68}
69
70impl FromStr for PolicyVersion {
71 type Err = AspenError;
72
73 fn from_str(s: &str) -> Result<Self, Self::Err> {
74 match s {
75 "2008-10-17" => Ok(Self::V2008_10_17),
76 "2012-10-17" => Ok(Self::V2012_10_17),
77 _ => Err(AspenError::InvalidPolicyVersion(s.to_string())),
78 }
79 }
80}
81
82impl Serialize for PolicyVersion {
83 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
84 serializer.serialize_str(self.to_string().as_str())
85 }
86}
87
88#[derive(Builder, Clone, Debug, Eq, PartialEq)]
95pub struct Policy {
96 #[builder(setter(into, strip_option), default)]
100 version: PolicyVersion,
101
102 #[builder(setter(into, strip_option), default)]
104 id: Option<String>,
105
106 #[builder(setter(into))]
109 statement: StatementList,
110}
111
112impl Policy {
113 #[inline]
114 pub fn builder() -> PolicyBuilder {
116 PolicyBuilder::default()
117 }
118
119 pub fn version(&self) -> PolicyVersion {
121 self.version
122 }
123
124 #[inline]
126 pub fn id(&self) -> Option<&str> {
127 self.id.as_deref()
128 }
129
130 #[inline]
132 pub fn statement(&self) -> &StatementList {
133 &self.statement
134 }
135
136 pub fn evaluate(&self, context: &Context) -> Result<Decision, crate::AspenError> {
157 for statement in self.statement.iter() {
158 match statement.evaluate(context, self.version()) {
159 Ok(Decision::Allow) => return Ok(Decision::Allow),
160 Ok(Decision::Deny) => return Ok(Decision::Deny),
161 Ok(Decision::DefaultDeny) => (),
162 Err(err) => return Err(err),
163 }
164 }
165 Ok(Decision::DefaultDeny)
166 }
167}
168
169display_json!(Policy);
170from_str_json!(Policy);
171
172impl<'de> Visitor<'de> for PolicyBuilder {
173 type Value = Policy;
174
175 fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
176 formatter.write_str("policy")
177 }
178
179 fn visit_map<A: MapAccess<'de>>(mut self, mut access: A) -> Result<Self::Value, A::Error> {
180 let builder = &mut self;
181 let mut version_seen = false;
182 let mut id_seen = false;
183 let mut statement_seen = false;
184
185 while let Some(key) = access.next_key()? {
186 match key {
187 "Version" => {
188 if version_seen {
189 return Err(de::Error::duplicate_field("Version"));
190 }
191 version_seen = true;
192 builder.version(access.next_value::<PolicyVersion>()?);
193 }
194 "Id" => {
195 if id_seen {
196 return Err(de::Error::duplicate_field("Id"));
197 }
198 id_seen = true;
199 builder.id(access.next_value::<String>()?);
200 }
201 "Statement" => {
202 if statement_seen {
203 return Err(de::Error::duplicate_field("Statement"));
204 }
205 statement_seen = true;
206 builder.statement(access.next_value::<StatementList>()?);
207 }
208 _ => return Err(de::Error::unknown_field(key, &["Version", "Id", "Statement"])),
209 }
210 }
211
212 if !statement_seen {
213 return Err(de::Error::missing_field("Statement"));
214 }
215
216 self.build().map_err(de::Error::custom)
217 }
218}
219
220impl<'de> Deserialize<'de> for Policy {
221 fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Policy, D::Error> {
222 d.deserialize_map(PolicyBuilder::default())
223 }
224}
225
226impl Serialize for Policy {
227 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
228 let mut state = serializer.serialize_map(None)?;
229 if self.version.is_some() {
230 state.serialize_entry("Version", &self.version)?;
231 }
232 if let Some(id) = &self.id {
233 state.serialize_entry("Id", id)?;
234 }
235 state.serialize_entry("Statement", &self.statement)?;
236 state.end()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use {
243 crate::{
244 serutil::JsonRep, Action, AspenError, AwsPrincipal, Context, Decision, Effect, Policy, PolicyBuilderError,
245 PolicyVersion, Principal, Resource, SpecifiedPrincipal, Statement,
246 },
247 indoc::indoc,
248 pretty_assertions::{assert_eq, assert_ne},
249 scratchstack_arn::Arn,
250 scratchstack_aws_principal::{Principal as PrincipalActor, Service, SessionData, SessionValue, User},
251 std::{
252 collections::hash_map::DefaultHasher,
253 hash::{Hash, Hasher},
254 str::FromStr,
255 },
256 };
257
258 #[test_log::test]
259 fn test_typical_policy_import() {
260 let policy_str = indoc! { r#"
261 {
262 "Version": "2012-10-17",
263 "Id": "PolicyId",
264 "Statement": [
265 {
266 "Sid": "1",
267 "Effect": "Allow",
268 "Action": [
269 "ec2:Get*",
270 "ecs:*"
271 ],
272 "Resource": "*",
273 "Principal": {
274 "AWS": "123456789012"
275 },
276 "Condition": {
277 "StringEquals": {
278 "ec2:Region": [
279 "us-west-2",
280 "us-west-1",
281 "us-east-2",
282 "us-east-1"
283 ]
284 }
285 }
286 },
287 {
288 "Sid": "2",
289 "Effect": "Deny",
290 "Action": "*",
291 "Resource": [
292 "arn:aws:s3:::my-bucket",
293 "arn:aws:s3:::my-bucket/*"
294 ],
295 "Principal": "*"
296 }
297 ]
298 }"# };
299 let policy = Policy::from_str(policy_str).unwrap();
300
301 assert_eq!(policy.version(), PolicyVersion::V2012_10_17);
302 assert_eq!(policy.id(), Some("PolicyId"));
303
304 assert_eq!(policy.statement().len(), 2);
305 let s = &policy.statement()[0];
306 assert_eq!(*s.effect(), Effect::Allow);
307 match &s.action() {
308 None => panic!("Expected a list of actions"),
309 Some(a_list) => {
310 assert_eq!(a_list.kind(), JsonRep::List);
311 assert_eq!(a_list[0].specific(), Some(("ec2", "Get*")));
312 assert_eq!(a_list[1].specific(), Some(("ecs", "*")));
313 }
314 }
315 assert!(s.condition().is_some());
316 let c = s.condition().unwrap();
317 let se = c.get("StringEquals");
318 assert!(se.is_some());
319
320 let new_policy_str = policy.to_string();
321 assert_eq!(new_policy_str, policy_str);
322 }
323
324 #[test_log::test]
325 fn test_bad_condition_variable() {
326 let policy_str = indoc! { r#"
327 {
328 "Version": "2012-10-17",
329 "Statement": {
330 "Effect": "Allow",
331 "Action": "*",
332 "Resource": "*",
333 "Condition": {
334 "StringEquals": {
335 "aws:username": "${"
336 }
337 }
338 }
339 }"# };
340
341 let policy = Policy::from_str(policy_str).unwrap();
342 let actor = PrincipalActor::from(User::new("aws", "123456789012", "/", "MyUser").unwrap());
343 let mut sd = SessionData::new();
344 sd.insert("aws:username", SessionValue::from("MyUser"));
345 let context = Context::builder()
346 .api("DescribeSecurityGroups")
347 .actor(actor)
348 .session_data(sd)
349 .service("ec2")
350 .build()
351 .unwrap();
352
353 assert_eq!(policy.evaluate(&context).unwrap_err().to_string(), "Invalid variable substitution: ${");
354 }
355
356 #[test_log::test]
357 fn test_bad_field_types() {
358 let policy_str = indoc! { r#"
359 {
360 "Version": "2012-10-17",
361 "Id": "PolicyId",
362 "Statement": "Deny"
363 }
364 }"# };
365 let e = Policy::from_str(policy_str).unwrap_err();
366 assert_eq!(
367 e.to_string(),
368 r#"invalid type: string "Deny", expected Statement or list of Statement at line 4 column 23"#
369 );
370
371 let policy_str = indoc! { r#"
372 {
373 "Version": "2012-10-17",
374 "Id": "PolicyId",
375 "Statement": {
376 3: "Deny"
377 }
378 }"# };
379 let e = Policy::from_str(policy_str).unwrap_err();
380 assert_eq!(e.to_string(), r#"key must be a string at line 5 column 9"#);
381
382 let policy_str = indoc! { r#"
383 {
384 "Version": "2012-10-17",
385 "Id": "PolicyId",
386 "Statement": {
387 "Sid": 1,
388 "Effect": "Allow",
389 "Action": [
390 "ec2:Get*",
391 "ecs:*"
392 ],
393 "Resource": "*",
394 "Principal": {
395 "AWS": "123456789012"
396 },
397 "Condition": {
398 "StringEquals": {
399 "ec2:Region": [
400 "us-west-2"
401 ]
402 }
403 }
404 }
405 }"# };
406 let e = Policy::from_str(policy_str).unwrap_err();
407 assert_eq!(e.to_string(), "invalid type: integer `1`, expected a borrowed string at line 5 column 16");
408
409 let policy_str = indoc! { r#"
410 {
411 "Version": "2012-10-17",
412 "Id": "PolicyId",
413 "Statement": {
414 "Sid": "1",
415 "Effect": ["Allow"],
416 "Action": [
417 "ec2:Get*",
418 "ecs:*"
419 ],
420 "Resource": "*",
421 "Principal": {
422 "AWS": "123456789012"
423 },
424 "Condition": {
425 "StringEquals": {
426 "ec2:Region": [
427 "us-west-2"
428 ]
429 }
430 }
431 }
432 }"# };
433 let e = Policy::from_str(policy_str).unwrap_err();
434 assert_eq!(e.to_string(), "expected value at line 6 column 19");
435
436 let policy_str = indoc! { r#"
437 {
438 "Version": "2012-10-17",
439 "Id": "PolicyId",
440 "Statement": {
441 "Sid": "1",
442 "Effect": "Allow",
443 "Action": {
444 "ec2": "RunInstances"
445 },
446 "Resource": "*",
447 "Principal": {
448 "AWS": "123456789012"
449 },
450 "Condition": {
451 "StringEquals": {
452 "ec2:Region": [
453 "us-west-2"
454 ]
455 }
456 }
457 }
458 }"# };
459 let e = Policy::from_str(policy_str).unwrap_err();
460 assert_eq!(e.to_string(), "invalid type: map, expected Action or list of Action at line 8 column 12");
461
462 let policy_str = indoc! { r#"
463 {
464 "Version": "2012-10-17",
465 "Id": "PolicyId",
466 "Statement": {
467 "Sid": "1",
468 "Effect": "Allow",
469 "NotAction": {
470 "ec2": "RunInstances"
471 },
472 "Resource": "*",
473 "Principal": {
474 "AWS": "123456789012"
475 },
476 "Condition": {
477 "StringEquals": {
478 "ec2:Region": [
479 "us-west-2"
480 ]
481 }
482 }
483 }
484 }"# };
485 let e = Policy::from_str(policy_str).unwrap_err();
486 assert_eq!(e.to_string(), "invalid type: map, expected Action or list of Action at line 8 column 12");
487
488 let policy_str = indoc! { r#"
489 {
490 "Version": "2012-10-17",
491 "Id": "PolicyId",
492 "Statement": {
493 "Sid": "1",
494 "Effect": "Allow",
495 "Action": "ec2:RunInstances",
496 "Resource": {"ec2": "Instance"},
497 "Principal": {
498 "AWS": "123456789012"
499 },
500 "Condition": {
501 "StringEquals": {
502 "ec2:Region": [
503 "us-west-2"
504 ]
505 }
506 }
507 }
508 }"# };
509 let e = Policy::from_str(policy_str).unwrap_err();
510 assert_eq!(e.to_string(), "invalid type: map, expected Resource or list of Resource at line 8 column 21");
511
512 let policy_str = indoc! { r#"
513 {
514 "Version": "2012-10-17",
515 "Id": "PolicyId",
516 "Statement": {
517 "Sid": "1",
518 "Effect": "Allow",
519 "Action": "ec2:RunInstances",
520 "NotResource": {"ec2": "Instance"},
521 "Principal": {
522 "AWS": "123456789012"
523 },
524 "Condition": {
525 "StringEquals": {
526 "ec2:Region": [
527 "us-west-2"
528 ]
529 }
530 }
531 }
532 }"# };
533 let e = Policy::from_str(policy_str).unwrap_err();
534 assert_eq!(e.to_string(), "invalid type: map, expected Resource or list of Resource at line 8 column 24");
535
536 let policy_str = indoc! { r#"
537 {
538 "Version": "2012-10-17",
539 "Id": "PolicyId",
540 "Statement": {
541 "Sid": "1",
542 "Effect": "Allow",
543 "Action": "ec2:RunInstances",
544 "Resource": "*",
545 "Principal": "123456789012",
546 "Condition": {
547 "StringEquals": {
548 "ec2:Region": [
549 "us-west-2"
550 ]
551 }
552 }
553 }
554 }"# };
555 let e = Policy::from_str(policy_str).unwrap_err();
556 assert_eq!(
557 e.to_string(),
558 r#"invalid value: string "123456789012", expected map of principal types to values or "*" at line 9 column 35"#
559 );
560
561 let policy_str = indoc! { r#"
562 {
563 "Version": "2012-10-17",
564 "Id": "PolicyId",
565 "Statement": {
566 "Sid": "1",
567 "Effect": "Allow",
568 "Action": "ec2:RunInstances",
569 "Resource": "*",
570 "NotPrincipal": "123456789012",
571 "Condition": {
572 "StringEquals": {
573 "ec2:Region": [
574 "us-west-2"
575 ]
576 }
577 }
578 }
579 }"# };
580 let e = Policy::from_str(policy_str).unwrap_err();
581 assert_eq!(
582 e.to_string(),
583 r#"invalid value: string "123456789012", expected map of principal types to values or "*" at line 9 column 38"#
584 );
585
586 let policy_str = indoc! { r#"
587 {
588 "Version": "2012-10-17",
589 "Id": "PolicyId",
590 "Statement": {
591 "Sid": "1",
592 "Effect": "Allow",
593 "Action": "ec2:RunInstances",
594 "Resource": "*",
595 "Principal": {"AWS": "123456789012"},
596 "Condition": {
597 "Foo": {
598 "ec2:Region": [
599 "us-west-2"
600 ]
601 }
602 }
603 }
604 }"# };
605 let e = Policy::from_str(policy_str).unwrap_err();
606 assert_eq!(e.to_string(), r#"Invalid condition operator: Foo at line 11 column 17"#);
607
608 let policy_str = indoc! { r#"
609 {
610 "Version": "2012-10-17",
611 "Id": "PolicyId",
612 "Statement": {
613 "Sid": "1",
614 "Effect": "Allow",
615 "Action": "ec2:RunInstances",
616 "Resource": "*",
617 "Principal": {"AWS": "123456789012"},
618 "Condition": {
619 ["1"]: {
620 "ec2:Region": "us-west-2"
621 }
622 }
623 }
624 }"# };
625 let e = Policy::from_str(policy_str).unwrap_err();
626 assert_eq!(e.to_string(), r#"key must be a string at line 11 column 13"#);
627 }
628
629 #[test_log::test]
630 fn test_bad_from_str() {
631 let e = Policy::from_str("{}").unwrap_err();
632 assert_eq!(e.to_string(), "missing field `Statement` at line 1 column 2");
633 }
634
635 #[test_log::test]
636 fn test_bad_types() {
637 let e = Policy::from_str("3").unwrap_err();
638 assert_eq!(e.to_string(), "invalid type: integer `3`, expected policy at line 1 column 1");
639
640 let e = Policy::from_str(r#"[1, 2]"#).unwrap_err();
641 assert_eq!(e.to_string(), "invalid type: sequence, expected policy at line 1 column 0");
642
643 let e = Policy::from_str(r#"{1: 1}"#).unwrap_err();
644 assert_eq!(e.to_string(), "key must be a string at line 1 column 2");
645 }
646
647 #[test_log::test]
648 #[allow(clippy::redundant_clone)]
649 fn test_builder() {
650 let e = Policy::builder().clone().build().unwrap_err();
651 assert_eq!(e.to_string(), "`statement` must be initialized");
652 assert_eq!(format!("{e}"), "`statement` must be initialized");
653 assert_eq!(format!("{e:?}"), r#"UninitializedField("statement")"#);
654 assert_eq!(format!("{}", PolicyBuilderError::from("Oops".to_string())), "Oops");
655
656 let s = Statement::builder()
657 .effect(Effect::Allow)
658 .action(Action::from_str("ec2:RunInstances").unwrap())
659 .resource(Resource::from_str("arn:aws:ec2:us-east-1:123456789012:instance/i-01234567890abcdef").unwrap())
660 .principal(
661 SpecifiedPrincipal::builder().aws(AwsPrincipal::from_str("123456789012").unwrap()).build().unwrap(),
662 )
663 .build()
664 .unwrap();
665 let p1a = Policy::builder().statement(s.clone()).build().unwrap();
666 let p1b = Policy::builder().statement(s.clone()).build().unwrap();
667 let p2 = Policy::builder().version(PolicyVersion::V2012_10_17).id("test").statement(s).build().unwrap();
668
669 assert_eq!(p1a, p1b);
670 assert_eq!(p1a, p1a.clone());
671 assert_ne!(p1a, p2);
672
673 let _ = format!("{p1a:?}");
674 let json = format!("{p2}");
675
676 assert_eq!(
677 json,
678 indoc! {r#"
679 {
680 "Version": "2012-10-17",
681 "Id": "test",
682 "Statement": {
683 "Effect": "Allow",
684 "Action": "ec2:RunInstances",
685 "Resource": "arn:aws:ec2:us-east-1:123456789012:instance/i-01234567890abcdef",
686 "Principal": {
687 "AWS": "123456789012"
688 }
689 }
690 }"#}
691 );
692
693 let s = Statement::builder()
694 .effect(Effect::Allow)
695 .action(Action::from_str("ec2:RunInstances").unwrap())
696 .resource(Resource::from_str("arn:aws:ec2:us-east-1:123456789012:instance/i-01234567890abcdef").unwrap())
697 .principal(
698 SpecifiedPrincipal::builder().aws(AwsPrincipal::from_str("123456789012").unwrap()).build().unwrap(),
699 )
700 .build()
701 .unwrap();
702 let p1a = Policy::builder().statement(s.clone()).build().unwrap();
703 let p1b = Policy::builder().statement(s.clone()).build().unwrap();
704 let p2 = Policy::builder().version(PolicyVersion::None).id("test").statement(s).build().unwrap();
705
706 assert_eq!(p1a, p1b);
707 assert_eq!(p1a, p1a.clone());
708 assert_ne!(p1a, p2);
709
710 let _ = format!("{p1a:?}");
711 let json = format!("{p2}");
712
713 assert_eq!(
714 json,
715 indoc! {r#"
716 {
717 "Id": "test",
718 "Statement": {
719 "Effect": "Allow",
720 "Action": "ec2:RunInstances",
721 "Resource": "arn:aws:ec2:us-east-1:123456789012:instance/i-01234567890abcdef",
722 "Principal": {
723 "AWS": "123456789012"
724 }
725 }
726 }"#}
727 );
728 }
729
730 #[test_log::test]
731 fn test_conflicting_blocks() {
732 let policy_str = indoc! { r#"
733 {
734 "Version": "2012-10-17",
735 "Id": "PolicyId",
736 "Statement": {
737 "Sid": "1",
738 "Effect": "Allow",
739 "Action": [
740 "ec2:Get*",
741 "ecs:*"
742 ],
743 "NotAction": [
744 "rds:*"
745 ],
746 "Resource": "*",
747 "Principal": {
748 "AWS": "123456789012"
749 },
750 "Condition": {
751 "StringEquals": {
752 "ec2:Region": [
753 "us-west-2"
754 ]
755 }
756 }
757 }
758 }"# };
759 let e = Policy::from_str(policy_str).unwrap_err();
760 assert_eq!(e.to_string(), "Action and NotAction cannot both be set at line 25 column 5");
761
762 let policy_str = indoc! { r#"
763 {
764 "Version": "2012-10-17",
765 "Id": "PolicyId",
766 "Statement": {
767 "Sid": "1",
768 "Effect": "Allow",
769 "Action": [
770 "ec2:Get*",
771 "ecs:*"
772 ],
773 "Resource": "*",
774 "NotResource": "*",
775 "Principal": {
776 "AWS": "123456789012"
777 },
778 "Condition": {
779 "StringEquals": {
780 "ec2:Region": [
781 "us-west-2"
782 ]
783 }
784 }
785 }
786 }"# };
787 let e = Policy::from_str(policy_str).unwrap_err();
788 assert_eq!(e.to_string(), "Resource and NotResource cannot both be set at line 23 column 5");
789
790 let policy_str = indoc! { r#"
791 {
792 "Version": "2012-10-17",
793 "Id": "PolicyId",
794 "Statement": {
795 "Sid": "1",
796 "Effect": "Allow",
797 "Action": [
798 "ec2:Get*",
799 "ecs:*"
800 ],
801 "Resource": "*",
802 "Principal": {
803 "AWS": "123456789012"
804 },
805 "NotPrincipal": {
806 "CanonicalUser": "abcd"
807 },
808 "Condition": {
809 "StringEquals": {
810 "ec2:Region": [
811 "us-west-2"
812 ]
813 }
814 }
815 }
816 }"# };
817 let e = Policy::from_str(policy_str).unwrap_err();
818 assert_eq!(e.to_string(), "Principal and NotPrincipal cannot both be set at line 25 column 5");
819
820 let policy_str = indoc! { r#"
821 {
822 "Version": "2012-10-17",
823 "Id": "PolicyId",
824 "Statement": {
825 "Sid": "1",
826 "Effect": "Allow",
827 "Action": [
828 "ec2:Get*",
829 "ecs:*"
830 ],
831 "Resource": "*",
832 "NotResource": [
833 "arn:aws:s3:::my-bucket"
834 ],
835 "Principal": {
836 "AWS": "123456789012"
837 },
838 "Condition": {
839 "StringEquals": {
840 "ec2:Region": [
841 "us-west-2"
842 ]
843 }
844 }
845 }
846 }"# };
847 let e = Policy::from_str(policy_str).unwrap_err();
848 assert_eq!(e.to_string(), "Resource and NotResource cannot both be set at line 25 column 5");
849
850 let policy_str = indoc! { r#"
851 {
852 "Version": "2012-10-17",
853 "Id": "PolicyId",
854 "Statement": {
855 "Sid": "1",
856 "Sid": "2",
857 "Effect": "Allow",
858 "Action": [
859 "ec2:Get*",
860 "ecs:*"
861 ],
862 "Resource": "*",
863 "Principal": {
864 "AWS": "123456789012"
865 },
866 "Condition": {
867 "StringEquals": {
868 "ec2:Region": [
869 "us-west-2"
870 ]
871 }
872 }
873 }
874 }"# };
875 let e = Policy::from_str(policy_str).unwrap_err();
876 assert_eq!(e.to_string(), "duplicate field `Sid` at line 6 column 13");
877
878 let policy_str = indoc! { r#"
879 {
880 "Version": "2012-10-17",
881 "Id": "PolicyId",
882 "Statement": {
883 "Sid": "1",
884 "Effect": "Allow",
885 "Effect": "Deny",
886 "Action": [
887 "ec2:Get*",
888 "ecs:*"
889 ],
890 "Resource": "*",
891 "Principal": {
892 "AWS": "123456789012"
893 },
894 "Condition": {
895 "StringEquals": {
896 "ec2:Region": [
897 "us-west-2"
898 ]
899 }
900 }
901 }
902 }"# };
903 let e = Policy::from_str(policy_str).unwrap_err();
904 assert_eq!(e.to_string(), "duplicate field `Effect` at line 7 column 16");
905
906 let policy_str = indoc! { r#"
907 {
908 "Version": "2012-10-17",
909 "Id": "PolicyId",
910 "Statement": {
911 "Sid": "1",
912 "Effect": "Allow",
913 "Action": [
914 "ec2:Get*",
915 "ecs:*"
916 ],
917 "Action": [
918 "rds:*"
919 ],
920 "Resource": "*",
921 "Principal": {
922 "AWS": "123456789012"
923 },
924 "Condition": {
925 "StringEquals": {
926 "ec2:Region": [
927 "us-west-2"
928 ]
929 }
930 }
931 }
932 }"# };
933 let e = Policy::from_str(policy_str).unwrap_err();
934 assert_eq!(e.to_string(), "duplicate field `Action` at line 11 column 16");
935
936 let policy_str = indoc! { r#"
937 {
938 "Version": "2012-10-17",
939 "Id": "PolicyId",
940 "Statement": {
941 "Sid": "1",
942 "Effect": "Allow",
943 "NotAction": [
944 "ec2:Get*",
945 "ecs:*"
946 ],
947 "NotAction": [
948 "rds:*"
949 ],
950 "Resource": "*",
951 "Principal": {
952 "AWS": "123456789012"
953 },
954 "Condition": {
955 "StringEquals": {
956 "ec2:Region": [
957 "us-west-2"
958 ]
959 }
960 }
961 }
962 }"# };
963 let e = Policy::from_str(policy_str).unwrap_err();
964 assert_eq!(e.to_string(), "duplicate field `NotAction` at line 11 column 19");
965
966 let policy_str = indoc! { r#"
967 {
968 "Version": "2012-10-17",
969 "Id": "PolicyId",
970 "Statement": {
971 "Sid": "1",
972 "Effect": "Allow",
973 "Action": [
974 "ec2:Get*",
975 "ecs:*"
976 ],
977 "Resource": "*",
978 "Resource": [
979 "arn:aws:s3:::my-bucket"
980 ],
981 "Principal": {
982 "AWS": "123456789012"
983 },
984 "Condition": {
985 "StringEquals": {
986 "ec2:Region": [
987 "us-west-2"
988 ]
989 }
990 }
991 }
992 }"# };
993 let e = Policy::from_str(policy_str).unwrap_err();
994 assert_eq!(e.to_string(), "duplicate field `Resource` at line 12 column 18");
995
996 let policy_str = indoc! { r#"
997 {
998 "Version": "2012-10-17",
999 "Id": "PolicyId",
1000 "Statement": {
1001 "Sid": "1",
1002 "Effect": "Allow",
1003 "Action": [
1004 "ec2:Get*",
1005 "ecs:*"
1006 ],
1007 "NotResource": "*",
1008 "NotResource": [
1009 "arn:aws:s3:::my-bucket"
1010 ],
1011 "Principal": {
1012 "AWS": "123456789012"
1013 },
1014 "Condition": {
1015 "StringEquals": {
1016 "ec2:Region": [
1017 "us-west-2"
1018 ]
1019 }
1020 }
1021 }
1022 }"# };
1023 let e = Policy::from_str(policy_str).unwrap_err();
1024 assert_eq!(e.to_string(), "duplicate field `NotResource` at line 12 column 21");
1025
1026 let policy_str = indoc! { r#"
1027 {
1028 "Version": "2012-10-17",
1029 "Id": "PolicyId",
1030 "Statement": {
1031 "Sid": "1",
1032 "Effect": "Allow",
1033 "Action": [
1034 "ec2:Get*",
1035 "ecs:*"
1036 ],
1037 "Resource": "*",
1038 "Principal": {
1039 "AWS": "123456789012"
1040 },
1041 "Principal": {
1042 "AWS": "123456789012"
1043 },
1044 "Condition": {
1045 "StringEquals": {
1046 "ec2:Region": [
1047 "us-west-2"
1048 ]
1049 }
1050 }
1051 }
1052 }"# };
1053 let e = Policy::from_str(policy_str).unwrap_err();
1054 assert_eq!(e.to_string(), "duplicate field `Principal` at line 15 column 19");
1055
1056 let policy_str = indoc! { r#"
1057 {
1058 "Version": "2012-10-17",
1059 "Id": "PolicyId",
1060 "Statement": {
1061 "Sid": "1",
1062 "Effect": "Allow",
1063 "Action": [
1064 "ec2:Get*",
1065 "ecs:*"
1066 ],
1067 "Resource": "*",
1068 "NotPrincipal": {
1069 "AWS": "123456789012"
1070 },
1071 "NotPrincipal": {
1072 "AWS": "123456789012"
1073 },
1074 "Condition": {
1075 "StringEquals": {
1076 "ec2:Region": [
1077 "us-west-2"
1078 ]
1079 }
1080 }
1081 }
1082 }"# };
1083 let e = Policy::from_str(policy_str).unwrap_err();
1084 assert_eq!(e.to_string(), "duplicate field `NotPrincipal` at line 15 column 22");
1085
1086 let policy_str = indoc! { r#"
1087 {
1088 "Version": "2012-10-17",
1089 "Id": "PolicyId",
1090 "Statement": {
1091 "Sid": "1",
1092 "Effect": "Allow",
1093 "Action": [
1094 "ec2:Get*",
1095 "ecs:*"
1096 ],
1097 "Resource": "*",
1098 "NotPrincipal": {
1099 "AWS": "123456789012"
1100 },
1101 "Condition": {
1102 "StringEquals": {
1103 "ec2:Region": [
1104 "us-west-2"
1105 ]
1106 }
1107 },
1108 "Condition": {
1109 }
1110 }
1111 }"# };
1112 let e = Policy::from_str(policy_str).unwrap_err();
1113 assert_eq!(e.to_string(), "duplicate field `Condition` at line 22 column 19");
1114 }
1115
1116 #[test_log::test]
1117 fn test_duplicate_fields() {
1118 let policy_str = indoc! { r#"
1119 {
1120 "Version": "2012-10-17",
1121 "Id": "PolicyId",
1122 "Statement": {
1123 "Effect": "Allow",
1124 "Action": "*",
1125 "Resource": "*"
1126 },
1127 "Version": "2012-10-17"
1128 }"# };
1129 let e = Policy::from_str(policy_str).unwrap_err();
1130 assert_eq!(e.to_string(), "duplicate field `Version` at line 9 column 13");
1131
1132 let policy_str = indoc! { r#"
1133 {
1134 "Version": "2012-10-17",
1135 "Id": "PolicyId",
1136 "Statement": {
1137 "Effect": "Allow",
1138 "Action": "*",
1139 "Resource": "*"
1140 },
1141 "Id": "2012-10-17"
1142 }"# };
1143 let e = Policy::from_str(policy_str).unwrap_err();
1144 assert_eq!(e.to_string(), "duplicate field `Id` at line 9 column 8");
1145
1146 let policy_str = indoc! { r#"
1147 {
1148 "Version": "2012-10-17",
1149 "Id": "PolicyId",
1150 "Statement": {
1151 "Effect": "Allow",
1152 "Action": "*",
1153 "Resource": "*"
1154 },
1155 "Statement": {
1156 "Effect": "Allow",
1157 "Action": "*",
1158 "Resource": "*"
1159 }
1160 }"# };
1161 let e = Policy::from_str(policy_str).unwrap_err();
1162 assert_eq!(e.to_string(), "duplicate field `Statement` at line 9 column 15");
1163 }
1164
1165 #[test_log::test]
1166 fn test_ec2_describe_bug() {
1167 let policy = Policy::from_str(indoc! {r#"
1168 {
1169 "Version": "2012-10-17",
1170 "Statement": [
1171 {
1172 "Effect": "Allow",
1173 "Action": [
1174 "ec2:Describe*"
1175 ],
1176 "Resource": "*"
1177 }
1178 ]
1179 }
1180 "#})
1181 .unwrap();
1182 let actor = PrincipalActor::from(User::new("aws", "123456789012", "/", "MyUser").unwrap());
1183 let mut sd = SessionData::new();
1184 sd.insert("aws:username", SessionValue::from("MyUser"));
1185 let context = Context::builder()
1186 .api("DescribeSecurityGroups")
1187 .actor(actor)
1188 .session_data(sd)
1189 .service("ec2")
1190 .build()
1191 .unwrap();
1192
1193 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1194 }
1195
1196 #[test_log::test]
1197 fn test_not_action() {
1198 let policy = Policy::from_str(indoc! {r#"
1199 {
1200 "Version": "2012-10-17",
1201 "Statement": [
1202 {
1203 "Effect": "Allow",
1204 "NotAction": [
1205 "ec2:Describe*"
1206 ],
1207 "Resource": "*"
1208 }
1209 ]
1210 }"# })
1211 .unwrap();
1212 let actor = PrincipalActor::from(User::new("aws", "123456789012", "/", "MyUser").unwrap());
1213 let sd = SessionData::new();
1214 let context = Context::builder()
1215 .api("DescribeSecurityGroups")
1216 .actor(actor.clone())
1217 .service("ec2")
1218 .session_data(sd.clone())
1219 .build()
1220 .unwrap();
1221 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1222 let context =
1223 Context::builder().api("RunInstances").actor(actor).service("ec2").session_data(sd).build().unwrap();
1224 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1225 }
1226
1227 #[test_log::test]
1228 fn test_not_resource() {
1229 let policy = Policy::from_str(indoc! {r#"
1230 {
1231 "Version": "2012-10-17",
1232 "Statement": [
1233 {
1234 "Effect": "Allow",
1235 "Action": [
1236 "ec2:TerminateInstances"
1237 ],
1238 "NotResource": [
1239 "arn:aws:ec2:*:*:instance/i-012*",
1240 "arn:aws:ec2:*:*:network-interface/eni-012*"
1241 ]
1242 }
1243 ]
1244 }"# })
1245 .unwrap();
1246
1247 let matching_instance =
1248 Arn::from_str("arn:aws:ec2:us-east-1:123456789012:instance/i-0123456789abcdef0").unwrap();
1249 let matching_eni =
1250 Arn::from_str("arn:aws:ec2:us-east-1:123456789012:network-interface/eni-0123456789abcdef0").unwrap();
1251 let nonmatching_instance =
1252 Arn::from_str("arn:aws:ec2:us-east-1:123456789012:instance/i-0223456789abcdef0").unwrap();
1253 let nonmatching_eni =
1254 Arn::from_str("arn:aws:ec2:us-east-1:123456789012:network-interface/eni-0223456789abcdef0").unwrap();
1255
1256 let actor = PrincipalActor::from(User::from_str("arn:aws:iam::123456789012:user/MyUser").unwrap());
1257 let sd = SessionData::new();
1258 let mut context_builder = Context::builder();
1259 context_builder.api("TerminateInstances").actor(actor).service("ec2").session_data(sd);
1260
1261 context_builder.resources(vec![matching_instance.clone(), matching_eni.clone()]);
1262 let context = context_builder.build().unwrap();
1263 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1264
1265 context_builder.resources(vec![matching_instance, nonmatching_eni.clone()]);
1266 let context = context_builder.build().unwrap();
1267 assert!(!context.resources().is_empty());
1268 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1269
1270 context_builder.resources(vec![nonmatching_instance.clone(), matching_eni]);
1271 let context = context_builder.build().unwrap();
1272 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1273
1274 context_builder.resources(vec![nonmatching_instance, nonmatching_eni]);
1275 let context = context_builder.build().unwrap();
1276 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1277
1278 context_builder.resources(vec![]);
1279 let context = context_builder.build().unwrap();
1280 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1281
1282 let policy = Policy::from_str(indoc! {r#"
1283 {
1284 "Version": "2012-10-17",
1285 "Statement": [
1286 {
1287 "Effect": "Allow",
1288 "Action": [
1289 "ec2:TerminateInstances"
1290 ],
1291 "NotResource": "*"
1292 }
1293 ]
1294 }"# })
1295 .unwrap();
1296
1297 let matching_instance =
1298 Arn::from_str("arn:aws:ec2:us-east-1:123456789012:instance/i-0123456789abcdef0").unwrap();
1299 let matching_eni =
1300 Arn::from_str("arn:aws:ec2:us-east-1:123456789012:network-interface/eni-0123456789abcdef0").unwrap();
1301 context_builder.resources(vec![matching_instance, matching_eni]);
1302 let context = context_builder.build().unwrap();
1303 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1304
1305 context_builder.resources(vec![]);
1306 let context = context_builder.build().unwrap();
1307 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1308 }
1309
1310 #[test_log::test]
1311 fn test_policy_version() {
1312 assert_eq!(PolicyVersion::default(), PolicyVersion::None);
1313
1314 assert_eq!(format!("{}", PolicyVersion::None), "");
1315 assert_eq!(format!("{}", PolicyVersion::V2008_10_17), "2008-10-17");
1316 assert_eq!(format!("{}", PolicyVersion::V2012_10_17), "2012-10-17");
1317
1318 assert_eq!(format!("{:?}", PolicyVersion::None), "None");
1319 assert_eq!(format!("{:?}", PolicyVersion::V2008_10_17), "V2008_10_17");
1320 assert_eq!(format!("{:?}", PolicyVersion::V2012_10_17), "V2012_10_17");
1321
1322 let mut h1 = DefaultHasher::new();
1323 let mut h2 = DefaultHasher::new();
1324 let mut h3 = DefaultHasher::new();
1325 PolicyVersion::None.hash(&mut h1);
1326 PolicyVersion::V2008_10_17.hash(&mut h2);
1327 PolicyVersion::V2012_10_17.hash(&mut h3);
1328 let h1 = h1.finish();
1329 let h2 = h2.finish();
1330 let h3 = h3.finish();
1331
1332 assert_ne!(h1, h2);
1333 assert_ne!(h1, h3);
1334 assert_ne!(h2, h3);
1335
1336 assert_eq!(PolicyVersion::from_str("2008-10-17").unwrap(), PolicyVersion::V2008_10_17);
1337 assert_eq!(PolicyVersion::from_str("2012-10-17").unwrap(), PolicyVersion::V2012_10_17);
1338 assert_eq!(
1339 PolicyVersion::from_str("2012-10-18").unwrap_err(),
1340 AspenError::InvalidPolicyVersion("2012-10-18".to_string())
1341 );
1342
1343 let e = serde_json::from_str::<PolicyVersion>(r#""2012-10-18""#).unwrap_err();
1344 assert_eq!(e.to_string(), "Invalid policy version: 2012-10-18");
1345
1346 let e = serde_json::from_str::<PolicyVersion>(r#"2012"#).unwrap_err();
1347 assert_eq!(e.to_string(), "invalid type: integer `2012`, expected a string at line 1 column 4");
1348 }
1349
1350 #[test_log::test]
1351 fn test_principals() {
1352 let policy_str = indoc! { r#"
1353 {
1354 "Version": "2012-10-17",
1355 "Statement": {
1356 "Effect": "Allow",
1357 "Action": "*",
1358 "Resource": "*",
1359 "Principal": {
1360 "AWS": "*"
1361 }
1362 }
1363 }"# };
1364 let policy = Policy::from_str(policy_str).unwrap();
1365 let statement = policy.statement();
1366 assert_eq!(statement.len(), 1);
1367 assert_eq!(statement.to_vec().len(), 1);
1368 let principal = statement[0].principal().unwrap();
1369 if let Principal::Specified(specified) = principal {
1370 let aws = specified.aws().unwrap();
1371 assert_eq!(aws.len(), 1);
1372 assert_eq!(aws[0], AwsPrincipal::Any);
1373 assert_eq!(format!("{}", aws[0]), "*");
1374 assert_eq!(aws.to_vec(), vec![&AwsPrincipal::Any]);
1375 } else {
1376 panic!("principal is not SpecifiedPrincipal");
1377 }
1378
1379 assert_eq!(
1380 format!("{}", statement[0]),
1381 indoc! {r#"
1382 {
1383 "Effect": "Allow",
1384 "Action": "*",
1385 "Resource": "*",
1386 "Principal": {
1387 "AWS": "*"
1388 }
1389 }"#}
1390 );
1391
1392 let actor = PrincipalActor::from(User::from_str("arn:aws:iam::123456789012:user/MyUser").unwrap());
1393 let instance = Arn::from_str("arn:aws:ec2:us-east-1:123456789012:instance/i-0123456789abcdef0").unwrap();
1394 let eni = Arn::from_str("arn:aws:ec2:us-east-1:123456789012:network-interface/eni-0123456789abcdef0").unwrap();
1395
1396 let sd = SessionData::new();
1397 let mut context_builder = Context::builder();
1398 context_builder
1399 .api("TerminateInstances")
1400 .actor(actor.clone())
1401 .service("ec2")
1402 .session_data(sd)
1403 .resources(vec![instance, eni]);
1404 let context = context_builder.build().unwrap();
1405 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1406
1407 let policy_str = indoc! { r#"
1408 {
1409 "Version": "2012-10-17",
1410 "Statement": {
1411 "Effect": "Allow",
1412 "Action": "*",
1413 "Resource": "*",
1414 "Principal": {
1415 "AWS": "arn:aws:iam::123456789012:root"
1416 }
1417 }
1418 }"# };
1419 let policy = Policy::from_str(policy_str).unwrap();
1420 let principal = policy.statement()[0].principal().unwrap();
1421 let specified = principal.specified().unwrap();
1422 let aws = specified.aws().unwrap();
1423 assert_eq!(aws.len(), 1);
1424 assert_eq!(aws[0], AwsPrincipal::Arn(Arn::from_str("arn:aws:iam::123456789012:root").unwrap()));
1425 assert_eq!(format!("{}", aws[0]), "arn:aws:iam::123456789012:root");
1426 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1427
1428 context_builder.actor(PrincipalActor::from(Service::new("ec2", None, "amazonaws.com").unwrap()));
1429 let context = context_builder.build().unwrap();
1430 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1431
1432 let policy_str = indoc! { r#"
1433 {
1434 "Version": "2012-10-17",
1435 "Statement": {
1436 "Effect": "Allow",
1437 "Action": "*",
1438 "Resource": "*",
1439 "NotPrincipal": {
1440 "AWS": "arn:aws:iam::123456789012:root"
1441 }
1442 }
1443 }"# };
1444 let policy = Policy::from_str(policy_str).unwrap();
1445 context_builder.actor(actor.clone());
1446 let context = context_builder.build().unwrap();
1447 assert_eq!(policy.evaluate(&context).unwrap(), Decision::DefaultDeny);
1448
1449 context_builder.actor(PrincipalActor::from(Service::new("ec2", None, "amazonaws.com").unwrap()));
1450 let context = context_builder.build().unwrap();
1451 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1452
1453 let policy_str = indoc! { r#"
1454 {
1455 "Version": "2012-10-17",
1456 "Statement": {
1457 "Effect": "Allow",
1458 "Action": "*",
1459 "Resource": "*",
1460 "Principal": "*"
1461 }
1462 }"# };
1463 let policy = Policy::from_str(policy_str).unwrap();
1464 context_builder.actor(actor);
1465 let context = context_builder.build().unwrap();
1466 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1467
1468 context_builder.actor(PrincipalActor::from(Service::new("ec2", None, "amazonaws.com").unwrap()));
1469 let context = context_builder.build().unwrap();
1470 assert_eq!(policy.evaluate(&context).unwrap(), Decision::Allow);
1471 }
1472
1473 #[test_log::test]
1474 fn test_serialization() {
1475 let p1_str = include_str!("test-policy-1.json");
1476 let p2_str = include_str!("test-policy-2.json");
1477 let p1 = Policy::from_str(p1_str).unwrap();
1478 let p2 = Policy::from_str(p2_str).unwrap();
1479
1480 let statements = p1.statement.to_vec();
1481 assert_eq!(statements.len(), 2);
1482 let actions = statements[0].action().unwrap().to_vec();
1483 assert_eq!(actions.len(), 1);
1484 assert_eq!(actions[0], &Action::new("s3", "ListBucket").unwrap());
1485
1486 assert_eq!(*statements[1].effect(), Effect::Deny);
1487
1488 let actions = statements[1].not_action().unwrap().to_vec();
1489 assert_eq!(actions.len(), 3);
1490 assert_eq!(actions[0], &Action::new("ec2", "*").unwrap());
1491 assert_eq!(actions[1], &Action::new("s3", "*").unwrap());
1492 assert_eq!(actions[2], &Action::new("rds", "*").unwrap());
1493
1494 let resources = statements[1].resource().unwrap().to_vec();
1495 assert_eq!(resources.len(), 3);
1496 assert_eq!(*resources[0], Resource::from_str("arn:aws:ec2:*:*:instance/*").unwrap());
1497 assert_eq!(*resources[1], Resource::from_str("arn:aws:s3:*:*:bucket/*").unwrap());
1498 assert_eq!(*resources[2], Resource::from_str("arn:aws:rds:*:*:db/*").unwrap());
1499
1500 let principals = statements[1].principal().unwrap();
1501 if let Principal::Specified(ref specified) = principals {
1502 let aws = specified.aws().unwrap().to_vec();
1503 assert_eq!(aws.len(), 2);
1504 assert_eq!(*aws[0], AwsPrincipal::from_str("arn:aws:iam::123456789012:root").unwrap());
1505 assert_eq!(*aws[1], AwsPrincipal::from_str("arn:aws:iam::123456789012:user/*").unwrap());
1506
1507 let canonical_users = specified.canonical_user().unwrap().to_vec();
1508 assert_eq!(canonical_users.len(), 2);
1509 assert_eq!(canonical_users[0], "d04207a7d9311e77f5837e0e4f4b025322bf2f626f0872c85be8c6bb1290c88b");
1510 assert_eq!(canonical_users[1], "2cdb0173470eb5b200f82c8e1b51a88562924cda12e2ccce60d7f00e1567ee7c");
1511
1512 let federated = specified.federated().unwrap().to_vec();
1513 assert_eq!(federated.len(), 1);
1514 assert_eq!(federated[0], "dacut@kanga.org");
1515
1516 let service = specified.service().unwrap().to_vec();
1517 assert_eq!(service.len(), 3);
1518 assert_eq!(service[0], "ec2.amazonaws.com");
1519 assert_eq!(service[1], "edgelambda.amazonaws.com");
1520 assert_eq!(service[2], "lambda.amazonaws.com");
1521 } else {
1522 panic!("Expected SpecifiedPrincipal");
1523 }
1524
1525 let json = serde_json::to_string_pretty(&p1).unwrap();
1526 assert_eq!(json, p1_str);
1527
1528 assert_ne!(p1, p2);
1529 }
1530
1531 #[test_log::test]
1532 fn test_unknown_field() {
1533 let policy_str = indoc! { r#"
1534 {
1535 "Version": "2012-10-17",
1536 "Id": "PolicyId",
1537 "Statement": {
1538 "Sid": "1",
1539 "Effect": "Allow",
1540 "Action": [
1541 "ec2:Get*",
1542 "ecs:*"
1543 ],
1544 "Instance": [
1545 "i-0123456789abcdef0",
1546 ],
1547 "Resource": "*",
1548 "Principal": {
1549 "AWS": "123456789012"
1550 },
1551 "Condition": {
1552 "StringEquals": {
1553 "ec2:Region": [
1554 "us-west-2"
1555 ]
1556 }
1557 }
1558 }
1559 }"# };
1560 let e = Policy::from_str(policy_str).unwrap_err();
1561 assert_eq!(e.to_string(), "unknown field `Instance`, expected one of `Sid`, `Effect`, `Action`, `NotAction`, `Resource`, `NotResource`, `Principal`, `NotPrincipal`, `Condition` at line 11 column 18");
1562
1563 let policy_str = indoc! { r#"
1564 {
1565 "Version": "2012-10-17",
1566 "Id": "PolicyId",
1567 "Statement": {
1568 "Effect": "Allow",
1569 "Action": "*",
1570 "Resource": "*"
1571 },
1572 "Test": true
1573 }"# };
1574 let e = Policy::from_str(policy_str).unwrap_err();
1575 assert_eq!(
1576 e.to_string(),
1577 "unknown field `Test`, expected one of `Version`, `Id`, `Statement` at line 9 column 10"
1578 );
1579 }
1580}