package iam import ( "encoding/json" "testing" "github.com/stretchr/testify/require" ) func TestUnmarshalIAMPolicy(t *testing.T) { t.Run("simple fields", func(t *testing.T) { policy := `{ "Version": "2012-10-17", "Id": "PutObjPolicy", "Statement": { "Sid": "DenyObjectsThatAreNotSSEKMS", "Principal": "*", "Effect": "Deny", "Action": "s3:PutObject", "Resource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*", "Condition": { "Null": { "s3:x-amz-server-side-encryption-aws-kms-key-id": "true" } } } }` expected := Policy{ Version: "2012-10-17", ID: "PutObjPolicy", Statement: []Statement{{ SID: "DenyObjectsThatAreNotSSEKMS", Principal: map[PrincipalType][]string{ "*": nil, }, Effect: DenyEffect, Action: []string{"s3:PutObject"}, Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"}, Conditions: map[string]Condition{ "Null": { "s3:x-amz-server-side-encryption-aws-kms-key-id": {"true"}, }, }, }}, } var p Policy err := json.Unmarshal([]byte(policy), &p) require.NoError(t, err) require.Equal(t, expected, p) }) t.Run("complex fields", func(t *testing.T) { policy := `{ "Version": "2012-10-17", "Statement": [{ "Principal":{ "AWS":[ "arn:aws:iam::111122223333:user/JohnDoe" ] }, "Effect": "Allow", "Action": [ "s3:PutObject" ], "Resource": [ "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*" ], "Condition": { "StringEquals": { "s3:RequestObjectTag/Department": ["Finance"] } } }] }` expected := Policy{ Version: "2012-10-17", Statement: []Statement{{ Principal: map[PrincipalType][]string{ AWSPrincipalType: {"arn:aws:iam::111122223333:user/JohnDoe"}, }, Effect: AllowEffect, Action: []string{"s3:PutObject"}, Resource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"}, Conditions: map[string]Condition{ "StringEquals": { "s3:RequestObjectTag/Department": {"Finance"}, }, }, }}, } var p Policy err := json.Unmarshal([]byte(policy), &p) require.NoError(t, err) require.Equal(t, expected, p) raw, err := json.Marshal(expected) require.NoError(t, err) require.JSONEq(t, policy, string(raw)) }) t.Run("check principal AWS", func(t *testing.T) { policy := `{ "Statement": [{ "Principal":{ "AWS":"arn:aws:iam::111122223333:user/JohnDoe" } }] }` expected := Policy{ Statement: []Statement{{ Principal: map[PrincipalType][]string{ AWSPrincipalType: {"arn:aws:iam::111122223333:user/JohnDoe"}, }, }}, } var p Policy err := json.Unmarshal([]byte(policy), &p) require.NoError(t, err) require.Equal(t, expected, p) }) t.Run("native example", func(t *testing.T) { policy := ` { "Version": "xyz", "Statement": [ { "Effect": "Allow", "Action": [ "native:*", "s3:PutObject", "s3:GetObject" ], "Resource": ["*"], "Principal": {"FrostFS": ["did:frostfs:039e3ee771a223361fe7862f532e9511b57baaae3c3e2622682e99d0e660f7671"]}, "Condition": {"StringEquals": {"native::object::attribute": "iamuser-admin"}} } ] }` var p Policy err := json.Unmarshal([]byte(policy), &p) require.NoError(t, err) }) t.Run("condition array", func(t *testing.T) { policy := ` { "Statement": [{ "Condition": {"StringLike": {"ec2:InstanceType": ["t1.*", "t2.*", "m3.*"]}} }] }` expected := Policy{ Statement: []Statement{{ Conditions: map[string]Condition{ "StringLike": {"ec2:InstanceType": {"t1.*", "t2.*", "m3.*"}}, }, }}, } var p Policy err := json.Unmarshal([]byte(policy), &p) require.NoError(t, err) require.Equal(t, expected, p) }) t.Run("'Not*' fields", func(t *testing.T) { policy := ` { "Id": "PutObjPolicy", "Statement": [{ "NotPrincipal": {"AWS":["arn:aws:iam::111122223333:user/Alice"]}, "Effect": "Deny", "NotAction": "s3:PutObject", "NotResource": "arn:aws:s3:::DOC-EXAMPLE-BUCKET/*" }] }` expected := Policy{ ID: "PutObjPolicy", Statement: []Statement{{ NotPrincipal: map[PrincipalType][]string{ AWSPrincipalType: {"arn:aws:iam::111122223333:user/Alice"}, }, Effect: DenyEffect, NotAction: []string{"s3:PutObject"}, NotResource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"}, }}, } var p Policy err := json.Unmarshal([]byte(policy), &p) require.NoError(t, err) require.Equal(t, expected, p) }) } func TestValidatePolicies(t *testing.T) { for _, tc := range []struct { name string policy Policy typ PolicyType isValid bool }{ { name: "valid permission boundaries", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Resource: []string{Wildcard}, }}, }, typ: GeneralPolicyType, isValid: true, }, { name: "general invalid effect", policy: Policy{ Statement: []Statement{{ Effect: "dummy", Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Resource: []string{Wildcard}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "general invalid principal block", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Resource: []string{Wildcard}, Principal: map[PrincipalType][]string{Wildcard: nil}, NotPrincipal: map[PrincipalType][]string{Wildcard: nil}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "general invalid not principal", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Resource: []string{Wildcard}, NotPrincipal: map[PrincipalType][]string{AWSPrincipalType: {"arn:aws:iam::111122223333:user/Alice"}}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "general invalid principal type", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, Resource: []string{Wildcard}, NotPrincipal: map[PrincipalType][]string{"dummy": {"arn:aws:iam::111122223333:user/Alice"}}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "general invalid action block", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:*", "cloudwatch:*", "ec2:*"}, NotAction: []string{"iam:*"}, Resource: []string{Wildcard}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "general invalid resource block", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Resource: []string{Wildcard}, NotResource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "invalid resource block", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Resource: []string{}, NotResource: []string{"arn:aws:s3:::DOC-EXAMPLE-BUCKET/*"}, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "missing resource block", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, }}, }, typ: GeneralPolicyType, isValid: false, }, { name: "identity based valid", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, }}, }, typ: IdentityBasedPolicyType, isValid: true, }, { name: "identity based invalid because of id presence", policy: Policy{ ID: "some-id", Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, }}, }, typ: IdentityBasedPolicyType, isValid: false, }, { name: "identity based invalid because of principal presence", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, Principal: map[PrincipalType][]string{AWSPrincipalType: {"arn:aws:iam::111122223333:user/Alice"}}, }}, }, typ: IdentityBasedPolicyType, isValid: false, }, { name: "identity based invalid because of not principal presence", policy: Policy{ Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, NotPrincipal: map[PrincipalType][]string{AWSPrincipalType: {"arn:aws:iam::111122223333:user/Alice"}}, }}, }, typ: IdentityBasedPolicyType, isValid: false, }, { name: "resource based valid principal", policy: Policy{ Statement: []Statement{{ Effect: DenyEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, Principal: map[PrincipalType][]string{AWSPrincipalType: {"arn:aws:iam::111122223333:user/Alice"}}, }}, }, typ: ResourceBasedPolicyType, isValid: true, }, { name: "resource based valid not principal", policy: Policy{ ID: "some-id", Statement: []Statement{{ Effect: DenyEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, NotPrincipal: map[PrincipalType][]string{AWSPrincipalType: {"arn:aws:iam::111122223333:user/Alice"}}, }}, }, typ: ResourceBasedPolicyType, isValid: true, }, { name: "resource based invalid missing principal", policy: Policy{ ID: "some-id", Statement: []Statement{{ Effect: AllowEffect, Action: []string{"s3:PutObject"}, Resource: []string{Wildcard}, }}, }, typ: ResourceBasedPolicyType, isValid: false, }, } { t.Run(tc.name, func(t *testing.T) { err := tc.policy.Validate(tc.typ) if tc.isValid { require.NoError(t, err) } else { require.Error(t, err) } }) } }