mirror of
https://github.com/inspec/inspec
synced 2024-11-27 07:00:39 +00:00
Merge pull request #2655 from chef/release-2.0
Add in release-2.0 changes to master
This commit is contained in:
commit
fc990346f2
210 changed files with 14055 additions and 942 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
@ -28,3 +28,8 @@ www/Gemfile.lock
|
|||
|
||||
meta-profile-0.2.0.tar.gz
|
||||
profile-1.0.0.tar.gz
|
||||
.attribute.yml
|
||||
.terraform/
|
||||
terraform.tfstate*
|
||||
terraform.tfstate.backup
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ sudo: required
|
|||
branches:
|
||||
only:
|
||||
- master
|
||||
- release-2.0
|
||||
language: ruby
|
||||
cache: bundler
|
||||
dist: trusty
|
||||
|
@ -17,6 +18,7 @@ matrix:
|
|||
include:
|
||||
- rvm: 2.3.6
|
||||
- rvm: 2.4.3
|
||||
- rvm: 2.5.0
|
||||
- rvm: 2.4.3
|
||||
script: bundle exec rake $SUITE
|
||||
env: SUITE="test:functional"
|
||||
|
@ -65,6 +67,7 @@ matrix:
|
|||
script: ./support/ci/deploy_website_to_acceptance.sh
|
||||
|
||||
allow_failures:
|
||||
- rvm: 2.5.0
|
||||
- env:
|
||||
- AFFECTED_DIRS="www"
|
||||
- secure: "jdzXUhP1o7RkfSikZLKgUcCIaKqLjqWa35dnxWnz7qAQ2draRKa7I7cXmUv76BZkW8HBUUH11dOi8YOVxPYPOzaqvcTCfqNqGVxsT9epgWa7rA8aXMXkECp548ry1rYJQpti9zpwsoe2GQyNPr9vNiWMiyj51CaABmZ6JzmFEEqlZc8vqpqWeqJvIqaibQGk7ByLKmi4R44fVwFKIG39RuxV+alc/G4nnQ2zmNTFuy8uFGs5EghQvRytzWY+s2AKtDiZ0YXYOII1Nl1unXNnNoQt9oI209ztlSm1+XOuTPelW6bEIx5i7OZFaSRPgJzWnkGN85C9nBE08L2az9Jz18/rYJF4fdVRttdGskueyYI21lh1FwlAg51ZG0RfLTYk2Pq+k4c+NO1cfmGcaXBwihfD5BWqrILU5HHkYszXCSmgl4hscC7/BS4Kgcq2z32JJwV8B+x4XngM0G4uzIn1Soia3lZXEKdnfVsxFDdMQ7FK60F3uQlq/44LRkZujRhqfAKOiz+0tsLexWzj7wK+DJY9Y00CUfh7xcxRxDxFNpOv1FWYFB9lUlaOt3HDHgUoksqbURiUzhOZZzTE/1MAtF2K6mbpME5CbN08J88L5JBlb+CX79XCzj30lNMeS0I/dCRQEmkygr2eJYxvRO2qsBNuphs4SWk8NZyS/llVZFI="
|
||||
|
|
4
Gemfile
4
Gemfile
|
@ -8,6 +8,7 @@ if Gem::Version.new(RUBY_VERSION) < Gem::Version.new('2.2.2')
|
|||
end
|
||||
|
||||
gem 'ffi', '>= 1.9.14'
|
||||
gem 'aws-sdk', '~> 2'
|
||||
|
||||
group :test do
|
||||
gem 'bundler', '~> 1.5'
|
||||
|
@ -15,11 +16,12 @@ group :test do
|
|||
gem 'rake', '>= 10'
|
||||
gem 'rubocop', '= 0.49.1'
|
||||
gem 'simplecov', '~> 0.10'
|
||||
gem 'concurrent-ruby', '~> 0.9'
|
||||
gem 'concurrent-ruby', '~> 1.0'
|
||||
gem 'mocha', '~> 1.1'
|
||||
gem 'ruby-progressbar', '~> 1.8'
|
||||
gem 'webmock', '~> 2.3.2'
|
||||
gem 'jsonschema', '~> 2.0.2'
|
||||
gem 'passgen'
|
||||
gem 'm'
|
||||
end
|
||||
|
||||
|
|
25
README.md
25
README.md
|
@ -242,6 +242,18 @@ inspec exec test.rb --sudo [--sudo-password ...] [--sudo-options ...] [--sudo_co
|
|||
|
||||
# run in a subshell
|
||||
inspec exec test.rb --shell [--shell-options ...] [--shell-command ...]
|
||||
|
||||
# run a profile targeting AWS using env vars
|
||||
inspec exec test.rb -t aws://
|
||||
|
||||
# or store your AWS credentials in your ~/.aws/credentials profiles file
|
||||
inspec exec test.rb -t aws://us-east-2/my-profile
|
||||
|
||||
# run a profile targeting Azure using env vars
|
||||
inspec exec test.rb -t azure://
|
||||
|
||||
# or store your Azure credentials in your ~/.azure/credentials profiles file
|
||||
inspec exec test.rb -t azure://subscription_id
|
||||
```
|
||||
|
||||
### detect
|
||||
|
@ -322,6 +334,7 @@ You may also [browse the Supermarket for shared Compliance Profiles](https://sup
|
|||
|
||||
InSpec is inspired by the wonderful [Serverspec](http://serverspec.org) project. Kudos to [mizzy](https://github.com/mizzy) and [all contributors](https://github.com/mizzy/serverspec/graphs/contributors)!
|
||||
|
||||
The AWS resources were inspired by [inspec-aws](https://github.com/arothian/inspec-aws) from [arothian](https://github.com/arothian).
|
||||
|
||||
## Contribute
|
||||
|
||||
|
@ -339,10 +352,11 @@ The InSpec community and maintainers are very active and helpful. This project b
|
|||
|
||||
## Testing InSpec
|
||||
|
||||
We perform `unit` and `integration` tests.
|
||||
We offer `unit`, `integration`, and `aws` tests.
|
||||
|
||||
- `unit` tests ensure the intended behaviour of the implementation
|
||||
- `integration` tests run against Docker-based VMs via test-kitchen and [kitchen-inspec](https://github.com/chef/kitchen-inspec)
|
||||
- `aws` tests exercise the AWS resources against real AWS accounts
|
||||
|
||||
### Unit tests
|
||||
|
||||
|
@ -360,7 +374,6 @@ You may also run a single test within a file by line number:
|
|||
|
||||
```bash
|
||||
bundle exec m test/unit/resources/user_test.rb -l 123
|
||||
```
|
||||
|
||||
### Integration tests
|
||||
|
||||
|
@ -396,14 +409,20 @@ You may test all instances in parallel with:
|
|||
bundle exec kitchen test -c
|
||||
```
|
||||
|
||||
### AWS Tests
|
||||
|
||||
Use the rake task `bundle exec rake test:aws` to test the AWS resources against a pair of real AWS accounts.
|
||||
|
||||
Please see TESTING_AGAINST_AWS.md for details on how to setup the needed AWS accounts to perform testing.
|
||||
|
||||
## License
|
||||
|
||||
| | |
|
||||
| ------ | --- |
|
||||
| **Author:** | Dominik Richter (<drichter@chef.io>) |
|
||||
| **Author:** | Christoph Hartmann (<chartmann@chef.io>) |
|
||||
| **Copyright:** | Copyright (c) 2015 Chef Software Inc. |
|
||||
| **Copyright:** | Copyright (c) 2015 Vulcano Security GmbH. |
|
||||
| **Copyright:** | Copyright (c) 2017 Chef Software Inc. |
|
||||
| **License:** | Apache License, Version 2.0 |
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
|
|
117
Rakefile
117
Rakefile
|
@ -4,9 +4,16 @@
|
|||
require 'bundler'
|
||||
require 'bundler/gem_tasks'
|
||||
require 'rake/testtask'
|
||||
require 'passgen'
|
||||
require 'train'
|
||||
require_relative 'tasks/maintainers'
|
||||
require_relative 'tasks/spdx'
|
||||
|
||||
def prompt(message)
|
||||
print(message)
|
||||
STDIN.gets.chomp
|
||||
end
|
||||
|
||||
# The docs tasks rely on ruby-progressbar. If we can't load it, then don't
|
||||
# load the docs tasks. This is necessary to allow this Rakefile to work
|
||||
# when the "tests" gem group in the Gemfile has been excluded, such as
|
||||
|
@ -84,6 +91,116 @@ namespace :test do
|
|||
|
||||
sh('sh', '-c', sh_cmd)
|
||||
end
|
||||
|
||||
project_dir = File.dirname(__FILE__)
|
||||
namespace :aws do
|
||||
['default', 'minimal'].each do |account|
|
||||
integration_dir = File.join(project_dir, 'test', 'aws', account)
|
||||
attribute_file = File.join(integration_dir, '.attribute.yml')
|
||||
|
||||
task :"setup:#{account}", :tf_workspace do |t, args|
|
||||
tf_workspace = args[:tf_workspace] || ENV['INSPEC_TERRAFORM_ENV']
|
||||
abort("You must either call the top-level test:aws:#{account} task, or set the INSPEC_TERRAFORM_ENV variable.") unless tf_workspace
|
||||
puts "----> Setup"
|
||||
abort("You must set the environment variable AWS_REGION") unless ENV['AWS_REGION']
|
||||
puts "----> Checking for required AWS profile..."
|
||||
sh("aws configure get aws_access_key_id --profile inspec-aws-test-#{account} > /dev/null")
|
||||
sh("cd #{integration_dir}/build/ && terraform init")
|
||||
sh("cd #{integration_dir}/build/ && terraform workspace new #{tf_workspace}")
|
||||
sh("cd #{integration_dir}/build/ && AWS_PROFILE=inspec-aws-test-#{account} terraform plan")
|
||||
sh("cd #{integration_dir}/build/ && AWS_PROFILE=inspec-aws-test-#{account} terraform apply")
|
||||
Rake::Task["test:aws:dump_attrs:#{account}"].execute
|
||||
end
|
||||
|
||||
task :"dump_attrs:#{account}" do
|
||||
sh("cd #{integration_dir}/build/ && AWS_PROFILE=inspec-aws-test-#{account} terraform output > #{attribute_file}")
|
||||
raw_output = File.read(attribute_file)
|
||||
yaml_output = raw_output.gsub(" = ", " : ")
|
||||
File.open(attribute_file, "w") {|file| file.puts yaml_output}
|
||||
end
|
||||
|
||||
task :"run:#{account}" do
|
||||
puts "----> Run"
|
||||
sh("bundle exec inspec exec #{integration_dir}/verify -t aws://${AWS_REGION}/inspec-aws-test-#{account} --attrs #{attribute_file}")
|
||||
end
|
||||
|
||||
task :"cleanup:#{account}", :tf_workspace do |t, args|
|
||||
tf_workspace = args[:tf_workspace] || ENV['INSPEC_TERRAFORM_ENV']
|
||||
abort("You must either call the top-level test:aws:#{account} task, or set the INSPEC_TERRAFORM_ENV variable.") unless tf_workspace
|
||||
puts "----> Cleanup"
|
||||
sh("cd #{integration_dir}/build/ && AWS_PROFILE=inspec-aws-test-#{account} terraform destroy -force")
|
||||
sh("cd #{integration_dir}/build/ && terraform workspace select default")
|
||||
sh("cd #{integration_dir}/build && terraform workspace delete #{tf_workspace}")
|
||||
end
|
||||
|
||||
task :"#{account}" do
|
||||
tf_workspace = ENV['INSPEC_TERRAFORM_ENV'] || prompt("Please enter a workspace for your integration tests to run in: ")
|
||||
begin
|
||||
Rake::Task["test:aws:setup:#{account}"].execute({:tf_workspace => tf_workspace})
|
||||
Rake::Task["test:aws:run:#{account}"].execute
|
||||
rescue
|
||||
abort("Integration testing has failed for the #{account} account")
|
||||
ensure
|
||||
Rake::Task["test:aws:cleanup:#{account}"].execute({:tf_workspace => tf_workspace})
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
desc "Perform AWS Integration Tests"
|
||||
task aws: [:'aws:default', :'aws:minimal']
|
||||
|
||||
namespace :azure do
|
||||
# Specify the directory for the integration tests
|
||||
integration_dir = 'test/azure'
|
||||
|
||||
|
||||
task :init_workspace do
|
||||
# Initialize terraform workspace
|
||||
sh("cd #{integration_dir}/build/ && terraform init")
|
||||
end
|
||||
|
||||
task :setup_integration_tests do
|
||||
puts '----> Setup'
|
||||
creds = Train.create('azure').connection.connect
|
||||
|
||||
# Determine the storage account name and the admin password
|
||||
sa_name = (0...15).map { (65 + rand(26)).chr }.join.downcase
|
||||
admin_password = Passgen::generate(length: 12, uppercase: true, lowercase: true, symbols: true, digits: true)
|
||||
|
||||
# Use the first 4 characters of the storage account to create a suffix
|
||||
suffix = sa_name[0..3]
|
||||
|
||||
# Create the plan that can be applied to Azure
|
||||
cmd = format("cd %s/build/ && terraform plan -var 'subscription_id=%s' -var 'client_id=%s' -var 'client_secret=%s' -var 'tenant_id=%s' -var 'storage_account_name=%s' -var 'admin_password=%s' -var 'suffix=%s' -out inspec-azure.plan", integration_dir, creds[:subscription_id], creds[:client_id], creds[:client_secret], creds[:tenant_id], sa_name, admin_password, suffix)
|
||||
sh(cmd)
|
||||
|
||||
# Apply the plan on Azure
|
||||
cmd = format("cd %s/build/ && terraform apply inspec-azure.plan", integration_dir)
|
||||
sh(cmd)
|
||||
end
|
||||
|
||||
task :run_integration_tests do
|
||||
puts '----> Run'
|
||||
sh("bundle exec inspec exec #{integration_dir}/verify -t azure://1e0b427a-d58b-494e-ae4f-ee558463ebbf")
|
||||
end
|
||||
|
||||
task :cleanup_integration_tests do
|
||||
puts '----> Cleanup'
|
||||
creds = Train.create('azure').connection.connect
|
||||
|
||||
cmd = format("cd %s/build/ && terraform destroy -force -var 'subscription_id=%s' -var 'client_id=%s' -var 'client_secret=%s' -var 'tenant_id=%s' -var 'admin_password=dummy' -var 'storage_account_name=dummy' -var 'suffix=dummy'", integration_dir, creds[:subscription_id], creds[:client_id], creds[:client_secret], creds[:tenant_id])
|
||||
sh(cmd)
|
||||
end
|
||||
end
|
||||
|
||||
desc "Perform Azure Integration Tests"
|
||||
task :azure do
|
||||
Rake::Task['test:azure:init_workspace'].execute
|
||||
Rake::Task['test:azure:cleanup_integration_tests'].execute
|
||||
Rake::Task['test:azure:setup_integration_tests'].execute
|
||||
Rake::Task['test:azure:run_integration_tests'].execute
|
||||
Rake::Task['test:azure:cleanup_integration_tests'].execute
|
||||
end
|
||||
end
|
||||
|
||||
# Print the current version of this gem or update it.
|
||||
|
|
108
TESTING_AGAINST_AWS.md
Normal file
108
TESTING_AGAINST_AWS.md
Normal file
|
@ -0,0 +1,108 @@
|
|||
# Testing Against AWS - Integration Testing
|
||||
|
||||
## Problem Statement
|
||||
|
||||
We want to be able to test AWS-related InSpec resources against AWS itself. This means we need to create constructs ("test fixtures") in AWS to examine using InSpec. For cost management, we also want to be able to destroy
|
||||
|
||||
## General Approach
|
||||
|
||||
We use Terraform to setup test fixtures in AWS, then run a defined set of InSpec controls against these (which should all pass), and finally tear down the test fixtures with Terraform. For fixtures that cannot be managed by Terraform, we manually setup fixtures using instructions below.
|
||||
|
||||
We use the AWS CLI credentials system to manage credentials.
|
||||
|
||||
|
||||
### Installing Terraform
|
||||
|
||||
Download [Terraform](https://www.terraform.io/downloads.html). We require at least v0.10 . To install and choose from multiple Terraform versions, consider using [tfenv](https://github.com/kamatama41/tfenv).
|
||||
|
||||
### Installing AWS CLI
|
||||
|
||||
Install the [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html). We will store profiles for testing in the `~/.aws/credentials` file.
|
||||
|
||||
## Limitations
|
||||
|
||||
There are some things that we can't (or very much shouldn't) do via Terraform - like manipulating the root account MFA settings.
|
||||
|
||||
Also, there are some singleton resources (such as the default VPC, or Config status) that we should not manipulate without consequences.
|
||||
|
||||
## Current Solution
|
||||
|
||||
Our solution is to create two AWS accounts, each dedicated to the task of integration testing inspec-aws.
|
||||
|
||||
In the "default" account, we setup all fixtures that can be handled by Terraform. For any remaining fixtures,
|
||||
such as enabling MFA on the root account, we manually set one value in the "default" account, and manually set the opposing value in the "minimal" account. This allows use to perform testing on any reachable resource or property, regardless of whether or not Terraform can manage it.
|
||||
|
||||
All tests (and test fixtures) that do not require special handling are placed in the "default" set. That includes both positive and negative checks.
|
||||
|
||||
Note that some tests will fail for the first day or two after you set up the accounts, due to the tests checking properties such as the last usage time of an access key, for example.
|
||||
|
||||
Additionally, the first time you run the tests, you will need to accept the user agreement in the AWS marketplace for the linux AMIs we use. You'll need to do it 4 times, once for each of debian and centos on the two accounts.
|
||||
|
||||
### Creating the Default account
|
||||
|
||||
Follow these instructions carefully. Do not perform any action not specified.
|
||||
|
||||
1. Create an AWS account. Make a note of the account email and root password in a secure secret storage system.
|
||||
2. Create an IAM user named `test-fixture-maker`.
|
||||
* Enable programmatic access (to generate an access key)
|
||||
* Direct-attach the policy AdministratorAccess
|
||||
* Note the access key and secret key ID that are generated.
|
||||
3. Using the aws command line tool, store the access key and secret key in a profile with a special name:
|
||||
`aws configure --profile inspec-aws-test-default`
|
||||
|
||||
#### Test Fixtures for the Default Account
|
||||
|
||||
1. As the root user, enable a virtual MFA device.
|
||||
2. Create an IAM user named 'test-user-last-key-use'.
|
||||
* Enable programmatic access (to generate an access key)
|
||||
* Note the access key and secret key ID that are generated.
|
||||
* Direct-attach the policy AmazonEC2ReadOnlyAccess
|
||||
* Using the AWS CLI and the credentials, execute the command `aws ec2 describe-instances`.
|
||||
* The goal here is to have an access key that was used at one point.
|
||||
|
||||
### Creating the Minimal Account
|
||||
|
||||
Follow these instructions carefully. Do not perform any action not specified.
|
||||
|
||||
1. Create an AWS account. Make a note of the account email and root password in a secure secret storage system.
|
||||
2. Create an IAM user named `test-fixture-maker`.
|
||||
* Enable programmatic access (to generate an access key)
|
||||
* Direct-attach the policy AdministratorAccess
|
||||
* Note the access key and secret key ID that are generated.
|
||||
3. Using the aws command line tool, store the access key and secret key in a profile with a special name:
|
||||
`aws configure --profile inspec-aws-test-minimal`
|
||||
|
||||
#### Test Fixtures for the Minimal Account
|
||||
|
||||
1. Create an Access Key for the root user. You do not have to save the access key.
|
||||
|
||||
## Running the integration tests
|
||||
|
||||
To run all AWS integration tests, run:
|
||||
|
||||
```
|
||||
bundle exec rake test:aws
|
||||
```
|
||||
|
||||
To run the tests against one account only:
|
||||
|
||||
```
|
||||
bundle exec rake test:aws:default
|
||||
```
|
||||
|
||||
or
|
||||
|
||||
```
|
||||
bundle exec rake test:aws:minimal
|
||||
```
|
||||
|
||||
Each account has separate tasks for setup, running the tests, and cleanup. You may run them separately:
|
||||
|
||||
```
|
||||
bundle exec rake test:aws:setup:default
|
||||
bundle exec rake test:aws:run:default
|
||||
bundle exec rake test:aws:cleanup:default
|
||||
```
|
||||
|
||||
|
||||
|
|
@ -6,13 +6,18 @@ environment:
|
|||
matrix:
|
||||
- ruby_version: "23"
|
||||
- ruby_version: "24"
|
||||
|
||||
- ruby_version: "25"
|
||||
clone_folder: c:\projects\inspec
|
||||
clone_depth: 1
|
||||
skip_tags: true
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- release-2.0
|
||||
|
||||
matrix:
|
||||
allow_failures:
|
||||
- ruby_version: "25"
|
||||
|
||||
cache:
|
||||
- vendor/bundle -> appveyor.yml
|
||||
|
|
117
docs/platforms.md
Normal file
117
docs/platforms.md
Normal file
|
@ -0,0 +1,117 @@
|
|||
# Using InSpec 2.0 with Platforms
|
||||
|
||||
New in InSpec 2.0, you may now use certain InSpec resources to audit properties of things that aren't individual machines - for example, an Amazon Web Services S3 bucket.
|
||||
|
||||
In the initial release of InSpec 2.0, support for selected AWS and Azure resources is included.
|
||||
|
||||
## AWS Platform Support
|
||||
|
||||
### Setting up AWS credentials for InSpec
|
||||
|
||||
InSpec uses the standard AWS authentication mechanisms. Typically, you will create an IAM user which will be used for auditing activities.
|
||||
|
||||
1. Create an IAM user in the AWS console, with your choice of username. Check the box marked "Programmatic Access."
|
||||
2. On the Permissions screen, choose Direct Attach. Select the AWS-managed IAM Profile named "ReadOnlyAccess." If you wish to restrict the user further, you may do so; see individual InSpec resources to identify which permissions are required.
|
||||
3. After the key is generated, record the Access Key ID and Secret Key.
|
||||
|
||||
#### Using Environment Variables to provide credentials
|
||||
|
||||
You may provide the credentials to InSpec by setting the following environment variables: `AWS_REGION`, `AWS_ACCESS_KEY_ID`, and `AWS_SECRET_KEY_ID`. You may also use `AWS_PROFILE`, or if you are using MFA, `AWS_SESSION_TOKEN`. See the [AWS Command Line Interface Docs](https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html) for details.
|
||||
|
||||
Once you have your environment variables set, you can verify your credentials by running:
|
||||
|
||||
```bash
|
||||
you$ inspec detect -t aws://
|
||||
|
||||
== Platform Details
|
||||
Name: aws
|
||||
Families: cloud, api
|
||||
```
|
||||
|
||||
#### Using the InSpec target option to provide credentials
|
||||
|
||||
Look for a file in your home directory named `~/.aws/credentials`. If it does not exist, create it. Choose a name for your profile; here, we're using the name 'auditing'. Add your credentials as a new profile, in INI format:
|
||||
|
||||
```
|
||||
[auditing]
|
||||
aws_access_key_id = AKIA....
|
||||
aws_secret_access_key = 1234....abcd
|
||||
```
|
||||
|
||||
You may now run InSpec using the `--target` / `-t` option, using the format `-t aws://region/profile`. For example, to connect to the Ohio region using a profile named 'auditing', use `-t aws://us-east-2/auditing`.
|
||||
|
||||
To verify your credentials,
|
||||
```bash
|
||||
you$ inspec detect -t aws://
|
||||
|
||||
== Platform Details
|
||||
Name: aws
|
||||
Families: cloud, api
|
||||
```
|
||||
|
||||
#### Verifying your credentials
|
||||
|
||||
To verify your credentials
|
||||
|
||||
## Azure Platform Support in InSpec 2.0
|
||||
|
||||
### Setting up Azure credentials for InSpec
|
||||
|
||||
To use InSpec Azure resources, you will need a Service Principal Name (SPN) to be created in the Azure subscription that is being audited.
|
||||
|
||||
This can be done on the command line or from the Azure Portal:
|
||||
|
||||
- Azure CLI: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal-cli
|
||||
- PowerShell: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-authenticate-service-principal
|
||||
- Azure Portal: https://docs.microsoft.com/en-us/azure/azure-resource-manager/resource-group-create-service-principal-portal
|
||||
|
||||
|
||||
The information from the SPN can be specified either in a file `~/.azure/credentials`, as environment variables, or using InSpec target URIs.
|
||||
|
||||
#### Using the Azure Credentials File
|
||||
|
||||
By default InSpec is configured to look at ~/.azure/credentials, and it should contain:
|
||||
|
||||
```
|
||||
[<SUBSCRIPTION_ID>]
|
||||
client_id = "<CLIENT_ID>"
|
||||
client_secret = "<CLIENT_SECRET>"
|
||||
tenant_id = "<TENANT_ID>"
|
||||
```
|
||||
|
||||
NOTE: In the Azure web portal, these values are labelled differently:
|
||||
* The client_id is referred to as the 'Application ID'
|
||||
* The client_secret is referred to as the 'Key (Password Type)'
|
||||
* The tenant_id is referred to as the 'Directory ID'
|
||||
|
||||
With the credentials are in place you may now execute InSpec:
|
||||
|
||||
```bash
|
||||
inspec exec my-inspec-profile -t azure://
|
||||
```
|
||||
|
||||
#### Using Environment variables
|
||||
|
||||
You may also set the Azure credentials via environment variables:
|
||||
|
||||
- `AZURE_SUBSCRIPTION_ID`
|
||||
- `AZURE_CLIENT_ID`
|
||||
- `AZURE_CLIENT_SECRET`
|
||||
- `AZURE_TENANT_ID`
|
||||
|
||||
For example:
|
||||
|
||||
```bash
|
||||
AZURE_SUBSCRIPTION_ID="2fbdbb02-df2e-11e6-bf01-fe55135034f3" \
|
||||
AZURE_CLIENT_ID="58dc4f6c-df2e-11e6-bf01-fe55135034f3" \
|
||||
AZURE_CLIENT_SECRET="Jibr4iwwaaZwBb6W" \
|
||||
AZURE_TENANT_ID="6ad89b58-df2e-11e6-bf01-fe55135034f3" inspec exec my-profile -t azure://
|
||||
```
|
||||
|
||||
#### Using InSpec Target Syntax
|
||||
|
||||
If you have created a `~/.azure/credentials` file as above, you may also use the InSpec command line `--target` / `-t` option to select a subscription ID. For example:
|
||||
|
||||
```bash
|
||||
inspec exec my-profile -t azure://2fbdbb02-df2e-11e6-bf01-fe55135034f3
|
||||
```
|
|
@ -4,7 +4,7 @@ title: About the audit_policy Resource
|
|||
|
||||
# audit_policy
|
||||
|
||||
Use the `audit_policy` Inspec audit resource to test auditing policies on the Windows platform. An auditing policy is a category of security-related events to be audited. Auditing is disabled by default and may be enabled for categories like account management, logon events, policy changes, process tracking, privilege use, system events, or object access. For each enabled auditing category property, the auditing level may be set to `No Auditing`, `Not Specified`, `Success`, `Success and Failure`, or `Failure`.
|
||||
Use the `audit_policy` InSpec audit resource to test auditing policies on the Windows platform. An auditing policy is a category of security-related events to be audited. Auditing is disabled by default and may be enabled for categories like account management, logon events, policy changes, process tracking, privilege use, system events, or object access. For each enabled auditing category property, the auditing level may be set to `No Auditing`, `Not Specified`, `Success`, `Success and Failure`, or `Failure`.
|
||||
|
||||
<br>
|
||||
|
||||
|
|
|
@ -1,116 +0,0 @@
|
|||
---
|
||||
title: About the auditd_rules Resource
|
||||
---
|
||||
|
||||
# auditd_rules
|
||||
|
||||
Use the `auditd_rules` InSpec audit resource to test the rules for logging that exist on the system. The `audit.rules` file is typically located under `/etc/audit/` and contains the list of rules that define what is captured in log files. This resource uses `auditctl` to query the run-time `auditd` rules setup, which may be different from `audit.rules`.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `auditd_rules` resource block declares one (or more) rules to be tested, and then what that rule should do. The syntax depends on the version of `audit`:
|
||||
|
||||
For `audit` >= 2.3:
|
||||
|
||||
describe auditd_rules do
|
||||
its('lines') { should contain_match(rule) }
|
||||
end
|
||||
|
||||
For `audit` < 2.3:
|
||||
|
||||
describe audit_daemon_rules do
|
||||
its("LIST_RULES") {
|
||||
rule
|
||||
}
|
||||
end
|
||||
|
||||
For example:
|
||||
|
||||
describe auditd_rules do
|
||||
its('LIST_RULES') { should eq [
|
||||
'exit,always syscall=rmdir,unlink',
|
||||
'exit,always auid=1001 (0x3e9) syscall=open',
|
||||
'exit,always watch=/etc/group perm=wa',
|
||||
'exit,always watch=/etc/passwd perm=wa',
|
||||
'exit,always watch=/etc/shadow perm=wa',
|
||||
'exit,always watch=/etc/sudoers perm=wa',
|
||||
'exit,always watch=/etc/secret_directory perm=r',
|
||||
] }
|
||||
end
|
||||
|
||||
or test that individual rules are defined:
|
||||
|
||||
describe auditd_rules do
|
||||
its('LIST_RULES') {
|
||||
should contain_match(/^exit,always watch=\/etc\/group perm=wa key=identity/)
|
||||
}
|
||||
its('LIST_RULES') {
|
||||
should contain_match(/^exit,always watch=\/etc\/passwd perm=wa key=identity/)
|
||||
}
|
||||
its('LIST_RULES') {
|
||||
should contain_match(/^exit,always watch=\/etc\/gshadow perm=wa key=identity/)
|
||||
}
|
||||
its('LIST_RULES') {
|
||||
should contain_match(/^exit,always watch=\/etc\/shadow perm=wa key=identity/)
|
||||
}
|
||||
its('LIST_RULES') {
|
||||
should contain_match(/^exit,always watch=\/etc\/security\/opasswd perm=wa key=identity/)
|
||||
}
|
||||
end
|
||||
|
||||
where each test must declare one (or more) rules to be tested.
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test if a rule contains a matching element that is identified by a regular expression
|
||||
|
||||
For `audit` >= 2.3:
|
||||
|
||||
describe auditd_rules do
|
||||
its('lines') { should contain_match(%r{-w /etc/ssh/sshd_config/}) }
|
||||
end
|
||||
|
||||
For `audit` < 2.3:
|
||||
|
||||
describe audit_daemon_rules do
|
||||
its("LIST_RULES") {
|
||||
should contain_match(/^exit,always arch=.*\
|
||||
key=time-change\
|
||||
syscall=adjtimex,settimeofday/)
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
### Query the audit daemon status
|
||||
|
||||
describe auditd_rules.status('backlog') do
|
||||
it { should cmp 0 }
|
||||
end
|
||||
|
||||
### Query properties of rules targeting specific syscalls or files
|
||||
|
||||
describe auditd_rules.syscall('open').action do
|
||||
it { should eq(['always']) }
|
||||
end
|
||||
|
||||
describe auditd_rules.key('sshd_config') do
|
||||
its('permissions') { should contain_match(/x/) }
|
||||
end
|
||||
|
||||
Filters may be chained. For example:
|
||||
|
||||
describe auditd_rules.syscall('open').action('always').list do
|
||||
it { should eq(['exit']) }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
140
docs/resources/aws_cloudtrail_trail.md.erb
Normal file
140
docs/resources/aws_cloudtrail_trail.md.erb
Normal file
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
title: About the aws_cloudtrail_trail Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_cloudtrail\_trail
|
||||
|
||||
Use the `aws_cloudtrail_trail` InSpec audit resource to test properties of a single AWS Cloudtrail Trail.
|
||||
|
||||
AWS CloudTrail is a service that enables governance, compliance, operational auditing, and risk auditing of your AWS account. With CloudTrail, you can log, continuously monitor, and retain account activity related to actions across your AWS infrastructure. CloudTrail provides event history of your AWS account activity, including actions taken through the AWS Management Console, AWS SDKs, command line tools, and other AWS services. This event history simplifies security analysis, resource change tracking, and troubleshooting.
|
||||
|
||||
Each AWS Cloudtrail Trail is uniquely identified by its `trail_name` or `trail_arn`.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_cloudtrail_trail` resource block identifies a trail by `trail_name`.
|
||||
|
||||
# Find a trail by name
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Hash syntax for trail name
|
||||
describe aws_cloudtrail_trail(trail_name: 'trail-name') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that the specified trail does exist
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### Test that the specified trail is encrypted using SSE-KMS
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should be_encrypted }
|
||||
end
|
||||
|
||||
### Test that the specified trail is a multi region trail
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should be_multi_region_trail }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `s3_bucket_name`, `trail_arn`, `cloud_watch_logs_role_arn`, `cloud_watch_logs_log_group_arn`, `kms_key_id`, `home_region`,
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### s3\_bucket\_name
|
||||
|
||||
Specifies the name of the Amazon S3 bucket designated for publishing log files.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
its('s3_bucket_name') { should cmp "s3-bucket-name" }
|
||||
end
|
||||
|
||||
### trail\_arn
|
||||
|
||||
The ARN identifier of the specified trail. An ARN uniquely identifies the trail within AWS.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
its('trail_arn') { should cmp "arn:aws:cloudtrail:us-east-1:484747447281:trail/trail-name" }
|
||||
end
|
||||
|
||||
### cloud\_watch\_logs\_role\_arn
|
||||
|
||||
Specifies the role for the CloudWatch Logs endpoint to assume to write to a user\'s log group.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
its('cloud_watch_logs_role_arn') { should include "arn:aws:iam:::role/CloudTrail_CloudWatchLogs_Role" }
|
||||
end
|
||||
|
||||
### cloud\_watch\_logs\_log\_group\_arn
|
||||
|
||||
Specifies a log group name using an Amazon Resource Name (ARN), a unique identifier that represents the log group to which CloudTrail logs will be delivered.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
its('cloud_watch_logs_log_group_arn') { should include "arn:aws:logs:us-east-1::log-group:test:*" }
|
||||
end
|
||||
|
||||
### kms\_key\_id
|
||||
|
||||
Specifies the KMS key ID to used to encrypt the logs delivered by CloudTrail.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
its('kms_key_id') { should include "key-arn" }
|
||||
end
|
||||
|
||||
### home\_region
|
||||
|
||||
Specifies the region in which the trail was created.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
its('home_region') { should include "us-east-1" }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### be\_multi\_region\_trail
|
||||
|
||||
The test will pass if the identified trail is a multi region trail.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should be_multi_region_trail }
|
||||
end
|
||||
|
||||
### be\_encrypted
|
||||
|
||||
The test will pass if the logs delivered by the identified trail is encrypted.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should be_encrypted }
|
||||
end
|
||||
|
||||
### be\_log\_file\_validation\_enabled
|
||||
|
||||
The test will pass if the identified trail has log file integrity validation is enabled.
|
||||
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should be_log_file_validation_enabled }
|
||||
end
|
81
docs/resources/aws_cloudtrail_trails.md.erb
Normal file
81
docs/resources/aws_cloudtrail_trails.md.erb
Normal file
|
@ -0,0 +1,81 @@
|
|||
---
|
||||
title: About the aws_cloudtrail_trails Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_cloudtrail\_trails
|
||||
|
||||
Use the `aws_cloudtrail_trails` InSpec audit resource to test properties of some or all AWS CloudTrail Trails.
|
||||
|
||||
AWS CloudTrail is a service that enables governance, compliance, operational auditing, and risk auditing of your AWS account. With CloudTrail, you can log, continuously monitor, and retain account activity related to actions across your AWS infrastructure. CloudTrail provides event history of your AWS account activity, including actions taken through the AWS Management Console, AWS SDKs, command line tools, and other AWS services. This event history simplifies security analysis, resource change tracking, and troubleshooting.
|
||||
|
||||
Each AWS CloudTrail Trails is uniquely identified by its trail name or trail arn.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_cloudtrail_trails` resource block collects a group of CloudTrail Trails and then tests that group.
|
||||
|
||||
# Verify the number of CloudTrail Trails in the AWS account
|
||||
describe aws_cloudtrail_trails do
|
||||
its('entries.count') { should cmp 10 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_cloudtrail_trails`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
* `entries`, `names`, `trail_arns`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### entries
|
||||
|
||||
Provides access to the raw results of the query. This can be useful for checking counts and other advanced operations.
|
||||
|
||||
# Allow at most 100 CloudTrail Trails on the account
|
||||
describe aws_cloudtrail_trails do
|
||||
its('entries.count') { should be <= 100}
|
||||
end
|
||||
|
||||
### names
|
||||
|
||||
Provides a list of trail names for all CloudTrail Trails in the AWS account.
|
||||
|
||||
describe aws_cloudtrail_trails do
|
||||
its('names') { should include('trail-1') }
|
||||
end
|
||||
|
||||
### trail\_arns
|
||||
|
||||
Provides a list of trail arns for all CloudTrail Trails in the AWS account.
|
||||
|
||||
describe aws_cloudtrail_trails do
|
||||
its('trail_arns') { should include('arn:aws:cloudtrail:us-east-1::trail/trail-1') }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# Verify that at least one CloudTrail Trail exists.
|
||||
describe aws_cloudtrail_trails
|
||||
it { should exist }
|
||||
end
|
||||
|
86
docs/resources/aws_cloudwatch_alarm.md.erb
Normal file
86
docs/resources/aws_cloudwatch_alarm.md.erb
Normal file
|
@ -0,0 +1,86 @@
|
|||
---
|
||||
title: About the aws_cloudwatch_alarm Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_cloudwatch\_alarm
|
||||
|
||||
Use the `aws_cloudwatch_alarm` InSpec audit resource to test properties of a single Cloudwatch Alarm.
|
||||
|
||||
Cloudwatch Alarms are currently identified using the metric name and metric namespace. Future work may allow other approaches to identifying alarms.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_cloudwatch_alarm` resource block searches for a Cloudwatch Alarm, specified by several search options. If more than one Alarm matches, an error occurs.
|
||||
|
||||
# Look for a specific alarm
|
||||
aws_cloudwatch_alarm(
|
||||
metric: 'my-metric-name',
|
||||
metric_namespace: 'my-metric-namespace',
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Ensure an Alarm has at least one alarm action
|
||||
|
||||
describe aws_cloudwatch_alarm(
|
||||
metric: 'my-metric-name',
|
||||
metric_namespace: 'my-metric-namespace',
|
||||
) do
|
||||
its('alarm_actions') { should_not be_empty }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `alarm_actions`
|
||||
|
||||
## Property Examples
|
||||
|
||||
### alarm\_actions
|
||||
|
||||
`alarm_actions` returns a list of strings. Each string is the ARN of an action that will be taken should the alarm be triggered.
|
||||
|
||||
# Ensure that the alarm has at least one action
|
||||
describe aws_cloudwatch_alarm(
|
||||
metric: 'bed-metric',
|
||||
metric_namespace: 'my-metric-namespace',
|
||||
) do
|
||||
its('alarm_actions') { should_not be_empty }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if a Cloudwatch Alarm could be found. Use `should_not` if you expect zero matches.
|
||||
|
||||
# Expect good metric
|
||||
describe aws_cloudwatch_alarm(
|
||||
metric: 'good-metric',
|
||||
metric_namespace: 'my-metric-namespace',
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Disallow alarms based on bad-metric
|
||||
describe aws_cloudwatch_alarm(
|
||||
metric: 'bed-metric',
|
||||
metric_namespace: 'my-metric-namespace',
|
||||
) do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
151
docs/resources/aws_cloudwatch_log_metric_filter.md.erb
Normal file
151
docs/resources/aws_cloudwatch_log_metric_filter.md.erb
Normal file
|
@ -0,0 +1,151 @@
|
|||
---
|
||||
title: About the aws_cloudwatch_log_metric_filter Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_cloudwatch\_log\_metric\_filter
|
||||
|
||||
Use the `aws_cloudwatch_log_metric_filter` InSpec audit resource to search for and test properties of individual AWS Cloudwatch Log Metric Filters.
|
||||
|
||||
A Log Metric Filter (LMF) is an AWS resource that observes log traffic, looks for a specified pattern, and updates a metric about the number times the match occurs. The metric can also be connected to AWS Cloudwatch Alarms, so that actions can be taken when a match occurs.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_cloudwatch_log_metric_filter` resource block searches for an LMF, specified by several search options. If more than one log metric filter matches, an error occurs.
|
||||
|
||||
# Look for a LMF by its filter name and log group name. This combination
|
||||
# will always either find at most one LMF - no duplicates.
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
filter_name: 'my-filter',
|
||||
log_group_name: 'my-log-group'
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Search for an LMF by pattern and log group.
|
||||
# This could result in an error if the results are not unique.
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
log_group_name: 'my-log-group',
|
||||
pattern: 'my-filter'
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Attributes
|
||||
|
||||
* `filter_name`, `log_group_name`, `pattern`
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Examples
|
||||
|
||||
### filter\_name
|
||||
|
||||
This is the identifier of the log metric filter within its log group. To ensure you have a unique result, you must also provide the `log_group_name`.
|
||||
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
filter_name: 'my-filter'
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### log\_group\_name
|
||||
|
||||
The name of the Cloudwatch Log Group that the LMF is watching. Together with `filter_name`, this uniquely identifies an LMF.
|
||||
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
log_group_name: 'my-log-group',
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### pattern
|
||||
|
||||
The filter pattern used to match entries from the logs in the log group.
|
||||
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
pattern: '"ERROR" - "Exiting"',
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `filter_name`, `log_group_name`,` metric_name`, `metric_namespace`, `pattern`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### filter\_name
|
||||
|
||||
The name of the LMF within the `log_group`.
|
||||
|
||||
# Check the name of the LMF that has a certain pattern
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
log_group_name: 'app-log-group',
|
||||
pattern: 'KERBLEWIE',
|
||||
) do
|
||||
its('filter_name') { should cmp 'kaboom_lmf' }
|
||||
end
|
||||
|
||||
### log\_group\_name
|
||||
|
||||
The name of the log group that the LMF is watching.
|
||||
|
||||
# Check which log group the LMF 'error-watcher' is watching
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
filter_name: 'error-watcher',
|
||||
) do
|
||||
its('log_group_name') { should cmp 'app-log-group' }
|
||||
end
|
||||
|
||||
### metric\_name, metric\_namespace
|
||||
|
||||
The name and namespace of the Cloudwatch Metric that will be updated when the LMF matches. You also need the `metric_namespace` to uniquely identify the metric.
|
||||
|
||||
# Ensure that the LMF has the right metric name
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
filter_name: 'my-filter',
|
||||
log_group_name: 'my-log-group',
|
||||
) do
|
||||
its('metric_name') { should cmp 'MyMetric' }
|
||||
its('metric_namespace') { should cmp 'MyFantasticMetrics' }
|
||||
end
|
||||
|
||||
### pattern
|
||||
|
||||
The pattern used to match entries from the logs in the log group.
|
||||
|
||||
# Ensure that the LMF is watching for errors
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
filter_name: 'error-watcher',
|
||||
log_group_name: 'app-log-group',
|
||||
) do
|
||||
its('pattern') { should cmp 'ERROR' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exist
|
||||
|
||||
Matches (i.e., passes the test) if the resource parameters (search criteria) were able to locate exactly one LMF.
|
||||
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
log_group_name: 'my-log-group',
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
|
||||
|
106
docs/resources/aws_ec2_instance.md.erb
Normal file
106
docs/resources/aws_ec2_instance.md.erb
Normal file
|
@ -0,0 +1,106 @@
|
|||
---
|
||||
title: About the aws_ec2_instance Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_ec2\_instance
|
||||
|
||||
Use the `aws_ec2_instance` InSpec audit resource to test properties of a single AWS EC2 instance.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_ec2_instance` resource block declares the tests for a single AWS EC2 instance by either name or id.
|
||||
|
||||
describe aws_ec2_instance('i-01a2349e94458a507') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe aws_ec2_instance(name: 'my-instance') do
|
||||
it { should be_running }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that an EC2 instance does not exist
|
||||
|
||||
describe aws_ec2_instance(name: 'dev-server') do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### Test that an EC2 instance is running
|
||||
|
||||
describe aws_ec2_instance(name: 'prod-database') do
|
||||
it { should be_running }
|
||||
end
|
||||
|
||||
### Test that an EC2 instance is using the correct image ID
|
||||
|
||||
describe aws_iam_instance(name: 'my-instance') do
|
||||
its('image_id') { should eq 'ami-27a58d5c' }
|
||||
end
|
||||
|
||||
### Test that an EC2 instance has the correct tag
|
||||
|
||||
describe aws_ec2_instance('i-090c29e4f4c165b74') do
|
||||
its('tags') { should include(key: 'Contact', value: 'Gilfoyle') }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `architecture`, `client_token`, `image_id`,`instance_type`, `key_name`, `launch_time`,`private_ip_address`, `private_dns_name`, `public_dns_name`, `public_ip_address`, `root_device_type`, `root_device_name`, `security_group_ids`, `subnet_id`, `tags`,`virtualization_type`, `vpc_id`
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### be\_pending
|
||||
|
||||
The `be\_pending` matcher tests if the described EC2 instance state is `pending`. This indicates that an instance is provisioning. This state should be temporary.
|
||||
|
||||
it { should be_pending }
|
||||
|
||||
### be\_running
|
||||
|
||||
The `be_running` matcher tests if the described EC2 instance state is `running`. This indicates the instance is fully operational from AWS's perspective.
|
||||
|
||||
it { should be_running }
|
||||
|
||||
### be\_shutting\_down
|
||||
|
||||
The `be_shutting_down` matcher tests if the described EC2 instance state is `shutting-down`. This indicates the instance has received a termination command and is in the process of being permanently halted and de-provisioned. This state should be temporary.
|
||||
|
||||
it { should be_shutting_down }
|
||||
|
||||
### be\_stopped
|
||||
|
||||
The `be_stopped` matcher tests if the described EC2 instance state is `stopped`. This indicates that the instance is suspended and may be started again.
|
||||
|
||||
it { should be_stopped }
|
||||
|
||||
### be\_stopping
|
||||
|
||||
The `be_stopping` matcher tests if the described EC2 instance state is `stopping`. This indicates that an AWS stop command has been issued, which will suspend the instance in an OS-unaware manner. This state should be temporary.
|
||||
|
||||
it { should be_stopping }
|
||||
|
||||
### be\_terminated
|
||||
|
||||
The `be_terminated` matcher tests if the described EC2 instance state is `terminated`. This indicates the instance is permanently halted and will be removed from the instance listing in a short period. This state should be temporary.
|
||||
|
||||
it { should be_terminated }
|
||||
|
||||
### be\_unknown
|
||||
|
||||
The `be_unknown` matcher tests if the described EC2 instance state is `unknown`. This indicates an error condition in the AWS management system. This state should be temporary.
|
||||
|
||||
it { should be_unknown }
|
123
docs/resources/aws_iam_access_key.md.erb
Normal file
123
docs/resources/aws_iam_access_key.md.erb
Normal file
|
@ -0,0 +1,123 @@
|
|||
---
|
||||
title: About the aws_iam_access_key Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_access\_key
|
||||
|
||||
Use the `aws_iam_access_key` InSpec audit resource to test properties of a single AWS IAM access key.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_access_key` resource block declares the tests for a single AWS IAM access key. An access key is uniquely identified by its access key id.
|
||||
|
||||
# This is unique - the key will either exist or it won't, but it will never be an error.
|
||||
describe aws_iam_access_key(access_key_id: 'AKIA12345678ABCD') do
|
||||
it { should exist }
|
||||
it { should_not be_active }
|
||||
its('create_date') { should be > Time.now - 365 * 86400 }
|
||||
its('last_used_date') { should be > Time.now - 90 * 86400 }
|
||||
end
|
||||
|
||||
# id is an alias for access_key_id
|
||||
describe aws_iam_access_key(id: 'AKIA12345678ABCD') do
|
||||
# Same
|
||||
end
|
||||
|
||||
|
||||
Access keys are associated with IAM users, who may have zero, one or two access keys. You may also lookup an access key by username. If the user has more than one access key, an error occurs (You may use `aws_iam_access_keys` with the `username` resource parameter to access a user's keys when they have multiple keys.)
|
||||
|
||||
# This is not unique. If the user has zero or one keys, it is not an error.
|
||||
# If they have two, it is an error.
|
||||
describe aws_iam_access_key(username: 'roderick') do
|
||||
it { should exist }
|
||||
it { should be_active }
|
||||
end
|
||||
|
||||
You may also use both username and access key id to ensure that a particular key is associated with a particular user.
|
||||
|
||||
describe aws_iam_access_key(username: 'roderick', access_key_id: 'AKIA12345678ABCD') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that an IAM access key is not active
|
||||
|
||||
describe aws_iam_access_key(username: 'username', id: 'access-key-id') do
|
||||
it { should_not be_active }
|
||||
end
|
||||
|
||||
### Test that an IAM access key is older than one year
|
||||
|
||||
describe aws_iam_access_key(username: 'username', id: 'access-key-id') do
|
||||
its('create_date') { should be > Time.now - 365 * 86400 }
|
||||
end
|
||||
|
||||
### Test that an IAM access key has been used in the past 90 days
|
||||
|
||||
describe aws_iam_access_key(username: 'username', id: 'access-key-id') do
|
||||
its('last_used_date') { should be > Time.now - 90 * 86400 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `access_key_id`, `create_date`, `last_used_date`, `username`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### access\_key\_id
|
||||
|
||||
The unique ID of this access key.
|
||||
|
||||
describe aws_iam_access_key(username: 'bob')
|
||||
its('access_key_id') { should cmp 'AKIA12345678ABCD' }
|
||||
end
|
||||
|
||||
### create\_date
|
||||
|
||||
The date and time, as a Ruby DateTime, at which the access key was created.
|
||||
|
||||
# Is the access key less than a year old?
|
||||
describe aws_iam_access_key(username: 'bob')
|
||||
its('create_date') { should be > Time.now - 365 * 86400 }
|
||||
end
|
||||
|
||||
### last\_used\_date
|
||||
|
||||
The date and time, as a Ruby DateTime, at which the access key was last_used.
|
||||
|
||||
# Has the access key been used in the last year?
|
||||
describe aws_iam_access_key(username: 'bob')
|
||||
its('last_used_date') { should be > Time.now - 365 * 86400 }
|
||||
end
|
||||
|
||||
### username
|
||||
|
||||
The IAM user that owns this key.
|
||||
|
||||
describe aws_iam_access_key(access_key_id: 'AKIA12345678ABCD')
|
||||
its('username') { should cmp 'bob' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### be\_active
|
||||
|
||||
The `be_active` matcher tests if the described IAM access key is active.
|
||||
|
||||
it { should be_active }
|
198
docs/resources/aws_iam_access_keys.md.erb
Normal file
198
docs/resources/aws_iam_access_keys.md.erb
Normal file
|
@ -0,0 +1,198 @@
|
|||
---
|
||||
title: About the aws_iam_access_keys Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_access\_keys
|
||||
|
||||
Use the `aws_iam_access_keys` InSpec audit resource to test properties of some or all IAM Access Keys.
|
||||
|
||||
To test properties of a single Access Key, use the `aws_iam_access_key` resource instead.
|
||||
To test properties of an individual user's access keys, use the `aws_iam_user` resource.
|
||||
|
||||
Access Keys are closely related to AWS User resources. Use this resource to perform audits of all keys or of keys specified by criteria unrelated to any particular user.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_access_keys` resource block uses an optional filter to select a group of access keys and then tests that group.
|
||||
|
||||
# Do not allow any access keys
|
||||
describe aws_iam_access_keys do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
# Don't let fred have access keys, using filter argument syntax
|
||||
describe aws_iam_access_keys.where(username: 'fred') do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
# Don't let fred have access keys, using filter block syntax (most flexible)
|
||||
describe aws_iam_access_keys.where { username == 'fred' } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Disallow access keys created more than 90 days ago
|
||||
|
||||
describe aws_iam_access_keys.where { created_age > 90 } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Criteria
|
||||
* `active`, `create_date`, `created_days_ago`, `created_hours_ago`, `created_with_user`, `ever_used`, `inactive`, `last_used_date`, `last_used_hours_ago`, `last_used_days_ago`, `never_used`, `user_created_date`
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Examples
|
||||
|
||||
### active
|
||||
|
||||
A true / false value indicating if an Access Key is currently "Active" (the normal state) in the AWS console. See also: `inactive`.
|
||||
|
||||
# Check if a particular key is enabled
|
||||
describe aws_iam_access_keys.where { active } do
|
||||
its('access_key_ids') { should include('AKIA1234567890ABCDEF')}
|
||||
end
|
||||
|
||||
### create\_date
|
||||
|
||||
A DateTime identifying when the Access Key was created. See also `created_days_ago` and `created_hours_ago`.
|
||||
|
||||
# Detect keys older than 2017
|
||||
describe aws_iam_access_keys.where { create_date < DateTime.parse('2017-01-01') } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### created\_days\_ago, created\_hours\_ago
|
||||
|
||||
An integer, representing how old the access key is.
|
||||
|
||||
# Don't allow keys that are older than 90 days
|
||||
describe aws_iam_access_keys.where { created_days_ago > 90 } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### created\_with\_user
|
||||
|
||||
A true / false value indicating if the Access Key was likely created at the same time as the user, by checking if the difference between created_date and user_created_date is less than 1 hour.
|
||||
|
||||
# Do not automatically create keys for users
|
||||
describe aws_iam_access_keys.where { created_with_user } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### ever\_used
|
||||
|
||||
A true / false value indicating if the Access Key has ever been used, based on the last_used_date. See also: `never_used`.
|
||||
|
||||
# Check to see if a particular key has ever been used
|
||||
describe aws_iam_access_keys.where { ever_used } do
|
||||
its('access_key_ids') { should include('AKIA1234567890ABCDEF')}
|
||||
end
|
||||
|
||||
### inactive
|
||||
|
||||
A true / false value indicating if the Access Key has been marked Inactive in the AWS console. See also: `active`.
|
||||
|
||||
# Don't leave inactive keys laying around
|
||||
describe aws_iam_access_keys.where { inactive } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### last\_used\_date
|
||||
|
||||
A DateTime identifying when the Access Key was last used. Returns nil if the key has never been used. See also: `ever_used`, `last_used_days_ago`, `last_used_hours_ago`, and `never_used`.
|
||||
|
||||
# No one should do anything on Mondays
|
||||
describe aws_iam_access_keys.where { ever_used and last_used_date.monday? } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### last\_used\_days\_ago, last\_used\_hours\_ago
|
||||
|
||||
An integer representing when the key was last used. See also: `ever_used`, `last_used_date`, and `never_used`.
|
||||
|
||||
# Don't allow keys that sit unused for more than 90 days
|
||||
describe aws_iam_access_keys.where { last_used_days_ago > 90 } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### never\_used
|
||||
|
||||
A true / false value indicating if the Access Key has never been used, based on the `last_used_date`. See also: `ever_used`.
|
||||
|
||||
# Don't allow unused keys to lay around
|
||||
describe aws_iam_access_keys.where { never_used } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### username
|
||||
|
||||
Searches for access keys owned by the named user. Each user may have zero, one, or two access keys.
|
||||
|
||||
describe aws_iam_access_keys(username: 'bob') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### user\_created\_date
|
||||
|
||||
The date at which the user was created.
|
||||
|
||||
# Users have to be a week old to have a key
|
||||
describe aws_iam_access_keys.where { user_created_date > Date.now - 7 }
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `access_key_ids`, `entries`
|
||||
|
||||
## Property Examples
|
||||
|
||||
### access\_key\_ids
|
||||
|
||||
Provides a list of all access key IDs matched.
|
||||
|
||||
describe aws_iam_access_keys do
|
||||
its('access_key_ids') { should include('AKIA1234567890ABCDEF') }
|
||||
end
|
||||
|
||||
### entries
|
||||
|
||||
Provides access to the raw results of the query. This can be useful for checking counts and other advanced operations.
|
||||
|
||||
# Allow at most 100 access keys on the account
|
||||
describe aws_iam_access_keys do
|
||||
its('entries.count') { should be <= 100}
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# Sally should have at least one access key
|
||||
describe aws_iam_access_keys.where(username: 'sally') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Don't let fred have access keys
|
||||
describe aws_iam_access_keys.where(username: 'fred') do
|
||||
it { should_not exist }
|
||||
end
|
46
docs/resources/aws_iam_group.md.erb
Normal file
46
docs/resources/aws_iam_group.md.erb
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: About the aws_iam_group Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_group
|
||||
|
||||
Use the `aws_iam_group` InSpec audit resource to test properties of a single IAM group.
|
||||
|
||||
To test properties of multiple or all groups, use the `aws_iam_groups` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_group` resource block identifies a group by group name.
|
||||
|
||||
# Find a group by group name
|
||||
describe aws_iam_group('mygroup') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Hash syntax for group name
|
||||
describe aws_iam_group(group_name: 'mygroup') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_iam_group`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if a group with the given group name exists.
|
||||
|
||||
describe aws_iam_group('mygroup')
|
||||
it { should exist }
|
||||
end
|
43
docs/resources/aws_iam_groups.md.erb
Normal file
43
docs/resources/aws_iam_groups.md.erb
Normal file
|
@ -0,0 +1,43 @@
|
|||
---
|
||||
title: About the aws_iam_groups Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_groups
|
||||
|
||||
Use the `aws_iam_groups` InSpec audit resource to test properties of all or multiple groups.
|
||||
|
||||
To test properties of a single group, use the `aws_iam_group` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_groups` resource block uses an optional filter to select a collection of IAM groups and then tests that collection.
|
||||
|
||||
# The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
describe aws_iam_groups do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_iam_groups`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
describe aws_iam_groups
|
||||
it { should exist }
|
||||
end
|
76
docs/resources/aws_iam_password_policy.md.erb
Normal file
76
docs/resources/aws_iam_password_policy.md.erb
Normal file
|
@ -0,0 +1,76 @@
|
|||
---
|
||||
title: About the aws_iam_password_policy Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_password\_policy
|
||||
|
||||
Use the `aws_iam_password_policy` InSpec audit resource to test properties of the AWS IAM Password Policy.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_password_policy` resource block takes no parameters. Several properties and matchers are available.
|
||||
|
||||
describe aws_iam_password_policy do
|
||||
it { should require_lowercase_characters }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `max_password_age_in_days`, `minimum_password_length`, `number_of_passwords_to_remember`
|
||||
|
||||
## Examples
|
||||
|
||||
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
|
||||
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
|
||||
it { should allow_users_to_change_passwords }
|
||||
end
|
||||
|
||||
### Test that the IAM Password Policy expires passwords
|
||||
|
||||
describe aws_iam_password_policy do
|
||||
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_in_days') { should be 90 }
|
||||
end
|
||||
|
||||
### Test that the IAM Password Policy prevents password reuse
|
||||
|
||||
describe aws_iam_password_policy do
|
||||
it { should prevent_password_reuse }
|
||||
end
|
||||
|
||||
### Test that the IAM Password Policy requires users to remember 3 previous passwords
|
||||
|
||||
describe aws_iam_password_policy do
|
||||
its('number_of_passwords_to_remember') { should eq 3 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
This resource uses the following special 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`
|
82
docs/resources/aws_iam_policies.md.erb
Normal file
82
docs/resources/aws_iam_policies.md.erb
Normal file
|
@ -0,0 +1,82 @@
|
|||
---
|
||||
title: About the aws_iam_policies Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_policies
|
||||
|
||||
Use the `aws_iam_policies` InSpec audit resource to test properties of some or all AWS IAM Policies.
|
||||
|
||||
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.
|
||||
|
||||
Each IAM Policy is uniquely identified by either its `policy_name` or `arn`.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_policies` resource block collects a group of IAM Policies and then tests that group.
|
||||
|
||||
# Verify the policy specified by the policy name is included in IAM Policies in the AWS account.
|
||||
describe aws_iam_policies do
|
||||
its('policy_names') { should include('test-policy-1') }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_iam_policies`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `arns`, `entries`, `policy_names`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### policy\_names
|
||||
|
||||
Provides a list of policy names for all IAM Policies in the AWS account.
|
||||
|
||||
describe aws_iam_policies do
|
||||
its('policy_names') { should include('test-policy-1') }
|
||||
end
|
||||
|
||||
### arns
|
||||
|
||||
Provides a list of policy arns for all IAM Policies in the AWS account.
|
||||
|
||||
describe aws_iam_policies do
|
||||
its('arns') { should include('arn:aws:iam::aws:policy/test-policy-1') }
|
||||
end
|
||||
|
||||
### entries
|
||||
|
||||
Provides access to the raw results of the query. This can be useful for checking counts and other advanced operations.
|
||||
|
||||
# Allow at most 100 IAM Policies on the account
|
||||
describe aws_iam_policies do
|
||||
its('entries.count') { should be <= 100}
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# Verify that at least one IAM Policies exists.
|
||||
describe aws_iam_policies
|
||||
it { should exist }
|
||||
end
|
||||
|
146
docs/resources/aws_iam_policy.md.erb
Normal file
146
docs/resources/aws_iam_policy.md.erb
Normal file
|
@ -0,0 +1,146 @@
|
|||
---
|
||||
title: About the aws_iam_policy Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_policy
|
||||
|
||||
Use the `aws_iam_policy` InSpec audit resource to test properties of a single managed AWS IAM Policy.
|
||||
|
||||
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.
|
||||
|
||||
Each IAM Policy is uniquely identified by either its policy_name or arn.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_policy` resource block identifies a policy by policy name.
|
||||
|
||||
# Find a policy by name
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Find a customer-managed by name
|
||||
describe aws_iam_policy('customer-managed-policy') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Hash syntax for policy name
|
||||
describe aws_iam_policy(policy_name: 'AWSSupportAccess') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that a policy does exist
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### Test that a policy is attached to at least one entity
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should be_attached }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `arn`, `attachment_count`, `attached_groups`, `attached_roles`,`attached_users`, `default_version_id`
|
||||
|
||||
## Property Examples
|
||||
|
||||
### arn
|
||||
|
||||
"The ARN identifier of the specified policy. An ARN uniquely identifies the policy within AWS."
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
its('arn') { should cmp "arn:aws:iam::aws:policy/AWSSupportAccess" }
|
||||
end
|
||||
|
||||
### attachment\_count
|
||||
|
||||
The count of attached entities for the specified policy.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
its('attachment_count') { should cmp 1 }
|
||||
end
|
||||
|
||||
### attached\_groups
|
||||
|
||||
The list of groupnames of the groups attached to the policy.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
its('attached_groups') { should include "test-group" }
|
||||
end
|
||||
|
||||
### attached\_roles
|
||||
|
||||
The list of rolenames of the roles attached to the policy.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
its('attached_roles') { should include "test-role" }
|
||||
end
|
||||
|
||||
### attached\_users
|
||||
|
||||
The list of usernames of the users attached to the policy.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
its('attached_users') { should include "test-user" }
|
||||
end
|
||||
|
||||
### default\_version\_id
|
||||
|
||||
The 'default_version_id' value of the specified policy.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
its('default_version_id') { should cmp "v1" }
|
||||
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/).
|
||||
|
||||
### be\_attached
|
||||
|
||||
The test will pass if the identified policy is attached to at least one IAM user, group, or role.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should be_attached }
|
||||
end
|
||||
|
||||
### be\_attached\_to\_group(GROUPNAME)
|
||||
|
||||
The test will pass if the identified policy attached the specified group.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should be_attached_to_group(GROUPNAME) }
|
||||
end
|
||||
|
||||
### be\_attached\_to\_user(USERNAME)
|
||||
|
||||
The test will pass if the identified policy attached the specified user.
|
||||
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should be_attached_to_user(USERNAME) }
|
||||
end
|
||||
|
||||
### be\_attached\_to\_role(ROLENAME)
|
||||
|
||||
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
|
||||
|
||||
|
65
docs/resources/aws_iam_role.md.erb
Normal file
65
docs/resources/aws_iam_role.md.erb
Normal file
|
@ -0,0 +1,65 @@
|
|||
---
|
||||
title: About the aws_iam_role Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_role
|
||||
|
||||
Use the `aws_iam_role` InSpec audit resource to test properties of a single IAM Role. A Role is a collection of permissions that may be temporarily assumed by a user, EC2 Instance, Lambda Function, or certain other resources.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
# Ensure that a certain role exists by name
|
||||
describe aws_iam_role('my-role') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Resource Parameters
|
||||
|
||||
### role\_name
|
||||
|
||||
This resource expects a single parameter that uniquely identifies the IAM Role, the Role Name. You may pass it as a string, or as the value in a hash:
|
||||
|
||||
describe aws_iam_role('my-role') do
|
||||
it { should exist }
|
||||
end
|
||||
# Same
|
||||
describe aws_iam_role(role_name: 'my-role') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
### description
|
||||
|
||||
A textual description of the IAM Role.
|
||||
|
||||
describe aws_iam_role('my-role') do
|
||||
its('description') { should be('Our most important Role')}
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exist
|
||||
|
||||
Indicates that the Role Name provided was found. Use should_not to test for IAM Roles that should not exist.
|
||||
|
||||
describe aws_iam_role('should-be-there') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe aws_iam_role('should-not-be-there') do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
|
58
docs/resources/aws_iam_root_user.md.erb
Normal file
58
docs/resources/aws_iam_root_user.md.erb
Normal file
|
@ -0,0 +1,58 @@
|
|||
---
|
||||
title: About the aws_iam_root_user Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_root\_user
|
||||
|
||||
Use the `aws_iam_root_user` InSpec audit resource to test properties of the root user (owner of the account).
|
||||
|
||||
To test properties of all or multiple users, use the `aws_iam_users` resource.
|
||||
|
||||
To test properties of a specific AWS user use the `aws_iam_user` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_root_user` resource block requires no parameters but has several matchers
|
||||
|
||||
describe aws_iam_root_user do
|
||||
its { should have_mfa_enabled }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that the AWS root account has at-least one access key
|
||||
|
||||
describe aws_iam_root_user do
|
||||
it { should have_access_key }
|
||||
end
|
||||
|
||||
### Test that the AWS root account has Multi-Factor Authentication enabled
|
||||
|
||||
describe aws_iam_root_user do
|
||||
it { should have_mfa_enabled }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### have\_mfa\_enabled
|
||||
|
||||
The `have_mfa_enabled` matcher tests if the AWS root user has Multi-Factor Authentication enabled, requiring them to enter a secondary code when they login to the web console.
|
||||
|
||||
it { should have_mfa_enabled }
|
||||
|
||||
### have\_access\_key
|
||||
|
||||
The `have_access_key` matcher tests if the AWS root user has at least one access key.
|
||||
|
||||
it { should have_access_key }
|
64
docs/resources/aws_iam_user.md.erb
Normal file
64
docs/resources/aws_iam_user.md.erb
Normal file
|
@ -0,0 +1,64 @@
|
|||
---
|
||||
title: About the aws_iam_user Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_user
|
||||
|
||||
Use the `aws_iam_user` InSpec audit resource to test properties of a single AWS IAM user.
|
||||
|
||||
To test properties of more than one user, use the `aws_iam_users` resource.
|
||||
|
||||
To test properties of the special AWS root user (which owns the account), use the `aws_iam_root_user` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_user` resource block declares a user by name, and then lists tests to be performed.
|
||||
|
||||
describe aws_iam_user(name: 'test_user') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that a user does not exist
|
||||
|
||||
describe aws_iam_user(name: 'gone') do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### Test that a user has multi-factor authentication enabled
|
||||
|
||||
describe aws_iam_user(name: 'test_user') do
|
||||
it { should have_mfa_enabled }
|
||||
end
|
||||
|
||||
### Test that a service user does not have a password
|
||||
|
||||
describe aws_iam_user(name: 'test_user') do
|
||||
it { should have_console_password }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### have\_console\_password
|
||||
|
||||
The `have_console_password` matcher tests if the user has a password that could be used to log into the AWS web console.
|
||||
|
||||
it { should have_console_password }
|
||||
|
||||
### have\_mfa\_enabled
|
||||
|
||||
The `have_mfa_enabled` matcher tests if the user has Multi-Factor Authentication enabled, requiring them to enter a secondary code when they login to the web console.
|
||||
|
||||
it { should have_mfa_enabled }
|
90
docs/resources/aws_iam_users.md.erb
Normal file
90
docs/resources/aws_iam_users.md.erb
Normal file
|
@ -0,0 +1,90 @@
|
|||
---
|
||||
title: About the aws_iam_users Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_iam\_users
|
||||
|
||||
Use the `aws_iam_users` InSpec audit resource to test properties of a all or multiple users.
|
||||
|
||||
To test properties of a single user, use the `aws_iam_user` resource.
|
||||
|
||||
To test properties of the special AWS root user (which owns the account), use the `aws_iam_root_user` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_iam_users` resource block users a filter to select a group of users and then tests that group
|
||||
|
||||
describe aws_iam_users.where(has_mfa_enabled?: false) do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Criteria
|
||||
|
||||
* `has_mfa_enabled`, `has_console_password`, `password_ever_used?`, `password_never_used?`, `password_last_used_days_ago`, `username`
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that all users have Multi-Factor Authentication enabled
|
||||
|
||||
describe aws_iam_users.where(has_mfa_enabled?: false) do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### Test that at least one user has a console password to log into the AWS web console
|
||||
|
||||
describe aws_iam_users.where(has_console_password?: true) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### Test that all users who have a console password have Multi-Factor Authentication enabled
|
||||
|
||||
console_users_without_mfa = aws_iam_users
|
||||
.where(has_console_password?: true)
|
||||
.where(has_mfa_enabled?: false)
|
||||
|
||||
describe console_users_without_mfa do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### Test that all users who have a console password have used it at least once
|
||||
|
||||
console_users_with_unused_password = aws_iam_users
|
||||
.where(has_console_password?: true)
|
||||
.where(password_never_used?: false)
|
||||
|
||||
describe console_users_with_unused_password do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### Test that at least one user exists who has a console password and has used it at least once
|
||||
|
||||
console_users_with_used_password = aws_iam_users
|
||||
.where(has_console_password?: true)
|
||||
.where(password_ever_used?: false)
|
||||
|
||||
describe console_users_with_used_password do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
|
||||
### Test that users with passwords that have not been used for 90 days do not
|
||||
|
||||
describe aws_iam_users.where { password_last_used_days_ago > 90 } do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
This InSpec audit resource has no specific matchers.
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
84
docs/resources/aws_kms_keys.md.erb
Normal file
84
docs/resources/aws_kms_keys.md.erb
Normal file
|
@ -0,0 +1,84 @@
|
|||
---
|
||||
title: About the aws_kms_keys Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_kms\_keys
|
||||
|
||||
Use the `aws_kms_keys` InSpec audit resource to test properties of some or all AWS KMS Keys.
|
||||
|
||||
AWS Key Management Service (KMS) is a managed service that makes creating and controlling your encryption keys for your data easier. KMS uses Hardware Security Modules (HSMs) to protect the security of your keys.
|
||||
|
||||
AWS Key Management Service is integrated with several other AWS services to help you protect the data you store with these services.
|
||||
|
||||
Each AWS KMS Key is uniquely identified by its key-id or key-arn.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_kms_keys` resource block uses an optional filter to select a group of KMS Keys and then tests that group.
|
||||
|
||||
# Verify the number of KMS keys in the AWS account
|
||||
describe aws_kms_keys do
|
||||
its('entries.count') { should cmp 10 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_kms_keys`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `entries`, `key_arns`, `key_ids`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### entries
|
||||
|
||||
Provides access to the raw results of a query. This can be useful for checking counts and other advanced operations.
|
||||
|
||||
# Allow at most 100 KMS Keys on the account
|
||||
describe aws_kms_keys do
|
||||
its('entries.count') { should be <= 100}
|
||||
end
|
||||
|
||||
### key\_arns
|
||||
|
||||
Provides a list of key arns for all KMS Keys in the AWS account.
|
||||
|
||||
describe aws_kms_keys do
|
||||
its('key_arns') { should include('arn:aws:kms:us-east-1::key/key-id') }
|
||||
end
|
||||
|
||||
### key\_ids
|
||||
|
||||
Provides a list of key ids for all KMS Keys in the AWS account.
|
||||
|
||||
describe aws_kms_keys do
|
||||
its('key_ids') { should include('fd7e608b-f435-4186-b8b5-111111111111') }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# Verify that at least one KMS Key exists.
|
||||
describe aws_kms_keys
|
||||
it { should exist }
|
||||
end
|
||||
|
47
docs/resources/aws_route_table.md.erb
Normal file
47
docs/resources/aws_route_table.md.erb
Normal file
|
@ -0,0 +1,47 @@
|
|||
---
|
||||
title: About the aws_route_table Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_route\_table
|
||||
|
||||
Use the `aws_route_table` InSpec audit resource to test properties of a single Route Table. A route table contains a set of rules, called routes, that are used to determine where network traffic is directed.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
# Ensure that a certain route table exists by name
|
||||
describe aws_route_table('rtb-123abcde') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
## Resource Parameters
|
||||
|
||||
### route\_table\_id
|
||||
|
||||
This resource expects a single parameter that uniquely identifies the Route Table. You may pass it as a string, or as the value in a hash:
|
||||
|
||||
describe aws_route_table('rtb-123abcde') do
|
||||
it { should exist }
|
||||
end
|
||||
# Same
|
||||
describe aws_route_table(route_table_id: 'rtb-123abcde') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### exist
|
||||
|
||||
Indicates that the Route Table provided was found. Use should_not to test for Route Tables that should not exist.
|
||||
|
||||
describe aws_route_table('should-be-there') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
describe aws_route_table('should-not-be-there') do
|
||||
it { should_not exist }
|
||||
end
|
134
docs/resources/aws_s3_bucket.md.erb
Normal file
134
docs/resources/aws_s3_bucket.md.erb
Normal file
|
@ -0,0 +1,134 @@
|
|||
---
|
||||
title: About the aws_s3_bucket Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_s3\_bucket
|
||||
|
||||
Use the `aws_s3_bucket` InSpec audit resource to test properties of a single AWS bucket.
|
||||
|
||||
To test properties of a multiple S3 buckets, use the `aws_s3_buckets` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Limitations
|
||||
|
||||
S3 bucket security is a complex matter. For details on how AWS evaluates requests for access, please see [the AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/how-s3-evaluates-access-control.html). S3 buckets and the objects they contain support three different types of access control: bucket ACLs, bucket policies, and object ACLs.
|
||||
|
||||
As of January 2018, this resource supports evaluating bucket ACLs and bucket policies. We do not support evaluating object ACLs because it introduces scalability concerns in the AWS API; we recommend using AWS mechanisms such as CloudTrail and Config to detect insecure object ACLs.
|
||||
|
||||
In particular, users of the `be_public` matcher should carefully examine the conditions under which the matcher will detect an insecure bucket. See the `be_public` section under the Matchers section below.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_s3_bucket` resource block declares a bucket by name, and then lists tests to be performed.
|
||||
|
||||
describe aws_s3_bucket(bucket_name: 'test_bucket') do
|
||||
it { should exist }
|
||||
it { should_not be_public }
|
||||
end
|
||||
|
||||
describe aws_s3_bucket('test_bucket') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test the bucket-level ACL
|
||||
|
||||
describe aws_s3_bucket('test_bucket') do
|
||||
its('bucket_acl.count') { should eq 1 }
|
||||
end
|
||||
|
||||
### Check if a bucket has a bucket policy
|
||||
|
||||
describe aws_s3_bucket('test_bucket') do
|
||||
its('bucket_policy') { should be_empty }
|
||||
end
|
||||
|
||||
### Check if a bucket appears to be exposed to the public
|
||||
|
||||
# See Limitations section above
|
||||
describe aws_s3_bucket('test_bucket') do
|
||||
it { should_not be_public }
|
||||
end
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
### region
|
||||
|
||||
The `region` property identifies the AWS Region in which the S3 bucket is located.
|
||||
|
||||
describe aws_s3_bucket('test_bucket') do
|
||||
# Check if the correct region is set
|
||||
its('region') { should eq 'us-east-1' }
|
||||
end
|
||||
|
||||
## Unsupported Properties
|
||||
|
||||
### bucket\_acl
|
||||
|
||||
The `bucket_acl` property is a low-level property that lists the individual Bucket ACL grants in effect on the bucket. Other higher-level properties, such as be\_public, are more concise and easier to use. You can use the `bucket_acl` property to investigate which grants are in effect, causing be\_public to fail.
|
||||
|
||||
The value of bucket_acl is an array of simple objects. Each object has a `permission` property and a `grantee` property. The `permission` property will be a string such as 'READ', 'WRITE' etc (See the [AWS documentation](https://docs.aws.amazon.com/sdkforruby/api/Aws/S3/Client.html#get_bucket_acl-instance_method) for a full list). The `grantee` property contains sub-properties, such as `type` and `uri`.
|
||||
|
||||
|
||||
bucket_acl = aws_s3_bucket('my-bucket')
|
||||
|
||||
# Look for grants to "AllUsers" (that is, the public)
|
||||
all_users_grants = bucket_acl.select do |g|
|
||||
g.grantee.type == 'Group' && g.grantee.uri =~ /AllUsers/
|
||||
end
|
||||
|
||||
# Look for grants to "AuthenticatedUsers" (that is, any authenticated AWS user - nearly public)
|
||||
auth_grants = bucket_acl.select do |g|
|
||||
g.grantee.type == 'Group' && g.grantee.uri =~ /AuthenticatedUsers/
|
||||
end
|
||||
|
||||
### bucket\_policy
|
||||
|
||||
The `bucket_policy` is a low-level property that describes the IAM policy document controlling access to the bucket. The `bucket_policy` property returns a Ruby structure that you can probe to check for particular statements. We recommend using a higher-level property, such as `be_public`, which is concise and easier to implement in your policy files.
|
||||
|
||||
The `bucket_policy` property returns an array of simple objects, each object being an IAM Policy Statement. See the [AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/dev/example-bucket-policies.html#example-bucket-policies-use-case-2) for details about the structure of this data.
|
||||
|
||||
If there is no bucket policy, this property returns an empty array.
|
||||
|
||||
bucket_policy = aws_s3_bucket('my-bucket')
|
||||
|
||||
# Look for statements that allow the general public to do things
|
||||
# This may be a false positive; it's possible these statements
|
||||
# could be protected by conditions, such as IP restrictions.
|
||||
public_statements = bucket_policy.select do |s|
|
||||
s.effect == 'Allow' && s.principal == '*'
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### be\_public
|
||||
|
||||
The `be_public` matcher tests if the bucket has potentially insecure access controls. This high-level matcher detects several insecure conditions, which may be enhanced in the future. Currently, the matcher reports an insecure bucket if any of the following conditions are met:
|
||||
|
||||
1. A bucket ACL grant exists for the 'AllUsers' group
|
||||
2. A bucket ACL grant exists for the 'AuthenticatedUsers' group
|
||||
3. A bucket policy has an effect 'Allow' and principal '*'
|
||||
|
||||
Note: This resource does not detect insecure object ACLs.
|
||||
|
||||
it { should_not be_public }
|
||||
|
||||
### have\_access\_logging\_enabled
|
||||
|
||||
The `have_access_logging_enabled` matcher tests if access logging is enabled for the s3 bucket.
|
||||
|
||||
it { should have_access_logging_enabled }
|
152
docs/resources/aws_security_group.md.erb
Normal file
152
docs/resources/aws_security_group.md.erb
Normal file
|
@ -0,0 +1,152 @@
|
|||
---
|
||||
title: About the aws_security_group Resource
|
||||
---
|
||||
|
||||
# aws\_security\_group
|
||||
|
||||
Use the `aws_security_group` InSpec audit resource to test detailed properties of an individual Security Group (SG).
|
||||
|
||||
SGs are a networking construct which contain ingress and egress rules for network communications. SGs may be attached to EC2 instances, as well as certain other AWS resources. Along with Network Access Control Lists, SGs are one of the two main mechanisms of enforcing network-level security.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_security_group` resource block uses resource parameters to search for a Security Group and then tests that Security Group. If no SGs match, no error is raised, but the `exists` matcher returns `false` and all properties will be `nil`. If more than one SG matches (due to vague search parameters), an error is raised.
|
||||
|
||||
# Ensure you have a security group with a certain ID
|
||||
# This is "safe" - SG IDs are unique within an account
|
||||
describe aws_security_group('sg-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Ensure you have a security group with a certain ID
|
||||
# This uses hash syntax
|
||||
describe aws_security_group(id: 'sg-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_security_group`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Resource Parameters
|
||||
|
||||
This InSpec resource accepts the following parameters, which are used to search for the Security Group.
|
||||
|
||||
### id, group\_id
|
||||
|
||||
The Security Group ID of the Security Group. This is of the format `sg-` followed by 8 hexadecimal characters. The ID is unique within your AWS account; using ID ensures that you will never match more than one SG. The ID is also the default resource parameter, so you may omit the hash syntax.
|
||||
|
||||
# Using Hash syntax
|
||||
describe aws_security_group(id: 'sg-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# group_id is an alias for id
|
||||
describe aws_security_group(group_id: 'sg-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Or omit hash syntax, rely on it being the default parameter
|
||||
describe aws_security_group('sg-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### group\_name
|
||||
|
||||
The string name of the Security Group. Every VPC has a security group named 'default'. Names are unique within a VPC, but not within an AWS account.
|
||||
|
||||
# Get default security group for a certain VPC
|
||||
describe aws_security_group(group_name: 'default', vpc_id: vpc_id: 'vpc-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# This will throw an error if there is a 'backend' SG in more than one VPC.
|
||||
describe aws_security_group(group_name: 'backend') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### vpc\_id
|
||||
|
||||
A string identifying the VPC that contains the security group. Since VPCs commonly contain many SGs, you should add additional parameters to ensure you find exactly one SG.
|
||||
|
||||
# This will error if there is more than the default SG
|
||||
describe aws_security_group(vpc_id: 'vpc-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
## Properties
|
||||
|
||||
* `description`, `group_id', `group_name`, `vpc_id`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### description
|
||||
|
||||
A String reflecting the human-meaningful description that was given to the SG at creation time.
|
||||
|
||||
# Require a description of a particular group
|
||||
describe aws_security_group('sg-12345678') do
|
||||
its('description') { should_not be_empty }
|
||||
end
|
||||
|
||||
### group\_id
|
||||
|
||||
Provides the Security Group ID.
|
||||
|
||||
# Inspect the group ID of the default group
|
||||
describe aws_security_group(group_name: 'default', vpc_id: vpc_id: 'vpc-12345678') do
|
||||
its('group_id') { should cmp 'sg-12345678' }
|
||||
end
|
||||
|
||||
# Store the group ID in a Ruby variable for use elsewhere
|
||||
sg_id = aws_security_group(group_name: 'default', vpc_id: vpc_id: 'vpc-12345678').group_id
|
||||
|
||||
### group\_name
|
||||
|
||||
A String reflecting the name that was given to the SG at creation time.
|
||||
|
||||
# Inspect the group name of a particular group
|
||||
describe aws_security_group('sg-12345678') do
|
||||
its('group_name') { should cmp 'my_group' }
|
||||
end
|
||||
|
||||
### vpc\_id
|
||||
|
||||
A String in the format 'vpc-' followed by 8 hexadecimal characters reflecting VPC that contains the security group.
|
||||
|
||||
# Inspec the VPC ID of a particular group
|
||||
describe aws_security_group('sg-12345678') do
|
||||
its('vpc_id') { should cmp 'vpc-12345678' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the specified SG was found. Use should_not if you want to verify that the specified SG does not exist.
|
||||
|
||||
# You will always have at least one SG, the VPC default SG
|
||||
describe aws_security_group(group_name: 'default')
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Make sure we don't have any security groups with the name 'nogood'
|
||||
describe aws_security_group(group_name: 'nogood')
|
||||
it { should_not exist }
|
||||
end
|
||||
|
92
docs/resources/aws_security_groups.md.erb
Normal file
92
docs/resources/aws_security_groups.md.erb
Normal file
|
@ -0,0 +1,92 @@
|
|||
---
|
||||
title: About the aws_security_groups Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_security\_groups
|
||||
|
||||
Use the `aws_security_groups` InSpec audit resource to test properties of some or all security groups.
|
||||
|
||||
Security groups are a networking construct that contain ingress and egress rules for network communications. Security groups may be attached to EC2 instances, as well as certain other AWS resources. Along with Network Access Control Lists, Security Groups are one of the two main mechanisms of enforcing network-level security.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_security_groups` resource block uses an optional filter to select a group of security groups and then tests that group.
|
||||
|
||||
# Verify you have more than the default security group
|
||||
describe aws_security_groups do
|
||||
its('entries.count') { should be > 1 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_security_groups`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Criteria
|
||||
|
||||
### vpc\_id
|
||||
|
||||
A string identifying the VPC which contains the security group.
|
||||
|
||||
# Look for a particular security group in just one VPC
|
||||
describe aws_security_groups.where( vpc_id: 'vpc-12345678') do
|
||||
its('group_ids') { should include('sg-abcdef12')}
|
||||
end
|
||||
|
||||
### group\_name
|
||||
|
||||
A string identifying a group. Since groups are contained in VPCs, group names are unique within the AWS account, but not across VPCs.
|
||||
|
||||
# Examine the default security group in all VPCs
|
||||
describe aws_security_groups.where( group_name: 'default') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `entries`, `group\_ids`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### entries
|
||||
|
||||
Provides access to the raw results of the query. This can be useful for checking counts and other advanced operations.
|
||||
|
||||
# Allow at most 100 security groups on the account
|
||||
describe aws_security_groups do
|
||||
its('entries.count') { should be <= 100}
|
||||
end
|
||||
|
||||
### group\_ids
|
||||
|
||||
Provides a list of all security group IDs matched.
|
||||
|
||||
describe aws_security_groups do
|
||||
its('group_ids') { should include('sg-12345678') }
|
||||
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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# You will always have at least one SG, the VPC default SG
|
||||
describe aws_security_groups
|
||||
it { should exist }
|
||||
end
|
||||
|
63
docs/resources/aws_sns_topic.md.erb
Normal file
63
docs/resources/aws_sns_topic.md.erb
Normal file
|
@ -0,0 +1,63 @@
|
|||
---
|
||||
title: About the aws_sns_topic Resource
|
||||
---
|
||||
|
||||
# aws\_sns\_topic
|
||||
|
||||
Use the `aws_sns_topic` InSpec audit resource to test properties of a single AWS Simple Notification Service Topic. SNS topics are channels for related events. AWS resources place events in the SNS topic, while other AWS resources _subscribe_ to receive notifications when new events have appeared.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
# Ensure that a topic exists and has at least one subscription
|
||||
describe aws_sns_topic('arn:aws:sns:*::my-topic-name') do
|
||||
it { should exist }
|
||||
its('confirmed_subscription_count') { should_not be_zero }
|
||||
end
|
||||
|
||||
# You may also use has syntax to pass the ARN
|
||||
describe aws_sns_topic(arn: 'arn:aws:sns:*::my-topic-name') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
## Resource Parameters
|
||||
|
||||
### ARN
|
||||
|
||||
This resource expects a single parameter that uniquely identifes the SNS Topic, an ARN. Amazon Resource Names for SNS topics have the format `arn:aws:sns:region:account-id:topicname`. AWS requires a fully-specified ARN for looking up an SNS topic. The account ID and region are required. Wildcards are not permitted.
|
||||
|
||||
See also the (AWS documentation on ARNs)[http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html].
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
### confirmed\_subscription\_count
|
||||
|
||||
An integer indicating the number of currently active subscriptions.
|
||||
|
||||
# Make sure someone is listening
|
||||
describe aws_sns_topic('arn:aws:sns:*::my-topic-name') do
|
||||
its('confirmed_subscription_count') { should_not be_zero}
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exist
|
||||
|
||||
Indicates that the ARN provided was found. Use should_not to test for SNS topics that should not exist.
|
||||
|
||||
# Expect good news
|
||||
describe aws_sns_topic('arn:aws:sns:*::good-news') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# No bad news allowed
|
||||
describe aws_sns_topic('arn:aws:sns:*::bad-news') do
|
||||
it { should_not exist }
|
||||
end
|
134
docs/resources/aws_subnet.md.erb
Normal file
134
docs/resources/aws_subnet.md.erb
Normal file
|
@ -0,0 +1,134 @@
|
|||
---
|
||||
title: About the aws_subnet Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_subnet
|
||||
|
||||
Use the `aws_subnet` InSpec audit resource to test properties of a vpc subnet.
|
||||
|
||||
To test properties of a single VPC subnet, use the `aws_subnet` resource.
|
||||
|
||||
To test properties of all or a group of VPC subnets, use the `aws_subnets` resource.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_subnet` resource block uses the parameter to select a VPC and a subnet in the VPC.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-1234567') do
|
||||
it { should exist }
|
||||
its('cidr_block') { should eq '10.0.1.0/24' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Resource Parameters
|
||||
|
||||
This InSpec resource accepts the following parameters, which are used to search for the VPCs subnet.
|
||||
|
||||
### subnet\_id
|
||||
|
||||
A string identifying the subnet that the VPC contains.
|
||||
|
||||
# This will error if there is more than the default SG
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `availavailability_zone`, `available_ip_address_count`, `cidr_block`, `subnet_id`, `vpc_id`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### availability\_zone
|
||||
|
||||
Provides the Availability Zone of the subnet.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
its('availability_zone') { should eq 'us-east-1c' }
|
||||
end
|
||||
|
||||
### available\_ip\_address\_count
|
||||
|
||||
Provides the number of available IPv4 addresses on the subnet.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
its('available_ip_address_count') { should eq 251 }
|
||||
end
|
||||
|
||||
### cidr\_block
|
||||
|
||||
Provides the block of ip addresses specified to the subnet.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
its('cidr_block') { should eq '10.0.1.0/24' }
|
||||
end
|
||||
|
||||
### subnet\_id
|
||||
|
||||
Provides the ID of the Subnet.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
its('subnet_id') { should eq 'subnet-12345678' }
|
||||
end
|
||||
|
||||
### vpc\_id
|
||||
|
||||
Provides the ID of the VPC the subnet is in.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
its('vpc_id') { should eq 'vpc-12345678' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### assigning\_ipv\_6\_address\_on\_creation
|
||||
|
||||
Detects if the network interface on the subnet accepts IPv6 addresses.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should be_assigning_ipv_6_address_on_creation }
|
||||
end
|
||||
|
||||
### available
|
||||
|
||||
Provides the current state of the subnet.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should be_available }
|
||||
end
|
||||
|
||||
### default\_for\_az
|
||||
|
||||
Detects if the subnet is the default subnet for the Availability Zone.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should be_default_for_az }
|
||||
end
|
||||
|
||||
### exist
|
||||
|
||||
The `exist` matcher indicates that a subnet exists for the specified vpc.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
### mapping\_public\_ip\_on\_launch
|
||||
|
||||
Provides the VPC ID for the subnet.
|
||||
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should be_mapping_public_ip_on_launch }
|
||||
end
|
126
docs/resources/aws_subnets.md.erb
Normal file
126
docs/resources/aws_subnets.md.erb
Normal file
|
@ -0,0 +1,126 @@
|
|||
---
|
||||
title: About the aws_subnets Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_subnets
|
||||
|
||||
Use the `aws_subnets` InSpec audit resource to test properties of some or all subnets.
|
||||
|
||||
Subnets are networks within a VPC that can have their own block of IP address's and ACL's.
|
||||
VPCs span across all availability zones in AWS, while a subnet in a VPC can only span a single availability zone.
|
||||
Separating IP addresses allows for protection if there is a failure in one availability zone.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_subnets` resource block uses an optional filter to select a group of subnets and then tests that group.
|
||||
|
||||
# Test all subnets within a single vpc
|
||||
describe aws_subnets.where(vpc_id: 'vpc-12345678') do
|
||||
its('subnet_ids') { should include 'subnet-12345678' }
|
||||
its('subnet_ids') { should include 'subnet-98765432' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_subnets`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## Filter Criteria
|
||||
|
||||
* `vpc_id`, `subnet_id`
|
||||
|
||||
## Filter Examples
|
||||
|
||||
### vpc\_id
|
||||
|
||||
A string identifying the VPC which may or may not contain subnets.
|
||||
|
||||
# Look for all subnts within a vpc.
|
||||
describe aws_subnets.where( vpc_id: 'vpc-12345678') do
|
||||
its('subnet_ids') { should include 'subnet-12345678' }
|
||||
its('subnet_ids') { should include 'subnet-98765432' }
|
||||
end
|
||||
|
||||
### subnet\_id
|
||||
|
||||
A string identifying a specific subnet.
|
||||
|
||||
# Examine a specific subnet
|
||||
describe aws_subnets.where(subnet_id: 'subnet-12345678') do
|
||||
its('cidr_blocks') { should eq ['10.0.1.0/24'] }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `cidr_blocks`, `states`, `subnet_ids`,`vpc_ids`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### cidr\_blocks
|
||||
|
||||
Provides a string that contains the cidr block of ip addresses that can be given in the subnet.
|
||||
|
||||
# Examine a specific subnets cidr_blocks
|
||||
describe aws_subnets.where( subnet_id: 'subnet-12345678') do
|
||||
its('cidr_blocks') { should eq ['10.0.1.0/24'] }
|
||||
end
|
||||
|
||||
### states
|
||||
|
||||
Provides an array of strings including if the subnets are available.
|
||||
|
||||
# Examine a specific vpcs Subnet IDs
|
||||
describe aws_subnets.where( vpc_id: 'vpc-12345678') do
|
||||
its('states') { should_not include 'pending' }
|
||||
end
|
||||
|
||||
### subnet\_ids
|
||||
|
||||
Provides an array of strings containing the subnet IDs associated with a vpc.
|
||||
|
||||
# Examine a specific vpcs Subnet IDs
|
||||
describe aws_subnets.where( vpc_id: 'vpc-12345678') do
|
||||
its('subnet_ids') { should include 'subnet-12345678' }
|
||||
its('subnet_ids') { should include 'subnet-98765432' }
|
||||
end
|
||||
|
||||
### vpc\_ids
|
||||
|
||||
Provides an array containing a string of the vpc_id associated with a subnet.
|
||||
|
||||
# Examine a specific subnets VPC IDS
|
||||
describe aws_subnets.where( subnet_id: 'subnet-12345678') do
|
||||
its('vpc_ids') { should include 'vpc-12345678' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# You dont always have subnets, so you can test if there are any.
|
||||
describe aws_subnets
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Test that there are subnets in a vpc
|
||||
describe aws_subnets.where(vpc_id: 'vpc-12345678')
|
||||
it { should exist }
|
||||
end
|
120
docs/resources/aws_vpc.md.erb
Normal file
120
docs/resources/aws_vpc.md.erb
Normal file
|
@ -0,0 +1,120 @@
|
|||
---
|
||||
title: About the aws_vpc Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_vpc
|
||||
|
||||
Use the `aws_vpc` InSpec audit resource to test properties of a single AWS Virtual Private Cloud (VPC).
|
||||
|
||||
To test properties of all or multiple VPCs, use the `aws_vpcs` resource.
|
||||
|
||||
A VPC is a networking construct that provides an isolated environment. A VPC is contained in a geographic region, but spans availability zones in that region. A VPC may have multiple subnets, internet gateways, and other networking resources. Computing resources--such as EC2 instances--reside on subnets within the VPC.
|
||||
|
||||
Each VPC is uniquely identified by its VPC ID. In addition, each VPC has a non-unique CIDR IP Address range (such as 10.0.0.0/16) which it manages.
|
||||
|
||||
Every AWS account has at least one VPC, the "default" VPC, in every region.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_vpc` resource block identifies a VPC by id. If no VPC ID is provided, the default VPC is used.
|
||||
|
||||
# Find the default VPC
|
||||
describe aws_vpc do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Find a VPC by ID
|
||||
describe aws_vpc('vpc-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Hash syntax for ID
|
||||
describe aws_vpc(vpc_id: 'vpc-12345678') do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
### Test that a VPC does not exist
|
||||
|
||||
describe aws_vpc('vpc-87654321') do
|
||||
it { should_not exist }
|
||||
end
|
||||
|
||||
### Test the CIDR of a named VPC
|
||||
|
||||
describe aws_vpc('vpc-87654321') do
|
||||
its('cidr_block') { should cmp '10.0.0.0/16' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `cidr_block`, `dhcp_options_id`, `state`, `vpc_id`, `instance_tenancy`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### cidr\_block
|
||||
|
||||
The IPv4 address range that is managed by the VPC.
|
||||
|
||||
describe aws_vpc('vpc-87654321') do
|
||||
its('cidr_block') { should cmp '10.0.0.0/16' }
|
||||
end
|
||||
|
||||
### dhcp\_options\_id
|
||||
|
||||
The ID of the set of DHCP options associated with the VPC (or `default` if the default options are associated with the VPC).
|
||||
|
||||
describe aws_vpc do
|
||||
its ('dhcp_options_id') { should eq 'dopt-a94671d0' }
|
||||
end
|
||||
|
||||
### instance\_tenancy
|
||||
|
||||
The allowed tenancy of instances launched into the VPC.
|
||||
|
||||
describe aws_vpc do
|
||||
its ('instance_tenancy') { should eq 'default' }
|
||||
end
|
||||
|
||||
### state
|
||||
|
||||
The state of the VPC (`pending` | `available`).
|
||||
|
||||
describe aws_vpc do
|
||||
its ('state') { should eq 'available' }
|
||||
end
|
||||
|
||||
### vpc\_id
|
||||
|
||||
The ID of the VPC.
|
||||
|
||||
describe aws_vpc do
|
||||
its('vpc_id') { should eq 'vpc-87654321' }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### be\_default
|
||||
|
||||
The test will pass if the identified VPC is the default VPC for the region.
|
||||
|
||||
describe aws_vpc('vpc-87654321') do
|
||||
it { should be_default }
|
||||
end
|
||||
|
48
docs/resources/aws_vpcs.md.erb
Normal file
48
docs/resources/aws_vpcs.md.erb
Normal file
|
@ -0,0 +1,48 @@
|
|||
---
|
||||
title: About the aws_vpcs Resource
|
||||
platform: aws
|
||||
---
|
||||
|
||||
# aws\_vpcs
|
||||
|
||||
Use the `aws_vpcs` InSpec audit resource to test properties of some or all AWS Virtual Private Clouds (VPCs).
|
||||
|
||||
A VPC is a networking construct that provides an isolated environment. A VPC is contained in a geographic region, but spans availability zones in that region. A VPC may have multiple subnets, internet gateways, and other networking resources. Computing resources--such as EC2 instances--reside on subnets within the VPC.
|
||||
|
||||
Each VPC is uniquely identified by its VPC ID. In addition, each VPC has a non-unique CIDR IP Address range (such as 10.0.0.0/16) which it manages.
|
||||
|
||||
Every AWS account has at least one VPC, the "default" VPC, in every region.
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
||||
An `aws_vpcs` resource block uses an optional filter to select a group of VPCs and then tests that group.
|
||||
|
||||
# The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
describe aws_vpcs do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
As this is the initial release of `aws_vpcs`, its limited functionality precludes examples.
|
||||
|
||||
<br>
|
||||
|
||||
## 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/).
|
||||
|
||||
### exists
|
||||
|
||||
The control will pass if the filter returns at least one result. Use `should_not` if you expect zero matches.
|
||||
|
||||
# You will always have at least one VPC
|
||||
describe aws_vpcs
|
||||
it { should exist }
|
||||
end
|
74
docs/resources/azure_generic_resource.md.erb
Normal file
74
docs/resources/azure_generic_resource.md.erb
Normal file
|
@ -0,0 +1,74 @@
|
|||
---
|
||||
title: About the azure_generic_resource Resource
|
||||
---
|
||||
|
||||
# azure\_generic\_resource
|
||||
|
||||
Use the `azure_generic_resource` InSpec audit resource to test any valid Azure Resource. This is very useful if you need to test something that we do not yet have a specific Inspec resource for.
|
||||
|
||||
## Syntax
|
||||
|
||||
describe azure_generic_resource(group_name: 'MyResourceGroup', name: 'MyResource') do
|
||||
its('property') { should eq 'value' }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
* `MyResourceGroup` is the name of the resource group that contains the Azure Resource to be validated
|
||||
* `MyResource` is the name of the resource that needs to be checked
|
||||
* `property` - This generic resource dynamically creates the properties on the fly based on the type of resource that has been targetted.
|
||||
* `value` is the expected output from the chosen property
|
||||
|
||||
The options that can be passed to the resource are as follows.
|
||||
|
||||
* `group_name`, the Azure resource group to be tested. Example: 'MyResourceGroup' (required)
|
||||
* `name`, the name of the Azure resource to test. Example: 'MyVM'
|
||||
* `type`, the Azure resource type. Example: 'Microsoft.Compute/virtualMachines'
|
||||
* `apiversion`, the API Version to use when querying the resource. Defaults to the latest version for the resoure type is used. Example: 2017-10-9
|
||||
|
||||
These options can also be set using the environment variables:
|
||||
|
||||
- `AZURE_RESOURCE_GROUP_NAME`
|
||||
- `AZURE_RESOURCE_NAME`
|
||||
- `AZURE_RESOURCE_TYPE`
|
||||
- `AZURE_RESOURCE_API_VERSION`
|
||||
|
||||
When the options have been set as well as the environment variables, the environment variables take priority.
|
||||
|
||||
There are _normally_ three standard tests that can be performed on a resource.
|
||||
|
||||
* `name`: tests the resource name
|
||||
* `type`: tests the resource type
|
||||
* `location`: tests the resource's location within Azure
|
||||
|
||||
## Example
|
||||
|
||||
describe azure_generic_resource(group_name: 'Inspec-Azure', name: 'Linux-Internal-VM') do
|
||||
its('location') { should eq 'westeurope' }
|
||||
end
|
||||
|
||||
## Properties
|
||||
|
||||
The properties that can be tested are entirely dependent on the Azure Resource that is under scrutiny. That means the properties vary. The best way to see what is available please use the [Azure Resources Portal](https://resources.azure.com) to select the resource you are interested in and see what can be tested.
|
||||
|
||||
This resource allows you to test _any_ valid Azure Resource. The trade off for this is that the language to check each item is not as natural as it would be for a native Inspec resource.
|
||||
|
||||
Please see the integration tests for in depth examples of how this resource can be used.
|
||||
|
||||
- [Generic External VM NIC](../test/integration/verify/controls/generic_external_vm_nic.rb)
|
||||
- [Generic External VM](../test/integration/verify/controls/generic_external_vm.rb)
|
||||
- [Generic Internal VM NIC](../test/integration/verify/controls/generic_internal_vm_nic.rb)
|
||||
- [Generic Internal VM](../test/integration/verify/controls/generic_internal_vm.rb)
|
||||
- [Generic Linux VM Managed OS Disk](../test/integration/verify/controls/generic_linux_vm_manmaged_osdisk.rb)
|
||||
- [Generic Network Security Group](../test/integration/verify/controls/generic_network_security_group.rb)
|
||||
- [Generic Public IP Address](../test/integration/verify/controls/generic_public_ip_address.rb)
|
||||
- [Generic Resources](../test/integration/verify/controls/generic_resources.rb)
|
||||
- [Generic Storage Account](../test/integration/verify/controls/generic_storage_account.rb)
|
||||
- [Generic Virtual Network](../test/integration/verify/controls/generic_virtual_network.rb)
|
||||
- [Generic Windows Internal VM NIC](../test/integration/verify/controls/generic_windows_internal_vm_nic.rb)
|
||||
- [Generic Windows Internal VM](../test/integration/verify/controls/generic_windows_internal_vm.rb)
|
||||
|
||||
## Matchers
|
||||
|
||||
For a full list of available matchers please visit our [matchers page](https://www.inspec.io/docs/reference/matchers/).
|
||||
|
296
docs/resources/azure_resource_group.md.erb
Normal file
296
docs/resources/azure_resource_group.md.erb
Normal file
|
@ -0,0 +1,296 @@
|
|||
---
|
||||
title: About the azure_resource_group_resource_counts Resource
|
||||
---
|
||||
|
||||
# azure\_resource\_group\_resource\_counts
|
||||
|
||||
Use the `azure_resource_group_resource_counts` InSpec audit resource to check the number of Azure resources in a resource group
|
||||
|
||||
## Syntax
|
||||
|
||||
The name of the resource group is specified as a parameter on the resource:
|
||||
|
||||
describe azure_resource_group(name: 'MyResourceGroup') do
|
||||
its('property') { should eq 'value' }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
* `MyResourceGroup` is the name of the resource group being interrogated
|
||||
* `property` is one a resource property
|
||||
* `value` is the expected output from the matcher
|
||||
|
||||
The options that can be passed to the resource are as follows.
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource
|
||||
|
||||
Please refer the integration tests for more in depth examples:
|
||||
|
||||
- [Resource Group](../../test/integration/verify/controls/resource_group.rb)
|
||||
|
||||
### Test Resource Group has the correct number of resources
|
||||
|
||||
describe azure_resource_group_resource_counts(name: 'Inspec-Azure') do
|
||||
its('total') { should eq 7}
|
||||
end
|
||||
|
||||
### Ensure that the Resource Group contains the correct resources
|
||||
|
||||
describe azure_resource_group_resource_counts(name: 'Inspec-Azure') do
|
||||
its('total') { should eq 7 }
|
||||
its('vm_count') { should eq 2 }
|
||||
its('nic_count') { should eq 2 }
|
||||
its('public_ip_count') { should eq 1 }
|
||||
its('sa_count') { should eq 1 }
|
||||
its('vnet_count') { should eq 1 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Resource Parameters
|
||||
|
||||
The options that can be passed to the resource are as follows.
|
||||
|
||||
### `group_name` (required)
|
||||
|
||||
Use this parameter to define the Azure Resource Group to be tested.
|
||||
|
||||
example: MyResourceGroup
|
||||
|
||||
### name
|
||||
|
||||
Use this parameter to define the name of the Azure resource to test
|
||||
|
||||
example: MyVM
|
||||
|
||||
If both `group_name` and `name` is set then `name` will take priority
|
||||
|
||||
These options can also be set using the environment variables:
|
||||
|
||||
- `AZURE_RESOURCE_GROUP_NAME`
|
||||
- `AZURE_RESOURCE_NAME`
|
||||
|
||||
When the options have been set as well as the environment variables, the environment variables take priority.
|
||||
|
||||
### Parameter Example
|
||||
|
||||
describe azure_resource_group_resource_counts(name: 'ChefAutomate') do
|
||||
its('total') { should eq 7}
|
||||
its('nic_count') { should eq 1 }
|
||||
its('vm_count') { should eq 1 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `name`, `location` ,`id`, `provisioning_state`, `subscription_id`, `total`, `nic_count`, `vm_count`, `extension_count`, `vnet_count`, `sa_count`, `public_ip_count`,`managed_disk_image_count`, `managed_disk_count`, `tag_count`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
This InSpec audit resource has the following properties:
|
||||
|
||||
### name
|
||||
|
||||
Returns the name of the resource group.
|
||||
|
||||
its(name) { should cmp 'nugget' }
|
||||
|
||||
### location
|
||||
|
||||
Returns where in Azure the resource group is located.
|
||||
|
||||
its(location) { should cmp 'us-west' }
|
||||
|
||||
### id
|
||||
|
||||
Returns the full qualified ID of the resource group.
|
||||
|
||||
This is in the format `/subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>`.
|
||||
|
||||
its(id) { should cmp 'FQDN' }
|
||||
|
||||
### provisioning_state
|
||||
|
||||
The provisioning state of the resource group.
|
||||
|
||||
its(provisioning_state) { should cmp '????' }
|
||||
|
||||
### subscription_id
|
||||
|
||||
Returns the subscription ID which contains the resource group.
|
||||
|
||||
This is derived from the `id`.
|
||||
|
||||
its(subscription_id) { should cmp '????' }
|
||||
|
||||
### total
|
||||
|
||||
The total number of resources in the resource group
|
||||
|
||||
its(total) { should eq 5 }
|
||||
|
||||
### nic_count
|
||||
|
||||
The number of network interface cards in the resource group
|
||||
|
||||
its(nic_count) { should eq 2 }
|
||||
|
||||
### vm_count
|
||||
|
||||
The number of virtual machines in the resource group
|
||||
|
||||
its(vm_count) { should eq 5 }
|
||||
|
||||
### vnet_count
|
||||
|
||||
The number of virtual networks in the resource group
|
||||
|
||||
its(vnet_count) { should eq 5 }
|
||||
|
||||
### sa_count
|
||||
|
||||
The number of storage accounts in the resource group
|
||||
|
||||
its(sa_count) { should eq 5 }
|
||||
|
||||
### public_ip_count
|
||||
|
||||
The number of Public IP Addresses in the resource group
|
||||
|
||||
its(public_ip_count) { should eq 5 }
|
||||
|
||||
### managed_disk_image_count
|
||||
|
||||
The number of managed disk images that are in the resource group.
|
||||
|
||||
These are the items from which managed disks are created which are attached to machines. Generally the images are created from a base image or a custom image (e.g. Packer)
|
||||
|
||||
its(managed_disk_image_count) { should eq 5 }
|
||||
|
||||
### managed_disk_count
|
||||
|
||||
The number of managed disks in the resource group.
|
||||
|
||||
If a resource group contains one virtual machine with an OS disk and 2 data disks that are all Managed Disks, then the count would be 3.
|
||||
|
||||
its(managed_disk_count) { should eq 3 }
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
This resource has a number of `have_xxxx` matchers that provide a simple way to test of a specific Azure Resoure Type exists in the resource group.
|
||||
|
||||
### `have_nics`
|
||||
|
||||
Use this resource to test `
|
||||
Microsoft.Network/networkInterfaces`
|
||||
|
||||
### `have_vms`
|
||||
|
||||
Use this resource to test `Microsoft.Compute/virtualMachines`
|
||||
|
||||
### `have_extensions`
|
||||
|
||||
Use this resource to test `Microsoft.Compute/virtualMachines/extensions``
|
||||
|
||||
### `have_nsgs`
|
||||
|
||||
Use this resource to test `Microsoft.Network/networkSecurityGroups`
|
||||
|
||||
### `have_vnets`
|
||||
|
||||
Use this resource to test `Microsoft.Network/virtualNetworks`
|
||||
|
||||
### `have_managed_disks`
|
||||
|
||||
Use this resource to test `Microsoft.Compute/disks`
|
||||
|
||||
### `have_managed_disk_images`
|
||||
|
||||
Use this resource to test `Microsoft.Compute/images`
|
||||
|
||||
### `have_sas`
|
||||
|
||||
Use this resource to test `Microsoft.Storage/storageAccounts`
|
||||
|
||||
### `have_public_ips`
|
||||
|
||||
Use this resource to test `Microsoft.Network/publicIPAddresses`
|
||||
|
||||
With these methods the following tests are possible
|
||||
|
||||
it { should have_nics }
|
||||
it { should_not have_extensions }
|
||||
|
||||
## Tags
|
||||
|
||||
It is possible to test the tags that have been assigned to the resource. There are a number of properties that can be called to check that it has tags, that it has the correct number and that the correct ones are assigned.
|
||||
|
||||
### have\_tags
|
||||
|
||||
This is a simple test to see if the machine has tags assigned to it or not.
|
||||
|
||||
it { should have_tags }
|
||||
|
||||
### tag\_count
|
||||
|
||||
Returns the number of tags that are assigned to the resource
|
||||
|
||||
its ('tag_count') { should eq 2 }
|
||||
|
||||
### tags
|
||||
|
||||
It is possible to check if a specific tag has been set on the resource.
|
||||
|
||||
its('tags') { should include 'Owner' }
|
||||
|
||||
### xxx\_tag
|
||||
|
||||
To get the value of the tag, a number of preoprties have been created from the tags that are set.
|
||||
|
||||
For example, if the following tag is set on a resource:
|
||||
|
||||
| Tag Name | Value |
|
||||
|----------|-------|
|
||||
| Owner | Russell Seymour |
|
||||
|
||||
Then a property is available called `Owner_tag`.
|
||||
|
||||
its('Owner_tag') { should cmp 'Russell Seymour' }
|
||||
|
||||
Note: The tag name is case sensitive which makes the test case sensitive. E.g. `owner_tag` does not equal `Owner_tag`.
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource
|
||||
|
||||
Please refer the integration tests for more in depth examples:
|
||||
|
||||
- [Resource Group](../../test/integration/verify/controls/resource_group.rb)
|
||||
|
||||
### Test Resource Group has the correct number of resources
|
||||
|
||||
describe azure_resource_group_resource_counts(name: 'Inspec-Azure') do
|
||||
its('total') { should eq 7}
|
||||
end
|
||||
|
||||
### Ensure that the Resource Group contains the correct resources
|
||||
|
||||
describe azure_resource_group_resource_counts(name: 'Inspec-Azure') do
|
||||
its('total') { should eq 7 }
|
||||
its('vm_count') { should eq 2 }
|
||||
its('nic_count') { should eq 2 }
|
||||
its('public_ip_count') { should eq 1 }
|
||||
its('sa_count') { should eq 1 }
|
||||
its('vnet_count') { should eq 1 }
|
||||
end
|
||||
|
||||
## References
|
||||
|
||||
- [Azure Ruby SDK - Resources](https://github.com/Azure/azure-sdk-for-ruby/tree/master/management/azure_mgmt_resources)
|
314
docs/resources/azure_virtual_machine.md.erb
Normal file
314
docs/resources/azure_virtual_machine.md.erb
Normal file
|
@ -0,0 +1,314 @@
|
|||
---
|
||||
title: About the azure_virtual_machine Resource
|
||||
---
|
||||
|
||||
# azure\_virtual\_machine
|
||||
|
||||
Use the `azure_virtual_machine` InSpec audit resource to ensure that a Virtual Machine has been provisionned correctly.
|
||||
|
||||
## Syntax
|
||||
|
||||
The name of the machine and the resourece group are required as properties to the resource.
|
||||
|
||||
describe azure_virtual_machine(group_name: 'MyResourceGroup', name: 'MyVM') do
|
||||
its('property') { should eq 'value' }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
* `MyVm` is the name of the virtual machine as seen in Azure. (It is **not** the hostname of the machine)
|
||||
* `MyResourceGroup` is the name of the resource group that the machine is in.
|
||||
* `property` is one of
|
||||
* `value` is the expected output from the matcher
|
||||
|
||||
The options that can be passed to the resource are as follows.
|
||||
|
||||
| Name | Description | Required | Example |
|
||||
|-------------|---------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------|
|
||||
| group_name: | Azure Resource Group to be tested | yes | MyResourceGroup |
|
||||
| name: | Name of the Azure resource to test | no | MyVM |
|
||||
| apiversion: | API Version to use when interrogating the resource. If not set then the latest version for the resoure type is used | no | 2017-10-9 |
|
||||
|
||||
These options can also be set using the environment variables:
|
||||
|
||||
- `AZURE_RESOURCE_GROUP_NAME`
|
||||
- `AZURE_RESOURCE_NAME`
|
||||
- `AZURE_RESOURCE_API_VERSION`
|
||||
|
||||
When the options have been set as well as the environment variables, the environment variables take priority.
|
||||
|
||||
For example:
|
||||
|
||||
describe azure_virtual_machine(group_name: 'Inspec-Azure', name: 'Linux-Internal-VM') do
|
||||
its('os_type') { should eq 'Linux' }
|
||||
it { should have_boot_diagnostics }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show how to use this InSpec audit resource.
|
||||
|
||||
Please refer the integration tests for more in depth examples:
|
||||
|
||||
- [Virtual Machine External VM](../../test/integration/verify/controls/virtual_machine_external_vm.rb)
|
||||
- [Virtual Machine Internal VM](../../test/integration/verify/controls/virtual_machine_internal_vm.rb)
|
||||
|
||||
### Test that the machine was built from a Windows image
|
||||
|
||||
describe azure_virtual_machine(name: 'Windows-Internal-VM', group_name: 'Inspec-Azure') do
|
||||
its('publisher') { should eq 'MicrosoftWindowsServer' }
|
||||
its('offer') { should eq 'WindowsServer' }
|
||||
its('sku') { should eq '2012-R2-Datacenter' }
|
||||
end
|
||||
|
||||
### Ensure the machine is in the correct location
|
||||
|
||||
describe azure_virtual_machine(name: 'Linux-Internal-VM', resource_group: 'Inspec-Azure') do
|
||||
its('location') { should eq 'westeurope' }
|
||||
end
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* [`type`](#type), [`location`](#location), [`name`](#name), [`publisher`](#publisher), [`offer`](#offer), [`sku`](#sku), [`os_type`](#"os_type"), [`os_disk_name`](#os_disk_name), [`have_managed_osdisk`](#have_managed_osdisk?), [`caching`](#caching), `create_option`, `disk_size_gb`, `have_data_disks`, `data_disk_count` , `storage_account_type`, `vm_size`, `computer_name`, `admin_username`, `have_nics`, `nic_count`, `connected_nics`, `have_password_authentication`, `password_authentication?`, `have_custom_data`, `custom_data?`, `have_ssh_keys`, `ssh_keys?`, `ssh_key_count`, `ssh_keys`, `have_boot_diagnostics`, `boot_diagnostics_storage_uri`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
This InSpec audit resource has the following properties that can be tested:
|
||||
|
||||
### type
|
||||
|
||||
THe Azure Resource type. For a virtual machine this will always return `Microsoft.Compute/virtualMachines`
|
||||
|
||||
### location
|
||||
|
||||
Where the machine is located
|
||||
|
||||
its('location') { should eq 'westeurope' }
|
||||
|
||||
### name
|
||||
|
||||
Name of the Virtual Machine in Azure. Be aware that this is not the computer name or hostname, rather the name of the machine when seen in the Azure Portal.
|
||||
|
||||
### publisher
|
||||
|
||||
The publisher of the image from which this machine was built.
|
||||
|
||||
This will be `nil` if the machine was created from a custom image.
|
||||
|
||||
### offer
|
||||
|
||||
The offer from the publisher of the build image.
|
||||
|
||||
This will be `nil` if the machine was created from a custom image.
|
||||
|
||||
### sku
|
||||
|
||||
The item from the publisher that was used to create the image.
|
||||
|
||||
This will be `nil` if the machine was created from a custom image.
|
||||
|
||||
### os\_type
|
||||
|
||||
Test that returns the classification in Azure of the operating system type. Ostensibly this will be either `Linux` or `Windows`.
|
||||
|
||||
### os\_disk\_name
|
||||
|
||||
Return the name of the operating system disk attached to the machine.
|
||||
|
||||
### have\_managed\_osdisk
|
||||
|
||||
Determine if the operating system disk is a Managed Disks or not.
|
||||
|
||||
This test can be used in the following way:
|
||||
|
||||
it { should have_managed_osdisk }
|
||||
|
||||
### caching
|
||||
|
||||
Returns the type of caching that has been set on the operating system disk.
|
||||
|
||||
### create\_option
|
||||
|
||||
When the operating system disk is created, how it was created is set as an property. This property returns how the disk was created.
|
||||
|
||||
### disk\_size\_gb
|
||||
|
||||
Returns the size of the operating system disk.
|
||||
|
||||
### have\_data\_disks
|
||||
|
||||
Denotes if the machine has data disks attached to it or not.
|
||||
|
||||
it { should have_data_disks }
|
||||
|
||||
### data\_disk\_count
|
||||
|
||||
Return the number of data disks that are attached to the machine
|
||||
|
||||
### storage\_account\_type
|
||||
|
||||
This provides the storage account type for a machine that is using managed disks for the operating system disk.
|
||||
|
||||
### vm\_size
|
||||
|
||||
The size of the machine in Azure
|
||||
|
||||
its('vm_size') { should eq 'Standard_DS2_v2' }
|
||||
|
||||
### computer\_name
|
||||
|
||||
The name of the machine. This is what was assigned to the machine during deployment and is what _should_ be returned by the `hostname` command.
|
||||
|
||||
### admin\_username
|
||||
|
||||
The admin username that was assigned to the machine
|
||||
|
||||
NOTE: Azure does not allow the use of `Administrator` as the admin username on a Windows machine
|
||||
|
||||
## have\_nics
|
||||
|
||||
Returns a boolean to state if the machine has NICs connected or not.
|
||||
|
||||
This has can be used in the following way:
|
||||
|
||||
it { should have_nics }
|
||||
|
||||
### nic\_count
|
||||
|
||||
The number of network interface cards that have been attached to the machine
|
||||
|
||||
### connected\_nics
|
||||
|
||||
This returns an array of the NIC ids that are connected to the machine. This means that it possible to check that the machine has the correct NIC(s) attached and thus on the correct subnet.
|
||||
|
||||
its('connected_nics') { should include /Inspec-NIC-1/ }
|
||||
|
||||
Note the use of the regular expression here. This is because the NIC id is a long string that contains the subscription id, resource group, machine id as well as other things. By using the regular expression the NIC can be checked withouth breaking this string up. It also means that other tests can be performed.
|
||||
|
||||
An example of the id string is `/subscriptions/1e0b427a-d58b-494e-ae4f-ee558463ebbf/resourceGroups/Inspec-Azure/providers/Microsoft.Network/networkInterfaces/Inspec-NIC-1`
|
||||
|
||||
### have\_password\_authentication
|
||||
|
||||
Returns a boolean to denote if the machine is accessible using a password.
|
||||
|
||||
it { should have_password_authentication }
|
||||
|
||||
### password\_authentication?
|
||||
|
||||
Boolean to state of password authentication is enabled or not for the admin user.
|
||||
|
||||
its('password_authentication?') { should be false }
|
||||
|
||||
This only applies to Linux machines and will always return `true` on Windows.
|
||||
|
||||
### have\_custom\_data
|
||||
|
||||
Returns a boolean stating if the machine has custom data assigned to it.
|
||||
|
||||
it { should have_custom_data }
|
||||
|
||||
### custom\_data?
|
||||
|
||||
Boolean to state if the machine has custom data or not
|
||||
|
||||
its('custom_data') { should be true }
|
||||
|
||||
### have\_ssh\_keys
|
||||
|
||||
Boolean to state if the machine has SSH keys assigned to it
|
||||
|
||||
it { should have_ssh_keys }
|
||||
|
||||
For a Windows machine this will always be false.
|
||||
|
||||
### ssh\_keys?
|
||||
|
||||
Boolean to state of the machine is accessible using SSH keys
|
||||
|
||||
its('ssh_keys?') { should be true }
|
||||
|
||||
### ssh\_key\_count
|
||||
|
||||
Returns how many SSH keys have been applied to the machine.
|
||||
|
||||
This only applies to Linux machines and will always return `0` on Windows.
|
||||
|
||||
### ssh\_keys
|
||||
|
||||
Returns an array of the keys that are assigned to the machine. This is check if the correct keys are assigned.
|
||||
|
||||
Most SSH public keys have a signature at the end of them that can be tested. For example:
|
||||
|
||||
its('ssh_keys') { should include /azure@inspec.local/ }
|
||||
|
||||
### boot\_diagnostics?
|
||||
|
||||
Boolean test to see if boot diagnostics have been enabled on the machine
|
||||
|
||||
it { should have_boot_diagnostics }
|
||||
|
||||
### boot\_diagnostics\_storage\_uri
|
||||
|
||||
If boot diagnostics are enabled for the machine they will be saved in a storage account. This method returns the URI for the storage account.
|
||||
|
||||
its('boot_diagnostics_storage_uri') { should match 'ghjgjhgjg' }
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
There are a number of built in comparison operrtors that are available to test the result with an expected value.
|
||||
|
||||
For information on all that are available please refer to the [Inspec Matchers Reference](https://www.inspec.io/docs/reference/matchers/) page.
|
||||
|
||||
<br>
|
||||
|
||||
|
||||
## Tags
|
||||
|
||||
It is possible to test the tags that have been assigned to the resource. There are a number of properties that can be called to check that it has tags, that it has the correct number and that the correct ones are assigned.
|
||||
|
||||
### have\_tags
|
||||
|
||||
This is a simple test to see if the machine has tags assigned to it or not.
|
||||
|
||||
it { should have_tags }
|
||||
|
||||
### tag\_count
|
||||
|
||||
Returns the number of tags that are assigned to the resource
|
||||
|
||||
its ('tag_count') { should eq 2 }
|
||||
|
||||
### tags
|
||||
|
||||
It is possible to check if a specific tag has been set on the resource.
|
||||
|
||||
its('tags') { should include 'Owner' }
|
||||
|
||||
### xxx\_tag
|
||||
|
||||
To get the value of the tag, a number of tests have been craeted from the tags that are set.
|
||||
|
||||
For example, if the following tag is set on a resource:
|
||||
|
||||
| Tag Name | Value |
|
||||
|----------|-------|
|
||||
| Owner | Russell Seymour |
|
||||
|
||||
Then a test is available called `Owner_tag`.
|
||||
|
||||
its('Owner_tag') { should cmp 'Russell Seymour' }
|
||||
|
||||
Note: The tag name is case sensitive which makes the test case sensitive. E.g. `owner_tag` does not equal `Owner_tag`.
|
||||
|
||||
|
||||
|
||||
## References
|
||||
|
||||
- [Azure Ruby SDK - Resources](https://github.com/Azure/azure-sdk-for-ruby/tree/master/management/azure_mgmt_resources)
|
182
docs/resources/azure_virtual_machine_datadisk.md.erb
Normal file
182
docs/resources/azure_virtual_machine_datadisk.md.erb
Normal file
|
@ -0,0 +1,182 @@
|
|||
---
|
||||
title: About the azure_virtual_machine_datadisk Resource
|
||||
---
|
||||
|
||||
# azure\_virtual\_machine\_datadisk
|
||||
|
||||
Use this resource to ensure that a specific data disk attached to a machine has been created properly.
|
||||
|
||||
## References
|
||||
|
||||
- [Azure Ruby SDK - Compute](https://github.com/Azure/azure-sdk-for-ruby/tree/master/management/azure_mgmt_compute)
|
||||
|
||||
## Syntax
|
||||
|
||||
The name of the resource group and machine are required to use this resource.
|
||||
|
||||
describe azure_virtual_machine_data_disk(group_name: 'MyResourceGroup', name: 'MyVM') do
|
||||
its('property') { should eq 'value' }
|
||||
end
|
||||
|
||||
where
|
||||
|
||||
* `MyVm` is the name of the virtual machine as seen in Azure. (It is **not** the hostname of the machine)
|
||||
* `MyResourceGroup` is the name of the resouce group that the machine is in.
|
||||
* `property` is a resource property
|
||||
* `value` is the expected output fdrom the matcher
|
||||
|
||||
The `count`, `have_data_disks` and `have_managed_disks` are catchall tests that give information about the virtual machine. The specific tests need to be used in conjunction with the `where` option as shown below.
|
||||
|
||||
| Name | Description | Required | Example |
|
||||
|-------------|---------------------------------------------------------------------------------------------------------------------|----------|-----------------------------------|
|
||||
| group_name: | Azure Resource Group to be tested | yes | MyResourceGroup |
|
||||
| name: | Name of the Azure resource to test | no | MyVM |
|
||||
| apiversion: | API Version to use when interrogating the resource. If not set then the latest version for the resoure type is used | no | 2017-10-9 |
|
||||
|
||||
These options can also be set using the environment variables:
|
||||
|
||||
- `AZURE_RESOURCE_GROUP_NAME`
|
||||
- `AZURE_RESOURCE_NAME`
|
||||
- `AZURE_RESOURCE_API_VERSION`
|
||||
|
||||
When the options have been set as well as the environment variables, the environment variables take priority.
|
||||
|
||||
For example:
|
||||
|
||||
describe azure_virtual_machine_data_disk(group_name: 'Inspec-Azure', name: 'Linux-Internal-VM') do
|
||||
its('count') { should cmp > 0 }
|
||||
it { should have_data_disks }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Examples
|
||||
|
||||
The following examples show to use this InSpec audit resource.
|
||||
|
||||
Please refer to the following integration tests for more in depth examples:
|
||||
|
||||
- [Linux Internal Data Disks](../../test/integration/verify/controls/virtual_machine_linux_external_vm_datadisk.rb)
|
||||
- [Windows Internal Data Disk](../../test/integration/verify/controls/virtual_machine_windows_internal_vm_datadisk.rb)
|
||||
|
||||
### Check that the first data disk is of the correct size
|
||||
|
||||
describe azure_virtual_machine_data_disk(group_name: 'Inspec-Azure', name: 'Linux-Internal-VM').where(number: 1) do
|
||||
its('size') { should cmp >= 15 }
|
||||
end
|
||||
|
||||
<br>
|
||||
|
||||
## Properties
|
||||
|
||||
* `count`, `have_data_disks`, `have_managed_disks`, `disk`, `number`, `name`, `size`, `lun`
|
||||
, `caching`, `create_option`, `is_managed_disk?`, `vhd_uri`, `storage_account_name`, `storage_account_type`, `id`, `subscription_id`, `resource_group`
|
||||
|
||||
<br>
|
||||
|
||||
## Property Examples
|
||||
|
||||
### count
|
||||
|
||||
Returns the number of data disks attached to the machine
|
||||
|
||||
its('count') { should eq 1 }
|
||||
|
||||
### have\_data\_disks
|
||||
|
||||
Returns a boolean denoting if any data disks are attached to the machine
|
||||
|
||||
it { should have_data_disks }
|
||||
|
||||
### have\_managed\_disks
|
||||
|
||||
Returns a boolean stating if the machine has Managed Disks for data disks.
|
||||
|
||||
it { should have_managed_disks }
|
||||
|
||||
**The next set of attributes require the `where` operation to be used on the describe.**
|
||||
|
||||
The following code shows an example of how to use the where clause.
|
||||
|
||||
describe azure_virtual_machine_data_disk(group_name: 'Inspec-Azure', name: 'Windows-Internal-VM').where(number: 1)
|
||||
end
|
||||
|
||||
### disk
|
||||
|
||||
The zero based index of the disk attached to the machine.
|
||||
|
||||
Typically used in the `where` clause
|
||||
|
||||
### number
|
||||
|
||||
The '1' based index of the disk attached to the machine.
|
||||
|
||||
Typically used in the `where` clause as showm above.
|
||||
|
||||
### name
|
||||
|
||||
Returns a string of the name of the disk.
|
||||
|
||||
### size
|
||||
|
||||
Returns an integer of size of this disk in GB.
|
||||
|
||||
### lun
|
||||
|
||||
The disk number as reported by Azure. This is a zero based index value.
|
||||
|
||||
### caching
|
||||
|
||||
String stating the caching that has been set on the disk.
|
||||
|
||||
### create\_option
|
||||
|
||||
How the disk was created. Typically for data disks this will be the string value 'Empty'.
|
||||
|
||||
### is\_managed\_disk?
|
||||
|
||||
Boolean stating if the disk is a managed disk or not. If it is not a managed disk then it is one that is stored in a Storage Account.
|
||||
|
||||
### vhd\_uri
|
||||
|
||||
If this _not_ a managed disk then the `vhd_uri` will be the full URI to the disk in the storage account.
|
||||
|
||||
### storage\_account\_name
|
||||
|
||||
If this is _not_ a managed disk this will be the storage account name in which the disk is stored.
|
||||
|
||||
This derived from the `vhd_uri`.
|
||||
|
||||
### storage\_account\_type
|
||||
|
||||
If this is a managed disk this is the storage account type, e.g. `Standard_LRS`
|
||||
|
||||
### id
|
||||
|
||||
If this is a managed disk then this is the fully qualified id to the disk in Azure.
|
||||
|
||||
### subscription\_id
|
||||
|
||||
If this is a managed disk, this returns the subscription id of where the disk is stored.
|
||||
|
||||
This is derived from the `id`.
|
||||
|
||||
### resource\_group
|
||||
|
||||
If this is a managed disk, this returns the resource group in which the disk is stored.
|
||||
|
||||
This is derived from the `id`.
|
||||
|
||||
<br>
|
||||
|
||||
## Matchers
|
||||
|
||||
This InSpec audit resource has the following matchers:
|
||||
|
||||
### eq
|
||||
|
||||
Use the `eq` matcher to test the equality of two values: `its('Port') { should eq '22' }`.
|
||||
|
||||
Using `its('Port') { should eq 22 }` will fail because `22` is not a string value! Use the `cmp` matcher for less restrictive value comparisons.
|
||||
|
||||
**The following properties are ones that are applied to the virtual machine itself and not specfic disks**
|
|
@ -6,13 +6,6 @@ title: About the http Resource
|
|||
|
||||
Use the `http` InSpec audit resource to test an http endpoint.
|
||||
|
||||
<p class="warning">In InSpec 1.40 and earlier, this resource always executes on the host on which <code>inspec exec</code> is run, even if you use the <code>--target</code> option to remotely scan a different host.<br>
|
||||
<br>
|
||||
Beginning with InSpec 1.41, you can enable remote targeting for the HTTP test, provided <code>curl</code> is available. See the "Local vs. Remote" section below.<br>
|
||||
<br>
|
||||
Executing the HTTP test on the remote target will be the default behavior in InSpec 2.0.
|
||||
</p>
|
||||
|
||||
<br>
|
||||
|
||||
## Syntax
|
||||
|
@ -38,15 +31,6 @@ where
|
|||
* `ssl_verify` may be specified to enable or disable verification of SSL certificates (default to `true`)
|
||||
|
||||
<br>
|
||||
## Local vs. Remote
|
||||
|
||||
Beginning with InSpec 1.41, you can enable the ability to have the HTTP test execute on the remote target:
|
||||
|
||||
describe http('http://www.example.com', enable_remote_worker: true) do
|
||||
its('body') { should cmp 'awesome' }
|
||||
end
|
||||
|
||||
In InSpec 2.0, the HTTP test will automatically execute remotely whenever InSpec is testing a remote node.
|
||||
|
||||
<br>
|
||||
## Properties
|
||||
|
|
|
@ -4,7 +4,7 @@ title: About the windows_task Resource
|
|||
|
||||
# windows_task
|
||||
|
||||
Use the `windows_task` Inspec audit resource to test a scheduled tasks configuration on a Windows platform.
|
||||
Use the `windows_task` InSpec audit resource to test a scheduled tasks configuration on a Windows platform.
|
||||
Microsoft and application vendors use scheduled tasks to perform a variety of system maintaince tasks but system administrators can schedule their own.
|
||||
|
||||
<br>
|
||||
|
|
|
@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
|
|||
|
||||
spec.required_ruby_version = '>= 2.3'
|
||||
|
||||
spec.add_dependency 'train', '~> 0.32'
|
||||
spec.add_dependency 'train', '~> 1.1'
|
||||
spec.add_dependency 'thor', '~> 0.19'
|
||||
spec.add_dependency 'json', '>= 1.8', '< 3.0'
|
||||
spec.add_dependency 'method_source', '~> 0.8'
|
||||
|
|
|
@ -46,38 +46,6 @@ module Compliance
|
|||
Compliance::API.login(options)
|
||||
end
|
||||
|
||||
desc "login_automate https://SERVER --insecure --user='USER' --ent='ENTERPRISE' --usertoken='TOKEN'", 'Log in to a Chef Automate SERVER (DEPRECATED: Please use `login`)'
|
||||
long_desc <<-LONGDESC
|
||||
This commmand is deprecated and will be removed, please use `--login`.
|
||||
|
||||
`login_automate` allows you to use InSpec with Chef Automate.
|
||||
|
||||
You need to a token for communication. More information about token retrieval
|
||||
is available at:
|
||||
https://docs.chef.io/api_automate.html#authentication-methods
|
||||
https://docs.chef.io/api_compliance.html#obtaining-an-api-token
|
||||
LONGDESC
|
||||
option :insecure, aliases: :k, type: :boolean,
|
||||
desc: 'Explicitly allows InSpec to perform "insecure" SSL connections and transfers'
|
||||
option :user, type: :string, required: true,
|
||||
desc: 'Username'
|
||||
option :usertoken, type: :string, required: false,
|
||||
desc: 'Access token (DEPRECATED: Please use `--token`)'
|
||||
option :token, type: :string, required: false,
|
||||
desc: 'Access token'
|
||||
option :dctoken, type: :string, required: false,
|
||||
desc: 'Data Collector token'
|
||||
option :ent, type: :string, required: true,
|
||||
desc: 'Enterprise for Chef Automate reporting'
|
||||
def login_automate(server)
|
||||
warn '[DEPRECATION] `inspec compliance login_automate` is deprecated. Please use `inspec compliance login`'
|
||||
options['server'] = server
|
||||
|
||||
options['token'] = options['usertoken'] if options['usertoken']
|
||||
|
||||
Compliance::API.login(options)
|
||||
end
|
||||
|
||||
desc 'profiles', 'list all available profiles in Chef Compliance'
|
||||
option :owner, type: :string, required: false,
|
||||
desc: 'owner whose profiles to list'
|
||||
|
|
|
@ -66,8 +66,6 @@ module Inspec
|
|||
desc: 'Use colors in output.'
|
||||
option :attrs, type: :array,
|
||||
desc: 'Load attributes file (experimental)'
|
||||
option :cache, type: :string,
|
||||
desc: '[DEPRECATED] Please use --vendor-cache - this will be removed in InSpec 2.0'
|
||||
option :vendor_cache, type: :string,
|
||||
desc: 'Use the given path for caching dependencies. (default: ~/.inspec/cache)'
|
||||
option :create_lockfile, type: :boolean,
|
||||
|
|
|
@ -162,12 +162,6 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
diagnose(o)
|
||||
configure_logger(o)
|
||||
|
||||
# TODO: REMOVE for inspec 2.0
|
||||
if o.key?('cache')
|
||||
o[:vendor_cache] = o[:cache]
|
||||
o[:logger].warn '[DEPRECATED] The use of `--cache` is being deprecated in InSpec 2.0. Please use `--vendor-cache` instead.'
|
||||
end
|
||||
|
||||
runner = Inspec::Runner.new(o)
|
||||
targets.each { |target| runner.add_target(target) }
|
||||
|
||||
|
@ -184,17 +178,27 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :format, type: :string
|
||||
def detect
|
||||
o = opts(:detect).dup
|
||||
o[:command] = 'os.params'
|
||||
o[:command] = 'platform.params'
|
||||
(_, res) = run_command(o)
|
||||
if o['format'] == 'json'
|
||||
puts res.to_json
|
||||
else
|
||||
headline('Operating System Details')
|
||||
%w{name family release arch}.each { |item|
|
||||
puts format('%-10s %s', item.to_s.capitalize + ':',
|
||||
mark_text(res[item.to_sym]))
|
||||
headline('Platform Details')
|
||||
%w{name families release arch}.each { |item|
|
||||
data = res[item.to_sym]
|
||||
|
||||
# Format Array for better output if applicable
|
||||
data = data.join(', ') if data.is_a?(Array)
|
||||
|
||||
# Do not output fields of data is missing ('unknown' is fine)
|
||||
next if data.nil?
|
||||
|
||||
puts format('%-10s %s', item.to_s.capitalize + ':', mark_text(data))
|
||||
}
|
||||
end
|
||||
rescue ArgumentError, RuntimeError, Train::UserError => e
|
||||
$stderr.puts e.message
|
||||
exit 1
|
||||
rescue StandardError => e
|
||||
pretty_handle_exception(e)
|
||||
end
|
||||
|
|
|
@ -23,6 +23,7 @@ module Inspec::Formatters
|
|||
run_data[:version] = Inspec::VERSION
|
||||
run_data[:statistics] = {
|
||||
duration: summary.duration,
|
||||
controls: statistics,
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -84,6 +85,47 @@ module Inspec::Formatters
|
|||
|
||||
private
|
||||
|
||||
def all_unique_controls
|
||||
unique_controls = Set.new
|
||||
run_data[:profiles].each do |profile|
|
||||
profile[:controls].map { |control| unique_controls.add(control) }
|
||||
end
|
||||
|
||||
unique_controls
|
||||
end
|
||||
|
||||
def statistics
|
||||
failed = 0
|
||||
skipped = 0
|
||||
passed = 0
|
||||
|
||||
all_unique_controls.each do |control|
|
||||
next unless control[:results]
|
||||
if control[:results].any? { |r| r[:status] == 'failed' }
|
||||
failed += 1
|
||||
elsif control[:results].any? { |r| r[:status] == 'skipped' }
|
||||
skipped += 1
|
||||
else
|
||||
passed += 1
|
||||
end
|
||||
end
|
||||
|
||||
total = failed + passed + skipped
|
||||
|
||||
{
|
||||
total: total,
|
||||
passed: {
|
||||
total: passed,
|
||||
},
|
||||
skipped: {
|
||||
total: skipped,
|
||||
},
|
||||
failed: {
|
||||
total: failed,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def exception_message(exception)
|
||||
if exception.is_a?(RSpec::Core::MultipleExceptionError)
|
||||
exception.all_exceptions.map(&:message).uniq.join("\n\n")
|
||||
|
@ -111,7 +153,7 @@ module Inspec::Formatters
|
|||
status: example.execution_result.status.to_s,
|
||||
code_desc: code_description,
|
||||
run_time: example.execution_result.run_time,
|
||||
start_time: example.execution_result.started_at.to_s,
|
||||
start_time: example.execution_result.started_at.to_datetime.rfc3339.to_s,
|
||||
resource_title: example.metadata[:described_class] || example.metadata[:example_group][:description],
|
||||
expectation_message: format_expectation_message(example),
|
||||
}
|
||||
|
|
|
@ -54,15 +54,9 @@ module Inspec
|
|||
end
|
||||
|
||||
def inspec_requirement
|
||||
inspec_in_supports = params[:supports].find { |x| !x[:inspec].nil? }
|
||||
if inspec_in_supports
|
||||
warn '[DEPRECATED] The use of inspec.yml `supports:inspec` is deprecated and will be removed in InSpec 2.0. Please use `inspec_version` instead.'
|
||||
Gem::Requirement.create(inspec_in_supports[:inspec])
|
||||
else
|
||||
# using Gem::Requirement here to allow nil values which
|
||||
# translate to [">= 0"]
|
||||
Gem::Requirement.create(params[:inspec_version])
|
||||
end
|
||||
# using Gem::Requirement here to allow nil values which
|
||||
# translate to [">= 0"]
|
||||
Gem::Requirement.create(params[:inspec_version])
|
||||
end
|
||||
|
||||
def supports_runtime?
|
||||
|
|
|
@ -43,8 +43,8 @@ module Inspec
|
|||
Inspec::Resource.registry
|
||||
end
|
||||
|
||||
def __register(name, obj) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
||||
cl = Class.new(obj) do # rubocop:disable Metrics/BlockLength
|
||||
def __register(name, obj) # rubocop:disable Metrics/MethodLength
|
||||
cl = Class.new(obj) do
|
||||
attr_reader :resource_exception_message
|
||||
|
||||
def initialize(backend, name, *args)
|
||||
|
@ -94,13 +94,6 @@ module Inspec
|
|||
@resource_skipped
|
||||
end
|
||||
|
||||
def resource_skipped
|
||||
warn('[DEPRECATION] Use `resource_exception_message` for the resource skipped message. This method will be removed in InSpec 2.0.')
|
||||
# Returning `nil` here to match previous behavior
|
||||
return nil if @resource_skipped == false
|
||||
@resource_exception_message
|
||||
end
|
||||
|
||||
def fail_resource(message)
|
||||
@resource_failed = true
|
||||
@resource_exception_message = message
|
||||
|
|
|
@ -7,9 +7,6 @@ module Inspec::Reporters
|
|||
# Most currently available Windows terminals have poor support
|
||||
# for ANSI extended colors
|
||||
COLORS = {
|
||||
'critical' => "\033[0;1;31m",
|
||||
'major' => "\033[0;1;31m",
|
||||
'minor' => "\033[0;36m",
|
||||
'failed' => "\033[0;1;31m",
|
||||
'passed' => "\033[0;1;32m",
|
||||
'skipped' => "\033[0;37m",
|
||||
|
@ -19,9 +16,6 @@ module Inspec::Reporters
|
|||
# Most currently available Windows terminals have poor support
|
||||
# for UTF-8 characters so use these boring indicators
|
||||
INDICATORS = {
|
||||
'critical' => '[CRIT]',
|
||||
'major' => '[MAJR]',
|
||||
'minor' => '[MINR]',
|
||||
'failed' => '[FAIL]',
|
||||
'skipped' => '[SKIP]',
|
||||
'passed' => '[PASS]',
|
||||
|
@ -30,9 +24,6 @@ module Inspec::Reporters
|
|||
else
|
||||
# Extended colors for everyone else
|
||||
COLORS = {
|
||||
'critical' => "\033[38;5;9m",
|
||||
'major' => "\033[38;5;208m",
|
||||
'minor' => "\033[0;36m",
|
||||
'failed' => "\033[38;5;9m",
|
||||
'passed' => "\033[38;5;41m",
|
||||
'skipped' => "\033[38;5;247m",
|
||||
|
@ -42,9 +33,6 @@ module Inspec::Reporters
|
|||
# Groovy UTF-8 characters for everyone else...
|
||||
# ...even though they probably only work on Mac
|
||||
INDICATORS = {
|
||||
'critical' => '×',
|
||||
'major' => '∅',
|
||||
'minor' => '⊚',
|
||||
'failed' => '×',
|
||||
'skipped' => '↺',
|
||||
'passed' => '✔',
|
||||
|
@ -177,27 +165,15 @@ module Inspec::Reporters
|
|||
end
|
||||
|
||||
def profile_summary
|
||||
return @profile_summary unless @profile_summary.nil?
|
||||
|
||||
failed = 0
|
||||
skipped = 0
|
||||
passed = 0
|
||||
critical = 0
|
||||
major = 0
|
||||
minor = 0
|
||||
|
||||
all_unique_controls.each do |control|
|
||||
next if control[:id].start_with? '(generated from '
|
||||
next unless control[:results]
|
||||
if control[:results].any? { |r| r[:status] == 'failed' }
|
||||
failed += 1
|
||||
if control[:impact] >= 0.7
|
||||
critical += 1
|
||||
elsif control[:impact] >= 0.4
|
||||
major += 1
|
||||
else
|
||||
minor += 1
|
||||
end
|
||||
elsif control[:results].any? { |r| r[:status] == 'skipped' }
|
||||
skipped += 1
|
||||
else
|
||||
|
@ -207,22 +183,15 @@ module Inspec::Reporters
|
|||
|
||||
total = failed + passed + skipped
|
||||
|
||||
@profile_summary = {
|
||||
{
|
||||
'total' => total,
|
||||
'failed' => {
|
||||
'total' => failed,
|
||||
'critical' => critical,
|
||||
'major' => major,
|
||||
'minor' => minor,
|
||||
},
|
||||
'failed' => failed,
|
||||
'skipped' => skipped,
|
||||
'passed' => passed,
|
||||
}
|
||||
end
|
||||
|
||||
def tests_summary
|
||||
return @tests_summary unless @tests_summary.nil?
|
||||
|
||||
total = 0
|
||||
failed = 0
|
||||
skipped = 0
|
||||
|
@ -241,7 +210,12 @@ module Inspec::Reporters
|
|||
end
|
||||
end
|
||||
|
||||
@tests_summary = { 'total' => total, 'failed' => failed, 'skipped' => skipped, 'passed' => passed }
|
||||
{
|
||||
'total' => total,
|
||||
'failed' => failed,
|
||||
'skipped' => skipped,
|
||||
'passed' => passed,
|
||||
}
|
||||
end
|
||||
|
||||
def print_profile_summary
|
||||
|
@ -249,11 +223,11 @@ module Inspec::Reporters
|
|||
return unless summary['total'] > 0
|
||||
|
||||
success_str = summary['passed'] == 1 ? '1 successful control' : "#{summary['passed']} successful controls"
|
||||
failed_str = summary['failed']['total'] == 1 ? '1 control failure' : "#{summary['failed']['total']} control failures"
|
||||
failed_str = summary['failed'] == 1 ? '1 control failure' : "#{summary['failed']} control failures"
|
||||
skipped_str = summary['skipped'] == 1 ? '1 control skipped' : "#{summary['skipped']} controls skipped"
|
||||
|
||||
success_color = summary['passed'] > 0 ? 'passed' : 'no_color'
|
||||
failed_color = summary['failed']['total'] > 0 ? 'failed' : 'no_color'
|
||||
failed_color = summary['failed'] > 0 ? 'failed' : 'no_color'
|
||||
skipped_color = summary['skipped'] > 0 ? 'skipped' : 'no_color'
|
||||
|
||||
s = format(
|
||||
|
@ -301,11 +275,6 @@ module Inspec::Reporters
|
|||
end
|
||||
|
||||
class Control
|
||||
IMPACT_SCORES = {
|
||||
critical: 0.7,
|
||||
major: 0.4,
|
||||
}.freeze
|
||||
|
||||
attr_reader :data
|
||||
|
||||
def initialize(control_hash)
|
||||
|
@ -358,12 +327,8 @@ module Inspec::Reporters
|
|||
'skipped'
|
||||
elsif results.nil? || results.empty? || results.all? { |r| r[:status] == 'passed' }
|
||||
'passed'
|
||||
elsif impact >= IMPACT_SCORES[:critical]
|
||||
'critical'
|
||||
elsif impact >= IMPACT_SCORES[:major]
|
||||
'major'
|
||||
else
|
||||
'minor'
|
||||
'failed'
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -374,12 +339,8 @@ module Inspec::Reporters
|
|||
'passed'
|
||||
elsif impact.nil?
|
||||
'unknown'
|
||||
elsif impact >= IMPACT_SCORES[:critical]
|
||||
'critical'
|
||||
elsif impact >= IMPACT_SCORES[:major]
|
||||
'major'
|
||||
else
|
||||
'minor'
|
||||
'failed'
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -12,10 +12,10 @@ module Inspec::Reporters
|
|||
{
|
||||
platform: platform,
|
||||
profiles: profiles,
|
||||
statistics: { duration: run_data[:statistics][:duration] },
|
||||
statistics: {
|
||||
duration: run_data[:statistics][:duration],
|
||||
},
|
||||
version: run_data[:version],
|
||||
controls: controls,
|
||||
other_checks: run_data[:other_checks],
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -28,28 +28,6 @@ module Inspec::Reporters
|
|||
}
|
||||
end
|
||||
|
||||
def controls
|
||||
controls = []
|
||||
return controls if run_data[:controls].nil?
|
||||
|
||||
run_data[:controls].each do |c|
|
||||
control = {
|
||||
status: c[:status],
|
||||
start_time: c[:start_time],
|
||||
run_time: c[:run_time],
|
||||
code_desc: c[:code_desc],
|
||||
}
|
||||
control[:resource] = c[:resource] if c[:resource]
|
||||
control[:skip_message] = c[:skip_message] if c[:skip_message]
|
||||
control[:message] = c[:message] if c[:message]
|
||||
control[:exception] = c[:exception] if c[:exception]
|
||||
control[:backtrace] = c[:backtrace] if c[:backtrace]
|
||||
|
||||
controls << control
|
||||
end
|
||||
controls
|
||||
end
|
||||
|
||||
def profile_results(control)
|
||||
results = []
|
||||
return results if control[:results].nil?
|
||||
|
|
|
@ -74,6 +74,17 @@ module Inspec
|
|||
end
|
||||
end
|
||||
|
||||
# Many resources use FilterTable.
|
||||
require 'utils/filter'
|
||||
|
||||
# AWS resources are included via their own file.
|
||||
require 'resource_support/aws'
|
||||
|
||||
require 'resources/azure/azure_backend.rb'
|
||||
require 'resources/azure/azure_generic_resource.rb'
|
||||
require 'resources/azure/azure_resource_group.rb'
|
||||
require 'resources/azure/azure_virtual_machine.rb'
|
||||
require 'resources/azure/azure_virtual_machine_data_disk.rb'
|
||||
require 'resources/aide_conf'
|
||||
require 'resources/apache'
|
||||
require 'resources/apache_conf'
|
||||
|
@ -81,7 +92,6 @@ require 'resources/apt'
|
|||
require 'resources/audit_policy'
|
||||
require 'resources/auditd'
|
||||
require 'resources/auditd_conf'
|
||||
require 'resources/auditd_rules'
|
||||
require 'resources/bash'
|
||||
require 'resources/bond'
|
||||
require 'resources/bridge'
|
||||
|
|
|
@ -129,10 +129,9 @@ module Inspec
|
|||
end
|
||||
|
||||
def run_tests(with = nil)
|
||||
status, @run_data = @test_collector.run(with)
|
||||
# dont output anything if we want a report
|
||||
render_output(@run_data) unless @conf['report']
|
||||
status
|
||||
run_data = @test_collector.run(with)
|
||||
render_output(run_data)
|
||||
@test_collector.exit_code
|
||||
end
|
||||
|
||||
# determine all attributes before the execution, fetch data from secrets backend
|
||||
|
|
|
@ -74,8 +74,24 @@ module Inspec
|
|||
# @return [int] 0 if all went well; otherwise nonzero
|
||||
def run(with = nil)
|
||||
with ||= RSpec::Core::Runner.new(nil)
|
||||
status = with.run_specs(tests)
|
||||
[status, @formatter.run_data]
|
||||
@rspec_exit_code = with.run_specs(tests)
|
||||
@formatter.results
|
||||
end
|
||||
|
||||
# Return a proper exit code to the runner
|
||||
#
|
||||
# @return [int] exit code
|
||||
def exit_code
|
||||
stats = @formatter.results[:statistics][:controls]
|
||||
if stats[:failed][:total] == 0 && stats[:skipped][:total] == 0
|
||||
0
|
||||
elsif stats[:failed][:total] > 0
|
||||
100
|
||||
elsif stats[:skipped][:total] > 0
|
||||
101
|
||||
else
|
||||
@rspec_exit_code
|
||||
end
|
||||
end
|
||||
|
||||
# Empty the list of registered tests.
|
||||
|
|
|
@ -8,6 +8,31 @@ module Inspec
|
|||
'additionalProperties' => false,
|
||||
'properties' => {
|
||||
'duration' => { 'type' => 'number' },
|
||||
'controls' => {
|
||||
'type' => 'object',
|
||||
'optional' => true,
|
||||
'properties' => {
|
||||
'total' => { 'type' => 'number' },
|
||||
'passed' => {
|
||||
'type' => 'object',
|
||||
'properties' => {
|
||||
'total' => { 'type' => 'number' },
|
||||
},
|
||||
},
|
||||
'skipped' => {
|
||||
'type' => 'object',
|
||||
'properties' => {
|
||||
'total' => { 'type' => 'number' },
|
||||
},
|
||||
},
|
||||
'failed' => {
|
||||
'type' => 'object',
|
||||
'properties' => {
|
||||
'total' => { 'type' => 'number' },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}.freeze
|
||||
|
||||
|
@ -137,10 +162,6 @@ module Inspec
|
|||
},
|
||||
'statistics' => STATISTICS,
|
||||
'version' => { 'type' => 'string' },
|
||||
|
||||
# DEPRECATED PROPERTIES!! These will be removed with the next major version bump
|
||||
'controls' => 'array',
|
||||
'other_checks' => 'array',
|
||||
},
|
||||
}.freeze
|
||||
|
||||
|
|
|
@ -66,25 +66,6 @@ RSpec::Matchers.define :be_executable do
|
|||
end
|
||||
end
|
||||
|
||||
# matcher to check /etc/passwd, /etc/shadow and /etc/group
|
||||
RSpec::Matchers.define :contain_legacy_plus do
|
||||
match do |file|
|
||||
warn '[DEPRECATION] `contain_legacy_plus` is deprecated and will be removed in the next major version. Please use `describe file(\'/etc/passwd\') do its(\'content\') { should_not match /^\+:/ } end`'
|
||||
file.content =~ /^\+:/
|
||||
end
|
||||
end
|
||||
|
||||
# verifies that no entry in an array contains a value
|
||||
RSpec::Matchers.define :contain_match do |regex|
|
||||
match do |arr|
|
||||
warn '[DEPRECATION] `contain_match` is deprecated and will be removed in the next major version. See https://github.com/chef/inspec/issues/738 for more details'
|
||||
arr.inject { |result, i|
|
||||
result = i.match(regex)
|
||||
result || i.match(/$/)
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
RSpec::Matchers.define :contain_duplicates do
|
||||
match do |arr|
|
||||
dup = arr.select { |element| arr.count(element) > 1 }
|
||||
|
@ -105,11 +86,6 @@ RSpec::Matchers.define :be_installed do
|
|||
chain :by do
|
||||
raise "[UNSUPPORTED] Please use the new resources 'gem', 'npm' or 'pip'."
|
||||
end
|
||||
|
||||
chain :with_version do |version|
|
||||
warn "[DEPRECATION] `with_version` is deprecated. Please use `its('version') { should eq '1.4.1' }` instead."
|
||||
@version = version
|
||||
end
|
||||
end
|
||||
|
||||
# for services
|
||||
|
@ -143,32 +119,6 @@ RSpec::Matchers.define :be_running do
|
|||
end
|
||||
end
|
||||
|
||||
# user resource matcher for serverspec compatibility
|
||||
# Deprecated: You should not use this matcher anymore
|
||||
RSpec::Matchers.define :belong_to_group do |compare_group|
|
||||
match do |user|
|
||||
warn "[DEPRECATION] `belong_to_group` is deprecated. Please use `its('groups') { should include('root') }` instead."
|
||||
user.groups.include?(compare_group)
|
||||
end
|
||||
|
||||
failure_message do |group|
|
||||
"expected that the user belongs to group `#{group}`"
|
||||
end
|
||||
end
|
||||
|
||||
# user resource matcher for serverspec compatibility
|
||||
# Deprecated: You should not use this matcher anymore
|
||||
RSpec::Matchers.define :belong_to_primary_group do |compare_group|
|
||||
match do |user|
|
||||
warn "[DEPRECATION] `belong_to_primary_group` is deprecated. Please use `its('group') { should eq 'root' }` instead."
|
||||
user.group == compare_group
|
||||
end
|
||||
|
||||
failure_message do |group|
|
||||
"expected that the user belongs to primary group `#{group}`"
|
||||
end
|
||||
end
|
||||
|
||||
# matcher to check if host is reachable
|
||||
RSpec::Matchers.define :be_reachable do
|
||||
match do |host|
|
||||
|
@ -214,14 +164,6 @@ RSpec::Matchers.define :have_rule do |rule|
|
|||
end
|
||||
end
|
||||
|
||||
# deprecated
|
||||
RSpec::Matchers.define :contain do |rule|
|
||||
match do |resource|
|
||||
warn "[DEPRECATION] `contain` matcher. Please use the following syntax `its('content') { should include('value') }`."
|
||||
expect(resource).to include(rule)
|
||||
end
|
||||
end
|
||||
|
||||
# `be_in` matcher
|
||||
# You can use it in the following cases:
|
||||
# - check if an item or array is included in a given array
|
||||
|
|
40
lib/resource_support/aws.rb
Normal file
40
lib/resource_support/aws.rb
Normal file
|
@ -0,0 +1,40 @@
|
|||
# Main AWS loader file. The intent is for this to be
|
||||
# loaded only if AWS resources are needed.
|
||||
|
||||
require 'aws-sdk' # TODO: split once ADK v3 is in use
|
||||
|
||||
require 'resource_support/aws/aws_backend_factory_mixin'
|
||||
require 'resource_support/aws/aws_resource_mixin'
|
||||
require 'resource_support/aws/aws_singular_resource_mixin'
|
||||
require 'resource_support/aws/aws_plural_resource_mixin'
|
||||
require 'resource_support/aws/aws_backend_base'
|
||||
|
||||
# Load all AWS resources
|
||||
# TODO: loop over and load entire directory
|
||||
# for f in ls lib/resources/aws/*; do t=$(echo $f | cut -c 5- | cut -f1 -d. ); echo "require '${t}'"; done
|
||||
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_ec2_instance'
|
||||
require 'resources/aws/aws_iam_access_key'
|
||||
require 'resources/aws/aws_iam_access_keys'
|
||||
require 'resources/aws/aws_iam_group'
|
||||
require 'resources/aws/aws_iam_groups'
|
||||
require 'resources/aws/aws_iam_password_policy'
|
||||
require 'resources/aws/aws_iam_policies'
|
||||
require 'resources/aws/aws_iam_policy'
|
||||
require 'resources/aws/aws_iam_role'
|
||||
require 'resources/aws/aws_iam_root_user'
|
||||
require 'resources/aws/aws_iam_user'
|
||||
require 'resources/aws/aws_iam_users'
|
||||
require 'resources/aws/aws_kms_keys'
|
||||
require 'resources/aws/aws_route_table'
|
||||
require 'resources/aws/aws_s3_bucket'
|
||||
require 'resources/aws/aws_security_group'
|
||||
require 'resources/aws/aws_security_groups'
|
||||
require 'resources/aws/aws_sns_topic'
|
||||
require 'resources/aws/aws_subnet'
|
||||
require 'resources/aws/aws_subnets'
|
||||
require 'resources/aws/aws_vpc'
|
||||
require 'resources/aws/aws_vpcs'
|
12
lib/resource_support/aws/aws_backend_base.rb
Normal file
12
lib/resource_support/aws/aws_backend_base.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
class AwsBackendBase
|
||||
attr_reader :aws_transport
|
||||
class << self; attr_accessor :aws_client_class end
|
||||
|
||||
def initialize(inspec = nil)
|
||||
@aws_transport = inspec ? inspec.backend : nil
|
||||
end
|
||||
|
||||
def aws_service_client
|
||||
aws_transport.aws_client(self.class.aws_client_class)
|
||||
end
|
||||
end
|
12
lib/resource_support/aws/aws_backend_factory_mixin.rb
Normal file
12
lib/resource_support/aws/aws_backend_factory_mixin.rb
Normal file
|
@ -0,0 +1,12 @@
|
|||
# Intended to be pulled in via extend, not include
|
||||
module AwsBackendFactoryMixin
|
||||
def create(inspec)
|
||||
@selected_backend.new(inspec)
|
||||
end
|
||||
|
||||
def select(klass)
|
||||
@selected_backend = klass
|
||||
end
|
||||
|
||||
alias set_default_backend select
|
||||
end
|
21
lib/resource_support/aws/aws_plural_resource_mixin.rb
Normal file
21
lib/resource_support/aws/aws_plural_resource_mixin.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
module AwsPluralResourceMixin
|
||||
include AwsResourceMixin
|
||||
attr_reader :table
|
||||
|
||||
# This sets up a class, AwsSomeResource::BackendFactory, that
|
||||
# provides a mechanism to create and use backends without
|
||||
# having to know which is selected. This is mainly used for
|
||||
# unit testing.
|
||||
# TODO: DRY up. This code exists in both the Singular and Plural mixins.
|
||||
# We'd like to put it in AwsResourceMixin, but included only sees the
|
||||
# directly-including class - we can't see second-order includers.
|
||||
def self.included(base)
|
||||
# Create a new class, whose body is simply to extend the
|
||||
# backend factory mixin
|
||||
resource_backend_factory_class = Class.new(Object) do
|
||||
extend AwsBackendFactoryMixin
|
||||
end
|
||||
# Name that class
|
||||
base.const_set('BackendFactory', resource_backend_factory_class)
|
||||
end
|
||||
end
|
62
lib/resource_support/aws/aws_resource_mixin.rb
Normal file
62
lib/resource_support/aws/aws_resource_mixin.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
module AwsResourceMixin
|
||||
def initialize(resource_params = {})
|
||||
validate_params(resource_params).each do |param, value|
|
||||
instance_variable_set(:"@#{param}", value)
|
||||
end
|
||||
catch_aws_errors do
|
||||
fetch_from_api
|
||||
end
|
||||
end
|
||||
|
||||
# Default implementation of validate params accepts everything.
|
||||
def validate_params(resource_params)
|
||||
resource_params
|
||||
end
|
||||
|
||||
def check_resource_param_names(raw_params: {}, allowed_params: [], allowed_scalar_name: nil, allowed_scalar_type: nil)
|
||||
# Some resources allow passing in a single ID value. Check and convert to hash if so.
|
||||
if allowed_scalar_name && !raw_params.is_a?(Hash)
|
||||
value_seen = raw_params
|
||||
if value_seen.is_a?(allowed_scalar_type)
|
||||
raw_params = { allowed_scalar_name => value_seen }
|
||||
else
|
||||
raise ArgumentError, 'If you pass a single value to the resource, it must ' \
|
||||
"be a #{allowed_scalar_type}, not an #{value_seen.class}."
|
||||
end
|
||||
end
|
||||
|
||||
# Remove all expected params from the raw param hash
|
||||
recognized_params = {}
|
||||
allowed_params.each do |expected_param|
|
||||
recognized_params[expected_param] = raw_params.delete(expected_param) if raw_params.key?(expected_param)
|
||||
end
|
||||
|
||||
# Any leftovers are unwelcome
|
||||
unless raw_params.empty?
|
||||
raise ArgumentError, "Unrecognized resource param '#{raw_params.keys.first}'. Expected parameters: #{allowed_params.join(', ')}"
|
||||
end
|
||||
|
||||
recognized_params
|
||||
end
|
||||
|
||||
def inspec_runner
|
||||
# When running under inspec-cli, we have an 'inspec' method that
|
||||
# returns the runner. When running under unit tests, we don't
|
||||
# have that, but we still have to call this to pass something
|
||||
# (nil is OK) to the backend.
|
||||
# TODO: remove with https://github.com/chef/inspec-aws/issues/216
|
||||
inspec if respond_to?(:inspec)
|
||||
end
|
||||
|
||||
# Intercept AWS exceptions
|
||||
def catch_aws_errors
|
||||
yield
|
||||
rescue Aws::Errors::MissingCredentialsError
|
||||
# The AWS error here is unhelpful:
|
||||
# "unable to sign request without credentials set"
|
||||
Inspec::Log.error "It appears that you have not set your AWS credentials. You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target. See https://www.inspec.io/docs/reference/platforms for details."
|
||||
fail_resource('No AWS credentials available')
|
||||
rescue Aws::Errors::ServiceError => e
|
||||
fail_resource e.message
|
||||
end
|
||||
end
|
24
lib/resource_support/aws/aws_singular_resource_mixin.rb
Normal file
24
lib/resource_support/aws/aws_singular_resource_mixin.rb
Normal file
|
@ -0,0 +1,24 @@
|
|||
module AwsSingularResourceMixin
|
||||
include AwsResourceMixin
|
||||
|
||||
def exists?
|
||||
@exists
|
||||
end
|
||||
|
||||
# This sets up a class, AwsSomeResource::BackendFactory, that
|
||||
# provides a mechanism to create and use backends without
|
||||
# having to know which is selected. This is mainly used for
|
||||
# unit testing.
|
||||
# TODO: DRY up. This code exists in both the Singular and Plural mixins.
|
||||
# We'd like to put it in AwsResourceMixin, but included only sees the
|
||||
# directly-including class - we can't see second-order includers.
|
||||
def self.included(base)
|
||||
# Create a new class, whose body is simply to extend the
|
||||
# backend factory mixin
|
||||
resource_backend_factory_class = Class.new(Object) do
|
||||
extend AwsBackendFactoryMixin
|
||||
end
|
||||
# Name that class
|
||||
base.const_set('BackendFactory', resource_backend_factory_class)
|
||||
end
|
||||
end
|
|
@ -1,205 +0,0 @@
|
|||
# encoding: utf-8
|
||||
# copyright: 2015, Vulcano Security GmbH
|
||||
# author: Christoph Hartmann
|
||||
# author: Dominik Richter
|
||||
|
||||
require 'forwardable'
|
||||
require 'utils/filter_array'
|
||||
|
||||
module Inspec::Resources
|
||||
class AuditdRulesLegacy
|
||||
def initialize(content)
|
||||
@content = content
|
||||
@opts = {
|
||||
assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
|
||||
multiple_values: true,
|
||||
}
|
||||
end
|
||||
|
||||
def params
|
||||
@params ||= SimpleConfig.new(@content, @opts).params
|
||||
end
|
||||
|
||||
def method_missing(name)
|
||||
params[name.to_s]
|
||||
end
|
||||
|
||||
def status(name)
|
||||
@status_opts = {
|
||||
assignment_regex: /^\s*([^:]*?)\s*:\s*(.*?)\s*$/,
|
||||
multiple_values: false,
|
||||
}
|
||||
@status_content ||= inspec.command('/sbin/auditctl -s').stdout.chomp
|
||||
@status_params = SimpleConfig.new(@status_content, @status_opts).params
|
||||
|
||||
status = @status_params['AUDIT_STATUS']
|
||||
return nil if status.nil?
|
||||
|
||||
items = Hash[status.scan(/([^=]+)=(\w*)\s*/)]
|
||||
items[name]
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Audit Daemon Rules (for auditd version < 2.3)'
|
||||
end
|
||||
end
|
||||
class AuditDaemonRules < Inspec.resource(1)
|
||||
extend Forwardable
|
||||
attr_accessor :rules, :lines
|
||||
|
||||
name 'auditd_rules'
|
||||
desc 'Use the auditd_rules InSpec audit resource to test the rules for logging that exist on the system. The audit.rules file is typically located under /etc/audit/ and contains the list of rules that define what is captured in log files.'
|
||||
example "
|
||||
# syntax for auditd < 2.3
|
||||
describe auditd_rules do
|
||||
its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=adjtimex,settimeofday/) }
|
||||
its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=stime,settimeofday,adjtimex/) }
|
||||
its('LIST_RULES') {should contain_match(/^exit,always arch=.* key=time-change syscall=clock_settime/)}
|
||||
its('LIST_RULES') {should contain_match(/^exit,always watch=\/etc\/localtime perm=wa key=time-change/)}
|
||||
end
|
||||
|
||||
# syntax for auditd >= 2.3
|
||||
describe auditd_rules.syscall('open').action do
|
||||
it { should eq(['always']) }
|
||||
end
|
||||
|
||||
describe auditd_rules.key('sshd_config') do
|
||||
its('permissions') { should contain_match(/x/) }
|
||||
end
|
||||
|
||||
describe auditd_rules do
|
||||
its('lines') { should contain_match(%r{-w /etc/ssh/sshd_config/}) }
|
||||
end
|
||||
"
|
||||
|
||||
def initialize
|
||||
@content = inspec.command('/sbin/auditctl -l').stdout.chomp
|
||||
|
||||
if @content =~ /^LIST_RULES:/
|
||||
# do not warn on centos 5
|
||||
unless inspec.os[:name] == 'centos' && inspec.os[:release].to_i == 5
|
||||
warn '[WARN] this version of auditd is outdated. Updating it allows for using more precise matchers.'
|
||||
end
|
||||
@legacy = AuditdRulesLegacy.new(@content)
|
||||
else
|
||||
parse_content
|
||||
@legacy = nil
|
||||
end
|
||||
|
||||
warn '[DEPRECATION] The `auditd_rules` resource is deprecated and will be removed in InSpec 2.0. Use the `auditd` resource instead.'
|
||||
end
|
||||
|
||||
# non-legacy instances are not asked for `its('LIST_RULES')`
|
||||
# rubocop:disable Style/MethodName
|
||||
def LIST_RULES
|
||||
return @legacy.LIST_RULES if @legacy
|
||||
raise 'Using legacy auditd_rules LIST_RULES interface with non-legacy audit package. Please use the new syntax.'
|
||||
end
|
||||
|
||||
def status(name = nil)
|
||||
return @legacy.status(name) if @legacy
|
||||
|
||||
@status_content ||= inspec.command('/sbin/auditctl -s').stdout.chomp
|
||||
@status_params ||= Hash[@status_content.scan(/^([^ ]+) (.*)$/)]
|
||||
|
||||
return @status_params[name] if name
|
||||
@status_params
|
||||
end
|
||||
|
||||
def parse_content
|
||||
@rules = {
|
||||
syscalls: [],
|
||||
files: [],
|
||||
}
|
||||
@lines = @content.lines.map(&:chomp)
|
||||
|
||||
lines.each do |line|
|
||||
if is_syscall?(line)
|
||||
syscalls = get_syscalls line
|
||||
action, list = get_action_list line
|
||||
fields, opts = get_fields line
|
||||
|
||||
# create a 'flatter' structure because sanity
|
||||
syscalls.each do |s|
|
||||
@rules[:syscalls] << { syscall: s, list: list, action: action, fields: fields }.merge(opts)
|
||||
end
|
||||
elsif is_file?(line)
|
||||
file = get_file line
|
||||
perms = get_permissions line
|
||||
key = get_key line
|
||||
|
||||
@rules[:files] << { file: file, key: key, permissions: perms }
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def syscall(name)
|
||||
select_name(:syscall, name)
|
||||
end
|
||||
|
||||
def file(name)
|
||||
select_name(:file, name)
|
||||
end
|
||||
|
||||
# both files and syscalls have `key` identifiers
|
||||
def key(name)
|
||||
res = rules.values.flatten.find_all { |rule| rule[:key] == name }
|
||||
FilterArray.new(res)
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Audit Daemon Rules'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def select_name(key, name)
|
||||
plural = "#{key}s".to_sym
|
||||
res = rules[plural].find_all { |rule| rule[key] == name }
|
||||
FilterArray.new(res)
|
||||
end
|
||||
|
||||
def is_syscall?(line)
|
||||
line.match(/\ -S /)
|
||||
end
|
||||
|
||||
def is_file?(line)
|
||||
line.match(/-w /)
|
||||
end
|
||||
|
||||
def get_syscalls(line)
|
||||
line.scan(/-S ([^ ]+) /).flatten.first.split(',')
|
||||
end
|
||||
|
||||
def get_action_list(line)
|
||||
line.scan(/-a ([^,]+),([^ ]+)/).flatten
|
||||
end
|
||||
|
||||
# NB only in file lines
|
||||
def get_key(line)
|
||||
line.match(/-k ([^ ]+)/)[1] if line.include?('-k ')
|
||||
end
|
||||
|
||||
# NOTE there are NO precautions wrt. filenames containing spaces in auditctl
|
||||
# `auditctl -w /foo\ bar` gives the following line: `-w /foo bar -p rwxa`
|
||||
def get_file(line)
|
||||
line.match(/-w (.+) -p/)[1]
|
||||
end
|
||||
|
||||
def get_permissions(line)
|
||||
line.match(/-p ([^ ]+)/)[1]
|
||||
end
|
||||
|
||||
def get_fields(line)
|
||||
fields = line.gsub(/-[aS] [^ ]+ /, '').split('-F ').map { |l| l.split(' ') }.flatten
|
||||
|
||||
opts = {}
|
||||
fields.find_all { |x| x.match(/[a-z]+=.*/) }.each do |kv|
|
||||
k, v = kv.split('=')
|
||||
opts[k.to_sym] = v
|
||||
end
|
||||
|
||||
[fields, opts]
|
||||
end
|
||||
end
|
||||
end
|
77
lib/resources/aws/aws_cloudtrail_trail.rb
Normal file
77
lib/resources/aws/aws_cloudtrail_trail.rb
Normal file
|
@ -0,0 +1,77 @@
|
|||
class AwsCloudTrailTrail < Inspec.resource(1)
|
||||
name 'aws_cloudtrail_trail'
|
||||
desc 'Verifies settings for an individual AWS CloudTrail Trail'
|
||||
example "
|
||||
describe aws_cloudtrail_trail('trail-name') do
|
||||
it { should exist }
|
||||
end
|
||||
"
|
||||
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :cloud_watch_logs_log_group_arn, :cloud_watch_logs_role_arn, :home_region,
|
||||
:kms_key_id, :s3_bucket_name, :trail_arn
|
||||
|
||||
def to_s
|
||||
"CloudTrail #{@trail_name}"
|
||||
end
|
||||
|
||||
def multi_region_trail?
|
||||
@is_multi_region_trail
|
||||
end
|
||||
|
||||
def log_file_validation_enabled?
|
||||
@log_file_validation_enabled
|
||||
end
|
||||
|
||||
def encrypted?
|
||||
!kms_key_id.nil?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:trail_name],
|
||||
allowed_scalar_name: :trail_name,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, "You must provide the parameter 'trail_name' to aws_cloudtrail_trail."
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
query = { trail_name_list: [@trail_name] }
|
||||
resp = backend.describe_trails(query)
|
||||
|
||||
@trail = resp.trail_list[0].to_h
|
||||
@exists = !@trail.empty?
|
||||
@s3_bucket_name = @trail[:s3_bucket_name]
|
||||
@is_multi_region_trail = @trail[:is_multi_region_trail]
|
||||
@trail_arn = @trail[:trail_arn]
|
||||
@log_file_validation_enabled = @trail[:log_file_validation_enabled]
|
||||
@cloud_watch_logs_role_arn = @trail[:cloud_watch_logs_role_arn]
|
||||
@cloud_watch_logs_log_group_arn = @trail[:cloud_watch_logs_log_group_arn]
|
||||
@kms_key_id = @trail[:kms_key_id]
|
||||
@home_region = @trail[:home_region]
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
AwsCloudTrailTrail::BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::CloudTrail::Client
|
||||
|
||||
def describe_trails(query)
|
||||
aws_service_client.describe_trails(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
47
lib/resources/aws/aws_cloudtrail_trails.rb
Normal file
47
lib/resources/aws/aws_cloudtrail_trails.rb
Normal file
|
@ -0,0 +1,47 @@
|
|||
class AwsCloudTrailTrails < Inspec.resource(1)
|
||||
name 'aws_cloudtrail_trails'
|
||||
desc 'Verifies settings for AWS CloudTrail Trails in bulk'
|
||||
example '
|
||||
describe aws_cloudtrail_trails do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
def validate_params(resource_params)
|
||||
unless resource_params.empty?
|
||||
raise ArgumentError, 'aws_cloudtrail_trails does not accept resource parameters.'
|
||||
end
|
||||
resource_params
|
||||
end
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:names, field: :name)
|
||||
.add(:trail_arns, field: :trail_arn)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'CloudTrail Trails'
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@table = backend.describe_trails({}).to_h[:trail_list]
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
AwsCloudTrailTrails::BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::CloudTrail::Client
|
||||
|
||||
def describe_trails(query)
|
||||
aws_service_client.describe_trails(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
62
lib/resources/aws/aws_cloudwatch_alarm.rb
Normal file
62
lib/resources/aws/aws_cloudwatch_alarm.rb
Normal file
|
@ -0,0 +1,62 @@
|
|||
class AwsCloudwatchAlarm < Inspec.resource(1)
|
||||
name 'aws_cloudwatch_alarm'
|
||||
desc <<-EOD
|
||||
# Look for a specific alarm
|
||||
aws_cloudwatch_alarm(
|
||||
metric: 'my-metric-name',
|
||||
metric_namespace: 'my-metric-namespace',
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
EOD
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :alarm_actions, :alarm_name, :metric_name, :metric_namespace
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
recognized_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:metric_name, :metric_namespace],
|
||||
)
|
||||
validated_params = {}
|
||||
# Currently you must specify exactly metric_name and metric_namespace
|
||||
[:metric_name, :metric_namespace].each do |param|
|
||||
raise ArgumentError, "Missing resource param #{param}" unless recognized_params.key?(param)
|
||||
validated_params[param] = recognized_params.delete(param)
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
aws_alarms = BackendFactory.create(inspec_runner).describe_alarms_for_metric(
|
||||
metric_name: @metric_name,
|
||||
namespace: @metric_namespace,
|
||||
)
|
||||
if aws_alarms.metric_alarms.empty?
|
||||
@exists = false
|
||||
elsif aws_alarms.metric_alarms.count > 1
|
||||
alarms = aws_alarms.metric_alarms.map(&:alarm_name)
|
||||
raise 'More than one Cloudwatch Alarm was matched. Try using ' \
|
||||
"more specific resource parameters. Alarms matched: #{alarms.join(', ')}"
|
||||
else
|
||||
@alarm_actions = aws_alarms.metric_alarms.first.alarm_actions
|
||||
@alarm_name = aws_alarms.metric_alarms.first.alarm_name
|
||||
@exists = true
|
||||
end
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
AwsCloudwatchAlarm::BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::CloudWatch::Client
|
||||
|
||||
def describe_alarms_for_metric(query)
|
||||
aws_service_client.describe_alarms_for_metric(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
100
lib/resources/aws/aws_cloudwatch_log_metric_filter.rb
Normal file
100
lib/resources/aws/aws_cloudwatch_log_metric_filter.rb
Normal file
|
@ -0,0 +1,100 @@
|
|||
class AwsCloudwatchLogMetricFilter < Inspec.resource(1)
|
||||
name 'aws_cloudwatch_log_metric_filter'
|
||||
desc 'Verifies individual Cloudwatch Log Metric Filters'
|
||||
example <<-EOX
|
||||
# Look for a LMF by its filter name and log group name. This combination
|
||||
# will always either find at most one LMF - no duplicates.
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
filter_name: 'my-filter',
|
||||
log_group_name: 'my-log-group'
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Search for an LMF by pattern and log group.
|
||||
# This could result in an error if the results are not unique.
|
||||
describe aws_cloudwatch_log_metric_filter(
|
||||
log_group_name: 'my-log-group',
|
||||
pattern: 'my-filter'
|
||||
) do
|
||||
it { should exist }
|
||||
end
|
||||
EOX
|
||||
supports platform: 'aws'
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :filter_name, :log_group_name, :metric_name, :metric_namespace, :pattern
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:filter_name, :log_group_name, :pattern],
|
||||
)
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, 'You must provide either filter_name, log_group, or pattern to aws_cloudwatch_log_metric_filter.'
|
||||
end
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
# get a backend
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
# Perform query with remote filtering
|
||||
aws_search_criteria = {}
|
||||
aws_search_criteria[:filter_name] = filter_name if filter_name
|
||||
aws_search_criteria[:log_group_name] = log_group_name if log_group_name
|
||||
begin
|
||||
aws_results = backend.describe_metric_filters(aws_search_criteria)
|
||||
rescue Aws::CloudWatchLogs::Errors::ResourceNotFoundException
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
|
||||
# Then perform local filtering
|
||||
if pattern
|
||||
aws_results.select! { |lmf| lmf.filter_pattern == pattern }
|
||||
end
|
||||
|
||||
# Check result count. We're a singular resource and can tolerate
|
||||
# 0 or 1 results, not multiple.
|
||||
if aws_results.count > 1
|
||||
raise 'More than one result was returned, but aws_cloudwatch_log_metric_filter '\
|
||||
'can only handle a single AWS resource. Consider passing more resource '\
|
||||
'parameters to narrow down the search.'
|
||||
elsif aws_results.empty?
|
||||
@exists = false
|
||||
else
|
||||
@exists = true
|
||||
# Unpack the funny-shaped object we got back from AWS into our instance vars
|
||||
lmf = aws_results.first
|
||||
@filter_name = lmf.filter_name
|
||||
@log_group_name = lmf.log_group_name
|
||||
@pattern = lmf.filter_pattern # Note inconsistent name
|
||||
# AWS SDK returns an array of metric transformations
|
||||
# but only allows one (mandatory) entry, let's flatten that
|
||||
@metric_name = lmf.metric_transformations.first.metric_name
|
||||
@metric_namespace = lmf.metric_transformations.first.metric_namespace
|
||||
end
|
||||
end
|
||||
|
||||
class Backend
|
||||
# Uses the cloudwatch API to really talk to AWS
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::CloudWatchLogs::Client
|
||||
|
||||
def describe_metric_filters(criteria)
|
||||
query = {}
|
||||
query[:filter_name_prefix] = criteria[:filter_name] if criteria[:filter_name]
|
||||
query[:log_group_name] = criteria[:log_group_name] if criteria[:log_group_name]
|
||||
# 'pattern' is not available as a remote filter,
|
||||
# we filter it after the fact locally
|
||||
# TODO: handle pagination? Max 50/page. Maybe you want a plural resource?
|
||||
aws_response = aws_service_client.describe_metric_filters(query)
|
||||
aws_response.metric_filters
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
157
lib/resources/aws/aws_ec2_instance.rb
Normal file
157
lib/resources/aws/aws_ec2_instance.rb
Normal file
|
@ -0,0 +1,157 @@
|
|||
# author: Christoph Hartmann
|
||||
class AwsEc2Instance < Inspec.resource(1)
|
||||
name 'aws_ec2_instance'
|
||||
desc 'Verifies settings for an EC2 instance'
|
||||
|
||||
example <<-EOX
|
||||
describe aws_ec2_instance('i-123456') do
|
||||
it { should be_running }
|
||||
it { should have_roles }
|
||||
end
|
||||
|
||||
describe aws_ec2_instance(name: 'my-instance') do
|
||||
it { should be_running }
|
||||
it { should have_roles }
|
||||
end
|
||||
EOX
|
||||
supports platform: 'aws'
|
||||
|
||||
# TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
|
||||
def initialize(opts, conn = nil)
|
||||
@opts = opts
|
||||
@opts.is_a?(Hash) ? @display_name = @opts[:name] : @display_name = opts
|
||||
@ec2_client = conn ? conn.ec2_client : inspec_runner.backend.aws_client(Aws::EC2::Client)
|
||||
@ec2_resource = conn ? conn.ec2_resource : inspec_runner.backend.aws_resource(Aws::EC2::Resource, {})
|
||||
@iam_resource = conn ? conn.iam_resource : inspec_runner.backend.aws_resource(Aws::IAM::Resource, {})
|
||||
end
|
||||
|
||||
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
|
||||
# Copied from resource_support/aws/aws_resource_mixin.rb
|
||||
def catch_aws_errors
|
||||
yield
|
||||
rescue Aws::Errors::MissingCredentialsError
|
||||
# The AWS error here is unhelpful:
|
||||
# "unable to sign request without credentials set"
|
||||
Inspec::Log.error "It appears that you have not set your AWS credentials. You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target. See https://www.inspec.io/docs/reference/platforms for details."
|
||||
fail_resource('No AWS credentials available')
|
||||
rescue Aws::Errors::ServiceError => e
|
||||
fail_resource e.message
|
||||
end
|
||||
|
||||
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
|
||||
# Copied from resource_support/aws/aws_singular_resource_mixin.rb
|
||||
def inspec_runner
|
||||
# When running under inspec-cli, we have an 'inspec' method that
|
||||
# returns the runner. When running under unit tests, we don't
|
||||
# have that, but we still have to call this to pass something
|
||||
# (nil is OK) to the backend.
|
||||
# TODO: remove with https://github.com/chef/inspec-aws/issues/216
|
||||
# TODO: remove after rewrite to include AwsSingularResource
|
||||
inspec if respond_to?(:inspec)
|
||||
end
|
||||
|
||||
def id
|
||||
return @instance_id if defined?(@instance_id)
|
||||
catch_aws_errors do
|
||||
if @opts.is_a?(Hash)
|
||||
first = @ec2_resource.instances(
|
||||
{
|
||||
filters: [{
|
||||
name: 'tag:Name',
|
||||
values: [@opts[:name]],
|
||||
}],
|
||||
},
|
||||
).first
|
||||
# catch case where the instance is not known
|
||||
@instance_id = first.id unless first.nil?
|
||||
else
|
||||
@instance_id = @opts
|
||||
end
|
||||
end
|
||||
end
|
||||
alias instance_id id
|
||||
|
||||
def exists?
|
||||
return false if instance.nil?
|
||||
instance.exists?
|
||||
end
|
||||
|
||||
# returns the instance state
|
||||
def state
|
||||
catch_aws_errors do
|
||||
instance&.state&.name
|
||||
end
|
||||
end
|
||||
|
||||
# helper methods for each state
|
||||
%w{
|
||||
pending running shutting-down
|
||||
terminated stopping stopped unknown
|
||||
}.each do |state_name|
|
||||
define_method state_name.tr('-', '_') + '?' do
|
||||
state == state_name
|
||||
end
|
||||
end
|
||||
|
||||
# attributes that we want to expose
|
||||
%w{
|
||||
public_ip_address private_ip_address key_name private_dns_name
|
||||
public_dns_name subnet_id architecture root_device_type
|
||||
root_device_name virtualization_type client_token launch_time
|
||||
instance_type image_id vpc_id
|
||||
}.each do |attribute|
|
||||
define_method attribute do
|
||||
catch_aws_errors do
|
||||
instance.send(attribute) if instance
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Don't document this - it's a bit hard to use. Our current doctrine
|
||||
# is to use dumb things, like arrays of strings - use security_group_ids instead.
|
||||
def security_groups
|
||||
catch_aws_errors do
|
||||
@security_groups ||= instance.security_groups.map { |sg|
|
||||
{ id: sg.group_id, name: sg.group_name }
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def security_group_ids
|
||||
catch_aws_errors do
|
||||
@security_group_ids ||= instance.security_groups.map(&:group_id)
|
||||
end
|
||||
end
|
||||
|
||||
def tags
|
||||
catch_aws_errors do
|
||||
@tags ||= instance.tags.map { |tag| { key: tag.key, value: tag.value } }
|
||||
end
|
||||
end
|
||||
|
||||
def to_s
|
||||
"EC2 Instance #{@display_name}"
|
||||
end
|
||||
|
||||
def has_roles?
|
||||
catch_aws_errors do
|
||||
instance_profile = instance.iam_instance_profile
|
||||
|
||||
if instance_profile
|
||||
roles = @iam_resource.instance_profile(
|
||||
instance_profile.arn.gsub(%r{^.*\/}, ''),
|
||||
).roles
|
||||
else
|
||||
roles = nil
|
||||
end
|
||||
|
||||
roles && !roles.empty?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def instance
|
||||
catch_aws_errors { @instance ||= @ec2_resource.instance(id) }
|
||||
end
|
||||
end
|
106
lib/resources/aws/aws_iam_access_key.rb
Normal file
106
lib/resources/aws/aws_iam_access_key.rb
Normal file
|
@ -0,0 +1,106 @@
|
|||
class AwsIamAccessKey < Inspec.resource(1)
|
||||
name 'aws_iam_access_key'
|
||||
desc 'Verifies settings for an individual IAM access key'
|
||||
example "
|
||||
describe aws_iam_access_key(username: 'username', id: 'access-key id') do
|
||||
it { should exist }
|
||||
it { should_not be_active }
|
||||
its('create_date') { should be > Time.now - 365 * 86400 }
|
||||
its('last_used_date') { should be > Time.now - 90 * 86400 }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :access_key_id, :create_date, :status, :username
|
||||
alias id access_key_id
|
||||
|
||||
def validate_params(raw_params)
|
||||
recognized_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:username, :id, :access_key_id],
|
||||
allowed_scalar_name: :access_key_id,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
# id and access_key_id are aliases; standardize on access_key_id
|
||||
recognized_params[:access_key_id] = recognized_params.delete(:id) if recognized_params.key?(:id)
|
||||
|
||||
# Validate format of access_key_id
|
||||
if recognized_params[:access_key_id] and
|
||||
recognized_params[:access_key_id] !~ /^AKIA[0-9A-Z]{16}$/
|
||||
raise ArgumentError, 'Incorrect format for Access Key ID - expected AKIA followed ' \
|
||||
'by 16 letters or numbers'
|
||||
end
|
||||
|
||||
# One of username and access_key_id is required
|
||||
if recognized_params[:username].nil? && recognized_params[:access_key_id].nil?
|
||||
raise ArgumentError, 'You must provide at lease one of access_key_id or username to aws_iam_access_key'
|
||||
end
|
||||
|
||||
recognized_params
|
||||
end
|
||||
|
||||
def active?
|
||||
return nil unless exists?
|
||||
status == 'Active'
|
||||
end
|
||||
|
||||
def to_s
|
||||
"IAM Access-Key #{access_key_id}"
|
||||
end
|
||||
|
||||
def last_used_date
|
||||
return nil unless exists?
|
||||
return @last_used_date if defined? @last_used_date
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
catch_aws_errors do
|
||||
@last_used_date = backend.get_access_key_last_used({ access_key_id: access_key_id }).access_key_last_used.last_used_date
|
||||
end
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
query = {}
|
||||
query[:user_name] = username if username
|
||||
|
||||
response = backend.list_access_keys(query)
|
||||
|
||||
access_keys = response.access_key_metadata.select do |key|
|
||||
if access_key_id
|
||||
key.access_key_id == access_key_id
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
if access_keys.empty?
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
|
||||
if access_keys.count > 1
|
||||
raise 'More than one access key matched for aws_iam_access_key. Use more specific paramaters, such as access_key_id.'
|
||||
end
|
||||
|
||||
@exists = true
|
||||
@access_key_id = access_keys[0].access_key_id
|
||||
@username = access_keys[0].user_name
|
||||
@create_date = access_keys[0].create_date
|
||||
@status = access_keys[0].status
|
||||
# Last used date is lazily loaded, separate API call
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
@exists = false
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def list_access_keys(query)
|
||||
aws_service_client.list_access_keys(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
144
lib/resources/aws/aws_iam_access_keys.rb
Normal file
144
lib/resources/aws/aws_iam_access_keys.rb
Normal file
|
@ -0,0 +1,144 @@
|
|||
class AwsIamAccessKeys < Inspec.resource(1)
|
||||
name 'aws_iam_access_keys'
|
||||
desc 'Verifies settings for AWS IAM Access Keys in bulk'
|
||||
example '
|
||||
describe aws_iam_access_keys do
|
||||
it { should_not exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
def validate_params(raw_params)
|
||||
recognized_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:username, :id, :access_key_id, :created_date],
|
||||
allowed_scalar_name: :access_key_id,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
# id and access_key_id are aliases; standardize on access_key_id
|
||||
recognized_params[:access_key_id] = recognized_params.delete(:id) if recognized_params.key?(:id)
|
||||
if recognized_params[:access_key_id] and
|
||||
recognized_params[:access_key_id] !~ /^AKIA[0-9A-Z]{16}$/
|
||||
raise 'Incorrect format for Access Key ID - expected AKIA followed ' \
|
||||
'by 16 letters or numbers'
|
||||
end
|
||||
|
||||
recognized_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
# TODO: this interface should be normalized to match the AWS API
|
||||
criteria = {}
|
||||
criteria[:username] = @username if defined? @username
|
||||
@table = BackendFactory.create(inspec_runner).fetch(criteria)
|
||||
end
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:where)
|
||||
.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:access_key_ids, field: :access_key_id)
|
||||
.add(:created_date, field: :create_date)
|
||||
.add(:created_days_ago, field: :created_days_ago)
|
||||
.add(:created_with_user, field: :created_with_user)
|
||||
.add(:created_hours_ago, field: :created_hours_ago)
|
||||
.add(:usernames, field: :username)
|
||||
.add(:active, field: :active)
|
||||
.add(:inactive, field: :inactive)
|
||||
.add(:last_used_date, field: :last_used_date)
|
||||
.add(:last_used_hours_ago, field: :last_used_hours_ago)
|
||||
.add(:last_used_days_ago, field: :last_used_days_ago)
|
||||
.add(:ever_used, field: :ever_used)
|
||||
.add(:never_used, field: :never_used)
|
||||
.add(:user_created_date, field: :user_created_date)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'IAM Access Keys'
|
||||
end
|
||||
|
||||
# Internal support class. This is used to fetch
|
||||
# the users and access keys. We have an abstract
|
||||
# class with a concrete AWS implementation provided here;
|
||||
# a few mock implementations are also provided in the unit tests.
|
||||
class Backend
|
||||
# Implementation of AccessKeyProvider which operates by looping over
|
||||
# all users, then fetching their access keys.
|
||||
# TODO: An alternate, more scalable implementation could be made
|
||||
# using the Credential Report.
|
||||
class AwsUserIterator < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def fetch(criteria)
|
||||
iam_client = aws_service_client
|
||||
|
||||
user_details = {}
|
||||
if criteria.key?(:username)
|
||||
begin
|
||||
user_details[criteria[:username]] = iam_client.get_user(user_name: criteria[:username]).user
|
||||
rescue Aws::IAM::Errors::NoSuchEntity # rubocop:disable Lint/HandleExceptions
|
||||
# Swallow - a miss on search results should return an empty table
|
||||
end
|
||||
else
|
||||
# TODO: pagination check and resume
|
||||
iam_client.list_users.users.each do |info|
|
||||
user_details[info.user_name] = info
|
||||
end
|
||||
end
|
||||
|
||||
access_key_data = []
|
||||
user_details.each_key do |username|
|
||||
begin
|
||||
user_keys = iam_client.list_access_keys(user_name: username)
|
||||
.access_key_metadata
|
||||
user_keys = user_keys.map do |metadata|
|
||||
{
|
||||
access_key_id: metadata.access_key_id,
|
||||
username: username,
|
||||
status: metadata.status,
|
||||
create_date: metadata.create_date, # DateTime.parse(metadata.create_date),
|
||||
}
|
||||
end
|
||||
|
||||
# Copy in from user data
|
||||
# Synthetics
|
||||
user_keys.each do |key_info|
|
||||
add_synthetic_fields(key_info, user_details[username])
|
||||
end
|
||||
access_key_data.concat(user_keys)
|
||||
rescue Aws::IAM::Errors::NoSuchEntity # rubocop:disable Lint/HandleExceptions
|
||||
# Swallow - a miss on search results should return an empty table
|
||||
end
|
||||
end
|
||||
access_key_data
|
||||
end
|
||||
|
||||
def add_synthetic_fields(key_info, user_details) # rubocop:disable Metrics/AbcSize
|
||||
key_info[:id] = key_info[:access_key_id]
|
||||
key_info[:active] = key_info[:status] == 'Active'
|
||||
key_info[:inactive] = key_info[:status] != 'Active'
|
||||
key_info[:created_hours_ago] = ((Time.now - key_info[:create_date]) / (60*60)).to_i
|
||||
key_info[:created_days_ago] = (key_info[:created_hours_ago] / 24).to_i
|
||||
key_info[:user_created_date] = user_details[:create_date]
|
||||
key_info[:created_with_user] = (key_info[:create_date] - key_info[:user_created_date]).abs < 1.0/24.0
|
||||
|
||||
# Last used is a separate API call
|
||||
iam_client = aws_service_client
|
||||
last_used =
|
||||
iam_client.get_access_key_last_used(access_key_id: key_info[:access_key_id])
|
||||
.access_key_last_used.last_used_date
|
||||
key_info[:ever_used] = !last_used.nil?
|
||||
key_info[:never_used] = last_used.nil?
|
||||
key_info[:last_used_time] = last_used
|
||||
return unless last_used
|
||||
key_info[:last_used_hours_ago] = ((Time.now - last_used) / (60*60)).to_i
|
||||
key_info[:last_used_days_ago] = (key_info[:last_used_hours_ago]/24).to_i
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
56
lib/resources/aws/aws_iam_group.rb
Normal file
56
lib/resources/aws/aws_iam_group.rb
Normal file
|
@ -0,0 +1,56 @@
|
|||
class AwsIamGroup < Inspec.resource(1)
|
||||
name 'aws_iam_group'
|
||||
desc 'Verifies settings for AWS IAM Group'
|
||||
example "
|
||||
describe aws_iam_group('mygroup') do
|
||||
it { should exist }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :group_name
|
||||
|
||||
def to_s
|
||||
"IAM Group #{group_name}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:group_name],
|
||||
allowed_scalar_name: :group_name,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, 'You must provide a group_name to aws_iam_group.'
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = AwsIamGroup::BackendFactory.create(inspec_runner)
|
||||
|
||||
begin
|
||||
@aws_group_struct = backend.get_group(group_name: group_name)[:group]
|
||||
@exists = true
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
@exists = false
|
||||
end
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def get_group(query)
|
||||
aws_service_client.get_group(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
45
lib/resources/aws/aws_iam_groups.rb
Normal file
45
lib/resources/aws/aws_iam_groups.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
class AwsIamGroups < Inspec.resource(1)
|
||||
name 'aws_iam_groups'
|
||||
desc 'Verifies settings for AWS IAM groups in bulk'
|
||||
example '
|
||||
describe aws_iam_groups do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
def validate_params(resource_params)
|
||||
unless resource_params.empty?
|
||||
raise ArgumentError, 'aws_iam_groups does not accept resource parameters.'
|
||||
end
|
||||
resource_params
|
||||
end
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'IAM Groups'
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@table = backend.list_groups.to_h[:groups]
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def list_groups(query = {})
|
||||
aws_service_client.list_groups(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
116
lib/resources/aws/aws_iam_password_policy.rb
Normal file
116
lib/resources/aws/aws_iam_password_policy.rb
Normal file
|
@ -0,0 +1,116 @@
|
|||
# author: Viktor Yakovlyev
|
||||
class AwsIamPasswordPolicy < Inspec.resource(1)
|
||||
name 'aws_iam_password_policy'
|
||||
desc 'Verifies iam password policy'
|
||||
|
||||
example <<-EOX
|
||||
describe aws_iam_password_policy do
|
||||
its('requires_lowercase_characters?') { should be true }
|
||||
end
|
||||
|
||||
describe aws_iam_password_policy do
|
||||
its('requires_uppercase_characters?') { should be true }
|
||||
end
|
||||
EOX
|
||||
supports platform: 'aws'
|
||||
|
||||
# TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
|
||||
def initialize(conn = nil)
|
||||
catch_aws_errors do
|
||||
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
|
||||
end
|
||||
|
||||
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
|
||||
# Copied from resource_support/aws/aws_resource_mixin.rb
|
||||
def catch_aws_errors
|
||||
yield
|
||||
rescue Aws::Errors::MissingCredentialsError
|
||||
# The AWS error here is unhelpful:
|
||||
# "unable to sign request without credentials set"
|
||||
Inspec::Log.error "It appears that you have not set your AWS credentials. You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target. See https://www.inspec.io/docs/reference/platforms for details."
|
||||
fail_resource('No AWS credentials available')
|
||||
rescue Aws::Errors::ServiceError => e
|
||||
fail_resource e.message
|
||||
end
|
||||
|
||||
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
|
||||
# Copied from resource_support/aws/aws_singular_resource_mixin.rb
|
||||
def inspec_runner
|
||||
# When running under inspec-cli, we have an 'inspec' method that
|
||||
# returns the runner. When running under unit tests, we don't
|
||||
# have that, but we still have to call this to pass something
|
||||
# (nil is OK) to the backend.
|
||||
# TODO: remove with https://github.com/chef/inspec-aws/issues/216
|
||||
# TODO: remove after rewrite to include AwsSingularResource
|
||||
inspec if respond_to?(:inspec)
|
||||
end
|
||||
|
||||
def to_s
|
||||
'IAM Password-Policy'
|
||||
end
|
||||
|
||||
def exists?
|
||||
!@policy.nil?
|
||||
end
|
||||
|
||||
#-------------------------- Properties ----------------------------#
|
||||
|
||||
def minimum_password_length
|
||||
@policy.minimum_password_length
|
||||
end
|
||||
|
||||
def max_password_age_in_days
|
||||
raise 'this policy does not expire passwords' unless expire_passwords?
|
||||
@policy.max_password_age
|
||||
end
|
||||
|
||||
def number_of_passwords_to_remember
|
||||
raise 'this policy does not prevent password reuse' \
|
||||
unless prevent_password_reuse?
|
||||
@policy.password_reuse_prevention
|
||||
end
|
||||
|
||||
#-------------------------- 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
|
46
lib/resources/aws/aws_iam_policies.rb
Normal file
46
lib/resources/aws/aws_iam_policies.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
class AwsIamPolicies < Inspec.resource(1)
|
||||
name 'aws_iam_policies'
|
||||
desc 'Verifies settings for AWS IAM Policies in bulk'
|
||||
example '
|
||||
describe aws_iam_policies do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
def validate_params(resource_params)
|
||||
unless resource_params.empty?
|
||||
raise ArgumentError, 'aws_iam_policies does not accept resource parameters.'
|
||||
end
|
||||
resource_params
|
||||
end
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:policy_names, field: :policy_name)
|
||||
.add(:arns, field: :arn)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'IAM Policies'
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@table = backend.list_policies({}).to_h[:policies]
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def list_policies(query)
|
||||
aws_service_client.list_policies(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
119
lib/resources/aws/aws_iam_policy.rb
Normal file
119
lib/resources/aws/aws_iam_policy.rb
Normal file
|
@ -0,0 +1,119 @@
|
|||
class AwsIamPolicy < Inspec.resource(1)
|
||||
name 'aws_iam_policy'
|
||||
desc 'Verifies settings for individual AWS IAM Policy'
|
||||
example "
|
||||
describe aws_iam_policy('AWSSupportAccess') do
|
||||
it { should be_attached }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
|
||||
attr_reader :arn, :attachment_count, :default_version_id
|
||||
|
||||
def to_s
|
||||
"Policy #{@policy_name}"
|
||||
end
|
||||
|
||||
def attached?
|
||||
!attachment_count.zero?
|
||||
end
|
||||
|
||||
def attached_users
|
||||
return @attached_users if defined? @attached_users
|
||||
fetch_attached_entities
|
||||
@attached_users
|
||||
end
|
||||
|
||||
def attached_groups
|
||||
return @attached_groups if defined? @attached_groups
|
||||
fetch_attached_entities
|
||||
@attached_groups
|
||||
end
|
||||
|
||||
def attached_roles
|
||||
return @attached_roles if defined? @attached_roles
|
||||
fetch_attached_entities
|
||||
@attached_roles
|
||||
end
|
||||
|
||||
def attached_to_user?(user_name)
|
||||
attached_users.include?(user_name)
|
||||
end
|
||||
|
||||
def attached_to_group?(group_name)
|
||||
attached_groups.include?(group_name)
|
||||
end
|
||||
|
||||
def attached_to_role?(role_name)
|
||||
attached_roles.include?(role_name)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:policy_name],
|
||||
allowed_scalar_name: :policy_name,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, "You must provide the parameter 'policy_name' to aws_iam_policy."
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
criteria = { max_items: 1000 } # maxItems max value is 1000
|
||||
resp = backend.list_policies(criteria)
|
||||
@policy = resp.policies.detect do |policy|
|
||||
policy.policy_name == @policy_name
|
||||
end
|
||||
|
||||
@exists = !@policy.nil?
|
||||
|
||||
return unless @exists
|
||||
@arn = @policy[:arn]
|
||||
@default_version_id = @policy[:default_version_id]
|
||||
@attachment_count = @policy[:attachment_count]
|
||||
end
|
||||
|
||||
def fetch_attached_entities
|
||||
unless @exists
|
||||
@attached_groups = nil
|
||||
@attached_users = nil
|
||||
@attached_roles = nil
|
||||
return
|
||||
end
|
||||
backend = AwsIamPolicy::BackendFactory.create(inspec_runner)
|
||||
criteria = { policy_arn: arn }
|
||||
resp = nil
|
||||
catch_aws_errors do
|
||||
resp = backend.list_entities_for_policy(criteria)
|
||||
end
|
||||
@attached_groups = resp.policy_groups.map(&:group_name)
|
||||
@attached_users = resp.policy_users.map(&:user_name)
|
||||
@attached_roles = resp.policy_roles.map(&:role_name)
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def list_policies(criteria)
|
||||
aws_service_client.list_policies(criteria)
|
||||
end
|
||||
|
||||
def list_entities_for_policy(criteria)
|
||||
aws_service_client.list_entities_for_policy(criteria)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
51
lib/resources/aws/aws_iam_role.rb
Normal file
51
lib/resources/aws/aws_iam_role.rb
Normal file
|
@ -0,0 +1,51 @@
|
|||
class AwsIamRole < Inspec.resource(1)
|
||||
name 'aws_iam_role'
|
||||
desc 'Verifies settings for an IAM Role'
|
||||
example "
|
||||
describe aws_iam_role('my-role') do
|
||||
it { should exist }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :description, :role_name
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:role_name],
|
||||
allowed_scalar_name: :role_name,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, 'You must provide a role_name to aws_iam_role.'
|
||||
end
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
role_info = nil
|
||||
begin
|
||||
role_info = BackendFactory.create(inspec_runner).get_role(role_name: role_name)
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
@exists = true
|
||||
@description = role_info.role.description
|
||||
end
|
||||
|
||||
# Uses the SDK API to really talk to AWS
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
def get_role(query)
|
||||
aws_service_client.get_role(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
60
lib/resources/aws/aws_iam_root_user.rb
Normal file
60
lib/resources/aws/aws_iam_root_user.rb
Normal file
|
@ -0,0 +1,60 @@
|
|||
class AwsIamRootUser < Inspec.resource(1)
|
||||
name 'aws_iam_root_user'
|
||||
desc 'Verifies settings for AWS root account'
|
||||
example "
|
||||
describe aws_iam_root_user do
|
||||
it { should have_access_key }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
# TODO: rewrite to avoid direct injection, match other resources, use AwsSingularResourceMixin
|
||||
def initialize(conn = nil)
|
||||
@client = conn ? conn.iam_client : inspec_runner.backend.aws_client(Aws::IAM::Client)
|
||||
end
|
||||
|
||||
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
|
||||
# Copied from resource_support/aws/aws_resource_mixin.rb
|
||||
def catch_aws_errors
|
||||
yield
|
||||
rescue Aws::Errors::MissingCredentialsError
|
||||
# The AWS error here is unhelpful:
|
||||
# "unable to sign request without credentials set"
|
||||
Inspec::Log.error "It appears that you have not set your AWS credentials. You may set them using environment variables, or using the 'aws://region/aws_credentials_profile' target. See https://www.inspec.io/docs/reference/platforms for details."
|
||||
fail_resource('No AWS credentials available')
|
||||
rescue Aws::Errors::ServiceError => e
|
||||
fail_resource e.message
|
||||
end
|
||||
|
||||
# TODO: DRY up, see https://github.com/chef/inspec/issues/2633
|
||||
# Copied from resource_support/aws/aws_singular_resource_mixin.rb
|
||||
def inspec_runner
|
||||
# When running under inspec-cli, we have an 'inspec' method that
|
||||
# returns the runner. When running under unit tests, we don't
|
||||
# have that, but we still have to call this to pass something
|
||||
# (nil is OK) to the backend.
|
||||
# TODO: remove with https://github.com/chef/inspec-aws/issues/216
|
||||
# TODO: remove after rewrite to include AwsSingularResource
|
||||
inspec if respond_to?(:inspec)
|
||||
end
|
||||
|
||||
def has_access_key?
|
||||
summary_account['AccountAccessKeysPresent'] == 1
|
||||
end
|
||||
|
||||
def has_mfa_enabled?
|
||||
summary_account['AccountMFAEnabled'] == 1
|
||||
end
|
||||
|
||||
def to_s
|
||||
'AWS Root-User'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def summary_account
|
||||
catch_aws_errors do
|
||||
@summary_account ||= @client.get_account_summary.summary_map
|
||||
end
|
||||
end
|
||||
end
|
111
lib/resources/aws/aws_iam_user.rb
Normal file
111
lib/resources/aws/aws_iam_user.rb
Normal file
|
@ -0,0 +1,111 @@
|
|||
# author: Alex Bedley
|
||||
# author: Steffanie Freeman
|
||||
# author: Simon Varlow
|
||||
# author: Chris Redekop
|
||||
class AwsIamUser < Inspec.resource(1)
|
||||
name 'aws_iam_user'
|
||||
desc 'Verifies settings for AWS IAM user'
|
||||
example "
|
||||
describe aws_iam_user(username: 'test_user') do
|
||||
it { should have_mfa_enabled }
|
||||
it { should_not have_console_password }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :access_keys, :has_console_password, :has_mfa_enabled, :username
|
||||
alias has_mfa_enabled? has_mfa_enabled
|
||||
alias has_console_password? has_console_password
|
||||
|
||||
def name
|
||||
warn "[DEPRECATION] - Property ':name' is deprecated on the aws_iam_user resource. Use ':username' instead."
|
||||
username
|
||||
end
|
||||
|
||||
def to_s
|
||||
"IAM User #{username}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:username, :aws_user_struct, :name, :user],
|
||||
allowed_scalar_name: :username,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
# If someone passed :name, rename it to :username
|
||||
if validated_params.key?(:name)
|
||||
warn "[DEPRECATION] - Resource parameter ':name' is deprecated on the aws_iam_user resource. Use ':username' instead."
|
||||
validated_params[:username] = validated_params.delete(:name)
|
||||
end
|
||||
|
||||
# If someone passed :user, rename it to :aws_user_struct
|
||||
if validated_params.key?(:user)
|
||||
warn "[DEPRECATION] - Resource parameter ':user' is deprecated on the aws_iam_user resource. Use ':aws_user_struct' instead."
|
||||
validated_params[:aws_user_struct] = validated_params.delete(:user)
|
||||
end
|
||||
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, 'You must provide a username to aws_iam_user.'
|
||||
end
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@aws_user_struct ||= nil # silence unitialized warning
|
||||
unless @aws_user_struct
|
||||
begin
|
||||
@aws_user_struct = backend.get_user(user_name: username)
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
end
|
||||
# TODO: extract properties from aws_user_struct?
|
||||
|
||||
@exists = true
|
||||
|
||||
begin
|
||||
_login_profile = backend.get_login_profile(user_name: username)
|
||||
@has_console_password = true
|
||||
# Password age also available here
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
@has_console_password = false
|
||||
end
|
||||
|
||||
mfa_info = backend.list_mfa_devices(user_name: username)
|
||||
@has_mfa_enabled = !mfa_info.mfa_devices.empty?
|
||||
|
||||
# TODO: consider returning Inspec AwsIamAccessKey objects
|
||||
@access_keys = backend.list_access_keys(user_name: username).access_key_metadata
|
||||
# If the above call fails, we get nil here; but we promise access_keys will be an array.
|
||||
@access_keys ||= []
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
def get_user(criteria)
|
||||
aws_service_client.get_user(criteria)
|
||||
end
|
||||
|
||||
def get_login_profile(criteria)
|
||||
aws_service_client.get_login_profile(criteria)
|
||||
end
|
||||
|
||||
def list_mfa_devices(criteria)
|
||||
aws_service_client.list_mfa_devices(criteria)
|
||||
end
|
||||
|
||||
def list_access_keys(criteria)
|
||||
aws_service_client.list_access_keys(criteria)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
96
lib/resources/aws/aws_iam_users.rb
Normal file
96
lib/resources/aws/aws_iam_users.rb
Normal file
|
@ -0,0 +1,96 @@
|
|||
# author: Alex Bedley
|
||||
# author: Steffanie Freeman
|
||||
# author: Simon Varlow
|
||||
# author: Chris Redekop
|
||||
class AwsIamUsers < Inspec.resource(1)
|
||||
name 'aws_iam_users'
|
||||
desc 'Verifies settings for AWS IAM users'
|
||||
example '
|
||||
describe aws_iam_users.where(has_mfa_enabled?: false) do
|
||||
it { should_not exist }
|
||||
end
|
||||
describe aws_iam_users.where(has_console_password?: true) do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:where)
|
||||
.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:has_mfa_enabled?, field: :has_mfa_enabled)
|
||||
.add(:has_console_password?, field: :has_console_password)
|
||||
.add(:password_ever_used?, field: :password_ever_used?)
|
||||
.add(:password_never_used?, field: :password_never_used?)
|
||||
.add(:password_last_used_days_ago, field: :password_last_used_days_ago)
|
||||
.add(:username, field: :user_name)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def validate_params(raw_params)
|
||||
# No params yet
|
||||
unless raw_params.empty?
|
||||
raise ArgumentError, 'aws_iam_users does not accept resource parameters'
|
||||
end
|
||||
raw_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@table = backend.list_users.users.map(&:to_h)
|
||||
|
||||
# TODO: lazy columns - https://github.com/chef/inspec-aws/issues/100
|
||||
@table.each do |user|
|
||||
begin
|
||||
_login_profile = backend.get_login_profile(user_name: user[:user_name])
|
||||
user[:has_console_password] = true
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
user[:has_console_password] = false
|
||||
end
|
||||
user[:has_console_password?] = user[:has_console_password]
|
||||
|
||||
begin
|
||||
aws_mfa_devices = backend.list_mfa_devices(user_name: user[:user_name])
|
||||
user[:has_mfa_enabled] = !aws_mfa_devices.mfa_devices.empty?
|
||||
rescue Aws::IAM::Errors::NoSuchEntity
|
||||
user[:has_mfa_enabled] = false
|
||||
end
|
||||
user[:has_mfa_enabled?] = user[:has_mfa_enabled]
|
||||
password_last_used = user[:password_last_used]
|
||||
user[:password_ever_used?] = !password_last_used.nil?
|
||||
user[:password_never_used?] = password_last_used.nil?
|
||||
next unless user[:password_ever_used?]
|
||||
user[:password_last_used_days_ago] = ((Time.now - password_last_used) / (24*60*60)).to_i
|
||||
end
|
||||
@table
|
||||
end
|
||||
|
||||
def to_s
|
||||
'IAM Users'
|
||||
end
|
||||
|
||||
#===========================================================================#
|
||||
# Backend Implementation
|
||||
#===========================================================================#
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::IAM::Client
|
||||
|
||||
# TODO: delegate this out
|
||||
def list_users(query = {})
|
||||
aws_service_client.list_users(query)
|
||||
end
|
||||
|
||||
def get_login_profile(query)
|
||||
aws_service_client.get_login_profile(query)
|
||||
end
|
||||
|
||||
def list_mfa_devices(query)
|
||||
aws_service_client.list_mfa_devices(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
46
lib/resources/aws/aws_kms_keys.rb
Normal file
46
lib/resources/aws/aws_kms_keys.rb
Normal file
|
@ -0,0 +1,46 @@
|
|||
class AwsKmsKeys < Inspec.resource(1)
|
||||
name 'aws_kms_keys'
|
||||
desc 'Verifies settings for AWS KMS Keys in bulk'
|
||||
example '
|
||||
describe aws_kms_keys do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
def validate_params(resource_params)
|
||||
unless resource_params.empty?
|
||||
raise ArgumentError, 'aws_kms_keys does not accept resource parameters.'
|
||||
end
|
||||
resource_params
|
||||
end
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:key_arns, field: :key_arn)
|
||||
.add(:key_ids, field: :key_id)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'KMS Keys'
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@table = backend.list_keys({ limit: 1000 }).to_h[:keys] # max value for limit is 1000
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::KMS::Client
|
||||
|
||||
def list_keys(query = {})
|
||||
aws_service_client.list_keys(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
61
lib/resources/aws/aws_route_table.rb
Normal file
61
lib/resources/aws/aws_route_table.rb
Normal file
|
@ -0,0 +1,61 @@
|
|||
class AwsRouteTable < Inspec.resource(1)
|
||||
name 'aws_route_table'
|
||||
desc 'Verifies settings for an AWS Route Table'
|
||||
example "
|
||||
describe aws_route_table do
|
||||
its('route_table_id') { should cmp 'rtb-2c60ec44' }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
|
||||
def to_s
|
||||
"Route Table #{@route_table_id}"
|
||||
end
|
||||
|
||||
attr_reader :route_table_id, :vpc_id
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:route_table_id],
|
||||
allowed_scalar_name: :route_table_id,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
if validated_params.key?(:route_table_id) && validated_params[:route_table_id] !~ /^rtb\-[0-9a-f]{8}/
|
||||
raise ArgumentError, 'aws_route_table Route Table ID must be in the' \
|
||||
' format "rtb-" followed by 8 hexadecimal characters.'
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
if @route_table_id.nil?
|
||||
args = nil
|
||||
else
|
||||
args = { filters: [{ name: 'route-table-id', values: [@route_table_id] }] }
|
||||
end
|
||||
|
||||
resp = backend.describe_route_tables(args)
|
||||
routetable = resp.to_h[:route_tables]
|
||||
@exists = !routetable.empty?
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_route_tables(query)
|
||||
aws_service_client.describe_route_tables(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
115
lib/resources/aws/aws_s3_bucket.rb
Normal file
115
lib/resources/aws/aws_s3_bucket.rb
Normal file
|
@ -0,0 +1,115 @@
|
|||
# author: Matthew Dromazos
|
||||
class AwsS3Bucket < Inspec.resource(1)
|
||||
name 'aws_s3_bucket'
|
||||
desc 'Verifies settings for a s3 bucket'
|
||||
example "
|
||||
describe aws_s3_bucket(bucket_name: 'test_bucket') do
|
||||
it { should exist }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :bucket_name, :has_access_logging_enabled, :region
|
||||
|
||||
def to_s
|
||||
"S3 Bucket #{@bucket_name}"
|
||||
end
|
||||
|
||||
def bucket_acl
|
||||
catch_aws_errors do
|
||||
@bucket_acl ||= BackendFactory.create(inspec_runner).get_bucket_acl(bucket: bucket_name).grants
|
||||
end
|
||||
end
|
||||
|
||||
def bucket_policy
|
||||
@bucket_policy ||= fetch_bucket_policy
|
||||
end
|
||||
|
||||
# RSpec will alias this to be_public
|
||||
def public?
|
||||
# first line just for formatting
|
||||
false || \
|
||||
bucket_acl.any? { |g| g.grantee.type == 'Group' && g.grantee.uri =~ /AllUsers/ } || \
|
||||
bucket_acl.any? { |g| g.grantee.type == 'Group' && g.grantee.uri =~ /AuthenticatedUsers/ } || \
|
||||
bucket_policy.any? { |s| s.effect == 'Allow' && s.principal == '*' }
|
||||
end
|
||||
|
||||
def has_access_logging_enabled?
|
||||
return unless @exists
|
||||
catch_aws_errors do
|
||||
@has_access_logging_enabled ||= !BackendFactory.create(inspec_runner).get_bucket_logging(bucket: bucket_name).logging_enabled.nil?
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:bucket_name],
|
||||
allowed_scalar_name: :bucket_name,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
if validated_params.empty? or !validated_params.key?(:bucket_name)
|
||||
raise ArgumentError, 'You must provide a bucket_name to aws_s3_bucket.'
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
# Since there is no basic "get_bucket" API call, use the
|
||||
# region fetch as the existence check.
|
||||
begin
|
||||
@region = backend.get_bucket_location(bucket: bucket_name).location_constraint
|
||||
rescue Aws::S3::Errors::NoSuchBucket
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
@exists = true
|
||||
end
|
||||
|
||||
def fetch_bucket_policy
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
catch_aws_errors do
|
||||
begin
|
||||
# AWS SDK returns a StringIO, we have to read()
|
||||
raw_policy = backend.get_bucket_policy(bucket: bucket_name).policy
|
||||
return JSON.parse(raw_policy.read)['Statement'].map do |statement|
|
||||
lowercase_hash = {}
|
||||
statement.each_key { |k| lowercase_hash[k.downcase] = statement[k] }
|
||||
@bucket_policy = OpenStruct.new(lowercase_hash)
|
||||
end
|
||||
rescue Aws::S3::Errors::NoSuchBucketPolicy
|
||||
@bucket_policy = []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Uses the SDK API to really talk to AWS
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::S3::Client
|
||||
|
||||
def get_bucket_acl(query)
|
||||
aws_service_client.get_bucket_acl(query)
|
||||
end
|
||||
|
||||
def get_bucket_location(query)
|
||||
aws_service_client.get_bucket_location(query)
|
||||
end
|
||||
|
||||
def get_bucket_policy(query)
|
||||
aws_service_client.get_bucket_policy(query)
|
||||
end
|
||||
|
||||
def get_bucket_logging(query)
|
||||
aws_service_client.get_bucket_logging(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
93
lib/resources/aws/aws_security_group.rb
Normal file
93
lib/resources/aws/aws_security_group.rb
Normal file
|
@ -0,0 +1,93 @@
|
|||
class AwsSecurityGroup < Inspec.resource(1)
|
||||
name 'aws_security_group'
|
||||
desc 'Verifies settings for an individual AWS Security Group.'
|
||||
example '
|
||||
describe aws_security_group("sg-12345678") do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :description, :group_id, :group_name, :vpc_id
|
||||
|
||||
def to_s
|
||||
"EC2 Security Group #{@group_id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
recognized_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:id, :group_id, :group_name, :vpc_id],
|
||||
allowed_scalar_name: :group_id,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
# id is an alias for group_id
|
||||
recognized_params[:group_id] = recognized_params.delete(:id) if recognized_params.key?(:id)
|
||||
|
||||
if recognized_params.key?(:group_id) && recognized_params[:group_id] !~ /^sg\-[0-9a-f]{8}/
|
||||
raise ArgumentError, 'aws_security_group security group ID must be in the format "sg-" followed by 8 hexadecimal characters.'
|
||||
end
|
||||
|
||||
if recognized_params.key?(:vpc_id) && recognized_params[:vpc_id] !~ /^vpc\-[0-9a-f]{8}/
|
||||
raise ArgumentError, 'aws_security_group VPC ID must be in the format "vpc-" followed by 8 hexadecimal characters.'
|
||||
end
|
||||
|
||||
validated_params = recognized_params
|
||||
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, 'You must provide parameters to aws_security_group, such as group_name, group_id, or vpc_id.g_group.'
|
||||
end
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
# Transform into filter format expected by AWS
|
||||
filters = []
|
||||
[
|
||||
:description,
|
||||
:group_id,
|
||||
:group_name,
|
||||
:vpc_id,
|
||||
].each do |criterion_name|
|
||||
instance_var = "@#{criterion_name}".to_sym
|
||||
next unless instance_variable_defined?(instance_var)
|
||||
val = instance_variable_get(instance_var)
|
||||
next if val.nil?
|
||||
filters.push(
|
||||
{
|
||||
name: criterion_name.to_s.tr('_', '-'),
|
||||
values: [val],
|
||||
},
|
||||
)
|
||||
end
|
||||
dsg_response = backend.describe_security_groups(filters: filters)
|
||||
|
||||
if dsg_response.security_groups.empty?
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
|
||||
@exists = true
|
||||
@description = dsg_response.security_groups[0].description
|
||||
@group_id = dsg_response.security_groups[0].group_id
|
||||
@group_name = dsg_response.security_groups[0].group_name
|
||||
@vpc_id = dsg_response.security_groups[0].vpc_id
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend self
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_security_groups(query)
|
||||
aws_service_client.describe_security_groups(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
68
lib/resources/aws/aws_security_groups.rb
Normal file
68
lib/resources/aws/aws_security_groups.rb
Normal file
|
@ -0,0 +1,68 @@
|
|||
class AwsSecurityGroups < Inspec.resource(1)
|
||||
name 'aws_security_groups'
|
||||
desc 'Verifies settings for AWS Security Groups in bulk'
|
||||
example <<-EOX
|
||||
# Verify that you have security groups defined
|
||||
describe aws_security_groups do
|
||||
it { should exist }
|
||||
end
|
||||
|
||||
# Verify you have more than the default security group
|
||||
describe aws_security_groups do
|
||||
its('entries.count') { should be > 1 }
|
||||
end
|
||||
EOX
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:where)
|
||||
.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:group_ids, field: :group_id)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'EC2 Security Groups'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_criteria)
|
||||
unless raw_criteria.is_a? Hash
|
||||
raise 'Unrecognized criteria for fetching Security Groups. ' \
|
||||
"Use 'criteria: value' format."
|
||||
end
|
||||
|
||||
# No criteria yet
|
||||
unless raw_criteria.empty?
|
||||
raise ArgumentError, 'aws_ec2_security_groups does not currently accept resource parameters.'
|
||||
end
|
||||
raw_criteria
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
@table = []
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
backend.describe_security_groups({}).security_groups.each do |sg_info|
|
||||
@table.push({
|
||||
group_id: sg_info.group_id,
|
||||
group_name: sg_info.group_name,
|
||||
vpc_id: sg_info.vpc_id,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend self
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_security_groups(query)
|
||||
aws_service_client.describe_security_groups(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
53
lib/resources/aws/aws_sns_topic.rb
Normal file
53
lib/resources/aws/aws_sns_topic.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
class AwsSnsTopic < Inspec.resource(1)
|
||||
name 'aws_sns_topic'
|
||||
desc 'Verifies settings for an SNS Topic'
|
||||
example "
|
||||
describe aws_sns_topic('arn:aws:sns:us-east-1:123456789012:some-topic') do
|
||||
it { should exist }
|
||||
its('confirmed_subscription_count') { should_not be_zero }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :arn, :confirmed_subscription_count
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:arn],
|
||||
allowed_scalar_name: :arn,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
# Validate the ARN
|
||||
unless validated_params[:arn] =~ /^arn:aws:sns:[\w\-]+:\d{12}:[\S]+$/
|
||||
raise ArgumentError, 'Malformed ARN for SNS topics. Expected an ARN of the form ' \
|
||||
"'arn:aws:sns:REGION:ACCOUNT-ID:TOPIC-NAME'"
|
||||
end
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
aws_response = BackendFactory.create(inspec_runner).get_topic_attributes(topic_arn: @arn).attributes
|
||||
@exists = true
|
||||
|
||||
# The response has a plain hash with CamelCase plain string keys and string values
|
||||
@confirmed_subscription_count = aws_response['SubscriptionsConfirmed'].to_i
|
||||
rescue Aws::SNS::Errors::NotFound
|
||||
@exists = false
|
||||
end
|
||||
|
||||
# Uses the SDK API to really talk to AWS
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::SNS::Client
|
||||
|
||||
def get_topic_attributes(criteria)
|
||||
aws_service_client.get_topic_attributes(criteria)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
88
lib/resources/aws/aws_subnet.rb
Normal file
88
lib/resources/aws/aws_subnet.rb
Normal file
|
@ -0,0 +1,88 @@
|
|||
class AwsSubnet < Inspec.resource(1)
|
||||
name 'aws_subnet'
|
||||
desc 'This resource is used to test the attributes of a VPC subnet'
|
||||
example "
|
||||
describe aws_subnet(subnet_id: 'subnet-12345678') do
|
||||
it { should exist }
|
||||
its('cidr_block') { should eq '10.0.1.0/24' }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
attr_reader :assigning_ipv_6_address_on_creation, :availability_zone, :available_ip_address_count,
|
||||
:available, :cidr_block, :default_for_az, :ipv_6_cidr_block_association_set,
|
||||
:mapping_public_ip_on_launch, :subnet_id, :vpc_id
|
||||
alias available? available
|
||||
alias default_for_az? default_for_az
|
||||
alias mapping_public_ip_on_launch? mapping_public_ip_on_launch
|
||||
alias assigning_ipv_6_address_on_creation? assigning_ipv_6_address_on_creation
|
||||
|
||||
def to_s
|
||||
"VPC Subnet #{@subnet_id}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:subnet_id],
|
||||
allowed_scalar_name: :subnet_id,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
# Make sure the subnet_id parameter was specified and in the correct form.
|
||||
if validated_params.key?(:subnet_id) && validated_params[:subnet_id] !~ /^subnet\-[0-9a-f]{8}/
|
||||
raise ArgumentError, 'aws_subnet Subnet ID must be in the format "subnet-" followed by 8 hexadecimal characters.'
|
||||
end
|
||||
|
||||
if validated_params.empty?
|
||||
raise ArgumentError, 'You must provide a subnet_id to aws_subnet.'
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
# Transform into filter format expected by AWS
|
||||
filters = []
|
||||
filters.push({ name: 'subnet-id', values: [@subnet_id] })
|
||||
ds_response = backend.describe_subnets(filters: filters)
|
||||
|
||||
# If no subnets exist in the VPC, exist is false.
|
||||
if ds_response.subnets.empty?
|
||||
@exists = false
|
||||
return
|
||||
end
|
||||
@exists = true
|
||||
assign_properties(ds_response)
|
||||
end
|
||||
|
||||
def assign_properties(ds_response)
|
||||
@vpc_id = ds_response.subnets[0].vpc_id
|
||||
@subnet_id = ds_response.subnets[0].subnet_id
|
||||
@cidr_block = ds_response.subnets[0].cidr_block
|
||||
@availability_zone = ds_response.subnets[0].availability_zone
|
||||
@available_ip_address_count = ds_response.subnets[0].available_ip_address_count
|
||||
@default_for_az = ds_response.subnets[0].default_for_az
|
||||
@mapping_public_ip_on_launch = ds_response.subnets[0].map_public_ip_on_launch
|
||||
@available = ds_response.subnets[0].state == 'available'
|
||||
@ipv_6_cidr_block_association_set = ds_response.subnets[0].ipv_6_cidr_block_association_set
|
||||
@assigning_ipv_6_address_on_creation = ds_response.subnets[0].assign_ipv_6_address_on_creation
|
||||
end
|
||||
|
||||
# Uses the SDK API to really talk to AWS
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_subnets(query)
|
||||
aws_service_client.describe_subnets(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
53
lib/resources/aws/aws_subnets.rb
Normal file
53
lib/resources/aws/aws_subnets.rb
Normal file
|
@ -0,0 +1,53 @@
|
|||
class AwsSubnets < Inspec.resource(1)
|
||||
name 'aws_subnets'
|
||||
desc 'Verifies settings for VPC Subnets in bulk'
|
||||
example "
|
||||
# you should be able to test the cidr_block of a subnet
|
||||
describe aws_subnets.where(vpc_id: 'vpc-123456789') do
|
||||
its('subnet_ids') { should eq ['subnet-12345678', 'subnet-87654321'] }
|
||||
its('cidr_blocks') { should eq ['172.31.96.0/20'] }
|
||||
its('states') { should_not include 'pending' }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
def validate_params(resource_params)
|
||||
unless resource_params.empty?
|
||||
raise ArgumentError, 'aws_vpc_subnets does not accept resource parameters.'
|
||||
end
|
||||
resource_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
@table = backend.describe_subnets.subnets.map(&:to_h)
|
||||
end
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:where)
|
||||
.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:vpc_ids, field: :vpc_id)
|
||||
.add(:subnet_ids, field: :subnet_id)
|
||||
.add(:cidr_blocks, field: :cidr_block)
|
||||
.add(:states, field: :state)
|
||||
filter.connect(self, :table)
|
||||
|
||||
def to_s
|
||||
'EC2 VPC Subnets'
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend self
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_subnets(query = {})
|
||||
aws_service_client.describe_subnets(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
69
lib/resources/aws/aws_vpc.rb
Normal file
69
lib/resources/aws/aws_vpc.rb
Normal file
|
@ -0,0 +1,69 @@
|
|||
class AwsVpc < Inspec.resource(1)
|
||||
name 'aws_vpc'
|
||||
desc 'Verifies settings for AWS VPC'
|
||||
example "
|
||||
describe aws_vpc do
|
||||
it { should be_default }
|
||||
its('cidr_block') { should cmp '10.0.0.0/16' }
|
||||
end
|
||||
"
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsSingularResourceMixin
|
||||
|
||||
def to_s
|
||||
"VPC #{vpc_id}"
|
||||
end
|
||||
|
||||
[:cidr_block, :dhcp_options_id, :state, :vpc_id, :instance_tenancy, :is_default].each do |property|
|
||||
define_method(property) do
|
||||
@vpc[property]
|
||||
end
|
||||
end
|
||||
|
||||
alias default? is_default
|
||||
|
||||
private
|
||||
|
||||
def validate_params(raw_params)
|
||||
validated_params = check_resource_param_names(
|
||||
raw_params: raw_params,
|
||||
allowed_params: [:vpc_id],
|
||||
allowed_scalar_name: :vpc_id,
|
||||
allowed_scalar_type: String,
|
||||
)
|
||||
|
||||
if validated_params.key?(:vpc_id) && validated_params[:vpc_id] !~ /^vpc\-[0-9a-f]{8}/
|
||||
raise ArgumentError, 'aws_vpc VPC ID must be in the format "vpc-" followed by 8 hexadecimal characters.'
|
||||
end
|
||||
|
||||
validated_params
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
backend = BackendFactory.create(inspec_runner)
|
||||
|
||||
if @vpc_id.nil?
|
||||
filter = { name: 'isDefault', values: ['true'] }
|
||||
else
|
||||
filter = { name: 'vpc-id', values: [@vpc_id] }
|
||||
end
|
||||
|
||||
resp = backend.describe_vpcs({ filters: [filter] })
|
||||
|
||||
@vpc = resp.vpcs[0].to_h
|
||||
@vpc_id = @vpc[:vpc_id]
|
||||
@exists = !@vpc.empty?
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_vpcs(query)
|
||||
aws_service_client.describe_vpcs(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
45
lib/resources/aws/aws_vpcs.rb
Normal file
45
lib/resources/aws/aws_vpcs.rb
Normal file
|
@ -0,0 +1,45 @@
|
|||
class AwsVpcs < Inspec.resource(1)
|
||||
name 'aws_vpcs'
|
||||
desc 'Verifies settings for AWS VPCs in bulk'
|
||||
example '
|
||||
describe aws_vpcs do
|
||||
it { should exist }
|
||||
end
|
||||
'
|
||||
supports platform: 'aws'
|
||||
|
||||
include AwsPluralResourceMixin
|
||||
|
||||
# Underlying FilterTable implementation.
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:entries)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
filter.connect(self, :table)
|
||||
|
||||
def validate_params(raw_params)
|
||||
# No params yet
|
||||
unless raw_params.empty?
|
||||
raise ArgumentError, 'aws_vpcs does not accept resource parameters'
|
||||
end
|
||||
raw_params
|
||||
end
|
||||
|
||||
def to_s
|
||||
'VPCs'
|
||||
end
|
||||
|
||||
def fetch_from_api
|
||||
@table = BackendFactory.create(inspec_runner).describe_vpcs.to_h[:vpcs]
|
||||
end
|
||||
|
||||
class Backend
|
||||
class AwsClientApi < AwsBackendBase
|
||||
BackendFactory.set_default_backend(self)
|
||||
self.aws_client_class = Aws::EC2::Client
|
||||
|
||||
def describe_vpcs(query = {})
|
||||
aws_service_client.describe_vpcs(query)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
377
lib/resources/azure/azure_backend.rb
Normal file
377
lib/resources/azure/azure_backend.rb
Normal file
|
@ -0,0 +1,377 @@
|
|||
# Base class for Azure Resources. This allows the generic class to work
|
||||
# as well as the specific target resources for Azure Resources
|
||||
#
|
||||
# @author Russell Seymour
|
||||
module Inspec::Resources
|
||||
class AzureResourceBase < Inspec.resource(1)
|
||||
attr_reader :opts, :client, :azure
|
||||
|
||||
# Constructor that retreives the specified resource
|
||||
#
|
||||
# The opts hash should contain the following
|
||||
# :group_name - name of the resource group in which to look for items
|
||||
# :type - the type of Azure resource to look for
|
||||
# :apiversion - API version to use when looking for a specific resource
|
||||
# :name - name of the resource to find
|
||||
#
|
||||
# @author Russell Seymour
|
||||
#
|
||||
# @param [Hash] opts Hashtable of options as highlighted above
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
def initialize(opts)
|
||||
# declare the hashtable of counts
|
||||
@counts = {}
|
||||
@total = 0
|
||||
@opts = opts
|
||||
|
||||
# Determine if the environment variables for the options have been set
|
||||
option_var_names = {
|
||||
group_name: 'AZURE_RESOURCE_GROUP_NAME',
|
||||
name: 'AZURE_RESOURCE_NAME',
|
||||
type: 'AZURE_RESOURCE_TYPE',
|
||||
apiversion: 'AZURE_RESOURCE_API_VERSION',
|
||||
}
|
||||
option_var_names.each do |option_name, env_var_name|
|
||||
opts[option_name] = ENV[env_var_name] unless ENV[env_var_name].nil?
|
||||
end
|
||||
|
||||
@azure = inspec.backend
|
||||
@client = azure.azure_client
|
||||
@failed_resource = false
|
||||
end
|
||||
|
||||
def failed_resource?
|
||||
@failed_resource
|
||||
end
|
||||
|
||||
def catch_azure_errors
|
||||
yield
|
||||
rescue MsRestAzure::AzureOperationError => e
|
||||
# e.message is actually a massive stringified JSON, which might be useful in the future.
|
||||
# You want error_message here.
|
||||
fail_resource e.error_message
|
||||
@failed_resource = true
|
||||
nil
|
||||
end
|
||||
|
||||
# Return information about the resource group
|
||||
def resource_group
|
||||
catch_azure_errors do
|
||||
resource_group = client.resource_groups.get(opts[:group_name])
|
||||
|
||||
# create the methods for the resource group object
|
||||
dm = AzureResourceDynamicMethods.new
|
||||
dm.create_methods(self, resource_group)
|
||||
end
|
||||
end
|
||||
|
||||
def resources
|
||||
resources = nil
|
||||
catch_azure_errors do
|
||||
resources = client.resources.list_by_resource_group(opts[:group_name])
|
||||
end
|
||||
return if failed_resource?
|
||||
|
||||
# filter the resources based on the type, and the name if they been specified
|
||||
resources = filter_resources(resources, opts)
|
||||
|
||||
# if there is one resource then define methods on this class
|
||||
if resources.count == 1
|
||||
@total = 1
|
||||
|
||||
resource = nil
|
||||
catch_azure_errors do
|
||||
# get the apiversion for the resource, if one has not been specified
|
||||
apiversion = azure.get_api_version(resources[0].type, opts)
|
||||
|
||||
# get the resource by id so it can be interrogated
|
||||
resource = client.resources.get_by_id(resources[0].id, apiversion)
|
||||
end
|
||||
return if failed_resource?
|
||||
|
||||
dm = AzureResourceDynamicMethods.new
|
||||
|
||||
dm.create_methods(self, resource)
|
||||
else
|
||||
|
||||
# As there are many resources, parse each one so that it can be
|
||||
# interrogated by the FilterTable
|
||||
# @probes = parse_resources(resources, azure)
|
||||
@probes = resources.each.map do |item|
|
||||
# update the total
|
||||
@total += 1
|
||||
|
||||
# determine the counts for each type
|
||||
namespace, type_name = item.type.split(/\./)
|
||||
counts.key?(namespace) ? false : counts[namespace] = {}
|
||||
counts[namespace].key?(type_name) ? counts[namespace][type_name] += 1 : counts[namespace][type_name] = 1
|
||||
|
||||
# get the detail about the resource
|
||||
apiversion = azure.get_api_version(item.type, opts)
|
||||
resource = client.resources.get_by_id(item.id, apiversion)
|
||||
|
||||
# parse the resource
|
||||
parse_resource(resource)
|
||||
end.compact
|
||||
|
||||
# Iterate around the counts and create the necessary classes
|
||||
counts.each do |namespace, ns_counts|
|
||||
define_singleton_method namespace do
|
||||
AzureResourceTypeCounts.new(ns_counts)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Does the resource have any tags?
|
||||
#
|
||||
# If it is a Hashtable then it does not, because there was nothing to parse so there is not
|
||||
# a nested object to work with
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def has_tags?
|
||||
tags.is_a?(Hash) ? false : true
|
||||
end
|
||||
|
||||
# Returns how many tags have been set on the resource
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def tag_count
|
||||
tags.count
|
||||
end
|
||||
|
||||
# It is necessary to be able to test the tags of a resource. It is possible to say of the
|
||||
# resource has tags or not, and it is possible to check that the tags include a specific tag
|
||||
# However the value is not accessible, this function creates methods for all the tags that
|
||||
# are available.
|
||||
#
|
||||
# The format of the method name is '<TAG_NAME>_tag' and will return the value of that tag
|
||||
#
|
||||
# Disabling rubopcop check. If this is set as a normal if..then..end statement there is a
|
||||
# violation stating it should use a guard. When using a guard it throws this error
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def create_tag_methods
|
||||
# Iterate around the items of the tags and create the necessary access methods
|
||||
tags.item.each do |name, value|
|
||||
method_name = format('%s_tag', name)
|
||||
define_singleton_method method_name do
|
||||
value
|
||||
end
|
||||
end if defined?(tags.item)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Filter the resources that are returned by the options that have been specified
|
||||
#
|
||||
def filter_resources(resources, opts)
|
||||
if opts[:type] && opts[:name]
|
||||
resources.select { |r| r.type == opts[:type] && r.name == opts[:name] }
|
||||
elsif opts[:type]
|
||||
resources.select { |r| r.type == opts[:type] }
|
||||
elsif opts[:name]
|
||||
resources.select { |r| r.name == opts[:name] }
|
||||
else
|
||||
resources
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Class to create methods on the calling object at run time.
|
||||
# Each of the Azure Resources have different attributes and properties, and they all need
|
||||
# to be testable. To do this no methods are hardcoded, each on is craeted based on the
|
||||
# information returned from Azure.
|
||||
#
|
||||
# The class is a helper class essentially as it creates the methods on the calling class
|
||||
# rather than itself. This means that there is less duplication of code and it can be
|
||||
# reused easily.
|
||||
#
|
||||
# @author Russell Seymour
|
||||
# @since 0.2.0
|
||||
class AzureResourceDynamicMethods
|
||||
# Given the calling object and its data, create the methods on the object according
|
||||
# to the data that has been retrieved. Various types of data can be returned so the method
|
||||
# checks the type to ensure that the necessary methods are configured correctly
|
||||
#
|
||||
# @param AzureResourceProbe|AzureResource object The object on which the methods should be craeted
|
||||
# @param variant data The data from which the methods should be created
|
||||
def create_methods(object, data)
|
||||
# Check the type of data as this affects the setup of the methods
|
||||
# If it is an Azure Generic Resource then setup methods for each of
|
||||
# the instance variables
|
||||
case data.class.to_s
|
||||
when /^Azure::Resources::Mgmt::.*::Models::GenericResource$/,
|
||||
/^Azure::Resources::Mgmt::.*::Models::ResourceGroup$/
|
||||
# iterate around the instance variables
|
||||
data.instance_variables.each do |var|
|
||||
create_method(object, var.to_s.delete('@'), data.instance_variable_get(var))
|
||||
end
|
||||
# When the data is a Hash object iterate around each of the key value pairs and
|
||||
# craete a method for each one.
|
||||
when 'Hash'
|
||||
data.each do |key, value|
|
||||
create_method(object, key, value)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Method that is responsible for creating the method on the calling object. This is
|
||||
# because some nesting maybe required. For example of the value is a Hash then it will
|
||||
# need to have an AzureResourceProbe create for each key, whereas if it is a simple
|
||||
# string then the value just needs to be returned
|
||||
#
|
||||
# @private
|
||||
#
|
||||
# @param AzureResourceProbe|AzureResource object Object on which the methods need to be created
|
||||
# @param string name The name of the method
|
||||
# @param variant value The value that needs to be returned by the method
|
||||
def create_method(object, name, value)
|
||||
# Create the necessary method based on the var that has been passed
|
||||
# Test the value for its type so that the method can be setup correctly
|
||||
case value.class.to_s
|
||||
when 'String', 'Integer', 'TrueClass', 'FalseClass', 'Fixnum'
|
||||
object.define_singleton_method name do
|
||||
value
|
||||
end
|
||||
when 'Hash'
|
||||
value.count.zero? ? return_value = value : return_value = AzureResourceProbe.new(value)
|
||||
object.define_singleton_method name do
|
||||
return_value
|
||||
end
|
||||
when /^Azure::Resources::Mgmt::.*::Models::ResourceGroupProperties$/
|
||||
# This is a special case where the properties of the resource group is not a simple JSON model
|
||||
# This is because the plugin is using the Azure SDK to get this information so it is an SDK object
|
||||
# that has to be interrogated in a different way. This is the only object type that behaves like this
|
||||
value.instance_variables.each do |var|
|
||||
create_method(object, var.to_s.delete('@'), value.instance_variable_get(var))
|
||||
end
|
||||
when 'Array'
|
||||
# Some things are just string or integer arrays
|
||||
# Check this by seeing if the first element is a string / integer / boolean or
|
||||
# a hashtable
|
||||
# This may not be the best methid, but short of testing all elements in the array, this is
|
||||
# the quickest test
|
||||
case value[0].class.to_s
|
||||
when 'String', 'Integer', 'TrueClass', 'FalseClass', 'Fixnum'
|
||||
probes = value
|
||||
else
|
||||
probes = []
|
||||
value.each do |value_item|
|
||||
probes << AzureResourceProbe.new(value_item)
|
||||
end
|
||||
end
|
||||
object.define_singleton_method name do
|
||||
probes
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Class object to maintain a count of the Azure Resource types that are found
|
||||
# when a less specific test is carried out. For example if all the resoures of a resource
|
||||
# group are called for, there will be variaous types and number of those types.
|
||||
#
|
||||
# Each type is namespaced, so for example a virtual machine has the type 'Microsoft.Compute/virtualMachines'
|
||||
# This is broken down into the 'Microsoft' class with the type 'Compute/virtualMachines'
|
||||
# This has been done for two reasons:
|
||||
# 1. Enable the dotted notation to work in the test
|
||||
# 2. Allow third party resource types ot be catered for if they are ever enabled by Microsoft
|
||||
#
|
||||
# @author Russell Seymour
|
||||
# @since 0.2.0
|
||||
class AzureResourceTypeCounts
|
||||
# Constructor to setup a new class for a specific Azure Resource type.
|
||||
# It should be passed a hashtable with information such as:
|
||||
# {
|
||||
# "Compute/virtualMachines" => 2,
|
||||
# "Network/networkInterfaces" => 3
|
||||
# }
|
||||
# This will result in two methods being created on the class:
|
||||
# - Compute/virtualNetworks
|
||||
# - Network/networkInterfaces
|
||||
# Each of which will return the corresponding count value
|
||||
#
|
||||
# @param Hash counts Hash table of types and the count of each one
|
||||
#
|
||||
# @return AzureResourceTypeCounts
|
||||
def initialize(counts)
|
||||
counts.each do |type, count|
|
||||
define_singleton_method type do
|
||||
count
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Class object that is created for each element that is returned by Azure.
|
||||
# This is what is interogated by Inspec. If they are nested hashes, then this results
|
||||
# in nested AzureResourceProbe objects.
|
||||
#
|
||||
# For example, if the following was seen in an Azure Resource
|
||||
# properties -> storageProfile -> imageReference
|
||||
# Would result in the following nestec classes
|
||||
# AzureResource -> AzureResourceProbe -> AzureResourceProbe
|
||||
#
|
||||
# The methods for each of the classes are dynamically defined at run time and will
|
||||
# match the items that are retrieved from Azure. See the 'test/integration/verify/controls' for
|
||||
# examples
|
||||
#
|
||||
# This class will not be called externally
|
||||
#
|
||||
# @author Russell Seymour
|
||||
# @since 0.2.0
|
||||
# @attr_reader string name Name of the Azure resource
|
||||
# @attr_reader string type Type of the Azure Resource
|
||||
# @attr_reader string location Location in Azure of the resource
|
||||
class AzureResourceProbe
|
||||
attr_reader :name, :type, :location, :item, :count
|
||||
|
||||
# Initialize method for the class. Accepts an item, be it a scalar value, hash or Azure object
|
||||
# It will then create the necessary dynamic methods so that they can be called in the tests
|
||||
# This is accomplished by call the AzureResourceDynamicMethods
|
||||
#
|
||||
# @param varaint The item from which the class will be initialized
|
||||
#
|
||||
# @return AzureResourceProbe
|
||||
def initialize(item)
|
||||
dm = AzureResourceDynamicMethods.new
|
||||
dm.create_methods(self, item)
|
||||
|
||||
# Set the item as a property on the class
|
||||
# This is so that it is possible to interrogate what has been added to the class and isolate them from
|
||||
# the standard methods that a Ruby class has.
|
||||
# This used for checking Tags on a resource for example
|
||||
# It also allows direct access if so required
|
||||
@item = item
|
||||
|
||||
# Set how many items have been set
|
||||
@count = item.length
|
||||
end
|
||||
|
||||
# Allows resources to respond to the include test
|
||||
# This means that things like tags can be checked for and then their value tested
|
||||
#
|
||||
# @author Russell Seymour
|
||||
#
|
||||
# @param [String] key Name of the item to look for in the @item property
|
||||
def include?(key)
|
||||
@item.key?(key)
|
||||
end
|
||||
|
||||
# Give a sting like `computer_name` return the camelCase version, e.g.
|
||||
# computerName
|
||||
#
|
||||
# @param string data Data that needs to be converted from snake_case to camelCase
|
||||
#
|
||||
# @return string
|
||||
def camel_case(data)
|
||||
camel_case_data = data.split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
|
||||
|
||||
# Ensure that gb (as in gigabytes) is uppercased
|
||||
camel_case_data.gsub(/[gb]/, &:upcase)
|
||||
end
|
||||
end
|
59
lib/resources/azure/azure_generic_resource.rb
Normal file
59
lib/resources/azure/azure_generic_resource.rb
Normal file
|
@ -0,0 +1,59 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'resources/azure/azure_backend'
|
||||
require 'utils/filter'
|
||||
|
||||
module Inspec::Resources
|
||||
class AzureGenericResource < AzureResourceBase
|
||||
name 'azure_generic_resource'
|
||||
|
||||
desc '
|
||||
Inspec Resource to interrogate any Resource type in Azure
|
||||
'
|
||||
|
||||
supports platform: 'azure'
|
||||
|
||||
attr_accessor :filter, :total, :counts, :name, :type, :location, :probes
|
||||
|
||||
def initialize(opts = {})
|
||||
# Call the parent class constructor
|
||||
super(opts)
|
||||
|
||||
# Get the resource group
|
||||
resource_group
|
||||
|
||||
# Get the resources
|
||||
resources
|
||||
|
||||
# Create the tag methods
|
||||
create_tag_methods
|
||||
end
|
||||
|
||||
# Define the filter table so that it can be interrogated
|
||||
@filter = FilterTable.create
|
||||
@filter.add_accessor(:count)
|
||||
.add_accessor(:entries)
|
||||
.add_accessor(:where)
|
||||
.add_accessor(:contains)
|
||||
.add(:exist?, field: 'exist?')
|
||||
.add(:type, field: 'type')
|
||||
.add(:name, field: 'name')
|
||||
.add(:location, field: 'location')
|
||||
.add(:properties, field: 'properties')
|
||||
|
||||
@filter.connect(self, :probes)
|
||||
|
||||
def parse_resource(resource)
|
||||
# return a hash of information
|
||||
parsed = {
|
||||
'location' => resource.location,
|
||||
'name' => resource.name,
|
||||
'type' => resource.type,
|
||||
'exist?' => true,
|
||||
'properties' => AzureResourceProbe.new(resource.properties),
|
||||
}
|
||||
|
||||
parsed
|
||||
end
|
||||
end
|
||||
end
|
152
lib/resources/azure/azure_resource_group.rb
Normal file
152
lib/resources/azure/azure_resource_group.rb
Normal file
|
@ -0,0 +1,152 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'resources/azure/azure_backend'
|
||||
|
||||
module Inspec::Resources
|
||||
class AzureResourceGroup < AzureResourceBase
|
||||
name 'azure_resource_group'
|
||||
|
||||
desc '
|
||||
Inspec Resource to get metadata about a specific Resource Group
|
||||
'
|
||||
|
||||
supports platform: 'azure'
|
||||
|
||||
attr_reader :name, :location, :id, :total, :counts, :mapping
|
||||
|
||||
# Constructor to get the resource group itself and perform some analysis on the
|
||||
# resources that in the resource group.
|
||||
#
|
||||
# This analysis is defined by the the mapping hashtable which is used to define
|
||||
# the 'has_xxx?' methods (see AzureResourceGroup#create_has_methods) and return
|
||||
# the counts for each type
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def initialize(opts)
|
||||
opts.key?(:name) ? opts[:group_name] = opts[:name] : false
|
||||
# Ensure that the opts only have the name of the resource group set
|
||||
opts.select! { |k, _v| k == :group_name }
|
||||
super(opts)
|
||||
|
||||
# set the mapping for the Azure Resources
|
||||
@mapping = {
|
||||
nic: 'Microsoft.Network/networkInterfaces',
|
||||
vm: 'Microsoft.Compute/virtualMachines',
|
||||
extension: 'Microsoft.Compute/virtualMachines/extensions',
|
||||
nsg: 'Microsoft.Network/networkSecurityGroups',
|
||||
vnet: 'Microsoft.Network/virtualNetworks',
|
||||
managed_disk: 'Microsoft.Compute/disks',
|
||||
managed_disk_image: 'Microsoft.Compute/images',
|
||||
sa: 'Microsoft.Storage/storageAccounts',
|
||||
public_ip: 'Microsoft.Network/publicIPAddresses',
|
||||
}
|
||||
|
||||
# Get information about the resource group itself
|
||||
resource_group
|
||||
|
||||
# Get information about the resources in the resource group
|
||||
resources
|
||||
|
||||
# Call method to create the has_xxxx? methods
|
||||
create_has_methods
|
||||
|
||||
# Call method to allow access to the tag values
|
||||
create_tag_methods
|
||||
end
|
||||
|
||||
# Return the provisioning state of the resource group
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def provisioning_state
|
||||
properties.provisioningState
|
||||
end
|
||||
|
||||
# Analyze the fully qualified id of the resource group to return the subscription id
|
||||
# that this resource group is part of
|
||||
#
|
||||
# The format of the id is
|
||||
# /subscriptions/<SUBSCRIPTION_ID>/resourceGroups/<RESOURCE_GROUP_NAME>
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def subscription_id
|
||||
id.split(%r{\/}).reject(&:empty?)[1]
|
||||
end
|
||||
|
||||
# Method to parse the resources that have been returned
|
||||
# This allows the calculations of the amount of resources to be determined
|
||||
#
|
||||
# @author Russell Seymour
|
||||
#
|
||||
# @param [Hash] resource A hashtable representing the resource group
|
||||
def parse_resource(resource)
|
||||
# return a hash of information
|
||||
parsed = {
|
||||
'name' => resource.name,
|
||||
'type' => resource.type,
|
||||
}
|
||||
|
||||
parsed
|
||||
end
|
||||
|
||||
# This method catches the xxx_count calls that are made on the resource.
|
||||
#
|
||||
# The method that is called is stripped of '_count' and then compared with the
|
||||
# mappings table. If that type exists then the number of those items is returned.
|
||||
# However if that type is not in the Resource Group then the method will return
|
||||
# a NoMethodError exception
|
||||
#
|
||||
# @author Russell Seymour
|
||||
#
|
||||
# @param [Symbol] method_id The name of the method that was called
|
||||
def method_missing(method_id)
|
||||
# Determine the mapping_key based on the method_id
|
||||
mapping_key = method_id.to_s.chomp('_count').to_sym
|
||||
|
||||
if mapping.key?(mapping_key)
|
||||
# based on the method id get the
|
||||
namespace, type_name = mapping[mapping_key].split(/\./)
|
||||
|
||||
# check that the type_name is defined, if not return 0
|
||||
if send(namespace).methods.include?(type_name.to_sym)
|
||||
# return the count for the method id
|
||||
send(namespace).send(type_name)
|
||||
else
|
||||
0
|
||||
end
|
||||
else
|
||||
msg = format('undefined method `%s` for %s', method_id, self.class)
|
||||
raise NoMethodError, msg
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# For each of the mappings this method creates the has_xxx? method. This allows the use
|
||||
# of the following type of test
|
||||
#
|
||||
# it { should have_nics }
|
||||
#
|
||||
# For example, it will create a has_nics? method that returns a boolean to state of the
|
||||
# resource group has any nics at all.
|
||||
#
|
||||
# @author Russell Seymour
|
||||
# @private
|
||||
def create_has_methods
|
||||
return if failed_resource?
|
||||
# Create the has methods for each of the mappings
|
||||
# This is a quick test to show that the resource group has at least one of these things
|
||||
mapping.each do |name, type|
|
||||
# Determine the name of the method name
|
||||
method_name = format('has_%ss?', name)
|
||||
namespace, type_name = type.split(/\./)
|
||||
|
||||
# use the namespace and the type_name to determine if the resource group has this type or not
|
||||
result = send(namespace).methods.include?(type_name.to_sym) ? true : false
|
||||
|
||||
define_singleton_method method_name do
|
||||
result
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
264
lib/resources/azure/azure_virtual_machine.rb
Normal file
264
lib/resources/azure/azure_virtual_machine.rb
Normal file
|
@ -0,0 +1,264 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'resources/azure/azure_backend'
|
||||
|
||||
module Inspec::Resources
|
||||
class AzureVirtualMachine < AzureResourceBase
|
||||
name 'azure_virtual_machine'
|
||||
|
||||
desc '
|
||||
Inspec Resource to test Azure Virtual Machines
|
||||
'
|
||||
|
||||
supports platform: 'azure'
|
||||
|
||||
# Constructor for the resource. This calls the parent constructor to
|
||||
# get the generic resource for the specified machine. This will provide
|
||||
# static methods that are documented
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def initialize(opts = {})
|
||||
# The generic resource needs to pass back a Microsoft.Compute/virtualMachines object so force it
|
||||
opts[:type] = 'Microsoft.Compute/virtualMachines'
|
||||
super(opts)
|
||||
|
||||
# Find the virtual machines
|
||||
resources
|
||||
|
||||
create_tag_methods
|
||||
end
|
||||
|
||||
# Method to catch calls that are not explicitly defined.
|
||||
# This allows the simple attributes of the virtual machine to be read without having
|
||||
# to define each one in turn.
|
||||
#
|
||||
# rubocop:disable Metrics/AbcSize
|
||||
#
|
||||
# @param symobl method_id The symbol of the method that has been called
|
||||
#
|
||||
# @return Value of attribute that has been called
|
||||
def method_missing(method_id)
|
||||
# Depending on the method that has been called, determine what value should be returned
|
||||
# These are set as camel case methods to comply with rubocop
|
||||
image_reference_attrs = %w{sku publisher offer}
|
||||
osdisk_attrs = %w{os_type caching create_option disk_size_gb}
|
||||
hardware_profile_attrs = %w{vm_size}
|
||||
os_profile_attrs = %w{computer_name admin_username}
|
||||
osdisk_managed_disk_attrs = %w{storage_account_type}
|
||||
|
||||
# determine the method name to call by converting the snake_case to camelCase
|
||||
# method_name = self.camel_case(method_id.to_s)
|
||||
method_name = method_id.to_s.split('_').inject([]) { |buffer, e| buffer.push(buffer.empty? ? e : e.capitalize) }.join
|
||||
method_name.end_with?('Gb') ? method_name.gsub!(/Gb/, &:upcase) : false
|
||||
|
||||
if image_reference_attrs.include?(method_id.to_s)
|
||||
properties.storageProfile.imageReference.send(method_name)
|
||||
elsif osdisk_attrs.include?(method_id.to_s)
|
||||
properties.storageProfile.osDisk.send(method_name)
|
||||
elsif hardware_profile_attrs.include?(method_id.to_s)
|
||||
properties.hardwareProfile.send(method_name)
|
||||
elsif os_profile_attrs.include?(method_id.to_s)
|
||||
properties.osProfile.send(method_name)
|
||||
elsif osdisk_managed_disk_attrs.include?(method_id.to_s)
|
||||
properties.storageProfile.osDisk.managedDisk.send(method_name)
|
||||
end
|
||||
end
|
||||
|
||||
# Return the name of the os disk
|
||||
#
|
||||
# @return string Name of the OS disk
|
||||
def os_disk_name
|
||||
properties.storageProfile.osDisk.name
|
||||
end
|
||||
|
||||
# Determine if the OS disk is a managed disk
|
||||
#
|
||||
# @return boolean
|
||||
def has_managed_osdisk?
|
||||
defined?(properties.storageProfile.osDisk.managedDisk)
|
||||
end
|
||||
|
||||
# Does the machine have any NICs connected
|
||||
#
|
||||
# @return boolean
|
||||
def has_nics?
|
||||
properties.networkProfile.networkInterfaces.count != 0
|
||||
end
|
||||
|
||||
# How many NICs are connected to the machine
|
||||
#
|
||||
# @return integer
|
||||
def nic_count
|
||||
properties.networkProfile.networkInterfaces.count
|
||||
end
|
||||
|
||||
# Return an array of the connected NICs so that it can be tested to ensure
|
||||
# the machine is connected properly
|
||||
#
|
||||
# @return array Array of NIC names connected to the machine
|
||||
def connected_nics
|
||||
nic_names = []
|
||||
properties.networkProfile.networkInterfaces.each do |nic|
|
||||
nic_names << nic.id.split(%r{/}).last
|
||||
end
|
||||
nic_names
|
||||
end
|
||||
|
||||
# Whether the machine has data disks or not
|
||||
#
|
||||
# @return boolean
|
||||
def has_data_disks?
|
||||
properties.storageProfile.dataDisks.count != 0
|
||||
end
|
||||
|
||||
# How many data disks are connected
|
||||
#
|
||||
# @return integer
|
||||
def data_disk_count
|
||||
properties.storageProfile.dataDisks.count
|
||||
end
|
||||
|
||||
# Does the machine allow password authentication
|
||||
#
|
||||
# This allows the use of
|
||||
# it { should have_password_authentication }
|
||||
# within the Inspec profile
|
||||
#
|
||||
# @return boolean
|
||||
def has_password_authentication?
|
||||
password_authentication?
|
||||
end
|
||||
|
||||
# Deteremine if the machine allows password authentication
|
||||
#
|
||||
# @return boolean
|
||||
def password_authentication?
|
||||
# if the osProfile property has a linuxConfiguration section then interrogate that
|
||||
# otherwise it is a Windows machine and that always has password auth
|
||||
if defined?(properties.osProfile.linuxConfiguration)
|
||||
!properties.osProfile.linuxConfiguration.disablePasswordAuthentication
|
||||
else
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
# Has the machine been given Custom Data at creation
|
||||
#
|
||||
# This allows the use of
|
||||
# it { should have_custom_data }
|
||||
# within the Inspec Profile
|
||||
#
|
||||
# @return boolean
|
||||
def has_custom_data?
|
||||
custom_data?
|
||||
end
|
||||
|
||||
# Determine if custom data has been set
|
||||
#
|
||||
# @return boolean
|
||||
def custom_data?
|
||||
if defined?(properties.osProfile.CustomData)
|
||||
true
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Are any SSH Keys assigned to the machine
|
||||
#
|
||||
# This allows the use of
|
||||
# it { should have_ssh_keys }
|
||||
# within the Inspec Profile
|
||||
#
|
||||
# @return boolean
|
||||
def has_ssh_keys?
|
||||
ssh_keys?
|
||||
end
|
||||
|
||||
# Determine if any ssh keys have been asigned to the machine
|
||||
#
|
||||
# @return boolean
|
||||
def ssh_keys?
|
||||
if defined?(properties.osProfile.linuxConfiguration.ssh)
|
||||
properties.osProfile.linuxConfiguration.ssh.publicKeys != 0
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Return the number of ssh keys that have been assigned to the machine
|
||||
#
|
||||
# @return integer
|
||||
def ssh_key_count
|
||||
if defined?(properties.osProfile.linuxConfiguration.ssh)
|
||||
properties.osProfile.linuxConfiguration.ssh.publicKeys.count
|
||||
else
|
||||
0
|
||||
end
|
||||
end
|
||||
|
||||
# Determine is the specified key is in the ssh_keys list
|
||||
#
|
||||
# @return array Array of the public keys that are assigned to allow for testing of that key
|
||||
def ssh_keys
|
||||
# iterate around the keys
|
||||
keys = []
|
||||
properties.osProfile.linuxConfiguration.ssh.publicKeys.each do |key|
|
||||
keys << key.keyData
|
||||
end
|
||||
keys
|
||||
end
|
||||
|
||||
# Does the machine have boot diagnostics enabled
|
||||
#
|
||||
# @return boolean
|
||||
def has_boot_diagnostics?
|
||||
if defined?(properties.diagnosticsProfile)
|
||||
properties.diagnosticsProfile.bootDiagnostics.enabled
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# Return the URI that has been set for the boot diagnostics storage
|
||||
#
|
||||
# @return string
|
||||
def boot_diagnostics_storage_uri
|
||||
properties.diagnosticsProfile.bootDiagnostics.storageUri
|
||||
end
|
||||
|
||||
# If this is a windows machine, returns whether the agent was provisioned or not
|
||||
#
|
||||
# @return boolean
|
||||
def has_provision_vmagent?
|
||||
if defined?(properties.osProfile.windowsConfiguration)
|
||||
properties.osProfile.windowsConfiguration.provisionVMAgent
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# If a windows machine see if automatic updates for the agent are enabled
|
||||
#
|
||||
# @return boolean
|
||||
def has_automatic_agent_update?
|
||||
if defined?(properties.osProfile.windowsConfiguration)
|
||||
properties.osProfile.windowsConfiguration.enableAutomaticUpdates
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
# If this is a windows machine return a boolean to state of the WinRM options
|
||||
# have been set
|
||||
#
|
||||
# @return boolean
|
||||
def has_winrm_options?
|
||||
if defined?(properties.osProfile.windowsConfiguration) && defined?(properties.osProfile.windowsConfiguration.winrm)
|
||||
properties.osProfile.windowsConfiguration.winrm.protocol
|
||||
else
|
||||
false
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
136
lib/resources/azure/azure_virtual_machine_data_disk.rb
Normal file
136
lib/resources/azure/azure_virtual_machine_data_disk.rb
Normal file
|
@ -0,0 +1,136 @@
|
|||
# encoding: utf-8
|
||||
|
||||
require 'resources/azure/azure_backend'
|
||||
require 'uri'
|
||||
|
||||
module Inspec::Resources
|
||||
class AzureVirtualMachineDataDisk < AzureResourceBase
|
||||
name 'azure_virtual_machine_data_disk'
|
||||
|
||||
desc '
|
||||
Inspec Resource to ensure that the data disks attached to a machine are correct
|
||||
'
|
||||
|
||||
supports platform: 'azure'
|
||||
|
||||
# Create a filter table so that tests on the disk can be performed
|
||||
filter = FilterTable.create
|
||||
filter.add_accessor(:where)
|
||||
.add_accessor(:entries)
|
||||
.add_accessor(:has_data_disks?)
|
||||
.add_accessor(:count)
|
||||
.add(:exists?) { |x| !x.entries.empty? }
|
||||
.add(:disk, field: :disk)
|
||||
.add(:number, field: :number)
|
||||
.add(:name, field: :name)
|
||||
.add(:size, field: :size)
|
||||
.add(:vhd_uri, field: :vhd_uri)
|
||||
.add(:storage_account_name, field: :storage_account_name)
|
||||
.add(:lun, field: :lun)
|
||||
.add(:caching, field: :caching)
|
||||
.add(:create_option, field: :create_option)
|
||||
.add(:is_managed_disk?, field: :is_managed_disk?)
|
||||
.add(:storage_account_type, field: :storage_account_type)
|
||||
.add(:subscription_id, field: :subscription_id)
|
||||
.add(:resource_group, field: :resource_group)
|
||||
filter.connect(self, :datadisk_details)
|
||||
|
||||
# Constructor for the resource. This calls the parent constructor to
|
||||
# get the generic resource for the specified machine. This will provide
|
||||
# static methods that are documented
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def initialize(opts = {})
|
||||
# The generic resource needs to pass back a Microsoft.Compute/virtualMachines object so force it
|
||||
opts[:type] = 'Microsoft.Compute/virtualMachines'
|
||||
super(opts)
|
||||
|
||||
# Get the data disks
|
||||
resources
|
||||
end
|
||||
|
||||
# Return information about the disks and add to the filter table so that
|
||||
# assertions can be performed
|
||||
#
|
||||
# @author Russell Seymour
|
||||
def datadisk_details
|
||||
return if failed_resource?
|
||||
# Iterate around the data disks on the machine
|
||||
properties.storageProfile.dataDisks.each_with_index.map do |datadisk, index|
|
||||
# Call function to parse the data disks and return an object based on the parameters
|
||||
parse_datadisk(datadisk, index)
|
||||
end
|
||||
end
|
||||
|
||||
# Return boolean to denote if the machine has data disks attached or not
|
||||
def has_data_disks?
|
||||
!entries.empty?
|
||||
end
|
||||
|
||||
# Return an integer stating how many data disks are attached to the machine
|
||||
def count
|
||||
entries.count
|
||||
end
|
||||
|
||||
# Return boolean to state if the machine is using managed disks for data disks
|
||||
def has_managed_disks?
|
||||
# iterate around the entries
|
||||
result = entries.each.select { |e| e[:is_managed_disk?] }
|
||||
result.empty? ? false : true
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# Parse the data disk to determine if these are managed disks or in a storage account
|
||||
# for example. The disk index, name and size will be returned
|
||||
#
|
||||
# params object disk Object containing the details of the disk
|
||||
# params integer index Index denoting which disk number this is on the machine
|
||||
#
|
||||
# return hashtable
|
||||
def parse_datadisk(disk, index)
|
||||
# Configure parsed hashtable to hold the information
|
||||
# Initialise this with common attributes from the different types of disk
|
||||
parsed = {
|
||||
disk: index,
|
||||
number: index + 1,
|
||||
lun: disk.lun,
|
||||
name: disk.name,
|
||||
size: disk.diskSizeGB,
|
||||
caching: disk.caching,
|
||||
create_option: disk.createOption,
|
||||
}
|
||||
|
||||
# Determine if the current disk is a managed disk or not
|
||||
if defined?(disk.vhd)
|
||||
# As this is in a storage account this is not a managed disk
|
||||
parsed[:is_managed_disk?] = false
|
||||
|
||||
# Set information about the disk
|
||||
# Parse the uri of the disk URI so that the storage account can be retrieved
|
||||
uri = URI.parse(disk.vhd.uri)
|
||||
parsed[:vhd_uri] = disk.vhd.uri
|
||||
parsed[:storage_account_name] = uri.host.split('.').first
|
||||
|
||||
elsif defined?(disk.managedDisk)
|
||||
# State that this is a managed disk
|
||||
parsed[:is_managed_disk?] = true
|
||||
|
||||
# Get information about the managed disk
|
||||
parsed[:storage_account_type] = disk.managedDisk.storageAccountType
|
||||
parsed[:id] = disk.managedDisk.id
|
||||
|
||||
# Break up the ID string so that the following information can get retreived
|
||||
# - subscription_id
|
||||
# - resource_group
|
||||
id_parts = parsed[:id].split(%r{/}).reject(&:empty?)
|
||||
|
||||
parsed[:subscription_id] = id_parts[1]
|
||||
parsed[:resource_group] = id_parts[3]
|
||||
end
|
||||
|
||||
# return the parsed object
|
||||
parsed
|
||||
end
|
||||
end
|
||||
end
|
|
@ -22,23 +22,27 @@ module Inspec::Resources
|
|||
its('Content-Length') { should cmp 258 }
|
||||
its('Content-Type') { should cmp 'text/html; charset=UTF-8' }
|
||||
end
|
||||
|
||||
# properly execute the HTTP call on the scanned machine instead of the
|
||||
# machine executing InSpec. This will be the default behavior in InSpec 2.0.
|
||||
describe http('http://localhost:8080', enable_remote_worker: true) do
|
||||
its('body') { should cmp 'local web server on target machine' }
|
||||
end
|
||||
"
|
||||
|
||||
def initialize(url, opts = {})
|
||||
@url = url
|
||||
@opts = opts
|
||||
|
||||
if use_remote_worker?
|
||||
return skip_resource 'curl is not available on the target machine' unless inspec.command('curl').exist?
|
||||
@worker = Worker::Remote.new(inspec, http_method, url, opts)
|
||||
else
|
||||
# Prior to InSpec 2.0 the HTTP test had to be instructed to run on the
|
||||
# remote target machine. This warning will be removed after a few months
|
||||
# to give users an opportunity to remove the unused option from their
|
||||
# profiles.
|
||||
if opts.key?(:enable_remote_worker) && !inspec.local_transport?
|
||||
warn 'Ignoring `enable_remote_worker` option, the `http` resource ',
|
||||
'remote worker is enabled by default for remote targets and ',
|
||||
'cannot be disabled'
|
||||
end
|
||||
|
||||
# Run locally if InSpec is ran locally and remotely if ran remotely
|
||||
if inspec.local_transport?
|
||||
@worker = Worker::Local.new(http_method, url, opts)
|
||||
else
|
||||
@worker = Worker::Remote.new(inspec, http_method, url, opts)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -62,17 +66,6 @@ module Inspec::Resources
|
|||
"http #{http_method} on #{@url}"
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def use_remote_worker?
|
||||
return false if inspec.local_transport?
|
||||
return true if @opts[:enable_remote_worker]
|
||||
|
||||
warn "[DEPRECATION] #{self} will execute locally instead of the target machine. To execute remotely, add `enable_remote_worker: true`."
|
||||
warn '[DEPRECATION] `enable_remote_worker: true` will be the default behavior in InSpec 2.0.'
|
||||
false
|
||||
end
|
||||
|
||||
class Worker
|
||||
class Base
|
||||
attr_reader :http_method, :opts, :url
|
||||
|
@ -154,6 +147,11 @@ module Inspec::Resources
|
|||
attr_reader :inspec
|
||||
|
||||
def initialize(inspec, http_method, url, opts)
|
||||
unless inspec.command('curl').exist?
|
||||
raise Inspec::Exceptions::ResourceSkipped,
|
||||
'curl is not available on the target machine'
|
||||
end
|
||||
|
||||
@inspec = inspec
|
||||
super(http_method, url, opts)
|
||||
end
|
||||
|
|
|
@ -29,16 +29,6 @@ module Inspec::Resources
|
|||
end
|
||||
end
|
||||
|
||||
# helper to collect a hash object easily
|
||||
def params
|
||||
{
|
||||
name: name,
|
||||
family: @platform[:family],
|
||||
release: @platform[:release],
|
||||
arch: @platform[:arch],
|
||||
}
|
||||
end
|
||||
|
||||
def to_s
|
||||
'Operating System Detection'
|
||||
end
|
||||
|
|
|
@ -26,7 +26,6 @@ module Inspec::Resources
|
|||
|
||||
describe passwd.uids(0) do
|
||||
its('users') { should cmp 'root' }
|
||||
its('count') { should eq 1 }
|
||||
end
|
||||
|
||||
describe passwd.shells(/nologin/) do
|
||||
|
@ -60,21 +59,6 @@ module Inspec::Resources
|
|||
.add(:homes, field: 'home')
|
||||
.add(:shells, field: 'shell')
|
||||
|
||||
filter.add(:count) { |t, _|
|
||||
warn '[DEPRECATION] `passwd.count` is deprecated. Please use `passwd.entries.length` instead. It will be removed in the next major version.'
|
||||
t.entries.length
|
||||
}
|
||||
|
||||
filter.add(:usernames) { |t, x|
|
||||
warn '[DEPRECATION] `passwd.usernames` is deprecated. Please use `passwd.users` instead. It will be removed in the next major version.'
|
||||
t.users(x)
|
||||
}
|
||||
|
||||
filter.add(:username) { |t, x|
|
||||
warn '[DEPRECATION] `passwd.username` is deprecated. Please use `passwd.users` instead. It will be removed in the next major version.'
|
||||
t.users(x)[0]
|
||||
}
|
||||
|
||||
# rebuild the passwd line from raw content
|
||||
filter.add(:content) { |t, _|
|
||||
t.entries.map do |e|
|
||||
|
@ -82,11 +66,6 @@ module Inspec::Resources
|
|||
end.join("\n")
|
||||
}
|
||||
|
||||
def uid(x)
|
||||
warn '[DEPRECATION] `passwd.uid(arg)` is deprecated. Please use `passwd.uids(arg)` instead. It will be removed in the next major version.'
|
||||
uids(x)
|
||||
end
|
||||
|
||||
filter.connect(self, :params)
|
||||
|
||||
def to_s
|
||||
|
|
|
@ -15,32 +15,22 @@ module Inspec::Resources
|
|||
"
|
||||
|
||||
def initialize
|
||||
@platform = inspec.backend.os
|
||||
@platform = inspec.backend.platform
|
||||
end
|
||||
|
||||
# add helper methods for easy access of properties
|
||||
%w{family release arch}.each do |property|
|
||||
define_method(property.to_sym) do
|
||||
@platform.send(property)
|
||||
@platform[property]
|
||||
end
|
||||
end
|
||||
|
||||
# This is a string override for platform.name.
|
||||
# TODO: removed in inspec 2.0
|
||||
class NameCleaned < String
|
||||
def ==(other)
|
||||
if other =~ /[A-Z ]/
|
||||
cleaned = other.downcase.tr(' ', '_')
|
||||
Inspec::Log.warn "[DEPRECATED] Platform names will become lowercase in InSpec 2.0. Please match on '#{cleaned}' instead of '#{other}'"
|
||||
super(cleaned)
|
||||
else
|
||||
super(other)
|
||||
end
|
||||
end
|
||||
def families
|
||||
@platform.family_hierarchy
|
||||
end
|
||||
|
||||
def name
|
||||
NameCleaned.new(@platform.name)
|
||||
@platform.name
|
||||
end
|
||||
|
||||
def [](key)
|
||||
|
@ -60,8 +50,19 @@ module Inspec::Resources
|
|||
@platform.family_hierarchy.include?(family)
|
||||
end
|
||||
|
||||
def families
|
||||
@platform.family_hierarchy
|
||||
def params
|
||||
h = {
|
||||
name: name,
|
||||
families: families,
|
||||
release: release,
|
||||
}
|
||||
|
||||
# Avoid adding Arch for APIs (not applicable)
|
||||
unless in_family?('api')
|
||||
h[:arch] = arch
|
||||
end
|
||||
|
||||
h
|
||||
end
|
||||
|
||||
def supported?(supports)
|
||||
|
@ -70,11 +71,7 @@ module Inspec::Resources
|
|||
status = true
|
||||
supports.each do |s|
|
||||
s.each do |k, v|
|
||||
# ignore the inspec check for supports
|
||||
# TODO: remove in inspec 2.0
|
||||
if k == :inspec
|
||||
next
|
||||
elsif %i(os_family os-family platform_family platform-family).include?(k)
|
||||
if %i(os_family os-family platform_family platform-family).include?(k)
|
||||
status = in_family?(v)
|
||||
elsif %i(os platform).include?(k)
|
||||
status = platform?(v)
|
||||
|
|
|
@ -54,18 +54,6 @@ class SimpleConfig
|
|||
end
|
||||
|
||||
def parse_params_line(line, opts)
|
||||
# Deprecation handling
|
||||
if opts.key?(:assignment_re)
|
||||
warn '[DEPRECATION] `:assignment_re` is deprecated in favor of `:assignment_regex` '\
|
||||
'and will be removed in the next major version. See: https://github.com/chef/inspec/issues/1709'
|
||||
opts[:assignment_regex] = opts[:assignment_re]
|
||||
end
|
||||
if opts.key?(:key_vals)
|
||||
warn '[DEPRECATION] `:key_vals` is deprecated in favor of `:key_values` '\
|
||||
'and will be removed in the next major version. See: https://github.com/chef/inspec/issues/1709'
|
||||
opts[:key_values] = opts[:key_vals]
|
||||
end
|
||||
|
||||
# now line contains what we are interested in parsing
|
||||
# check if it is an assignment
|
||||
m = opts[:assignment_regex].match(line)
|
||||
|
|
|
@ -227,7 +227,7 @@ namespace :docs do # rubocop:disable Metrics/BlockLength
|
|||
list = ''
|
||||
resources.each do |file|
|
||||
name = File.basename(file).sub(/\.md\.erb$/, '')
|
||||
list << f.li(f.a(name.sub('_', '\\_'), 'resources/' + name + '.html'))
|
||||
list << f.li(f.a(name.gsub('_', '\\_'), 'resources/' + name + '.html'))
|
||||
end
|
||||
res << f.ul(list)
|
||||
dst = File.join(src, 'resources.md')
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue