mirror of
https://github.com/inspec/inspec
synced 2024-11-10 15:14:23 +00:00
New Resource aws_config_recorder (#2635)
* Initial commit of new resource * Removes deprecated matcher in example * Adds a new terraform file for config resources * Fixes and clarifies documentation * Wraps calls to api in catch_aws_errors method * Changes the names of two matchers Signed-off-by: Matthew Dromazos <dromazmj@dukes.jmu.edu>
This commit is contained in:
parent
4e6b3bb1ba
commit
4394c5efc8
6 changed files with 411 additions and 0 deletions
71
docs/resources/aws_config_recorder.md.erb
Normal file
71
docs/resources/aws_config_recorder.md.erb
Normal file
|
@ -0,0 +1,71 @@
|
|||
---
|
||||
title: About the aws_config_recorder Resource
|
||||
---
|
||||
|
||||
# aws\_config\_recorder
|
||||
|
||||
Use the `aws_config_recorder` InSpec audit resource to test properties of your AWS Config Service.
|
||||
|
||||
The AWS Config service can monitor and record changes to your AWS resource configurations. The Aws Config Recorder is used to detect changes in resource configurations and capture these changes as configuration items.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_config_recorder` resource block declares the tests for a single AWS configuration recorder.
|
||||
|
||||
describe aws_config_recorder('my_recorder') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe aws_config_recorder(recorder_name: 'my-recorder') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test if the recorder is active and recording.
|
||||
|
||||
describe aws_config_recorder(recorder_name: 'my-recorder') do
|
||||
it { should be_recording }
|
||||
end
|
||||
|
||||
## Properties
|
||||
|
||||
### role\_arn
|
||||
|
||||
Provides the IAM role arn associated with the configuration recorder. The role is used to grant permissions to S3 Buckets, SNS topics and to get configuration details for supported AWS resources.
|
||||
|
||||
describe aws_config_recorder(username: 'bob')
|
||||
its('role_arn') { should eq 'arn:aws:iam::721741954427:role/My_Recorder' }
|
||||
end
|
||||
|
||||
### resource\_types
|
||||
|
||||
Provides a list of AWS resource types for which the AWS Config records configuration will change. Note that if be_recording_all_resource_types is true than this property is meaningless and will return and empty array.
|
||||
|
||||
describe aws_config_recorder(username: 'bob')
|
||||
its('resource_types') { should include 'AWS::EC2::CustomerGateway' }
|
||||
its('resource_types') { should include 'AWS::EC2::EIP' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
### be\_recording\_all\_resource\_types
|
||||
|
||||
Indicates if the ConfigurationRecorder will record changes for all resources, regardless of type. If this is true, resource_types is ignored.
|
||||
|
||||
it { should be_all_supported }
|
||||
|
||||
### be\_recording\_all\_global\_types
|
||||
|
||||
Indicates whether the ConfigurationRecorder will record changes for global resource types (such as IAM Users).
|
||||
|
||||
it { should be_recording_all_global_types }
|
||||
|
|
@ -16,6 +16,7 @@ require 'resources/aws/aws_cloudtrail_trail'
|
|||
require 'resources/aws/aws_cloudtrail_trails'
|
||||
require 'resources/aws/aws_cloudwatch_alarm'
|
||||
require 'resources/aws/aws_cloudwatch_log_metric_filter'
|
||||
require 'resources/aws/aws_config_recorder'
|
||||
require 'resources/aws/aws_ec2_instance'
|
||||
require 'resources/aws/aws_iam_access_key'
|
||||
require 'resources/aws/aws_iam_access_keys'
|
||||
|
|
98
lib/resources/aws/aws_config_recorder.rb
Normal file
98
lib/resources/aws/aws_config_recorder.rb
Normal file
|
@ -0,0 +1,98 @@
|
|||
class AwsConfigurationRecorder < Inspec.resource(1)
|
||||
name 'aws_config_recorder'
|
||||
desc 'Verifies settings for AWS Configuration Recorder'
|
||||
example "
|
||||
describe aws_config_recorder('My_Recorder') do
|
||||
it { should exist }
|
||||
it { should be_recording }
|
||||
it { should be_all_supported }
|
||||
it { should have_include_global_resource_types }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :role_arn, :resource_types, :recorder_name, :resp
|
||||
|
||||
def to_s
|
||||
"Configuration_Recorder: #{@recorder_name}"
|
||||
end
|
||||
|
||||
def recording_all_resource_types?
|
||||
@recording_all_resource_types
|
||||
end
|
||||
|
||||
def recording_all_global_types?
|
||||
@recording_all_global_types
|
||||
end
|
||||
|
||||
def status
|
||||
return unless @exists
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
catch_aws_errors do
|
||||
@resp = backend.describe_configuration_recorder_status(@query)
|
||||
@status = @resp.configuration_recorders_status.first.to_h
|
||||
end
|
||||
end
|
||||
|
||||
def recording?
|
||||
return unless @exists
|
||||
status[:recording]
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:recorder_name],
|
||||
allowed_scalar_name: :recorder_name,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
# Must give it a recorder_name
|
||||
if validated_params[:recorder_name].nil?
|
||||
raise ArgumentError, 'You must provide recorder_name to aws_config_recorder'
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@query = { configuration_recorder_names: [@recorder_name] }
|
||||
|
||||
catch_aws_errors do
|
||||
begin
|
||||
@resp = backend.describe_configuration_recorders(@query)
|
||||
rescue Aws::ConfigService::Errors::NoSuchConfigurationRecorderException
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
@exists = !@resp.empty?
|
||||
return unless @exists
|
||||
|
||||
@recorder = @resp.configuration_recorders.first.to_h
|
||||
@recorder_name = @recorder[:name]
|
||||
@role_arn = @recorder[:role_arn]
|
||||
@recording_all_resource_types = @recorder[:recording_group][:all_supported]
|
||||
@recording_all_global_types = @recorder[:recording_group][:include_global_resource_types]
|
||||
@resource_types = @recorder[:recording_group][:resource_types]
|
||||
end
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::ConfigService::Client
|
||||
|
||||
def describe_configuration_recorders(query)
|
||||
aws_service_client.describe_configuration_recorders(query)
|
||||
end
|
||||
|
||||
def describe_configuration_recorder_status(query)
|
||||
aws_service_client.describe_configuration_recorder_status(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
36
test/integration/aws/default/build/config.tf
Normal file
36
test/integration/aws/default/build/config.tf
Normal file
|
@ -0,0 +1,36 @@
|
|||
#======================================================#
|
||||
# Configuration Recorder
|
||||
#======================================================#
|
||||
|
||||
resource "aws_config_configuration_recorder" "config_recorder" {
|
||||
name = "config_recorder"
|
||||
role_arn = "${aws_iam_role.role_for_config_recorder.arn}"
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "role_for_config_recorder" {
|
||||
name = "role_for_config_recorder"
|
||||
|
||||
assume_role_policy = <<POLICY
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
{
|
||||
"Action": "sts:AssumeRole",
|
||||
"Principal": {
|
||||
"Service": "config.amazonaws.com"
|
||||
},
|
||||
"Effect": "Allow",
|
||||
"Sid": ""
|
||||
}
|
||||
]
|
||||
}
|
||||
POLICY
|
||||
}
|
||||
|
||||
output "role_for_config_recorder_arn" {
|
||||
value = "${aws_iam_role.role_for_config_recorder.arn}"
|
||||
}
|
||||
|
||||
output "config_recorder_name" {
|
||||
value = "${aws_config_configuration_recorder.config_recorder.name}"
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
fixtures = {}
|
||||
[
|
||||
'role_for_config_recorder_arn',
|
||||
'config_recorder_name',
|
||||
].each do |fixture_name|
|
||||
fixtures[fixture_name] = attribute(
|
||||
fixture_name,
|
||||
default: "default.#{fixture_name}",
|
||||
description: 'See ../build/config.tf',
|
||||
)
|
||||
end
|
||||
|
||||
#======================================================#
|
||||
# aws_config_recorder - Singular
|
||||
#======================================================#
|
||||
|
||||
#------------------- Recall / Miss -------------------#
|
||||
|
||||
control "aws_config_recorder recall" do
|
||||
# Test scalar param
|
||||
describe aws_config_recorder(fixtures['config_recorder_name']) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Test hash parameter
|
||||
describe aws_config_recorder(recorder_name: fixtures['config_recorder_name']) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Test recorder that doesnt exist
|
||||
describe aws_config_recorder(recorder_name: 'NonExistentRecorder') do
|
||||
it { should_not exist }
|
||||
end
|
||||
end
|
||||
|
||||
#------------------- Properties -------------------#
|
||||
control "aws_config_recorder properties" do
|
||||
describe aws_config_recorder(fixtures['config_recorder_name']) do
|
||||
its('recorder_name') { should eq fixtures['config_recorder_name'] }
|
||||
its('role_arn') { should eq fixtures['role_for_config_recorder_arn'] }
|
||||
its('resource_types') { should eq [] }
|
||||
end
|
||||
end
|
||||
|
||||
#------------------- Matchers -------------------#
|
||||
control "aws_config_recorder matchers" do
|
||||
describe aws_config_recorder(fixtures['config_recorder_name']) do
|
||||
it { should_not be_recording }
|
||||
it { should be_recording_all_resource_types }
|
||||
it { should_not be_recording_all_global_types }
|
||||
end
|
||||
end
|
153
test/unit/resources/aws_config_recorder_test.rb
Normal file
153
test/unit/resources/aws_config_recorder_test.rb
Normal file
|
@ -0,0 +1,153 @@
|
|||
# encoding: utf-8
|
||||
require 'helper'
|
||||
|
||||
# MCRSB = MockConfigRecorderSingleBackend
|
||||
# Abbreviation not used outside this file
|
||||
|
||||
#=============================================================================#
|
||||
# Constructor Tests
|
||||
#=============================================================================#
|
||||
class AwsConfigurationRecorderConstructorTest < Minitest::Test
|
||||
def setup
|
||||
AwsConfigurationRecorder::BackendFactory.select(AwsMCRSB::Basic)
|
||||
end
|
||||
|
||||
def test_constructor_expected_well_formed_args_scalar
|
||||
AwsConfigurationRecorder.new('default')
|
||||
end
|
||||
|
||||
def test_constructor_expected_well_formed_args_hash
|
||||
AwsConfigurationRecorder.new(recorder_name: 'default')
|
||||
end
|
||||
|
||||
def test_constructor_reject_no_params
|
||||
assert_raises(ArgumentError) { AwsConfigurationRecorder.new }
|
||||
end
|
||||
|
||||
def test_constructor_reject_unknown_resource_params
|
||||
assert_raises(ArgumentError) { AwsConfigurationRecorder.new(bla: 'blabla') }
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================#
|
||||
# Recall
|
||||
#=============================================================================#
|
||||
|
||||
class AwsConfigurationRecorderRecallTest < Minitest::Test
|
||||
def setup
|
||||
AwsConfigurationRecorder::BackendFactory.select(AwsMCRSB::Basic)
|
||||
end
|
||||
|
||||
def test_search_hit_via_scalar
|
||||
assert AwsConfigurationRecorder.new('default').exists?
|
||||
end
|
||||
|
||||
def test_search_hit_via_hash
|
||||
assert AwsConfigurationRecorder.new(recorder_name: 'default').exists?
|
||||
end
|
||||
|
||||
def test_search_miss_is_not_an_exception
|
||||
refute AwsConfigurationRecorder.new(recorder_name: 'NonExistentRecorder').exists?
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================#
|
||||
# properties
|
||||
#=============================================================================#
|
||||
|
||||
class AwsConfigurationRecorderPropertiesTest < Minitest::Test
|
||||
def setup
|
||||
AwsConfigurationRecorder::BackendFactory.select(AwsMCRSB::Basic)
|
||||
end
|
||||
|
||||
def test_property_recorder_name
|
||||
assert_equal('default', AwsConfigurationRecorder.new(recorder_name: 'default').recorder_name)
|
||||
end
|
||||
|
||||
def test_property_role_arn
|
||||
assert_equal('arn:aws:iam::721741954427:role/default', AwsConfigurationRecorder.new(recorder_name: 'default').role_arn)
|
||||
assert_nil(AwsConfigurationRecorder.new(recorder_name: 'NonExistentRecorder').role_arn)
|
||||
end
|
||||
|
||||
def test_property_resource_types
|
||||
assert_equal(['AWS::EC2::CustomerGateway', 'AWS::EC2::EIP'], AwsConfigurationRecorder.new(recorder_name: 'Recorder_2').resource_types)
|
||||
assert_nil(AwsConfigurationRecorder.new(recorder_name: 'NonExistentRecorder').resource_types)
|
||||
end
|
||||
end
|
||||
|
||||
#=============================================================================#
|
||||
# Test Matchers
|
||||
#=============================================================================#
|
||||
class AwsConfigurationRecorderPropertiesTest < Minitest::Test
|
||||
def test_matcher_all_supported
|
||||
assert AwsConfigurationRecorder.new(recorder_name: 'default').recording_all_resource_types?
|
||||
refute AwsConfigurationRecorder.new(recorder_name: 'Recorder_1').recording_all_resource_types?
|
||||
end
|
||||
|
||||
def test_matcher_has_include_global_resource_types
|
||||
assert AwsConfigurationRecorder.new(recorder_name: 'default').recording_all_global_types?
|
||||
refute AwsConfigurationRecorder.new(recorder_name: 'Recorder_1').recording_all_global_types?
|
||||
end
|
||||
|
||||
def test_matcher_recording
|
||||
assert AwsConfigurationRecorder.new(recorder_name: 'default').recording?
|
||||
refute AwsConfigurationRecorder.new(recorder_name: 'Recorder_1').recording?
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#=============================================================================#
|
||||
# Test Fixtures
|
||||
#=============================================================================#
|
||||
|
||||
module AwsMCRSB
|
||||
class Basic < AwsBackendBase
|
||||
def describe_configuration_recorders(query)
|
||||
recorders = {
|
||||
'default' => OpenStruct.new({
|
||||
:configuration_recorders => [
|
||||
name: "default",
|
||||
role_arn: "arn:aws:iam::721741954427:role/default",
|
||||
:recording_group => OpenStruct.new({
|
||||
all_supported: true,
|
||||
include_global_resource_types: true,
|
||||
resource_types: []
|
||||
}),
|
||||
]
|
||||
}),
|
||||
'Recorder_2' => OpenStruct.new({
|
||||
:configuration_recorders => [
|
||||
name: "Recorder_2",
|
||||
role_arn: "arn:aws:iam::721741954427:role/Recorder_1",
|
||||
:recording_group => OpenStruct.new({
|
||||
all_supported: false,
|
||||
include_global_resource_types: false,
|
||||
resource_types: ['AWS::EC2::CustomerGateway', 'AWS::EC2::EIP']
|
||||
}),
|
||||
]
|
||||
}),
|
||||
'empty' => {}
|
||||
}
|
||||
return recorders[query[:configuration_recorder_names][0]] unless recorders[query[:configuration_recorder_names][0]].nil?
|
||||
recorders['empty']
|
||||
end
|
||||
|
||||
def describe_configuration_recorder_status(query)
|
||||
recorders = {
|
||||
'default' => OpenStruct.new({
|
||||
:configuration_recorders_status => [
|
||||
recording: true,
|
||||
]
|
||||
}),
|
||||
'Recorder_1' => OpenStruct.new({
|
||||
:configuration_recorders_status => [
|
||||
recording: false,
|
||||
]
|
||||
}),
|
||||
'empty' => {}
|
||||
}
|
||||
return recorders[query[:configuration_recorder_names][0]] unless recorders[query[:configuration_recorder_names][0]].nil?
|
||||
raise Aws::ConfigService::Errors::NoSuchConfigurationRecorderException(nil, nil)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue