diff --git a/custom_rule_development.md b/custom_rule_development.md index 62f7638a..b1d17cd3 100644 --- a/custom_rule_development.md +++ b/custom_rule_development.md @@ -1,38 +1,44 @@ ## Crash Course in (New) Rule Development 1. Create a class that ends with the name Rule. This is a convention that must be observed in order for cfn-nag to load - the rule. Additionally, derive this class from BaseRule: + the rule. Additionally, derive this class from `BaseRule`: - require 'cfn-nag/violation' - require_relative 'base' + ```ruby + require 'cfn-nag/violation' + require_relative 'base' - class IamManagedPolicyNotActionRule < BaseRule + class IamManagedPolicyNotActionRule < BaseRule + ``` 2. Define methods that describe some of the bookkeeping for the rule, like whether it is a WARNING/FAILING_VIOLATION, its unique identifier among rules, and error text shown when it matches: - def rule_text - 'IAM managed policy should not allow Allow+NotAction' - end + ```ruby + def rule_text + 'IAM managed policy should not allow Allow+NotAction' + end - def rule_type - Violation::WARNING - end + def rule_type + Violation::WARNING + end - def rule_id - 'W17' - end + def rule_id + 'W17' + end + ``` 3. Define the `audit_impl` method to do the actual work of the analysis. This method should return an array of logical resource identifiers from the CloudFormation template: - def audit_impl(cfn_model) - violating_policies = cfn_model.resources_by_type('AWS::IAM::ManagedPolicy').select do |policy| - !policy.policy_document.allows_not_action.empty? - end + ```ruby + def audit_impl(cfn_model) + violating_policies = cfn_model.resources_by_type('AWS::IAM::ManagedPolicy').select do |policy| + !policy.policy_document.allows_not_action.empty? + end - violating_policies.map { |policy| policy.logical_resource_id } - end + violating_policies.map { |policy| policy.logical_resource_id } + end + ``` 4. The cfn_model object passed into the `audit_impl` method is where a majority of the improvement lies. When a CloudFormation document is parsed, it is mapped into a collection of objects that mirror the resource types. @@ -43,6 +49,7 @@ * Attributes/properties are returned in camelCase. * For example, if dealing with an object type of 'AWS::RDS::DBCluster', and needing to write logic against the 'StorageEncrypted' property, then an example `audit_impl` might look like this: + ```ruby def audit_impl(cfn_model) violating_rdscluster = cfn_model.resources_by_type('AWS::RDS::DBCluster').select do |cluster| cluster.storageEncrypted.nil? || cluster.storageEncrypted.to_s.downcase == 'false' @@ -50,6 +57,7 @@ violating_rdscluster.map { |cluster| cluster.logical_resource_id } end + ``` * cfn-model provides special handling for a subset of objects whereby properties may be transformed into something simpler and/or objects may be linked together. @@ -114,4 +122,4 @@ install` to install dependencies. For any generic rules you want to share with the community, submit a PR of the rule to `lib/custom_rules`. Code should be linted using [Rubocop](https://github.com/rubocop-hq/rubocop). Please be sure to use a unique -`rule_id` and write the rule according section 1 of the above "Where to Place Rule Files" area. \ No newline at end of file +`rule_id` and write the rule according section 1 of the above "Where to Place Rule Files" area.