Skip to content

Commit 8dec0e2

Browse files
author
AWS
committed
Release: 1.19.0
1 parent 5f3d1f7 commit 8dec0e2

31 files changed

Lines changed: 665 additions & 107 deletions

File tree

README.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,25 @@ Now that you have configured and deployed AWS Control Tower Account Factory for
5757
## Collection of Operational Metrics
5858
As of version 1.6.0, AFT collects anonymous operational metrics to help AWS improve the quality and features of the solution. For more information, including how to disable this capability, please see the [documentation here](https://docs.aws.amazon.com/controltower/latest/userguide/aft-operational-metrics.html).
5959

60+
## Security Considerations
61+
62+
### HCP Terraform / Terraform Enterprise OIDC Workspace Governance
63+
64+
When you enable the HCP Terraform or Terraform Enterprise OIDC integration (`terraform_oidc_integration = true`), AFT configures the `AWSAFTAdmin` IAM role with a trust policy that allows any workspace within your configured TFC/TFE organization and project to assume the role via OIDC. The trust policy uses a `sub` claim condition scoped to your organization, project, and audience, but uses a wildcard (`workspace:*`) for the workspace name.
65+
66+
**Existing mitigations:**
67+
- The trust policy is scoped to your specific TFC/TFE **organization**, **project**, and **audience** — only workspaces within the configured project can obtain credentials.
68+
- The `AWSAFTAdmin` role is limited to `sts:AssumeRole` permissions only (to assume `AWSAFTExecution` and `AWSAFTService` roles). It has no direct permissions to access or modify AWS resources.
69+
70+
**Customer responsibility:**
71+
72+
> **Important:** Workspace governance within your configured TFC/TFE project is your responsibility. Any workspace created within the project specified by `terraform_project_name` can assume the `AWSAFTAdmin` role via OIDC. You should take the following steps to secure your environment:
73+
74+
- **Restrict workspace creation:** Limit who can create new workspaces within the TFC/TFE project used by AFT. Use [TFC/TFE team permissions](https://developer.hashicorp.com/terraform/cloud-docs/users-teams-organizations/permissions) to control workspace management access.
75+
- **Audit workspace access:** Regularly review the workspaces in your AFT project to ensure only authorized workspaces exist.
76+
- **Use a dedicated project:** Use a dedicated TFC/TFE project exclusively for AFT workspaces. Avoid sharing the project with unrelated workspaces.
77+
- **Consider workspace-specific scoping (optional):** For additional security, after deployment you can manually modify the `AWSAFTAdmin` trust policy to replace the `workspace:*` wildcard with explicit workspace names (e.g., `workspace:my-aft-workspace`). Note that this customization must be maintained outside of AFT and re-applied after AFT updates.
78+
6079

6180
<!-- BEGIN_TF_DOCS -->
6281
## Requirements
@@ -145,6 +164,9 @@ As of version 1.6.0, AFT collects anonymous operational metrics to help AWS impr
145164
| <a name="input_tags"></a> [tags](#input\_tags) | Map of tags to apply to resources deployed by AFT. | `map(any)` | `null` | no |
146165
| <a name="input_terraform_api_endpoint"></a> [terraform\_api\_endpoint](#input\_terraform\_api\_endpoint) | API Endpoint for Terraform. Must be in the format of https://xxx.xxx. | `string` | `"https://app.terraform.io/api/v2/"` | no |
147166
| <a name="input_terraform_distribution"></a> [terraform\_distribution](#input\_terraform\_distribution) | Terraform distribution being used for AFT - valid values are oss, tfc, or tfe | `string` | `"oss"` | no |
167+
| <a name="input_terraform_oidc_aws_audience"></a> [terraform\_oidc\_aws\_audience](#input\_terraform\_oidc\_aws\_audience) | The audience value to use in run identity tokens for HCP dynamic credentials (OIDC). var.aft\_feature\_hcp\_oidc must be set to true to enable OIDC. | `string` | `"aws.workload.identity"` | no |
168+
| <a name="input_terraform_oidc_hostname"></a> [terraform\_oidc\_hostname](#input\_terraform\_oidc\_hostname) | The hostname of the TFC or TFE instance to use with AWS when configuring dynamic credentials (OIDC). var.aft\_feature\_hcp\_oidc must be set to true to enable OIDC. | `string` | `"app.terraform.io"` | no |
169+
| <a name="input_terraform_oidc_integration"></a> [terraform\_oidc\_integration](#input\_terraform\_oidc\_integration) | Enable HCP Terraform’s native OpenID Connect integration with AWS to get dynamic credentials for the AWS provider in your HCP Terraform runs | `bool` | `false` | no |
148170
| <a name="input_terraform_org_name"></a> [terraform\_org\_name](#input\_terraform\_org\_name) | Organization name for Terraform Cloud or Enterprise | `string` | `"null"` | no |
149171
| <a name="input_terraform_project_name"></a> [terraform\_project\_name](#input\_terraform\_project\_name) | Project name for Terraform Cloud or Enterprise - project must exist before deployment | `string` | `"Default Project"` | no |
150172
| <a name="input_terraform_token"></a> [terraform\_token](#input\_terraform\_token) | Terraform token for Cloud or Enterprise | `string` | `"null"` | no |

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1.18.1
1+
1.19.0

examples/bitbucket+tf_enterprise/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ module "aft" {
2121
terraform_api_endpoint = "https://terraform.example.com/api/v2/"
2222
terraform_token = "EXAMPLE-uoc1c1qsw7poexampleewjeno1pte3rw"
2323
terraform_org_name = "ExampleOrg"
24+
# Enable OIDC
25+
terraform_oidc_integration = true
2426
}

examples/githubenterprise+tf_cloud/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ module "aft" {
2121
terraform_distribution = "tfc"
2222
terraform_token = "EXAMPLE-uoc1c1qsw7poexampleewjeno1pte3rw"
2323
terraform_org_name = "ExampleOrg"
24+
# Enable OIDC
25+
terraform_oidc_integration = true
2426
}

examples/gitlabselfmanaged+tf_cloud/main.tf

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,6 @@ module "aft" {
2121
terraform_distribution = "tfc"
2222
terraform_token = "EXAMPLE-uoc1c1qsw7poexampleewjeno1pte3rw"
2323
terraform_org_name = "ExampleOrg"
24+
# Enable OIDC
25+
terraform_oidc_integration = true
2426
}

main.tf

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,13 @@ module "aft_iam_roles" {
190190
aws.log_archive = aws.log_archive
191191
aws.aft_management = aws.aft_management
192192
}
193+
194+
terraform_oidc_integration = var.terraform_oidc_integration
195+
terraform_oidc_aws_audience = var.terraform_oidc_aws_audience
196+
terraform_oidc_hostname = var.terraform_oidc_hostname
197+
terraform_org_name = var.terraform_org_name
198+
terraform_distribution = var.terraform_distribution
199+
terraform_project_name = var.terraform_project_name
193200
}
194201

195202
module "aft_lambda_layer" {
@@ -274,6 +281,8 @@ module "aft_ssm_parameters" {
274281
terraform_version = var.terraform_version
275282
terraform_org_name = var.terraform_org_name
276283
terraform_project_name = var.terraform_project_name
284+
terraform_oidc_integration = var.terraform_oidc_integration
285+
terraform_oidc_aws_audience = var.terraform_oidc_aws_audience
277286
aft_feature_cloudtrail_data_events = var.aft_feature_cloudtrail_data_events
278287
aft_feature_enterprise_support = var.aft_feature_enterprise_support
279288
aft_feature_delete_default_vpcs_enabled = var.aft_feature_delete_default_vpcs_enabled

modules/aft-backend/main.tf

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ data "aws_caller_identity" "current" {
55
provider = aws.primary_region
66
}
77

8+
data "aws_partition" "current" {}
9+
810
# S3 Resources
911
#tfsec:ignore:aws-s3-enable-bucket-logging
1012
resource "aws_s3_bucket" "primary-backend-bucket" {
@@ -304,6 +306,37 @@ resource "aws_s3_bucket_versioning" "aft_access_logs_primary_backend_bucket" {
304306
resource "aws_kms_key" "aft_access_logs_primary_backend_bucket" {
305307
provider = aws.primary_region
306308
enable_key_rotation = true
309+
310+
policy = jsonencode({
311+
Version = "2012-10-17"
312+
Statement = [
313+
{
314+
Sid = "EnableRootAccountAccess"
315+
Effect = "Allow"
316+
Principal = { AWS = "arn:${data.aws_partition.current.partition}:iam::${var.aft_management_account_id}:root" }
317+
Action = "kms:*"
318+
Resource = "*"
319+
},
320+
{
321+
Sid = "AllowS3LoggingServiceAccess"
322+
Effect = "Allow"
323+
Principal = { Service = "logging.s3.amazonaws.com" }
324+
Action = [
325+
"kms:GenerateDataKey",
326+
"kms:Encrypt"
327+
]
328+
Resource = "*"
329+
Condition = {
330+
ArnLike = {
331+
"aws:SourceArn" = aws_s3_bucket.primary-backend-bucket.arn
332+
}
333+
StringEquals = {
334+
"aws:SourceAccount" = var.aft_management_account_id
335+
}
336+
}
337+
}
338+
]
339+
})
307340
}
308341

309342
resource "aws_s3_bucket_server_side_encryption_configuration" "aft_access_logs_primary_backend_bucket" {

modules/aft-code-repositories/buildspecs/ct-aft-account-provisioning-customizations.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ phases:
7777
TF_PROJECT_NAME=$(aws ssm get-parameter --name "/aft/config/terraform/project-name" --query "Parameter.Value" --output text)
7878
TF_WORKSPACE_NAME="ct-aft-account-provisioning-customizations"
7979
TF_CONFIG_PATH="./temp_configuration_file.tar.gz"
80+
TF_HCP_OIDC_ENABLED=$(aws ssm get-parameter --name /aft/config/terraform/oidc-integration | jq --raw-output ".Parameter.Value")
81+
TF_HCP_OIDC_AUDIENCE=$(aws ssm get-parameter --name /aft/config/terraform/oidc-aws-audience | jq --raw-output ".Parameter.Value")
82+
8083
cd $DEFAULT_PATH/terraform
8184
if ls *.jinja >/dev/null 2>&1; then
8285
for f in *.jinja; do
@@ -90,7 +93,19 @@ phases:
9093
for f in *.tf; do echo "\n \n"; echo $f; cat $f; done
9194
cd $DEFAULT_PATH
9295
tar -czf temp_configuration_file.tar.gz -C terraform --exclude .git --exclude venv .
93-
python3 $DEFAULT_PATH/aws-aft-core-framework/sources/scripts/workspace_manager.py --operation "deploy" --organization_name $TF_ORG_NAME --workspace_name $TF_WORKSPACE_NAME --assume_role_arn $AFT_ADMIN_ROLE_ARN --assume_role_session_name $ROLE_SESSION_NAME --api_endpoint $TF_ENDPOINT --api_token $TF_TOKEN --terraform_version $TF_VERSION --config_file $TF_CONFIG_PATH --project_name "$TF_PROJECT_NAME"
96+
python3 $DEFAULT_PATH/aws-aft-core-framework/sources/scripts/workspace_manager.py \
97+
--operation "deploy" \
98+
--organization_name $TF_ORG_NAME \
99+
--workspace_name $TF_WORKSPACE_NAME \
100+
--assume_role_arn $AFT_ADMIN_ROLE_ARN \
101+
--assume_role_session_name $ROLE_SESSION_NAME \
102+
--api_endpoint $TF_ENDPOINT \
103+
--api_token $TF_TOKEN \
104+
--terraform_version $TF_VERSION \
105+
--config_file $TF_CONFIG_PATH \
106+
--project_name "$TF_PROJECT_NAME" \
107+
--hcp_oidc_enabled "$TF_HCP_OIDC_ENABLED" \
108+
--hcp_oidc_audience "$TF_HCP_OIDC_AUDIENCE"
94109
fi
95110
96111
build:

modules/aft-code-repositories/buildspecs/ct-aft-account-request.yml

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,9 @@ phases:
7676
TF_PROJECT_NAME=$(aws ssm get-parameter --name "/aft/config/terraform/project-name" --query "Parameter.Value" --output text)
7777
TF_WORKSPACE_NAME="ct-aft-account-request"
7878
TF_CONFIG_PATH="./temp_configuration_file.tar.gz"
79+
TF_HCP_OIDC_ENABLED=$(aws ssm get-parameter --name /aft/config/terraform/oidc-integration | jq --raw-output ".Parameter.Value")
80+
TF_HCP_OIDC_AUDIENCE=$(aws ssm get-parameter --name /aft/config/terraform/oidc-aws-audience | jq --raw-output ".Parameter.Value")
81+
7982
cd $DEFAULT_PATH/terraform
8083
if ls *.jinja >/dev/null 2>&1; then
8184
for f in *.jinja; do
@@ -89,7 +92,19 @@ phases:
8992
for f in *.tf; do echo "\n \n"; echo $f; cat $f; done
9093
cd $DEFAULT_PATH
9194
tar -czf temp_configuration_file.tar.gz -C terraform --exclude .git --exclude venv .
92-
python3 $DEFAULT_PATH/aws-aft-core-framework/sources/scripts/workspace_manager.py --operation "deploy" --organization_name $TF_ORG_NAME --workspace_name $TF_WORKSPACE_NAME --assume_role_arn $AFT_ADMIN_ROLE_ARN --assume_role_session_name $ROLE_SESSION_NAME --api_endpoint $TF_ENDPOINT --api_token $TF_TOKEN --terraform_version $TF_VERSION --config_file $TF_CONFIG_PATH --project_name "$TF_PROJECT_NAME"
95+
python3 $DEFAULT_PATH/aws-aft-core-framework/sources/scripts/workspace_manager.py \
96+
--operation "deploy" \
97+
--organization_name $TF_ORG_NAME \
98+
--workspace_name $TF_WORKSPACE_NAME \
99+
--assume_role_arn $AFT_ADMIN_ROLE_ARN \
100+
--assume_role_session_name $ROLE_SESSION_NAME \
101+
--api_endpoint $TF_ENDPOINT \
102+
--api_token $TF_TOKEN \
103+
--terraform_version $TF_VERSION \
104+
--config_file $TF_CONFIG_PATH \
105+
--project_name "$TF_PROJECT_NAME" \
106+
--hcp_oidc_enabled "$TF_HCP_OIDC_ENABLED" \
107+
--hcp_oidc_audience "$TF_HCP_OIDC_AUDIENCE"
93108
fi
94109
95110
build:

modules/aft-customizations/buildspecs/aft-account-customizations-terraform.yml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ phases:
134134
TF_PROJECT_NAME=$(aws ssm get-parameter --name "/aft/config/terraform/project-name" --query "Parameter.Value" --output text)
135135
TF_WORKSPACE_NAME=$VENDED_ACCOUNT_ID-aft-account-customizations
136136
TF_CONFIG_PATH="./temp_configuration_file.tar.gz"
137+
TF_HCP_OIDC_ENABLED=$(aws ssm get-parameter --name /aft/config/terraform/oidc-integration | jq --raw-output ".Parameter.Value")
138+
TF_HCP_OIDC_AUDIENCE=$(aws ssm get-parameter --name /aft/config/terraform/oidc-aws-audience | jq --raw-output ".Parameter.Value")
137139
138140
cd $DEFAULT_PATH/$CUSTOMIZATION/terraform
139141
if ls *.jinja >/dev/null 2>&1; then
@@ -150,7 +152,19 @@ phases:
150152
151153
cd $DEFAULT_PATH/$CUSTOMIZATION
152154
tar -czf temp_configuration_file.tar.gz -C terraform --exclude .git --exclude venv .
153-
python3 $DEFAULT_PATH/aws-aft-core-framework/sources/scripts/workspace_manager.py --operation "deploy" --organization_name $TF_ORG_NAME --workspace_name $TF_WORKSPACE_NAME --assume_role_arn $AFT_ADMIN_ROLE_ARN --assume_role_session_name $ROLE_SESSION_NAME --api_endpoint $TF_ENDPOINT --api_token $TF_TOKEN --terraform_version $TF_VERSION --config_file $TF_CONFIG_PATH --project_name "$TF_PROJECT_NAME"
155+
python3 $DEFAULT_PATH/aws-aft-core-framework/sources/scripts/workspace_manager.py \
156+
--operation "deploy" \
157+
--organization_name $TF_ORG_NAME \
158+
--workspace_name $TF_WORKSPACE_NAME \
159+
--assume_role_arn $AFT_ADMIN_ROLE_ARN \
160+
--assume_role_session_name $ROLE_SESSION_NAME \
161+
--api_endpoint $TF_ENDPOINT \
162+
--api_token $TF_TOKEN \
163+
--terraform_version $TF_VERSION \
164+
--config_file $TF_CONFIG_PATH \
165+
--project_name "$TF_PROJECT_NAME" \
166+
--hcp_oidc_enabled "$TF_HCP_OIDC_ENABLED" \
167+
--hcp_oidc_audience "$TF_HCP_OIDC_AUDIENCE"
154168
fi
155169
fi
156170
post_build:

0 commit comments

Comments
 (0)