mirror of
https://github.com/inspec/inspec
synced 2024-11-10 15:14:23 +00:00
Policy Statement Search capability for aws_iam_policy (#2918)
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
parent
ceec3fc66c
commit
7130a77c06
5 changed files with 563 additions and 5 deletions
|
@ -5,9 +5,9 @@ platform: aws
|
|||
|
||||
# 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.
|
||||
|
||||
|
@ -50,11 +50,22 @@ The following examples show how to use this InSpec audit resource.
|
|||
it { should be_attached }
|
||||
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>
|
||||
|
||||
## 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
|
||||
|
||||
|
@ -106,10 +117,38 @@ The 'default_version_id' value of the specified policy.
|
|||
its('default_version_id') { should cmp "v1" }
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
|
@ -142,3 +181,59 @@ The test will pass if the identified policy attached the specified role.
|
|||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should be_attached_to_role(ROLENAME) }
|
||||
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
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
require 'json'
|
||||
require 'set'
|
||||
require 'uri'
|
||||
|
||||
class AwsIamPolicy < Inspec.resource(1)
|
||||
name '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
|
||||
|
||||
EXPECTED_CRITERIA = %w{
|
||||
Action
|
||||
Effect
|
||||
Resource
|
||||
Sid
|
||||
}.freeze
|
||||
|
||||
UNIMPLEMENTED_CRITERIA = %w{
|
||||
Conditional
|
||||
NotAction
|
||||
NotPrincipal
|
||||
NotResource
|
||||
Principal
|
||||
}.freeze
|
||||
|
||||
def to_s
|
||||
"Policy #{@policy_name}"
|
||||
end
|
||||
|
@ -50,8 +69,133 @@ class AwsIamPolicy < Inspec.resource(1)
|
|||
attached_roles.include?(role_name)
|
||||
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
|
||||
|
||||
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)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
|
@ -113,6 +257,10 @@ class AwsIamPolicy < Inspec.resource(1)
|
|||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def get_policy_version(criteria)
|
||||
aws_service_client.get_policy_version(criteria)
|
||||
end
|
||||
|
||||
def list_policies(criteria)
|
||||
aws_service_client.list_policies(criteria)
|
||||
end
|
||||
|
|
|
@ -116,6 +116,99 @@ output "iam_group_administrators" {
|
|||
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
|
||||
#======================================================#
|
||||
|
|
|
@ -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
|
||||
describe aws_iam_policy("AWSSupportAccess") do
|
||||
it { should exist }
|
||||
|
@ -6,6 +18,10 @@ control "aws_iam_policy recall" do
|
|||
describe aws_iam_policy(policy_name: "AWSSupportAccess") do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe aws_iam_policy(fixtures['aws_iam_policy_alpha_name']) do
|
||||
it { should exist }
|
||||
end
|
||||
end
|
||||
|
||||
control "aws_iam_policy properties" do
|
||||
|
@ -17,6 +33,11 @@ control "aws_iam_policy properties" do
|
|||
its('attached_groups') { should be_empty }
|
||||
its('attached_roles') { should be_empty }
|
||||
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
|
||||
|
||||
control "aws_iam_policy matchers" do
|
||||
|
@ -26,4 +47,36 @@ control "aws_iam_policy matchers" do
|
|||
describe aws_iam_policy("AdministratorAccess") do
|
||||
it { should be_attached_to_user("test-fixture-maker") }
|
||||
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
|
|
@ -90,6 +90,20 @@ class AwsIamPolicyPropertiesTest < Minitest::Test
|
|||
assert_equal(['test-role'], AwsIamPolicy.new('test-policy-1').attached_roles)
|
||||
assert_nil(AwsIamPolicy.new(policy_name: 'non-existant').attached_roles)
|
||||
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
|
||||
|
||||
|
||||
|
@ -133,6 +147,113 @@ class AwsIamPolicyMatchersTest < Minitest::Test
|
|||
def test_matcher_attached_to_role_negative
|
||||
refute AwsIamPolicy.new('test-policy-2').attached_to_role?('test-role')
|
||||
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
|
||||
|
||||
#=============================================================================#
|
||||
|
@ -158,7 +279,7 @@ module MAIPSB
|
|||
OpenStruct.new({
|
||||
policy_name: 'test-policy-2',
|
||||
arn: 'arn:aws:iam::aws:policy/test-policy-2',
|
||||
default_version_id: 'v2',
|
||||
default_version_id: 'v1',
|
||||
attachment_count: 0,
|
||||
is_attachable: false,
|
||||
}),
|
||||
|
@ -197,5 +318,53 @@ module MAIPSB
|
|||
}
|
||||
OpenStruct.new( policy[query[:policy_arn]] )
|
||||
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
|
Loading…
Reference in a new issue