Policy Statement Search capability for aws_iam_policy (#2918)

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2018-04-12 14:31:02 -04:00 committed by GitHub
parent ceec3fc66c
commit 7130a77c06
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 563 additions and 5 deletions

View file

@ -5,9 +5,9 @@ platform: aws
# aws\_iam\_policy # aws\_iam\_policy
Use the `aws_iam_policy` InSpec audit resource to test properties of a single managed AWS IAM Policy. Use the `aws_iam_policy` InSpec audit resource to test properties of a single managed AWS IAM Policy. Use `aws_iam_policies` to audit IAM policies in bulk.
A policy is an entity in AWS that, when attached to an identity or resource, defines their permissions. AWS evaluates these policies when a principal, such as a user, makes a request. Permissions in the policies determine if the request is allowed or denied. A policy defines the permissions of an identity or resource within AWS. AWS evaluates these policies when a principal, such as a user, makes a request. Policy permissions, also called "policy statements" in AWS, determine if a request is authorized -- and allow or deny it accordingly.
Each IAM Policy is uniquely identified by either its policy\_name or arn. Each IAM Policy is uniquely identified by either its policy\_name or arn.
@ -50,11 +50,22 @@ The following examples show how to use this InSpec audit resource.
it { should be_attached } it { should be_attached }
end end
### Examine the policy statements
describe aws_iam_policy('my-policy') do
# Verify that there is at least one statement allowing access to S3
it { should have_statement(Action: 's3:PutObject', Effect: 'allow') }
# have_statement does not expand wildcards. If you want to verify
# they are absent, an explicit check is required.
it { should_not have_statement(Action: 's3:*') }
end
<br> <br>
## Properties ## Properties
* `arn`, `attachment_count`, `attached_groups`, `attached_roles`,`attached_users`, `default_version_id` * `arn`, `attachment_count`, `attached_groups`, `attached_roles`,`attached_users`, `default_version_id`, `policy`, `statement_count`
## Property Examples ## Property Examples
@ -106,10 +117,38 @@ The 'default_version_id' value of the specified policy.
its('default_version_id') { should cmp "v1" } its('default_version_id') { should cmp "v1" }
end end
### policy
This is a low-level, unsupported property.
Returns the default version of the policy document after decoding as a Ruby hash. This hash contains the policy statements and is useful for performing checks that cannot be expressed using higher-level matchers like `have_statement`.
For details regarding the contents of this structure, refer to the [AWS IAM Policy JSON Reference](https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies.html). A set of examples is [also available](https://docs.aws.amazon.com/IAM/latest/UserGuide/access_policies_examples.html).
Example:
# Fetch the policy structure as a Ruby object
policy_struct = aws_iam_policy('my-policy').policy
# Write a manually-constructed test to check that the policy
# has an IP constraint on the first statement
# ( Based on https://docs.aws.amazon.com/IAM/latest/UserGuide/reference_policies_examples_aws_deny-ip.html )
describe 'Check that we are restricting IP access' do
subject { policy_struct['Statement'].first['Condition'] }
it { should include 'NotIpAddress' }
end
### statement\_count
Returns the number of statements present in the `policy`.
# Make sure there are exactly two statements.
describe aws_iam_policy('my-policy') do
its('statement_count') { should cmp 2 }
end
## Matchers ## Matchers
This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/). This InSpec audit resource has the following special matchers. For a full list of available matchers, please visit our [Universal Matchers page](https://www.inspec.io/docs/reference/matchers/).
### be\_attached ### be\_attached
@ -142,3 +181,59 @@ The test will pass if the identified policy attached the specified role.
describe aws_iam_policy('AWSSupportAccess') do describe aws_iam_policy('AWSSupportAccess') do
it { should be_attached_to_role(ROLENAME) } it { should be_attached_to_role(ROLENAME) }
end end
### have\_statement
Examines the list of statements contained in the policy and passes if at least one of the statements matches. This matcher does _not_ interpret the policy in a request authorization context, as AWS does when a request processed. Rather, `have_statement` examines the literal contents of the IAM policy, and reports on what is present (or absent, when used with `should_not`).
`have_statement` accepts the following criteria to search for matching statements. If any statement matches all the criteria, the test is successful.
* `Action` - Expresses the requested operation. Acceptable literal values are any AWS operation name, including the '*' wildcard character. `Action` may also use a list of AWS operation names.
* `Effect` - Expresses if the operation is permitted. Acceptable values are 'Deny' and 'Allow'.
* `Sid` - A user-provided string identifier for the statement.
* `Resource` - Expresses the operation's target. Acceptable values are ARNs, including the '*' wildcard. `Resource` may also use a list of ARN values.
Please note the following about the behavior of `have_statement`:
* `Action`, `Sid`, and `Resource` allow using a regular expression as the search critera instead of a string literal.
* it does not support wildcard expansion; to check for a wildcard value, check for it explicitly. For example, if the policy includes a statement with `"Action": "s3:*"` and the test checks for `Action: "s3:PutObject"`, the test _will not match_. You must write an additional test checking for the wildcard case.
* it supports searching list values. For example, if a statement contains a list of 3 resources, and a `have_statement` test specifes _one_ of those resources, it will match.
* `Action` and `Resource` allow using a list of string literals or regular expressions in a test, in which case _all_ must match on the _same_ statement for the test to match. Order is ignored.
* it does not support the `Principal` or `Conditional` key, or any of `NotAction`, `NotPrincipal`, or `NotResource`.
Examples:
# Verify there is no full-admin statement
describe aws_iam_policy('kryptonite') do
it { should_not have_statement(Effect: 'Allow', Resource: '*', Action: '*')}
end
# Verify bob is allowed to manage things on S3 buckets that start with bobs-stuff
describe aws_iam_policy('bob-is-a-packrat') do
it { should have_statement(Effect: 'Allow',
# Using the AWS wildcard - this must match exactly
Resource: 'arn:aws:s3:::bobs-stuff*',
# Specify a list of actions - all must match, no others, order isn't important
Action: ['s3:PutObject', 's3:GetObject', 's3:DeleteObject'])}
# Bob would make new buckets constantly if we let him.
it { should_not have_statement(Effect: 'Allow', Action: 's3:CreateBucket')}
it { should_not have_statement(Effect: 'Allow', Action: 's3:*')}
it { should_not have_statement(Effect: 'Allow', Action: '*')}
# An alternative to checking for wildcards is to specify the
# statements you expect, then restrict statement count
its('statement_count') { should cmp 1 }
end
# Use regular expressions to examine the policy
describe aws_iam_policy('regex-demo') do
# Check to see if anything mentions RDS at all.
# This catches `rds:CreateDBinstance` and `rds:*`, but would not catch '*'.
it { should_not have_statement(Action: /^rds:.+$/)}
# This policy should refer to both sally and kim's s3 buckets.
# This will only match if there is a statement that refers to both resources.
it { should have_statement(Resource: [/arn:aws:s3.+:sally/, /arn:aws:s3.+:kim/]) }
# The following also matches on a statement mentioning only one of them
it { should have_statement(Resource: /arn:aws:s3.+:(sally|kim)/) }
end

View file

@ -1,3 +1,7 @@
require 'json'
require 'set'
require 'uri'
class AwsIamPolicy < Inspec.resource(1) class AwsIamPolicy < Inspec.resource(1)
name 'aws_iam_policy' name 'aws_iam_policy'
desc 'Verifies settings for individual AWS IAM Policy' desc 'Verifies settings for individual AWS IAM Policy'
@ -12,6 +16,21 @@ class AwsIamPolicy < Inspec.resource(1)
attr_reader :arn, :attachment_count, :default_version_id attr_reader :arn, :attachment_count, :default_version_id
EXPECTED_CRITERIA = %w{
Action
Effect
Resource
Sid
}.freeze
UNIMPLEMENTED_CRITERIA = %w{
Conditional
NotAction
NotPrincipal
NotResource
Principal
}.freeze
def to_s def to_s
"Policy #{@policy_name}" "Policy #{@policy_name}"
end end
@ -50,8 +69,133 @@ class AwsIamPolicy < Inspec.resource(1)
attached_roles.include?(role_name) attached_roles.include?(role_name)
end end
def policy
return nil unless exists?
return @policy if defined?(@policy)
catch_aws_errors do
backend = BackendFactory.create(inspec_runner)
gpv_response = backend.get_policy_version(policy_arn: arn, version_id: default_version_id)
@policy = JSON.parse(URI.decode_www_form_component(gpv_response.policy_version.document))
end
@policy
end
def statement_count
return nil unless exists?
policy['Statement'].count
end
def has_statement?(raw_criteria = {})
return nil unless exists?
criteria = has_statement__normalize_criteria(has_statement__validate_criteria(raw_criteria))
@normalized_statements ||= has_statement__normalize_statements
statements = has_statement__focus_on_sid(@normalized_statements, criteria)
statements.any? do |statement|
true && \
has_statement__effect(statement, criteria) && \
has_statement__array_criterion(:action, statement, criteria) && \
has_statement__array_criterion(:resource, statement, criteria)
end
end
private private
def has_statement__validate_criteria(raw_criteria)
recognized_criteria = {}
EXPECTED_CRITERIA.each do |expected_criterion|
if raw_criteria.key?(expected_criterion)
recognized_criteria[expected_criterion] = raw_criteria.delete(expected_criterion)
end
end
# Special message for valid, but unimplemented statement attributes
UNIMPLEMENTED_CRITERIA.each do |unimplemented_criterion|
if raw_criteria.key?(unimplemented_criterion)
raise ArgumentError, "Criterion '#{unimplemented_criterion}' is not supported for performing have_statement queries."
end
end
# If anything is left, it's spurious
unless raw_criteria.empty?
raise ArgumentError, "Unrecognized criteria #{raw_criteria.keys.join(', ')} to have_statement. Recognized criteria: #{EXPECTED_CRITERIA.join(', ')}"
end
# Effect has only 2 permitted values
if recognized_criteria.key?('Effect')
unless %w{Allow Deny}.include?(recognized_criteria['Effect'])
raise ArgumentError, "Criterion 'Effect' for have_statement must be one of 'Allow' or 'Deny' - got '#{recognized_criteria['Effect']}'"
end
end
recognized_criteria
end
def has_statement__normalize_criteria(criteria)
# Transform keys into lowercase symbols
criteria.keys.each do |provided_key|
criteria[provided_key.downcase.to_sym] = criteria.delete(provided_key)
end
criteria
end
def has_statement__normalize_statements
policy['Statement'].map do |statement|
# Coerce some values into arrays
%w{Action Resource}.each do |field|
if statement.key?(field)
statement[field] = Array(statement[field])
end
end
# Symbolize all keys
statement.keys.each do |field|
statement[field.downcase.to_sym] = statement.delete(field)
end
statement
end
end
def has_statement__focus_on_sid(statements, criteria)
return statements unless criteria.key?(:sid)
sid_seek = criteria[:sid]
statements.select do |statement|
if sid_seek.is_a? Regexp
statement[:sid] =~ sid_seek
else
statement[:sid] == sid_seek
end
end
end
def has_statement__effect(statement, criteria)
!criteria.key?(:effect) || criteria[:effect] == statement[:effect]
end
def has_statement__array_criterion(crit_name, statement, criteria)
return true unless criteria.key?(crit_name)
check = criteria[crit_name]
values = statement[crit_name] # This is an array due to normalize_statements
if check.is_a?(String)
# If check is a string, it only has to match one of the values
values.any? { |v| v == check }
elsif check.is_a?(Regexp)
# If check is a regex, it only has to match one of the values
values.any? { |v| v =~ check }
elsif check.is_a?(Array) && check.all? { |c| c.is_a? String }
# If check is an array of strings, perform setwise check
Set.new(values) == Set.new(check)
elsif check.is_a?(Array) && check.all? { |c| c.is_a? Regexp }
# If check is an array of regexes, all values must match all regexes
values.all? { |v| check.all? { |r| v =~ r } }
else
false
end
end
def validate_params(raw_params) def validate_params(raw_params)
validated_params = check_resource_param_names( validated_params = check_resource_param_names(
raw_params: raw_params, raw_params: raw_params,
@ -113,6 +257,10 @@ class AwsIamPolicy < Inspec.resource(1)
BackendFactory.set_default_backend(self) BackendFactory.set_default_backend(self)
self.aws_client_class = Aws::IAM::Client self.aws_client_class = Aws::IAM::Client
def get_policy_version(criteria)
aws_service_client.get_policy_version(criteria)
end
def list_policies(criteria) def list_policies(criteria)
aws_service_client.list_policies(criteria) aws_service_client.list_policies(criteria)
end end

View file

@ -116,6 +116,99 @@ output "iam_group_administrators" {
value = "${aws_iam_group.administrators.name}" value = "${aws_iam_group.administrators.name}"
} }
#======================================================#
# IAM Policies
#======================================================#
# Test fixtures:
# Note: Principal is not allowed on an IAM Policy. (May be allowed on a role? certainly on s3 bucket?)
# alpha
# has 2 statements
# one is a wildcard on ec2
# both have IDs
# one is a resource full wildcard
# one is Allow, one is Deny
# scalar values throughout
# beta
# one statement
# list values for Resource and Action
# gamma
# allow all
resource "aws_iam_policy" "alpha" {
name = "${terraform.env}-alpha"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "alpha01",
"Action": "ec2:Describe*",
"Effect": "Allow",
"Resource": "*"
},
{
"Sid": "alpha02",
"Action": "s3:GetObject",
"Effect": "Deny",
"Resource": "arn:aws:s3:::bobs-stuff"
}
]
}
EOF
}
output "aws_iam_policy_alpha_name" {
value = "${aws_iam_policy.alpha.name}"
}
resource "aws_iam_policy" "beta" {
name = "${terraform.env}-beta"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "beta01",
"Action": [
"ec2:DescribeSubnets",
"ec2:DescribeSecurityGroups"
],
"Effect": "Deny",
"Resource": [
"arn:aws:ec2:::*",
"*"
]
}
]
}
EOF
}
output "aws_iam_policy_beta_name" {
value = "${aws_iam_policy.beta.name}"
}
resource "aws_iam_policy" "gamma" {
name = "${terraform.env}-gamma"
policy = <<EOF
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "gamma01",
"Action": "*",
"Effect": "Allow",
"Resource": "*"
}
]
}
EOF
}
output "aws_iam_policy_gamma_name" {
value = "${aws_iam_policy.gamma.name}"
}
#======================================================# #======================================================#
# IAM Group Memberships # IAM Group Memberships
#======================================================# #======================================================#

View file

@ -1,3 +1,15 @@
fixtures = {}
[
'aws_iam_policy_alpha_name',
'aws_iam_policy_beta_name',
].each do |fixture_name|
fixtures[fixture_name] = attribute(
fixture_name,
default: "default.#{fixture_name}",
description: 'See ../build/iam.tf',
)
end
control "aws_iam_policy recall" do control "aws_iam_policy recall" do
describe aws_iam_policy("AWSSupportAccess") do describe aws_iam_policy("AWSSupportAccess") do
it { should exist } it { should exist }
@ -6,6 +18,10 @@ control "aws_iam_policy recall" do
describe aws_iam_policy(policy_name: "AWSSupportAccess") do describe aws_iam_policy(policy_name: "AWSSupportAccess") do
it { should exist } it { should exist }
end end
describe aws_iam_policy(fixtures['aws_iam_policy_alpha_name']) do
it { should exist }
end
end end
control "aws_iam_policy properties" do control "aws_iam_policy properties" do
@ -17,6 +33,11 @@ control "aws_iam_policy properties" do
its('attached_groups') { should be_empty } its('attached_groups') { should be_empty }
its('attached_roles') { should be_empty } its('attached_roles') { should be_empty }
end end
describe aws_iam_policy(fixtures['aws_iam_policy_alpha_name']) do
its('statement_count') { should cmp 2 }
its('policy') { should be_kind_of(Hash) }
end
end end
control "aws_iam_policy matchers" do control "aws_iam_policy matchers" do
@ -26,4 +47,36 @@ control "aws_iam_policy matchers" do
describe aws_iam_policy("AdministratorAccess") do describe aws_iam_policy("AdministratorAccess") do
it { should be_attached_to_user("test-fixture-maker") } it { should be_attached_to_user("test-fixture-maker") }
end end
describe aws_iam_policy(fixtures['aws_iam_policy_alpha_name']) do
it { should have_statement('Resource' => '*')}
it { should have_statement('Resource' => '*', 'Sid' => 'alpha01')}
it { should have_statement('Resource' => 'arn:aws:s3:::bobs-stuff', 'Sid' => 'alpha02')}
it { should have_statement('Effect' => 'Allow', 'Sid' => 'alpha01')}
it { should have_statement('Effect' => 'Deny', 'Sid' => 'alpha02')}
it { should have_statement('Action' => 'ec2:Describe*', 'Sid' => 'alpha01')}
it { should_not have_statement('Action' => 'ec2:Describe')}
it { should have_statement('Action' => /^ec2:Describe\*$/, 'Sid' => 'alpha01')}
it { should have_statement('Action' => /^ec2:.+$/, 'Sid' => 'alpha01')}
it { should have_statement('Action' => 'ec2:Describe*', 'Resource' => '*', 'Effect' => 'Allow') }
it { should_not have_statement('Action' => 'ec2:Describe*', 'Resource' => 'arn:aws:s3:::bobs-stuff') }
end
describe aws_iam_policy(fixtures['aws_iam_policy_beta_name']) do
it { should have_statement('Action' => 'ec2:DescribeSubnets')}
it { should have_statement('Action' => 'ec2:DescribeSecurityGroups')}
# Array indicates all must match
it { should_not have_statement('Action' => ['ec2:DescribeSecurityGroups'])}
it { should have_statement('Action' => ['ec2:DescribeSubnets', 'ec2:DescribeSecurityGroups'])}
it { should have_statement('Action' => ['ec2:DescribeSecurityGroups', 'ec2:DescribeSubnets'])}
it { should have_statement('Resource' => 'arn:aws:ec2:::*')}
it { should have_statement('Resource' => '*')}
it { should_not have_statement('Resource' => ['*'])}
it { should have_statement('Resource' => ['arn:aws:ec2:::*', '*'])}
it { should have_statement('Resource' => ['*', 'arn:aws:ec2:::*'])}
end
end end

View file

@ -90,6 +90,20 @@ class AwsIamPolicyPropertiesTest < Minitest::Test
assert_equal(['test-role'], AwsIamPolicy.new('test-policy-1').attached_roles) assert_equal(['test-role'], AwsIamPolicy.new('test-policy-1').attached_roles)
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attached_roles) assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attached_roles)
end end
def test_property_policy
policy = AwsIamPolicy.new('test-policy-1').policy
assert_kind_of(Hash, policy)
assert(policy.key?('Statement'), "test-policy-1 should have a Statement key when unpacked")
assert_equal(1, policy['Statement'].count, "test-policy-1 should have 1 statements when unpacked")
assert_nil(AwsIamPolicy.new('non-existant').policy)
end
def test_property_statement_count
assert_nil(AwsIamPolicy.new('non-existant').statement_count)
assert_equal(1, AwsIamPolicy.new('test-policy-1').statement_count)
assert_equal(2, AwsIamPolicy.new('test-policy-2').statement_count)
end
end end
@ -133,6 +147,113 @@ class AwsIamPolicyMatchersTest < Minitest::Test
def test_matcher_attached_to_role_negative def test_matcher_attached_to_role_negative
refute AwsIamPolicy.new('test-policy-2').attached_to_role?('test-role') refute AwsIamPolicy.new('test-policy-2').attached_to_role?('test-role')
end end
def test_have_statement_when_policy_does_not_exist
assert_nil AwsIamPolicy.new('nonesuch').has_statement?('Effect' => 'foo')
end
def test_have_statement_when_provided_no_criteria
AwsIamPolicy.new('test-policy-1').has_statement?
end
def test_have_statement_when_provided_acceptable_criteria
{
'Action' => 'dummy',
'Effect' => 'Deny', # This has restictions on the value provided
'Resource' => 'dummy',
'Sid' => 'dummy',
}.each do |criterion, test_value|
AwsIamPolicy.new('test-policy-1').has_statement?(criterion => test_value)
end
end
def test_have_statement_when_provided_unimplemented_criteria
[
'Conditional',
'NotAction',
'NotPrincipal',
'NotResource',
'Principal'
].each do |criterion|
ex = assert_raises(ArgumentError) {AwsIamPolicy.new('test-policy-1').has_statement?(criterion => 'dummy')}
assert_match(/not supported/, ex.message)
end
end
def test_have_statement_when_provided_unrecognized_criteria
ex = assert_raises(ArgumentError) {AwsIamPolicy.new('test-policy-1').has_statement?('foo' => 'dummy')}
assert_match(/Unrecognized/, ex.message)
end
def test_have_statement_when_sid_is_provided
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Sid' => 'beta01'))
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Sid' => 'CloudWatchEventsFullAccess'))
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Sid' => 'IAMPassRoleForCloudWatchEvents'))
refute(AwsIamPolicy.new('test-policy-2').has_statement?('Sid' => 'beta01'))
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Sid' => /eta/))
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Sid' => /CloudWatch/))
refute(AwsIamPolicy.new('test-policy-2').has_statement?('Sid' => /eta/))
end
def test_have_statement_when_provided_invalid_effect
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?('Effect' => 'Disallow') }
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?('Effect' => 'allow') }
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?('Effect' => :Allow) }
assert_raises(ArgumentError) { AwsIamPolicy.new('test-policy-1').has_statement?('Effect' => :allow) }
end
def test_have_statement_when_effect_is_provided
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Effect' => 'Deny'))
refute(AwsIamPolicy.new('test-policy-1').has_statement?('Effect' => 'Allow'))
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Effect' => 'Allow'))
end
def test_have_statement_when_action_is_provided
# Able to match a simple string action when multiple statements present
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Action' => 'iam:PassRole'))
# Able to match a wildcard string action
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Action' => 'events:*'))
# Do not match a wildcard when using strings
refute(AwsIamPolicy.new('test-policy-2').has_statement?('Action' => 'events:EnableRule'))
# Do match when using a regex
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Action' => /^events\:/))
# Able to match one action when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Action' => 'ec2:DescribeSubnets'))
# Do not match if only one action specified as an array when the statement has an array of actions
refute(AwsIamPolicy.new('test-policy-1').has_statement?('Action' => ['ec2:DescribeSubnets']))
# Do match if two actions specified when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Action' => ['ec2:DescribeSubnets', 'ec2:DescribeSecurityGroups']))
# Do match setwise if two actions specified when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Action' => ['ec2:DescribeSecurityGroups', 'ec2:DescribeSubnets']))
# Do match if only one regex action specified when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Action' => /^ec2\:Describe/))
# Do match if one regex action specified in an array when the statement has an array of actions
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Action' => [/^ec2\:Describe/]))
end
def test_have_statement_when_resource_is_provided
# Able to match a simple string resource when multiple statements present
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Resource' => 'arn:aws:iam::*:role/AWS_Events_Invoke_Targets'))
# Able to match a wildcard string resource
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Resource' => '*'))
# Do not match a wildcard when using strings
refute(AwsIamPolicy.new('test-policy-2').has_statement?('Resource' => 'arn:aws:events:us-east-1:123456789012:rule/my-rule'))
# Do match when using a regex
assert(AwsIamPolicy.new('test-policy-2').has_statement?('Resource' => /AWS_Events_Invoke_Targets$/))
# Able to match one resource when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Resource' => 'arn:aws:ec2:::*'))
# Do not match if only one resource specified as an array when the statement has an array of resources
refute(AwsIamPolicy.new('test-policy-1').has_statement?('Resource' => ['arn:aws:ec2:::*']))
# Do match if two resources specified when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Resource' => ['arn:aws:ec2:::*', '*']))
# Do match setwise if two resources specified when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Resource' => ['*', 'arn:aws:ec2:::*']))
# Do match if only one regex resource specified when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Resource' => /^arn\:aws\:ec2/))
# Do match if one regex resource specified in an array when the statement has an array of resources
assert(AwsIamPolicy.new('test-policy-1').has_statement?('Resource' => [/\*/]))
end
end end
#=============================================================================# #=============================================================================#
@ -158,7 +279,7 @@ module MAIPSB
OpenStruct.new({ OpenStruct.new({
policy_name: 'test-policy-2', policy_name: 'test-policy-2',
arn: 'arn:aws:iam::aws:policy/test-policy-2', arn: 'arn:aws:iam::aws:policy/test-policy-2',
default_version_id: 'v2', default_version_id: 'v1',
attachment_count: 0, attachment_count: 0,
is_attachable: false, is_attachable: false,
}), }),
@ -197,5 +318,53 @@ module MAIPSB
} }
OpenStruct.new( policy[query[:policy_arn]] ) OpenStruct.new( policy[query[:policy_arn]] )
end end
def get_policy_version(query)
fixtures = {
'arn:aws:iam::aws:policy/test-policy-1' => {
'v1' => OpenStruct.new(
# This is the integration test fixture "beta"
# {
# "Version"=>"2012-10-17",
# "Statement"=> [
# {
# "Sid"=>"beta01",
# "Action"=>["ec2:DescribeSubnets", "ec2:DescribeSecurityGroups"],
# "Effect"=>"Deny",
# "Resource"=>["arn:aws:ec2:::*", "*"]
# }
# ]
# }
document: '%7B%0A%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%7B%0A%20%20%20%20%20%20%22Sid%22%3A%20%22beta01%22%2C%0A%20%20%20%20%20%20%22Action%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22ec2%3ADescribeSubnets%22%2C%0A%20%20%20%20%20%20%20%20%22ec2%3ADescribeSecurityGroups%22%0A%20%20%20%20%20%20%5D%2C%0A%20%20%20%20%20%20%22Effect%22%3A%20%22Deny%22%2C%0A%20%20%20%20%20%20%22Resource%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%22arn%3Aaws%3Aec2%3A%3A%3A%2A%22%2C%0A%20%20%20%20%20%20%20%20%22%2A%22%0A%20%20%20%20%20%20%5D%0A%20%20%20%20%7D%0A%20%20%5D%0A%7D%0A'
)
},
'arn:aws:iam::aws:policy/test-policy-2' => {
'v1' => OpenStruct.new(
# This is AWS-managed CloudWatchEventsFullAccess
# {
# "Version"=>"2012-10-17",
# "Statement"=> [
# {
# "Sid"=>"CloudWatchEventsFullAccess",
# "Effect"=>"Allow",
# "Action"=>"events:*",
# "Resource"=>"*"
# },
# {
# "Sid"=>"IAMPassRoleForCloudWatchEvents",
# "Effect"=>"Allow",
# "Action"=>"iam:PassRole",
# "Resource"=>"arn:aws:iam::*:role/AWS_Events_Invoke_Targets"
# }
# ]
# }
document: '%7B%0A%20%20%20%20%22Version%22%3A%20%222012-10-17%22%2C%0A%20%20%20%20%22Statement%22%3A%20%5B%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Sid%22%3A%20%22CloudWatchEventsFullAccess%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Action%22%3A%20%22events%3A%2A%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Resource%22%3A%20%22%2A%22%0A%20%20%20%20%20%20%20%20%7D%2C%0A%20%20%20%20%20%20%20%20%7B%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Sid%22%3A%20%22IAMPassRoleForCloudWatchEvents%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Effect%22%3A%20%22Allow%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Action%22%3A%20%22iam%3APassRole%22%2C%0A%20%20%20%20%20%20%20%20%20%20%20%20%22Resource%22%3A%20%22arn%3Aaws%3Aiam%3A%3A%2A%3Arole%2FAWS_Events_Invoke_Targets%22%0A%20%20%20%20%20%20%20%20%7D%0A%20%20%20%20%5D%0A%7D'
)
}
}
pv = fixtures.dig(query[:policy_arn], query[:version_id])
return OpenStruct.new(policy_version: pv) if pv
raise Aws::IAM::Errors::NoSuchEntity.new(nil, nil)
end
end end
end end