Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow deny list replacement terms #563

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 30 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,15 @@ For more background on the tool, please see this post at Stelligent's blog:
# Installation

## Gem Install

Presuming Ruby >= 2.5.x is installed, installation is just a matter of:

```bash
gem install cfn-nag
```

## Brew Install

On MacOS or Linux you can alternatively install with brew:

```bash
Expand Down Expand Up @@ -111,11 +113,12 @@ $ docker run -v `pwd`/spec/test_templates:/templates -t stelligent/cfn_nag /temp
`cfn_nag_scan` can be run as part of a GitHub Workflow to evaluate code during continuous integration pipelines.

In your GitHub Workflow file, create a step which uses the cfn_nag Action:
```
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests

```yaml
- name: Simple test
uses: stelligent/cfn_nag@master
with:
input_path: tests
```

More information about the [GitHub Action can be found here](github-action/README.md).
Expand All @@ -124,7 +127,7 @@ More information about the [GitHub Action can be found here](github-action/READM

### Profiles

cfn-nag supports the notion of a "profile" which is effectively a whitelist of rules to apply. The profile is a text file
cfn-nag supports the notion of a "profile" which is effectively an allow list of rules to apply. The profile is a text file
that must contain a rule identifier per line. When specified via the `--profile-path` command line argument,
cfn-nag will ONLY return violations from those particular rules.

Expand All @@ -134,27 +137,26 @@ IAM resources and therefore not care about those rules.

Here is an example profile:

```
```text
F1
F2
F27
W3
W5
```

### Global Blacklist
### Global Deny List

The blacklist is basically the opposite of the profile: it's a list of rules to NEVER apply. When specified via the
`--blacklist-path` command line argument, cfn-nag will NEVER return violations from those particular rules specified
The deny list is basically the opposite of the profile: it's a list of rules to NEVER apply. When specified via the
`--deny-list-path` command line argument, cfn-nag will NEVER return violations from those particular rules specified
in the file.

In case a rule is specified in both, the blacklist will take priority over the profile, and the rule will not be applieed.
In case a rule is specified in both, the deny list will take priority over the profile, and the rule will not be applied.

The format is as follows. The only two salient fields are `RulesToSuppress` and the `id` per item. The `reason` won't
be interpreted by cfn-nag, but it is recommended to justify and document why the rule should never be applied.

```yaml
---
RulesToSuppress:
- id: W3
reason: W3 is something we never care about at enterprise X
Expand Down Expand Up @@ -290,6 +292,7 @@ leave them be, and so they would appear as Hash values to rules. For example: `

Starting in 0.5.55, the model will attempt to compute the value for a call to FindInMap and present that value to the
rules. This evaluation supports keys that are:

* static text
* references to parameters (with parameter substitution)
* references to AWS pseudofunctions (see next section)
Expand All @@ -307,7 +310,7 @@ map evaluation.

Starting in 0.5.55, the model will present the following AWS pseudofunctions to rules with the default values:

```
```text
'AWS::URLSuffix' => 'amazonaws.com',
'AWS::Partition' => 'aws',
'AWS::NotificationARNs' => '',
Expand All @@ -319,10 +322,10 @@ Starting in 0.5.55, the model will present the following AWS pseudofunctions to

Additionally, the end user can override the value supplied via the traditional parameter substitution mechanism. For example:

```
```json
{
"Parameters": {
"AWS::Region": eu-west-1"
"AWS::Region": "eu-west-1"
}
}
```
Expand Down Expand Up @@ -367,9 +370,10 @@ The format of the JSON is a a dictionary with each key/value pair mapping to the

# Stelligent Policy Complexity Metrics (spcm)

The basis for SPCM is described in the blog post: https://stelligent.com/2020/03/27/thought-experiment-proposed-complexity-metric-for-iam-policy-documents/
The basis for SPCM is described in the blog post [Thought Experiment Proposed Complexity Metric for IAM Policy Documents](https://stelligent.com/2020/03/27/thought-experiment-proposed-complexity-metric-for-iam-policy-documents/).

Starting in version 0.6.0 of cfn_nag:

* `spcm_scan` can scan a directory of CloudFormation templates (like cfn_nag_scan) and generate a report with the SPCM
metrics in either JSON or HTML format
* A rule is added (to cfn_nag) to warn on an IAM::Policy or IAM::Role with a SPCM score of >= 50 (default)
Expand Down Expand Up @@ -438,7 +442,8 @@ A screencast demonstrating soup-to-nuts TDD custom rule development is available
## Specs

To run the specs, you need to ensure you have Docker installed and cfn_nag dependencies installed via
```

```bash
gem install bundle
bundle install
```
Expand All @@ -448,23 +453,25 @@ Then, to run all of the specs, just run `rake test:all`.
To run the end-to-end tests, run `rake test:e2e`. The script will bundle all gems in the Gemfile, build and install the cfn_nag gem locally, install spec dependencies, and then executes tests tagged with 'end_to_end'. It will also pull down sample templates provided by Amazon and run cfn_nag_scan against them, to see if any known-good templates cause exceptions within cfn-nag.

## Local Install

To install the current git branch locally:
```

```bash
bundle install
scripts/deploy_local.sh
```

## VS Code Remote Development

There is a complete remote development environment created and setup with all the tools and settings pre-configured for ease in rule development and creation. You can enable this by using the VS Code Remote development functionality.

- Install the VS Code [Remote Development extension pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)
- Open the repo in VS Code
- When prompted "`Folder contains a dev container configuration file. Reopen folder to develop in a container`" click the "`Reopen in Container`" button
- When opening in the future use the "`[Dev Container] cfn_nag Development`" option
* Install the VS Code [Remote Development extension pack](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.vscode-remote-extensionpack)
* Open the repo in VS Code
* When prompted `Folder contains a dev container configuration file. Reopen folder to develop in a container` click the `Reopen in Container` button
* When opening in the future use the `[Dev Container] cfn_nag Development` option

More information about the VS Code Remote Development setup can be found here, [VS Code Remote Development](vscode_remote_development.md).

# Support

To report a bug or request a feature, submit an issue through the GitHub repository via: <https://github.com/stelligent/cfn_nag/issues/new>

43 changes: 0 additions & 43 deletions lib/cfn-nag/blacklist_loader.rb

This file was deleted.

14 changes: 7 additions & 7 deletions lib/cfn-nag/cfn_nag.rb
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ def audit(cloudformation_string:, parameter_values_string: nil, condition_values
@config.custom_rule_loader.rule_definitions
)

violations = filter_violations_by_blacklist_and_profile(violations)
violations = filter_violations_by_deny_list_and_profile(violations)
violations = mark_line_numbers(violations, cfn_model)
rescue RuleRepoException, Psych::SyntaxError, ParserError => fatal_error
violations << fatal_violation(fatal_error.to_s)
Expand Down Expand Up @@ -127,21 +127,21 @@ def mark_line_numbers(violations, cfn_model)
violations
end

def filter_violations_by_blacklist_and_profile(violations)
def filter_violations_by_deny_list_and_profile(violations)
violations = filter_violations_by_profile(
profile_definition: @config.profile_definition,
rule_definitions: @config.custom_rule_loader.rule_definitions,
violations: violations
)

# this must come after - blacklist should always win
filter_violations_by_blacklist(
blacklist_definition: @config.blacklist_definition,
# this must come after - deny list should always win
filter_violations_by_deny_list(
deny_list_definition: @config.deny_list_definition,
rule_definitions: @config.custom_rule_loader.rule_definitions,
violations: violations
)
rescue StandardError => blacklist_or_profile_parse_error
violations << fatal_violation(blacklist_or_profile_parse_error.to_s)
rescue StandardError => deny_list_or_profile_parse_error
violations << fatal_violation(deny_list_or_profile_parse_error.to_s)
violations
end

Expand Down
6 changes: 3 additions & 3 deletions lib/cfn-nag/cfn_nag_config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
class CfnNagConfig
# rubocop:disable Metrics/ParameterLists
def initialize(profile_definition: nil,
blacklist_definition: nil,
deny_list_definition: nil,
rule_directory: nil,
allow_suppression: true,
print_suppression: false,
Expand All @@ -21,14 +21,14 @@ def initialize(profile_definition: nil,
rule_repository_definitions: rule_repository_definitions
)
@profile_definition = profile_definition
@blacklist_definition = blacklist_definition
@deny_list_definition = deny_list_definition
@fail_on_warnings = fail_on_warnings
@rule_repositories = rule_repositories
@rule_arguments = rule_arguments
@ignore_fatal = ignore_fatal
end
# rubocop:enable Metrics/ParameterLists

attr_reader :rule_arguments, :rule_directory, :custom_rule_loader, :profile_definition, :blacklist_definition, \
attr_reader :rule_arguments, :rule_directory, :custom_rule_loader, :profile_definition, :deny_list_definition, \
:fail_on_warnings, :rule_repositories, :ignore_fatal
end
6 changes: 3 additions & 3 deletions lib/cfn-nag/cfn_nag_executor.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
class CfnNagExecutor
def initialize
@profile_definition = nil
@blacklist_definition = nil
@deny_list_definition = nil
@parameter_values_string = nil
@condition_values_string = nil
@rule_repository_definitions = []
Expand Down Expand Up @@ -89,7 +89,7 @@ def validate_options(opts)
def execute_io_options(opts)
@profile_definition = read_conditionally(opts[:profile_path])

@blacklist_definition = read_conditionally(opts[:blacklist_path])
@deny_list_definition = read_conditionally(opts[:deny_list_path]) || read_conditionally(opts[:blacklist_path])

@parameter_values_string = read_conditionally(opts[:parameter_values_path])

Expand Down Expand Up @@ -122,7 +122,7 @@ def merge_rule_arguments(opts)
def cfn_nag_config(opts)
CfnNagConfig.new(
profile_definition: @profile_definition,
blacklist_definition: @blacklist_definition,
deny_list_definition: @deny_list_definition,
rule_directory: opts[:rule_directory],
allow_suppression: opts[:allow_suppression],
print_suppression: opts[:print_suppression],
Expand Down
14 changes: 12 additions & 2 deletions lib/cfn-nag/cli_options.rb
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,13 @@ def self.file_options
type: :string,
required: false,
default: nil
opt :deny_list_path,
'Path to a deny list file',
type: :string,
required: false,
default: nil
opt :blacklist_path,
'Path to a blacklist file',
'(Deprecated) Path to a deny list file',
type: :string,
required: false,
default: nil
Expand Down Expand Up @@ -145,8 +150,13 @@ def self.scan_options
type: :string,
required: false,
default: nil
opt :deny_list_path,
'Path to a deny list file',
type: :string,
required: false,
default: nil
opt :blacklist_path,
'Path to a blacklist file',
'(Deprecated) Path to a deny list file',
type: :string,
required: false,
default: nil
Expand Down
43 changes: 43 additions & 0 deletions lib/cfn-nag/deny_list_loader.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# frozen_string_literal: true

require 'yaml'

class DenyListLoader
def initialize(rules_registry)
@rules_registry = rules_registry
end

def load(deny_list_definition:)
raise 'Empty profile' if deny_list_definition.strip == ''

deny_list_ruleset = RuleIdSet.new

deny_list_hash = load_deny_list_yaml(deny_list_definition)
raise 'Deny list is malformed' unless deny_list_hash.is_a? Hash

rules_to_suppress = deny_list_hash.fetch('RulesToSuppress', {})
raise 'Missing RulesToSuppress key in deny list' if rules_to_suppress.empty?

rule_ids_to_suppress = rules_to_suppress.map { |rule| rule['id'] }
rule_ids_to_suppress.each do |rule_id|
check_valid_rule_id rule_id
deny_list_ruleset.add_rule rule_id
end

deny_list_ruleset
end

private

def load_deny_list_yaml(deny_list_definition)
YAML.safe_load(deny_list_definition)
rescue StandardError => yaml_parse_error
raise "YAML parse of deny list failed: #{yaml_parse_error}"
end

def check_valid_rule_id(rule_id)
return true unless @rules_registry.by_id(rule_id).nil?

raise "#{rule_id} is not a legal rule identifier from: #{@rules_registry.ids}"
end
end
Loading