Skip to content

Latest commit

 

History

293 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Appvia Banner

Terraform Registry Latest Release Slack Community Contributors

Github Actions

Description

This Terraform module provisions the baseline infrastructure and access controls for an AWS Landing Zone environment. It establishes secure pipeline access patterns using OIDC authentication, implements IAM permission boundaries, and configures security monitoring through CIS CloudWatch alarms.

Key Features

  • OIDC-based Pipeline Access: Creates IAM roles with OIDC authentication for secure CI/CD pipeline access to AWS resources
  • IAM Permission Boundaries: Enforces security guardrails on pipeline roles in both Management and Audit accounts
  • CIS CloudWatch Alarms: Monitors and alerts on security events based on CIS AWS Foundations Benchmark
  • Multi-channel Notifications: Supports email, Slack, and Microsoft Teams notifications for security alerts
  • Breakglass Access: Optional emergency access users with MFA enforcement
  • AWS Support Integration: Deploys AWS Support role across all accounts via CloudFormation StackSets
  • Cost Management: Dedicated IAM policies and roles for cost visibility and management

Supported Pipeline Types

The module supports creating OIDC roles for the following pipeline types:

  • Accelerator: Manages landing zone configuration and deployment
  • Accounts: Provisions and manages AWS accounts via Organizations
  • Bootstrap: Handles initial landing zone setup
  • Compliance: Manages security and compliance configurations
  • Cost Management: Controls costs and budgets
  • Identity: Manages IAM Identity Center (formerly AWS SSO)
  • Organizations: Configures AWS Organizations structure

Usage

Basic Example

module "landing_zone" {
  source  = "appvia/cloudaccess-lza/aws"
  version = "0.0.1"

  aws_accounts = {
    audit_account_id = "123456789012"
  }

  repositories = {
    accelerator = {
      url = "https://github.com/your-org/aws-accelerator-config"
    }
    identity = {
      url = "https://github.com/your-org/terraform-aws-identity"
    }
  }

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }

  providers = {
    aws.audit      = aws.audit
    aws.management = aws.management
  }
}

Complete Example with All Features

module "landing_zone" {
  source  = "appvia/cloudaccess-lza/aws"
  version = "0.0.1"

  aws_accounts = {
    audit_account_id = "123456789012"
  }

  enable_aws_support = true
  enable_breakglass  = true
  enable_cis_alarms  = true
  breakglass_users   = 2

  notifications = {
    email = {
      addresses = ["security@example.com", "ops@example.com"]
    }
    slack = {
      webhook_url = "https://hooks.slack.com/services/YOUR/WEBHOOK/URL"
    }
  }

  repositories = {
    accelerator = {
      url       = "https://github.com/your-org/aws-accelerator-config"
      role_name = "lz-aws-accelerator"
      shared    = ["https://github.com/your-org/shared-repo"]
    }
    identity = {
      url       = "https://github.com/your-org/terraform-aws-identity"
      role_name = "lz-aws-identity"
    }
    compliance = {
      url = "https://github.com/your-org/aws-compliance"
    }
    cost_management = {
      url = "https://github.com/your-org/aws-cost-management"
    }
  }

  tags = {
    Environment = "production"
    ManagedBy   = "terraform"
  }

  providers = {
    aws.audit      = aws.audit
    aws.management = aws.management
  }
}

CIS Alarms & Notifications

This module implements CIS AWS Foundations Benchmark security monitoring using CloudWatch alarms. The alarms monitor CloudTrail logs for security-relevant events and send notifications through your configured channels.

Enabling CIS Alarms

CIS alarms are enabled by default (enable_cis_alarms = true). They monitor the AWS Control Tower organizational CloudTrail trail by default.

Email Notifications

Configure email notifications to receive alerts:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_cis_alarms = true

  notifications = {
    email = {
      addresses = ["security@example.com", "compliance@example.com"]
    }
  }

  # ... other configuration
}

Slack Notifications

Configure Slack notifications using an Incoming Webhook:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_cis_alarms = true

  notifications = {
    slack = {
      webhook_url = "https://hooks.slack.com/services/T00000000/B00000000/XXXXXXXXXXXXXXXXXXXX"
    }
  }

  # ... other configuration
}

Microsoft Teams Notifications

Configure Microsoft Teams notifications using an Incoming Webhook:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_cis_alarms = true

  notifications = {
    teams = {
      webhook_url = "https://outlook.office.com/webhook/..."
    }
  }

  # ... other configuration
}

Multiple Notification Channels

You can configure multiple notification channels simultaneously:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_cis_alarms = true

  notifications = {
    email = {
      addresses = ["security@example.com"]
    }
    slack = {
      webhook_url = "https://hooks.slack.com/services/..."
    }
    teams = {
      webhook_url = "https://outlook.office.com/webhook/..."
    }
  }

  # ... other configuration
}

Custom CloudWatch Log Group

By default, the module uses the AWS Control Tower organizational trail log group (aws-controltower/CloudTrailLogs). You can specify a different log group:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_cis_alarms             = true
  organization_log_group_name   = "my-custom-cloudtrail-logs"

  # ... other configuration
}

Excluding Services from Unauthorized API Call Alerts

Some AWS services may generate false positives for unauthorized API call alerts. You can exclude additional services:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_cis_alarms = true
  unauthorized_api_calls_extra_excluded_services = [
    "s3.amazonaws.com",
    "dynamodb.amazonaws.com"
  ]

  # ... other configuration
}

Breakglass Users

The module can provision emergency "breakglass" IAM users with administrator access to the management account. These users are intended for emergency access when normal authentication methods fail.

Features

  • MFA Enforcement: Breakglass users must use MFA for all operations (except MFA device management)
  • Administrator Access: Full administrative privileges when authenticated with MFA
  • Configurable Count: Create multiple breakglass users (default: 2)

Enabling Breakglass Users

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_breakglass = true
  breakglass_users  = 2  # Creates lza-breakglass0 and lza-breakglass1

  # ... other configuration
}

Security Note: After deployment, you must manually:

  1. Configure MFA devices for each breakglass user
  2. Securely store credentials in a separate system (e.g., password manager, vault)
  3. Regularly rotate credentials according to your security policy

AWS Support Role

The module can deploy the AWS Support Access role across all accounts in your organization using CloudFormation StackSets.

Enabling AWS Support

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  enable_aws_support     = true
  aws_support_role_name  = "AWSSupportAccessRole"  # Default value
  aws_support_stack_name = "lz-aws-support-role"   # Default value

  # ... other configuration
}

This creates:

  • A CloudFormation StackSet in the management account
  • StackSet instances deployed to all accounts in the organization root
  • An IAM role in each account allowing AWS Support to access resources

Permission Boundaries

The module creates IAM permission boundaries to enforce security guardrails on pipeline roles:

Default Permissions Boundary

Applied to all pipeline roles (except cost management). This boundary:

  • Allows full administrative access
  • Denies modification of the boundary policy itself
  • Protects Terraform remote state S3 buckets from deletion
  • Protects Terraform state lock DynamoDB tables from deletion
module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  default_permissions_boundary_name = "lz-base-default-boundary"  # Default value

  # ... other configuration
}

Cost Management Boundary

A separate boundary for cost management roles with restricted permissions:

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  costs_boundary_name = "lz-costs-boundary"  # Default value

  # ... other configuration
}

Repository Configuration

Each repository configuration supports the following options:

repositories = {
  <pipeline_type> = {
    # Required: Git repository URL (must be HTTPS)
    url = "https://github.com/your-org/repo-name"

    # Optional: IAM role name (defaults vary by pipeline type)
    role_name = "custom-role-name"

    # Optional: Additional repositories that can assume this role
    shared = [
      "https://github.com/your-org/shared-repo-1",
      "https://github.com/your-org/shared-repo-2"
    ]

    # Optional: Additional read-only IAM policy statements
    additional_read_permissions = {
      CustomReadPolicy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Effect   = "Allow"
            Action   = ["s3:GetObject", "s3:ListBucket"]
            Resource = "*"
          }
        ]
      })
    }

    # Optional: Additional read-write IAM policy statements
    additional_write_permissions = {
      CustomWritePolicy = jsonencode({
        Version = "2012-10-17"
        Statement = [
          {
            Effect   = "Allow"
            Action   = ["dynamodb:PutItem", "dynamodb:DeleteItem"]
            Resource = "*"
          }
        ]
      })
    }
  }
}

Provider Configuration

This module requires two AWS provider configurations:

provider "aws" {
  alias  = "management"
  region = "us-east-1"
  # Configure for your management account
}

provider "aws" {
  alias  = "audit"
  region = "us-east-1"
  # Configure for your audit/security account
  assume_role {
    role_arn = "arn:aws:iam::123456789012:role/OrganizationAccountAccessRole"
  }
}

module "landing_zone" {
  source = "appvia/cloudaccess-lza/aws"

  # ... configuration

  providers = {
    aws.audit      = aws.audit
    aws.management = aws.management
  }
}

Update Documentation

The terraform-docs utility is used to generate this README. Follow the below steps to update:

  1. Make changes to the .terraform-docs.yml file
  2. Fetch the terraform-docs binary (https://terraform-docs.io/user-guide/installation/)
  3. Run terraform-docs markdown table --output-file ${PWD}/README.md --output-mode inject .

Providers

Name Version
aws >= 6.0.0
aws.audit >= 6.0.0
aws.management >= 6.0.0

Inputs

Name Description Type Default Required
aws_accounts Map of AWS account names to their account IDs
object({
audit_account_id = string
})
n/a yes
aws_support_role_name Name of the AWS Support role string "AWSSupportAccessRole" no
aws_support_stack_name Name of the stackset used to deploy the aws support role string "lz-aws-support-role" no
azuredevops_organization_id The Azure DevOps organization ID (GUID, found under Organization Settings) used to build the OIDC issuer URL (https://vstoken.dev.azure.com/<organization_id>). Required when common_provider is 'azuredevops'. Repository values in var.repositories must then be formatted as '//', and the read-only role for each repository requires a distinct '-ro' suffixed service connection. string null no
breakglass_users The number of breakglass users to create number 2 no
common_provider The CI/CD identity provider used as the OIDC trust for all roles provisioned by this module string "github" no
costs_boundary_name Name of the IAM policy to use as a permissions boundary for cost-related roles string "lz-costs-boundary" no
default_permissions_boundary_name Name of the default IAM policy used by roles we provision string "lz-base-default-boundary" no
enable_aws_support Indicates if we should enable AWS Support role bool true no
enable_breakglass Indicates if we should enable breakglass users and group bool false no
enable_cis_alarms Indicates if we should enable CIS alerts bool true no
notifications Configuration for the notifications
object({
# The name of the Lambda function to use for notifications
lambda_name = optional(string, "lz-ca-notifications-slack")
# A list of email addresses to send notifications to
email = optional(object({
# A list of email addresses to send notifications to
addresses = list(string)
}), null)
# The configuration for Slack notifications
slack = optional(object({
# The webhook URL for Slack notifications
webhook_url = string
}), null)
# The configuration for Microsoft Teams notifications
teams = optional(object({
# The webhook URL for Microsoft Teams notifications
webhook_url = string
}), null)
})
{
"email": {
"addresses": []
},
"lambda_name": "lz-ca-notifications-slack",
"slack": null,
"teams": null
}
no
organization_log_group_name The name of the CloudWatch log group for the AWS Organization. If not provided, will use the default log group. string "aws-controltower/CloudTrailLogs" no
repositories List of repository locations for the pipelines. Each 'url' field should be a Git repository URL when common_provider is 'github' or 'gitlab', or '//' when common_provider is 'azuredevops'.
object({
accelerator = optional(object({
# The URL for the repository containing the accelerator pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-accelerator")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
accounts = optional(object({
# The URL for the repository containing the accounts pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-accounts")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
bootstrap = optional(object({
# The URL for the repository containing the bootstrap pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-bootstrap")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
compliance = optional(object({
# The URL for the repository containing the compliance pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-compliance")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
cost_management = optional(object({
# The URL for the repository containing the cost management pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-cost-management")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
identity = optional(object({
# The URL for the repository containing the identity pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-identity")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
organizations = optional(object({
# The URL for the repository containing the organizations pipeline code. This should be a Git repository URL.
url = string
# The name of the IAM role to use when accessing the permissions
role_name = optional(string, "lz-aws-organizations")
# A list of other repositories whom should have access to the terraform state
shared = optional(list(string), [])
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_read_permissions = optional(map(string), {})
# A map of additional permissions (in the form of IAM policy actions) that should be added to the role
additional_write_permissions = optional(map(string), {})
}), null)
})
{} no
tags Tags to apply to all resources map(string) {} no
unauthorized_api_calls_extra_excluded_services Optional list of additional AWS services to exclude from unauthorized API call metric filter. list(string) [] no

Outputs

No outputs.

About

Terraform module used to provision resources and access for a landing zone

Topics

Resources

Code of conduct

Security policy

Stars

0 stars

Watchers

5 watching

Forks

Releases

Used by

Contributors

Languages