Improvements and matcher renaming on aws_iam_password_policy (#2638)

Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
Clinton Wolfe 2018-02-14 15:59:57 -05:00 committed by GitHub
parent 33787124a7
commit 6c0422fbf0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 127 additions and 51 deletions

View file

@ -11,17 +11,17 @@ Use the `aws_iam_password_policy` InSpec audit resource to test properties of th
## Syntax
An `aws_iam_password_policy` resource block takes no parameters, but uses several matchers.
An `aws_iam_password_policy` resource block takes no parameters. Several properties and matchers are available.
describe aws_iam_password_policy do
its('requires_lowercase_characters?') { should be true }
it { should require_lowercase_characters }
end
<br>
## Properties
* `allows_users_to_change_password?`, `expires_passwords`, `max_password_age`, `minimum_password_length`, `number_of_passwords_to_remember`, `prevents_password_reuse?`, `requires_lowercase_characters` , `requires_uppercase_characters?`, `requires_numbers?`, `requires_symbols?`
* `max_password_age_in_days`, `minimum_password_length`, `number_of_passwords_to_remember`
## Examples
@ -30,35 +30,35 @@ The following examples show how to use this InSpec audit resource.
### Test that the IAM Password Policy requires lowercase characters, uppercase characters, numbers, symbols, and a minimum length greater than eight
describe aws_iam_password_policy do
its('requires_lowercase_characters?') { should be true }
its('requires_uppercase_characters?') { should be true }
its('requires_numbers?') { should be true }
its('requires_symbols?') { should be true }
it { should require_lowercase_characters }
it { should require_uppercase_characters }
it { should require_symbols }
it { should require_numbers }
its('minimum_password_length') { should be > 8 }
end
### Test that the IAM Password Policy allows users to change their password
describe aws_iam_password_policy do
its('allows_user_to_change_password?') { should be true }
it { should allow_users_to_change_passwords }
end
### Test that the IAM Password Policy expires passwords
describe aws_iam_password_policy do
its('expires_passwords?') { should be true }
it { should expire_passwords }
end
### Test that the IAM Password Policy has a max password age
describe aws_iam_password_policy do
its('max_password_age') { should be > 90 * 86400 }
its('max_password_age_in_days') { should be 90 }
end
### Test that the IAM Password Policy prevents password reuse
describe aws_iam_password_policy do
its('prevents_password_reuse?') { should be true }
it { should prevent_password_reuse }
end
### Test that the IAM Password Policy requires users to remember 3 previous passwords
@ -71,4 +71,4 @@ The following examples show how to use this InSpec audit resource.
## Matchers
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
* `allows_users_to_change_passwords`, `expire_passwords`, `prevent_password_reuse`, `require_lowercase_characters` , `require_uppercase_characters`, `require_numbers`, `require_symbols`

View file

@ -17,11 +17,20 @@ EOX
# TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
def initialize(conn = nil)
catch_aws_errors do
iam_resource = conn ? conn.iam_resource : inspec_runner.backend.aws_resource(Aws::IAM::Resource, {})
@policy = iam_resource.account_password_policy
begin
if conn
# We're in a mocked unit test.
@policy = conn.iam_resource.account_password_policy
else
# Don't use the resource approach. It's a CRUD operation
# - if the policy does not exist, you get back a blank object to populate and save.
# Using the Client will throw an exception if no policy exists.
@policy = inspec_runner.backend.aws_client(Aws::IAM::Client).get_account_password_policy.password_policy
end
rescue Aws::IAM::Errors::NoSuchEntity
@policy = nil
end
end
rescue Aws::IAM::Errors::NoSuchEntity
@policy = nil
end
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
@ -49,54 +58,59 @@ EOX
inspec if respond_to?(:inspec)
end
def to_s
'IAM Password-Policy'
end
def exists?
!@policy.nil?
end
def requires_lowercase_characters?
@policy.require_lowercase_characters
end
def requires_uppercase_characters?
@policy.require_uppercase_characters
end
#-------------------------- Properties ----------------------------#
def minimum_password_length
@policy.minimum_password_length
end
def requires_numbers?
@policy.require_numbers
end
def requires_symbols?
@policy.require_symbols
end
def allows_users_to_change_password?
@policy.allow_users_to_change_password
end
def expires_passwords?
@policy.expire_passwords
end
def max_password_age
raise 'this policy does not expire passwords' unless expires_passwords?
def max_password_age_in_days
raise 'this policy does not expire passwords' unless expire_passwords?
@policy.max_password_age
end
def prevents_password_reuse?
!@policy.password_reuse_prevention.nil?
end
def number_of_passwords_to_remember
raise 'this policy does not prevent password reuse' \
unless prevents_password_reuse?
unless prevent_password_reuse?
@policy.password_reuse_prevention
end
def to_s
'IAM Password-Policy'
#-------------------------- Matchers ----------------------------#
[
:require_lowercase_characters,
:require_uppercase_characters,
:require_symbols,
:require_numbers,
:expire_passwords,
].each do |matcher_stem|
# Create our predicates (for example, 'require_symbols?')
stem_with_question_mark = (matcher_stem.to_s + '?').to_sym
define_method stem_with_question_mark do
@policy.send(matcher_stem)
end
# RSpec will expose that as (for example) `be_require_symbols`.
# To undo that, we have to make a matcher alias.
stem_with_be = ('be_' + matcher_stem.to_s).to_sym
RSpec::Matchers.alias_matcher matcher_stem, stem_with_be
end
# This one has an awkward name mapping
def allow_users_to_change_passwords?
@policy.allow_users_to_change_password
end
RSpec::Matchers.alias_matcher :allow_users_to_change_passwords, :be_allow_users_to_change_passwords
# This one has custom logic and renaming
def prevent_password_reuse?
!@policy.password_reuse_prevention.nil?
end
RSpec::Matchers.alias_matcher :prevent_password_reuse, :be_prevent_password_reuse
end

View file

@ -6,6 +6,21 @@ variable "login_profile_pgp_key" {
default = "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"
}
#======================================================#
# Accoount Password Policy
#======================================================#
# Only one of these is allowed
resource "aws_iam_account_password_policy" "fixture" {
minimum_password_length = 10
require_lowercase_characters = true
require_numbers = true
require_uppercase_characters = true
require_symbols = true
allow_users_to_change_password = true
max_password_age = 365
password_reuse_prevention = 7
}
#======================================================#
# IAM Users
#======================================================#

View file

@ -0,0 +1,33 @@
# There are other tests in the "minimal" test account.
#---------------------- Recall ------------------------#
# Password policy is a per-account singleton. If it's been configured, it exists.
control "aws_iam_password_policy existence" do
describe aws_iam_password_policy do
it { should exist }
end
end
#------------- Properties -------------#
control "aws_iam_password_policy properties" do
describe aws_iam_password_policy do
its('max_password_age_in_days') { should cmp 365 }
its('number_of_passwords_to_remember') { should cmp 7 }
end
end
#------------- Matchers - Positive Case -------------#
control "aws_iam_password_policy matchers" do
describe aws_iam_password_policy do
it { should require_lowercase_characters }
it { should require_uppercase_characters }
it { should require_numbers }
it { should require_symbols }
it { should allow_users_to_change_passwords }
it { should expire_passwords }
it { should prevent_password_reuse }
end
end

View file

@ -0,0 +1,14 @@
#---------------------- Recall ------------------------#
# Password policy is a per-account singleton. If it's been configured, it exists.
control "aws_iam_password_policy properties" do
describe aws_iam_password_policy do
it { should_not exist }
end
end
#------------- Properties - Negative Case -------------#
# No negative tests yet - we'd need a third account
#------------- Matchers - Negative Case -------------#
# No negative tests yet - we'd need a third account

View file

@ -30,7 +30,7 @@ class AwsIamPasswordPolicyTest < Minitest::Test
@mock_resource.expect :account_password_policy, @mock_policy
e = assert_raises Exception do
AwsIamPasswordPolicy.new(@mock_conn).max_password_age
AwsIamPasswordPolicy.new(@mock_conn).max_password_age_in_days
end
assert_equal e.message, 'this policy does not expire passwords'
@ -39,13 +39,13 @@ class AwsIamPasswordPolicyTest < Minitest::Test
def test_prevents_password_reuse_returns_true_when_not_nil
configure_policy_password_reuse_prevention(value: Object.new)
assert AwsIamPasswordPolicy.new(@mock_conn).prevents_password_reuse?
assert AwsIamPasswordPolicy.new(@mock_conn).prevent_password_reuse?
end
def test_prevents_password_reuse_returns_false_when_nil
configure_policy_password_reuse_prevention(value: nil)
refute AwsIamPasswordPolicy.new(@mock_conn).prevents_password_reuse?
refute AwsIamPasswordPolicy.new(@mock_conn).prevent_password_reuse?
end
def test_number_of_passwords_to_remember_throws_when_nil