Skip to content

Latest commit

 

History

History
273 lines (223 loc) · 8.97 KB

COMPLEX_COMPOSITION.md

File metadata and controls

273 lines (223 loc) · 8.97 KB

Guard: Complex Composition

This chapter is a more advanced topic. Readers are encouraged to read all the other chapters before reading this one.

Let’s recall key learnings from the Guard: Clauses document.

  1. Structure of a named rule block:
rule <rule name> [when <condition>] {
    Guard_rule_A
    Guard_rule_B
    ...
}

Where Guard rule is an umbrella term for clause, query block, when block or named rule block.

  1. Named rule blocks allow for re-usability, improved composition, and reduced verbosity and repetition. This document will focus on demonstrating these features of named rule blocks in-depth.

Complex Composition with Named Rule Blocks

There are two styles of composition where named rule blocks can demonstrate its capability for re-usability, improved composition, and reduced verbosity:

  • Conditional dependency composition
  • Correlational dependency composition

Conditional dependency composition

In this style of composition, the evaluation of a when block or a named rule block has a conditional dependency on the evaluation result of one or more other named rule blocks and/or clauses. This can be achieved as follows:

# Named rule block, rule_name_A
rule rule_name_A {
    Guard_rule_1
    Guard_rule_2
    ...
}

# Example-1, Named rule block, rule_name_B, taking a conditional dependency on rule_name_A
rule rule_name_B when rule_name_A {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-2, When block taking a conditional dependency on rule_name_A
when rule_name_A {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-3, Named rule block, rule_name_C, taking a conditional dependency on rule_name_A ^ rule_name_B
rule rule_name_C when rule_name_A
                      rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}

# Example-4, Named rule block, rule_name_D, taking a conditional dependency on (rule_name_A v clause_A) ^ clause_B ^ rule_name_B
rule rule_name_D when rule_name_A OR
                      clause_A
                      clause_B
                      rule_name_B {
    Guard_rule_3
    Guard_rule_4
    ...
}

Let us take Example-1 to analyze how the evaluation result of rule_name_A influences the evaluation of rule_name_B.

  • rule_name_A evaluates to a PASS

The Guard rules encapsulated by rule_name_B are evaluated.

  • rule_name_A evaluates to a FAIL

The Guard rules encapsulated by rule_name_B are not evaluated. rule_name_B evaluates to a SKIP.

  • rule_name_A evaluates to a SKIP - This can happen if rule_name_A conditionally depended on a Guard rule which evaluated to a FAIL and resulted in rule_name_A evaluating to a SKIP.

The Guard rules encapsulated by rule_name_B are not evaluated. rule_name_B evaluates to a SKIP.

Let us take an example of examining a configuration management database (CMDB) configuration item from an AWS Config item for ingress and egress security groups information to see conditional dependency composition in action.

rule check_resource_type_and_parameter {
    resourceType == /AWS::EC2::SecurityGroup/
    InputParameters.TcpBlockedPorts NOT EMPTY 
}

rule check_parameter_validity when check_resource_type_and_parameter {
    InputParameters.TcpBlockedPorts[*] {
        this in r[0,65535] 
    }
}

rule check_ip_protocol_and_port_range_validity when check_parameter_validity {
    let ports = InputParameters.TcpBlockedPorts[*]

    # 
    # select all ipPermission instances that can be reached by ANY IP address
    # IPv4 or IPv6 and not UDP
    #
    let configuration = configuration.ipPermissions[ 
        some ipv4Ranges[*].cidrIp == "0.0.0.0/0" or
        some ipv6Ranges[*].cidrIpv6 == "::/0"
        ipProtocol != 'udp' ] 
    when %configuration !empty {
        %configuration {
            ipProtocol != '-1'

            when fromPort exists 
                toPort exists {
                let ip_perm_block = this
                %ports {
                    this < %ip_perm_block.fromPort or
                    this > %ip_perm_block.toPort
                }
            }
        }
    }
}

check_parameter_validity is conditionally dependent on check_resource_type_and_parameter and check_ip_protocol_and_port_range_validity is conditionally dependent on check_parameter_validity. Below is a CMDB configuration item which conforms to the above rules:

---
version: '1.3'
resourceType: 'AWS::EC2::SecurityGroup'
resourceId: sg-12345678abcdefghi
configuration:
  description: Delete-me-after-testing
  groupName: good-sg-test-delete-me
  ipPermissions:
    - fromPort: 172
      ipProtocol: tcp
      ipv6Ranges: []
      prefixListIds: []
      toPort: 172
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
    - fromPort: 89
      ipProtocol: tcp
      ipv6Ranges:
        - cidrIpv6: '::/0'
      prefixListIds: []
      toPort: 89
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  ipPermissionsEgress:
    - ipProtocol: '-1'
      ipv6Ranges: []
      prefixListIds: []
      userIdGroupPairs: []
      ipv4Ranges:
        - cidrIp: 0.0.0.0/0
      ipRanges:
        - 0.0.0.0/0
  tags:
    - key: Name
      value: good-sg-delete-me
  vpcId: vpc-0123abcd
InputParameters:
  TcpBlockedPorts:
    - 3389
    - 20
    - 110
    - 142
    - 1434
    - 5500
supplementaryConfiguration: {}
resourceTransitionStatus: None

Correlational dependency composition

In this style of composition the evaluation of a when block or a named rule block has a correlational dependency on the evaluation result of one or more other Guard rules. This can be achieved as follows:

# Named rule block, rule_name_A, taking a correlational dependency on all the Guard rules encapsulated by the named rule block
rule rule_name_A {
    Guard_rule_1
    Guard_rule_2
    ...
}

# When block taking a correlational dependency on all the Guard rules encapsulated by the when block
when condition {
    Guard_rule_1
    Guard_rule_2
    ...
}

Let us look at an example set of Guard rules to help you understand correlational dependency composition better.

#
# Allowed valid protocols for ELB
#
let allowed_protocols = [ "HTTPS", "TLS" ]

let elbs = Resources.*[ Type == 'AWS::ElasticLoadBalancingV2::Listener' ]

#
# If there ELBs present, ensure that ELBs have protocols specified from the 
# allows list and the Certificates are not empty
#
rule ensure_all_elbs_are_secure when %elbs !empty {
    %elbs.Properties {
        Protocol in %allowed_protocols
        Certificates !empty
    }
}

# 
# In addition to secure settings, ensure that ELBs are only private
#
rule ensure_elbs_are_internal_and_secure when %elbs !empty {
    ensure_all_elbs_are_secure
    %elbs.Properties.Scheme == 'internal'
}

ensure_elbs_are_internal_and_secure has a correlational dependency with ensure_all_elbs_are_secure. Below is an example CloudFormation template which conforms to the above rules.

Resources:
  ServiceLBPublicListener46709EAA:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      Scheme: internal
      Protocol: HTTPS
      Certificates:
        - CertificateArn: 'arn:aws:acm...'
  ServiceLBPublicListener4670GGG:
    Type: 'AWS::ElasticLoadBalancingV2::Listener'
    Properties:
      Scheme: internal
      Protocol: HTTPS
      Certificates:
        - CertificateArn: 'arn:aws:acm...'

Validating Multiple Rules against Multiple Data Files

Guard is purpose-built for policy definition and evaluation on structured JSON- and YAML- formatted data. For better maintainability of rules, rule authors can write rules into multiple files and section them however they see fit and still be able to validate multiple rule files against a data file or multiple data files. The cfn-guard validate command can take a directory of files for the --data and --rules options. More information can be found in the cfn-guard README.

cfn-guard validate --data /path/to/dataDirectory --rules /path/to/ruleDirectory

Where /path/to/dataDirectory has one or more data files and /path/to/ruleDirectory has one or more rules files.

Consider you are writing rules to check if various resources defined in multiple CloudFormation templates have appropriate property assignments which guarantees encryption at rest. For ease of searchability and maintainability you can decide to have rules for checking encryption at rest in each resource in separate files, e.g. s3_bucket_encryption.guard, ec2_volume_encryption.guard, rds_dbinstance_encryption.guard and so on all inside one directory, ~/GuardRules/encryption_at_rest. You have all your CloudFormation templates that you need to validate under ~/CloudFormation/templates. The command will be as follows:

cfn-guard validate --data ~/CloudFormation/templates --rules ~/GuardRules/encryption_at_rest

We are currently working on a feature which might offer rule reusability across multiple files. More details to come as we make progress on the feature.