/terraform-aws-mcaf-avm

Terraform module providing an AWS Account Vending Machine (AVM)

Primary LanguageHCLApache License 2.0Apache-2.0

terraform-aws-mcaf-avm

Terraform module providing an AWS Account Vending Machine (AVM). This module provisions an AWS account using the "AWS Control Tower Account Factory" product in Service Catalog with one or more Terraform Cloud/Enterprise (TFE) workspaces backed by a VCS project.

Workspace authentication

This module provides three modes of workspace authentication:

  • Using the default values, this module will create an IAM user per workspace in the provisioned AWS account.
  • An IAM role using OpenID Connect integration with the AWS account can be used. This works for remote runners or with using self-hosted Terraform Cloud agents (agent version v1.7.0+).
  • An IAM role using an external ID to authenticate with the AWS account can be used in combination with using self-hosted Terraform Cloud agents.

Using one of the last 2 authentication methods is in line with authentication best practices to use IAM roles over IAM users with long-lived tokens.

IAM Roles with OIDC

To use IAM roles with OIDC for authentication:

  • Set var.tfe_workspace.auth_method or (auth_method if specifying additional workspaces) to iam_role_oidc

This will create an IAM role with a trust policy allowing the OIDC provider created as part of this module. The workspace will be configured to use OIDC by feeding the AWS provider with the right environment variables.

Using multiple configurations (e.g. provider aliases), is currently not supported. Use one of the other two authentication modes if this is a requirement.

IAM Roles

To use IAM roles for authentication:

  • Set var.tfe_workspace.agent_pool_id or (agent_pool_id if specifying additional workspaces) to the Terraform Cloud agent pool ID.
  • Set var.tfe_workspace.auth_method or (auth_method if specifying additional workspaces) to iam_role.
  • Set var.tfe_workspace.agent_role_arns or (agent_role_arns if specifying additional workspaces) to the IAM role assumed by the Terraform Cloud agents in the specified agent pool.

This will create an IAM role in the provisioned AWS account with a randomly generated external ID which can only be assumed by the Terraform Cloud agent role. The created role and external ID value are stored in the new workspace as Terraform variables which can be used to configure your AWS provider. Using the default workspace the created role will be called TPEPipelineRole, role names for additional workspaces will be calculated for you based on the workspace name but you can always set your own via the role_name variable (similarly you can set your own role name in the default workspace via var.tfe_workspace.role_name); but please be aware that each IAM role must have a unique name.

To use the created IAM role, use the following when configuring your AWS provider:

provider "aws" {
  assume_role {
    role_arn     = var.aws_assume_role
    external_id  = var.aws_assume_role_external_id
    session_name = "tfe-agent"
  }
}

Workspace team access

Team access can be configured per workspace using the team_access variable.

As the state is considered sensitive, we recommend the following custom role permissions which is similar to the pre-existing "write" permission but blocks read access to the state (viewing outputs is still allowed):

team_access = {
  "MyTeamName" = {
    permissions = {
      run_tasks         = false
      runs              = "apply"
      sentinel_mocks    = "read"
      state_versions    = "read-outputs"
      variables         = "write"
      workspace_locking = true
    }
  }
}

More complete usage information can be found in the underlying terraform-aws-mcaf-workspace module README.

Note: the team should already exist, this module will not create it for you.

AWS SSO Configuration

In the account variable, the SSO attributes (sso_email, sso_firstname and sso_lastname) will be used by AWS Service Catalog to provide initial access to the newly created account.

You should use the details from the AWS Control Tower Admin user.

How to use

Basic configuration

module "aws_account" {
  source = "github.com/schubergphilis/terraform-aws-mcaf-avm?ref=VERSION"

  name = "my-aws-account"
  tags = { Terraform = true }

  account = {
    email               = "my-aws-account@email.com"
    environment         = "prod"
    organizational_unit = "Production"
    sso_email           = "control-tower-admin@company.com"
  }

  tfe_workspace = {
    default_region        = "eu-west-1"
    repository_identifier = "myorg/myworkspacerepo"
    organization          = "myorg"
    vcs_oauth_token_id    = var.oauth_token_id
  }
}

Additional workspaces

module "aws_account" {
  source = "github.com/schubergphilis/terraform-aws-mcaf-avm?ref=VERSION"

  name = "my-aws-account"
  tags = { Terraform = true }

  account = {
    email               = "my-aws-account@email.com"
    environment         = "prod"
    organizational_unit = "Production"
    sso_email           = "control-tower-admin@company.com"
  }

  tfe_workspace = {
    default_region        = "eu-west-1"
    repository_identifier = "schubergphilis/terraform-aws-mcaf-avm"
    organization          = "schubergphilis"
    vcs_oauth_token_id    = var.oauth_token_id
  }

  additional_tfe_workspaces = {
    baseline-my-aws-account = {
      auto_apply            = true
      repository_identifier = "schubergphilis/terraform-aws-mcaf-account-baseline"
    }
  }
}

Only deploy additional workspaces

module "aws_account" {
  source = "github.com/schubergphilis/terraform-aws-mcaf-avm?ref=VERSION"

  create_default_workspace = false
  name                     = "my-aws-account"
  tags                     = { Terraform = true }

  account = {
    email               = "my-aws-account@email.com"
    environment         = "prod"
    organizational_unit = "Production"
    sso_email           = "control-tower-admin@company.com"
  }

  tfe_workspace = {
    default_region        = "eu-west-1"
    repository_identifier = "schubergphilis/terraform-aws-mcaf-avm"
    organization          = "schubergphilis"
    vcs_oauth_token_id    = var.oauth_token_id
  }

  additional_tfe_workspaces = {
    my-aws-account-subsystem1 = {
      working_directory = "terraform/subsystem1"
    }
    my-aws-account-subsystem2 = {
      working_directory = "terraform/subsystem2"
    }
  }
}

IAM Permissions Boundaries

The module supports setting a Permission Boundary on the workspace iam_user or iam_role by passing down permissions_boundaries.workspace_boundary, which needs to be referencing the path where the permissions boundary is stored in git and the name: permissions_boundaries.workspace_boundary_name. By setting var.tfe_workspace.add_permissions_boundary or var.additional_tfe_workspaces.add_permissions_boundary to true, the permissions boundary will be attached to that specific workspace user/role.

In case you want to reference a permission boundary that needs to be attached to every IAM role/user that will be created by the workspace role/user then you can create this permission boundary by specifying permissions_boundaries.workload_boundary which needs to be referencing the path where the permissions boundary is stored in git and the name: permissions_boundaries.workload_boundary_name.

module "aws_account" {
  source = "github.com/schubergphilis/terraform-aws-mcaf-avm?ref=VERSION"
  ...
  permissions_boundaries = {
    workspace_boundary      = "${path.module}/workspace_boundary.json"
    workspace_boundary_name = "workspace_boundary"
    workload_boundary       = "${path.module}/workload_boundary.json"
    workload_boundary_name  = "workload_boundary"
  }
  ...
}

Note: the workspace_boundary and workload_boundary can be templated files, account_id will be replaced by AVM by the account ID of the AWS account created.

Requirements

Name Version
terraform >= 1.3.0
aws >= 4.9.0
mcaf >= 0.4.2
tfe >= 0.51.0
tls >= 4.0.4

Providers

Name Version
aws.account >= 4.9.0
tls >= 4.0.4

Modules

Name Source Version
account github.com/schubergphilis/terraform-aws-mcaf-account v0.5.1
additional_tfe_workspaces github.com/schubergphilis/terraform-aws-mcaf-workspace v1.1.2
tfe_workspace github.com/schubergphilis/terraform-aws-mcaf-workspace v1.1.2

Resources

Name Type
aws_account_alternate_contact.billing resource
aws_account_alternate_contact.operations resource
aws_account_alternate_contact.security resource
aws_iam_account_alias.alias resource
aws_iam_openid_connect_provider.tfc_provider resource
aws_iam_policy.workload_boundary resource
aws_iam_policy.workspace_boundary resource
tls_certificate.oidc_certificate data source

Inputs

Name Description Type Default Required
account AWS account settings
object({
alias_prefix = optional(string, null)
contact_billing = optional(object({
email_address = string
name = string
phone_number = string
title = string
}), null)
contact_operations = optional(object({
email_address = string
name = string
phone_number = string
title = string
}), null)
contact_security = optional(object({
email_address = string
name = string
phone_number = string
title = string
}), null)
email = string
environment = optional(string, null)
organizational_unit = string
provisioned_product_name = optional(string, null)
sso_email = string
sso_firstname = optional(string, "AWS Control Tower")
sso_lastname = optional(string, "Admin")
})
n/a yes
name Name of the account and default TFE workspace string n/a yes
tfe_workspace TFE workspace settings
object({
add_permissions_boundary = optional(bool, false)
agent_pool_id = optional(string, null)
agent_role_arns = optional(list(string), null)
auth_method = optional(string, "iam_user")
auto_apply = optional(bool, false)
branch = optional(string, "main")
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
connect_vcs_repo = optional(bool, true)
default_region = string
execution_mode = optional(string, "remote")
file_triggers_enabled = optional(bool, true)
global_remote_state = optional(bool, false)
name = optional(string, null)
policy = optional(string, null)
policy_arns = optional(list(string), ["arn:aws:iam::aws:policy/AdministratorAccess"])
project_id = optional(string, null)
queue_all_runs = optional(bool, null)
remote_state_consumer_ids = optional(set(string))
repository_identifier = optional(string, null)
role_name = optional(string, "TFEPipeline")
sensitive_env_variables = optional(map(string), {})
sensitive_hcl_variables = optional(map(object({ sensitive = string })), {})
sensitive_terraform_variables = optional(map(string), {})
ssh_key_id = optional(string, null)
organization = string
terraform_version = optional(string, null)
trigger_prefixes = optional(list(string), ["modules"])
username = optional(string, "TFEPipeline")
vcs_oauth_token_id = string
working_directory = optional(string, null)
workspace_tags = optional(list(string), null)

notification_configuration = optional(list(object({
destination_type = string
enabled = optional(bool, true)
url = string
triggers = optional(list(string), [
"run:created",
"run:planning",
"run:needs_attention",
"run:applying",
"run:completed",
"run:errored",
])
})), [])

team_access = optional(map(object({
access = optional(string, null),
permissions = optional(object({
run_tasks = bool
runs = string
sentinel_mocks = string
state_versions = string
variables = string
workspace_locking = bool
}), null)
})), {})
})
n/a yes
additional_tfe_workspaces Additional TFE workspaces
map(object({
add_permissions_boundary = optional(bool, false)
agent_pool_id = optional(string, null)
agent_role_arns = optional(list(string), null)
auth_method = optional(string, null)
auto_apply = optional(bool, false)
branch = optional(string, null)
clear_text_env_variables = optional(map(string), {})
clear_text_hcl_variables = optional(map(string), {})
clear_text_terraform_variables = optional(map(string), {})
connect_vcs_repo = optional(bool, true)
default_region = optional(string, null)
execution_mode = optional(string, null)
file_triggers_enabled = optional(bool, true)
global_remote_state = optional(bool, false)
name = optional(string, null)
policy = optional(string, null)
policy_arns = optional(list(string), ["arn:aws:iam::aws:policy/AdministratorAccess"])
project_id = optional(string, null)
queue_all_runs = optional(bool, null)
remote_state_consumer_ids = optional(set(string))
repository_identifier = optional(string, null)
role_name = optional(string, null)
sensitive_env_variables = optional(map(string), {})
sensitive_hcl_variables = optional(map(object({ sensitive = string })), {})
sensitive_terraform_variables = optional(map(string), {})
ssh_key_id = optional(string, null)
terraform_version = optional(string, null)
trigger_prefixes = optional(list(string), null)
username = optional(string, null)
vcs_oauth_token_id = optional(string, null)
working_directory = optional(string, null)
workspace_tags = optional(list(string), null)

notification_configuration = optional(list(object({
destination_type = string
enabled = optional(bool, true)
url = string
triggers = optional(list(string), [
"run:created",
"run:planning",
"run:needs_attention",
"run:applying",
"run:completed",
"run:errored",
])
})), [])

team_access = optional(map(object({
access = optional(string, null),
permissions = optional(object({
run_tasks = bool
runs = string
sentinel_mocks = string
state_versions = string
variables = string
workspace_locking = bool
}), null)
})), {})
}))
{} no
create_default_workspace Set to false to skip creating default workspace bool true no
path Optional path for all IAM users, user groups, roles, and customer managed policies created by this module string "/" no
permissions_boundaries n/a
object({
workspace_boundary = optional(string, null)
workspace_boundary_name = optional(string, null)
workload_boundary = optional(string, null)
workload_boundary_name = optional(string, null)
})
{} no
tags A map of tags to assign to all resources map(string) {} no

Outputs

Name Description
additional_tfe_workspace Map of any additional Terraform Cloud workspace names and IDs
environment The environment name
id The AWS account ID
name The AWS account name
repository_identifier The repository identifier if one is specified
tfe_workspace_id Workspace ID of default workspace ID when create_default_workspace is true
tfe_workspaces List of Terraform Cloud workspaces
workload_permissions_boundary_arn The ARN of the workload permissions boundary