shoc-backend/terraform/bootstrap/iam.tf
Adam Moussa 25c2e84e8f feat(terraform): adopt live deployment roles safely
Add import-only dev and staging state with least-privilege HCP authentication and plan safety guards.
2026-08-28 19:12:34 -04:00

228 lines
6.1 KiB
HCL

# Account-global HCP Terraform OIDC provider. An account may hold exactly one
# provider per URL. Default is data-source because the frontend stack owns it.
resource "aws_iam_openid_connect_provider" "terraform_cloud" {
count = var.create_tfc_oidc_provider ? 1 : 0
url = "https://app.terraform.io"
client_id_list = ["aws.workload.identity"]
thumbprint_list = ["9e99a48a9960b14926bb7f3b02e22da2b0ab7280"]
lifecycle {
prevent_destroy = true
}
}
data "aws_iam_policy_document" "hcp_plan_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [local.tfc_oidc_arn]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:aud"
values = ["aws.workload.identity"]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:sub"
values = ["${local.hcp_sub_prefix}:plan"]
}
}
}
data "aws_iam_policy_document" "hcp_apply_assume" {
statement {
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = [local.tfc_oidc_arn]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:aud"
values = ["aws.workload.identity"]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:sub"
values = ["${local.hcp_sub_prefix}:apply"]
}
}
}
data "aws_iam_policy_document" "hcp_refresh" {
statement {
sid = "RefreshManagedIam"
effect = "Allow"
actions = [
"iam:GetInstanceProfile",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListInstanceProfilesForRole",
"iam:ListRolePolicies",
"iam:ListRoleTags",
]
resources = [
local.github_deploy_role_arn,
local.eb_ec2_role_arn,
local.eb_service_role_arn,
"arn:aws:iam::${local.account_id}:instance-profile/tf-managed/${var.eb_ec2_role_name}",
]
}
statement {
sid = "ListOidcProviders"
effect = "Allow"
actions = ["iam:ListOpenIDConnectProviders"]
resources = ["*"]
}
statement {
sid = "ReadGithubOidcProvider"
effect = "Allow"
actions = ["iam:GetOpenIDConnectProvider"]
resources = [local.github_oidc_arn]
}
statement {
sid = "RefreshRds"
effect = "Allow"
actions = [
"rds:DescribeDBInstances",
"rds:DescribeDBParameterGroups",
"rds:DescribeDBSubnetGroups",
"rds:ListTagsForResource",
]
# RDS describe APIs do not support resource-level permissions.
resources = ["*"]
}
statement {
sid = "RefreshSecrets"
effect = "Allow"
actions = [
"secretsmanager:DescribeSecret",
"secretsmanager:GetResourcePolicy",
"secretsmanager:GetSecretValue",
"secretsmanager:ListSecretVersionIds",
]
resources = [
"arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:shoc-backend-tf-poc/jwt-JXLaUx",
"arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:shoc-backend-tf-poc/webhook-hmac-eThZhu",
"arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:rds!db-6e0e2e34-dea1-47b0-8e92-b90bde9cfe20-Mxeu3J",
]
}
statement {
sid = "ListCertificates"
effect = "Allow"
actions = ["acm:ListCertificates"]
resources = ["*"]
}
statement {
sid = "RefreshElasticBeanstalk"
effect = "Allow"
actions = [
"elasticbeanstalk:DescribeApplications",
"elasticbeanstalk:DescribeConfigurationOptions",
"elasticbeanstalk:DescribeConfigurationSettings",
"elasticbeanstalk:DescribeEnvironmentResources",
"elasticbeanstalk:DescribeEnvironments",
"elasticbeanstalk:ListTagsForResource",
]
# Elastic Beanstalk describe APIs do not support resource-level permissions.
resources = ["*"]
}
statement {
sid = "RefreshPocCertificate"
effect = "Allow"
actions = [
"acm:DescribeCertificate",
"acm:GetCertificate",
"acm:ListTagsForCertificate",
]
resources = ["arn:aws:acm:${var.aws_region}:${local.account_id}:certificate/4fcc2dff-bb11-4204-9107-86d6ce2b95a2"]
}
statement {
sid = "RefreshRoute53"
effect = "Allow"
actions = [
"route53:GetChange",
"route53:GetHostedZone",
"route53:ListResourceRecordSets",
"route53:ListTagsForResource",
]
resources = [
local.hosted_zone_arn,
"arn:aws:route53:::change/*",
]
}
statement {
sid = "RefreshNetwork"
effect = "Allow"
actions = [
"ec2:DescribeSecurityGroupRules",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVpcs",
]
# EC2 describe APIs do not support resource-level permissions.
resources = ["*"]
}
}
resource "aws_iam_role" "hcp_plan" {
name = "hcptf-shoc-backend-tf-poc-plan"
description = "HCP Terraform PLAN role for shoc-backend-tf-poc"
assume_role_policy = data.aws_iam_policy_document.hcp_plan_assume.json
max_session_duration = 3600
permissions_boundary = var.execution_boundary_arn
depends_on = [terraform_data.account_guard]
lifecycle {
prevent_destroy = true
}
}
resource "aws_iam_role_policy" "hcp_plan_refresh" {
name = "shoc-backend-tf-poc-plan-refresh"
role = aws_iam_role.hcp_plan.id
policy = data.aws_iam_policy_document.hcp_refresh.json
}
resource "aws_iam_role" "hcp_apply" {
name = "hcptf-shoc-backend-tf-poc"
description = "Read-only HCP Terraform APPLY role for the stabilized shoc-backend-tf-poc"
assume_role_policy = data.aws_iam_policy_document.hcp_apply_assume.json
max_session_duration = 3600
permissions_boundary = var.execution_boundary_arn
depends_on = [terraform_data.account_guard]
lifecycle {
prevent_destroy = true
}
}
resource "aws_iam_role_policy" "hcp_apply_iam" {
name = "shoc-backend-tf-poc-iam"
role = aws_iam_role.hcp_apply.id
policy = data.aws_iam_policy_document.hcp_refresh.json
}