Skip to main content

Use Folder Structure for Compliance Components

Date: 21 Mar 2022

Rejected!

The proposal in this ADR was rejected! For questions, please reach out to Cloud Posse.

Status

DECIDED

Problem

  • Too many files clutter the stack config folders

  • Files are very terse and seldom edited

  • So many files that code generation of YAML is the only practical way of managing them

Context

Considered Options

Option 1: Use Virtual Components

Define one file for each default region (17). Having multiple files is convenient for GitOps and detecting what files changed in order to trigger CI.

# stacks/catalog/compliance/ue1.yaml
components:
terraform:
compliance-ue1:
component: compliance
vars:
region: us-east-1
environment: ue1

The root component should follow the same pattern, with one file for each default region.

# stacks/catalog/compliance/root/ue1.yaml
components:
terraform:
compliance-root-ue1:
component: compliance-root
vars:
region: us-east-1
environment: ue1
caution

There’s one downside with this naming convention, is that the final stack names (e.g. in Spacelift) will look like acme-ue1-root-compliance-ue1, with the ue1 being repeated in the name because it is pulled from the component. This will be fixed in .

Define a baseline that imports all 17 default regions

# stacks/catalog/compliance/baseline.yaml
imports:
- catalog/compliance/ue1
- catalog/compliance/ue2
...

Repeat for the “root” baseline should import all 17 default regions

# stacks/catalog/compliance/root/baseline.yaml
imports:
- catalog/compliance/root/ue1
- catalog/compliance/root/ue2
...

Then in each account-level stack configuration, import the compliance baseline.

Here are some examples:

# stacks/globals.yaml
imports:
- catalog/compliance/baseline

# stacks/plat/prod.yaml
imports:
- globals

# stacks/plat/staging.yaml
imports:
- globals

# stacks/core/security.yaml
imports:
- globals

# stacks/core/dns.yaml
imports:
- globals

The root account is the only exception, which would look like this:

# stacks/core/root.yaml
imports:
- globals
- compliance/root/baseline

Option 2: Use YAML Separators

# mock stack config

stages:
- a
- b
- c

components:
terraform:
compliance:
vars:
region: us-east-1
environment: ue1
---
# mock stack config

stages:
- a
- b
- c

components:
terraform:
compliance:
vars:
environment: ue2
region: us-east-1
---
# mock stack config

stages:
- a
- b
- c

components:
terraform:
compliance:
vars:
region: us-west-1
environment: uw1

Option 2: Current Solution


###
### stack: stacks/mdev/euw3/mdw3-audit.yaml
### chain: stacks/mdev/euw3/mdw3-audit.yaml
###
import:
- mdev/mdev-globals
- euw3/euw3-audit
###
### stack: stacks/mdev/mdev-globals.yaml
### chain: stacks/mdev/euw3/mdw3-audit.yaml > stacks/mdev/mdev-globals.yaml
###

import:
- globals

vars:
tenant: mdev

terraform:
backend:
s3:
bucket: "vygr-mdev-use2-root-tfstate"
dynamodb_table: "vygr-mdev-use2-root-tfstate-lock"
role_arn: "arn:aws:iam::807952753552:role/vygr-mdev-gbl-root-terraform"
remote_state_backend:
s3:
bucket: "vygr-mdev-use2-root-tfstate"
dynamodb_table: "vygr-mdev-use2-root-tfstate-lock"
role_arn: "arn:aws:iam::807952753552:role/vygr-mdev-gbl-root-terraform"

settings:
spacelift:
worker_pool_name: vygr-mdev-use2-auto-spacelift-worker-pool
###
### stack: stacks/globals.yaml
### chain: stacks/mdev/euw3/mdw3-audit.yaml > stacks/mdev/mdev-globals.yaml > stacks/globals.yaml
###

vars:
namespace: vygr
required_tags:
- Team
- Service
tags:
# We set the default team here, this means everything will be tagged Team:sre unless otherwise specified.
# This is used because it is the default alerted team.
Team: sre

terraform:
vars:
label_order: ["namespace", "tenant", "environment", "stage", "name", "attributes"]
descriptor_formats:
stack:
format: "%v-%v-%v"
labels: ["tenant", "environment", "stage"]
# This is needed for the transit-gateway component
account_name:
format: "%v"
labels: ["stage"]

backend_type: s3 # s3, remote, vault, etc.
backend:
s3:
encrypt: true
key: "terraform.tfstate"
acl: "bucket-owner-full-control"
region: "us-east-2"

remote_state_backend_type: s3 # s3, remote, vault, etc.
remote_state_backend:
s3:
encrypt: true
key: "terraform.tfstate"
acl: "bucket-owner-full-control"
region: "us-east-2"

###
### stack: stacks/euw3/euw3-audit.yaml
### chain: stacks/mdev/euw3/mdw3-audit.yaml > stacks/euw3/euw3-audit.yaml
###

import:
- euw3/euw3-globals

vars:
stage: audit

terraform:
vars: {}

helmfile:
vars: {}

components:
terraform:
compliance:
settings:
spacelift:
workspace_enabled: false

aws-inspector:
settings:
spacelift:
workspace_enabled: false
###
### stack: stacks/euw3/euw3-globals.yaml
### chain: stacks/mdev/euw3/mdw3-audit.yaml > stacks/euw3/euw3-audit.yaml > stacks/euw3/euw3-globals.yaml
###

import:
- catalog/compliance/compliance
# - catalog/aws-inspector
# @TODO aws-inspector is not yet supported in Paris, it is likely this will change in the future
# https://docs.aws.amazon.com/inspector/v1/userguide/inspector_rules-arns.html

vars:
region: eu-west-3
environment: euw3

components:
terraform:
vpc:
vars:
availability_zones:
- "eu-west-3a"
- "eu-west-3b"
- "eu-west-3c"

eks/eks:
vars:
availability_zones:
- "eu-west-3a"
- "eu-west-3b"
- "eu-west-3c"
###
### stack: stacks/catalog/compliance/compliance.yaml
### chain: stacks/mdev/euw3/mdw3-audit.yaml > stacks/euw3/euw3-audit.yaml > stacks/euw3/euw3-globals.yaml > stacks/catalog/compliance/compliance.yaml
###

components:
terraform:
compliance:
settings:
spacelift:
workspace_enabled: true
vars:
enabled: true
tags:
Team: sre
Service: compliance
config_bucket_env: use2
config_bucket_stage: audit
config_rules_paths:
- https://raw.githubusercontent.com/cloudposse/terraform-aws-config/0.14.1/catalog/account.yaml
...
central_logging_account: audit
central_resource_collector_account: security
cloudtrail_bucket_stage: audit
cloudtrail_bucket_env: use2
create_iam_role: true
global_resource_collector_region: us-east-2
guardduty_admin_delegated: true
securityhub_admin_delegated: true
securityhub_create_sns_topic: true
securityhub_enabled_standards:
- ruleset/cis-aws-foundations-benchmark/v/1.2.0
securityhub_opsgenie_sns_topic_subscription_enabled: false # TODO, enable this once /opsgenie/opsgenie_securityhub_uri SSM param is set
securityhub_opsgenie_integration_uri_ssm_account: corp
securityhub_opsgenie_integration_uri_ssm_region: us-east-2
default_vpc_deletion_enabled: true
az_abbreviation_type: short

Decision

DECIDED: Use Option 1 with virtual components

Consequences

  • Refactor configurations to be more DRY

  • to avoid duplication of the environment in stack names

References