forgejo/terraform/hcp_iam.tf
Adam Moussa b2164d8c7e
fix(terraform): allow HCP refresh of the log group and parameter (PLAT-80) (#101)
* fix(terraform): allow HCP refresh of the log group and parameter

The scoped apply role can create those resources, but CloudWatch and SSM list them on a wildcard ARN. DescribeLogGroups and DescribeParameters need that resource.

* fix(terraform): let the plan role read bucket website config

The S3 provider refreshes GetBucketWebsite. The plan role was denied on the three Forgejo buckets.

* fix(terraform): let the plan role read backup object metadata

HeadObject on the Lambda zip is s3:GetObject. The plan role only had the bucket ARNs.

* fix(terraform): let the plan role read object tags and retention

The S3 provider refreshes tagging, ACL, attributes, and Object Lock on the Lambda zip.

* fix(terraform): scope plan object reads and restore on the data volume

The plan role only needs object reads for the verification zip. Unpacking a dump in /tmp fills the root volume.

* fix(terraform): accept dumps that already contain data/forgejo.db

Today's archive has no gitea-db.sqlite3 at the root. Copy that file only when it is present.

* docs(terraform): keep restore runbook on one bucket and fail closed

Glacier and the download use the prod bucket. GCS unpacks on the data volume. Neither path deletes live repos until the dump has a database.

* docs(terraform): keep optional restore copies from aborting under set -e

if/fi matches user_data.sh. The file comment now says forgejo-services versions also go through the org-account role.

* fix(terraform): restore the dump app.ini with the database

INTERNAL_TOKEN, JWT_SECRET, and LFS_JWT_SECRET live in that file. A restore that keeps the generated file cannot decrypt the dumped secrets.
2026-09-30 00:10:09 +00:00

852 lines
23 KiB
HCL

# HCP plan/apply roles for forgejo-prod (PLAT-80).
# Copy of seahaven-org-baseline/examples/hcptf-workspace-iam/hcp_iam.tf.example
# with the Forgejo EC2, ALB, S3 CRR, DLM, and Lambda service set. Create, do not import.
#
# First-apply sequence:
# 1. seahaven-org-baseline scripts/create-hcptf-bootstrap-roles.sh
# --account prod --allow-workspace forgejo-prod
# 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap / hcptf-bootstrap-plan
# (workspace vars, never a project set).
# 3. One Manual apply. Bootstrap can write hcptf-* and policy/tf-managed/*.
# It cannot PassRole to ec2 or create the buckets, so non-IAM resources
# error and stay out of state.
# 4. Point TFC_AWS_* at hcptf-forgejo / hcptf-forgejo-plan.
# 5. Re-run the script without --allow-workspace.
# 6. Second Manual apply creates the instance, buckets, ALB, and Lambda.
# Later edits to hcptf-* inline policies and to policy/tf-managed/forgejo-services
# need the org-account role. The scoped role cannot PutRolePolicy or
# CreatePolicyVersion. Do not add StringLike on bootstrap trust.
# CreatePolicy stays on hcptf-bootstrap.
data "aws_iam_policy_document" "hcptf_apply_trust" {
statement {
sid = "HcpApply"
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = ["arn:aws:iam::${local.account_id}:oidc-provider/app.terraform.io"]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:aud"
values = ["aws.workload.identity"]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:sub"
values = [
"organization:seahaven:project:${local.hcp_project}:workspace:${local.hcp_workspace}:run_phase:apply",
]
}
}
}
data "aws_iam_policy_document" "hcptf_plan_trust" {
statement {
sid = "HcpPlan"
effect = "Allow"
actions = ["sts:AssumeRoleWithWebIdentity"]
principals {
type = "Federated"
identifiers = ["arn:aws:iam::${local.account_id}:oidc-provider/app.terraform.io"]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:aud"
values = ["aws.workload.identity"]
}
condition {
test = "StringEquals"
variable = "app.terraform.io:sub"
values = [
"organization:seahaven:project:${local.hcp_project}:workspace:${local.hcp_workspace}:run_phase:plan",
]
}
}
}
data "aws_iam_policy_document" "hcptf_scoped_iam" {
statement {
sid = "DenyCreatePolicy"
effect = "Deny"
actions = [
"iam:CreatePolicy",
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
]
resources = ["*"]
}
statement {
sid = "CreateExecRoleWithBoundary"
effect = "Allow"
actions = ["iam:CreateRole"]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*",
]
condition {
test = "StringLike"
variable = "iam:PermissionsBoundary"
values = [
"arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*",
]
}
}
statement {
sid = "MutateExecRoleWithBoundary"
effect = "Allow"
actions = [
"iam:AttachRolePolicy",
"iam:PutRolePolicy",
"iam:PutRolePermissionsBoundary",
]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*",
]
condition {
test = "StringLike"
variable = "iam:PermissionsBoundary"
values = [
"arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*",
]
}
}
statement {
sid = "WriteExecRoles"
effect = "Allow"
actions = [
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DetachRolePolicy",
"iam:TagRole",
"iam:UntagRole",
"iam:UpdateAssumeRolePolicy",
"iam:UpdateRole",
"iam:UpdateRoleDescription",
]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*",
]
}
statement {
sid = "PassInstanceRoleToEc2"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.instance_role_name}",
]
condition {
test = "StringEquals"
variable = "iam:PassedToService"
values = ["ec2.amazonaws.com"]
}
}
statement {
sid = "PassDlmRole"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.dlm_role_name}",
]
condition {
test = "StringEquals"
variable = "iam:PassedToService"
values = ["dlm.amazonaws.com"]
}
}
statement {
sid = "PassReplicationRoleToS3"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.replication_role_name}",
]
condition {
test = "StringEquals"
variable = "iam:PassedToService"
values = ["s3.amazonaws.com"]
}
}
statement {
sid = "PassLambdaRole"
effect = "Allow"
actions = ["iam:PassRole"]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.lambda_role_name}",
]
condition {
test = "StringEquals"
variable = "iam:PassedToService"
values = ["lambda.amazonaws.com"]
}
}
statement {
sid = "InstanceProfiles"
effect = "Allow"
actions = [
"iam:AddRoleToInstanceProfile",
"iam:CreateInstanceProfile",
"iam:DeleteInstanceProfile",
"iam:GetInstanceProfile",
"iam:ListInstanceProfileTags",
"iam:RemoveRoleFromInstanceProfile",
"iam:TagInstanceProfile",
"iam:UntagInstanceProfile",
]
resources = [
"arn:aws:iam::${local.account_id}:instance-profile/tf-managed/${local.instance_profile_name}",
]
}
statement {
sid = "GcsTransferUser"
effect = "Allow"
actions = [
"iam:CreateUser",
"iam:DeleteUser",
"iam:GetUser",
"iam:GetUserPolicy",
"iam:ListUserPolicies",
"iam:ListUserTags",
"iam:PutUserPermissionsBoundary",
"iam:PutUserPolicy",
"iam:DeleteUserPolicy",
"iam:TagUser",
"iam:UntagUser",
]
resources = [
"arn:aws:iam::${local.account_id}:user/tf-managed/${local.gcs_user_name}",
]
}
statement {
sid = "IamReadOnly"
effect = "Allow"
actions = [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:ListAttachedRolePolicies",
"iam:ListInstanceProfilesForRole",
"iam:ListPolicies",
"iam:ListPolicyVersions",
"iam:ListRolePolicies",
"iam:ListRoleTags",
"iam:ListRoles",
]
resources = ["*"]
}
statement {
sid = "TagExecBoundary"
effect = "Allow"
actions = [
"iam:GetPolicy",
"iam:GetPolicyVersion",
"iam:ListPolicyTags",
"iam:ListPolicyVersions",
"iam:TagPolicy",
"iam:UntagPolicy",
]
resources = [
"arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*",
]
}
statement {
sid = "DenySelfMutation"
effect = "Deny"
actions = [
"iam:AttachRolePolicy",
"iam:DeleteRole",
"iam:DeleteRolePolicy",
"iam:DeleteRolePermissionsBoundary",
"iam:DetachRolePolicy",
"iam:PutRolePolicy",
"iam:PutRolePermissionsBoundary",
"iam:UpdateAssumeRolePolicy",
"iam:UpdateRole",
"iam:UpdateRoleDescription",
]
resources = [
"arn:aws:iam::${local.account_id}:role/hcptf-*",
"arn:aws:iam::${local.account_id}:role/github-cfn-execution-role",
"arn:aws:iam::${local.account_id}:role/githubdeploy-*",
"arn:aws:iam::${local.account_id}:role/cdk-hnb659fds-*",
"arn:aws:iam::${local.account_id}:role/OrganizationAccountAccessRole",
"arn:aws:iam::${local.account_id}:role/seahaven-*",
]
}
statement {
sid = "DenyBoundaryTampering"
effect = "Deny"
actions = [
"iam:DeleteRolePermissionsBoundary",
"iam:DeleteUserPermissionsBoundary",
]
resources = [
"arn:aws:iam::${local.account_id}:role/*",
"arn:aws:iam::${local.account_id}:user/*",
]
}
statement {
sid = "DenyBoundaryPolicyEdit"
effect = "Deny"
actions = [
"iam:CreatePolicyVersion",
"iam:DeletePolicy",
"iam:DeletePolicyVersion",
"iam:SetDefaultPolicyVersion",
]
resources = [
"arn:aws:iam::${local.account_id}:policy/seahaven-*",
"arn:aws:iam::${local.account_id}:policy/tf-managed/*",
]
}
}
data "aws_iam_policy_document" "hcptf_apply_services" {
statement {
sid = "BackupAndArtifactBuckets"
effect = "Allow"
actions = ["s3:*"]
resources = [
"arn:aws:s3:::${local.backup_bucket_name}",
"arn:aws:s3:::${local.backup_bucket_name}/*",
"arn:aws:s3:::${local.replica_bucket_name}",
"arn:aws:s3:::${local.replica_bucket_name}/*",
"arn:aws:s3:::${local.artifacts_bucket_name}",
"arn:aws:s3:::${local.artifacts_bucket_name}/*",
]
}
# RunInstances and CreateVolume do not support a useful resource ARN.
# This document is a managed policy so it is not counted against the
# apply role's 10,240-character inline quota. The plan role does not get it.
statement {
sid = "Ec2Host"
effect = "Allow"
actions = [
"ec2:AssociateIamInstanceProfile",
"ec2:AttachVolume",
"ec2:AuthorizeSecurityGroupEgress",
"ec2:AuthorizeSecurityGroupIngress",
"ec2:CreateSecurityGroup",
"ec2:CreateTags",
"ec2:CreateVolume",
"ec2:DeleteSecurityGroup",
"ec2:DeleteTags",
"ec2:DeleteVolume",
"ec2:DescribeAccountAttributes",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeIamInstanceProfileAssociations",
"ec2:DescribeImages",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstanceCreditSpecifications",
"ec2:DescribeInstanceStatus",
"ec2:DescribeInstanceTypes",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeSecurityGroupRules",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVolumeAttribute",
"ec2:DescribeVolumeStatus",
"ec2:DescribeVolumes",
"ec2:DescribeVpcAttribute",
"ec2:DescribeVpcs",
"ec2:DetachVolume",
"ec2:DisassociateIamInstanceProfile",
"ec2:ModifyInstanceAttribute",
"ec2:ModifySecurityGroupRules",
"ec2:ModifyVolume",
"ec2:ReplaceIamInstanceProfileAssociation",
"ec2:RevokeSecurityGroupEgress",
"ec2:RevokeSecurityGroupIngress",
"ec2:RunInstances",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:TerminateInstances",
]
resources = ["*"]
}
# Listener and rule ARNs are allocated at create time.
statement {
sid = "LoadBalancer"
effect = "Allow"
actions = ["elasticloadbalancing:*"]
resources = ["*"]
}
statement {
sid = "Certificate"
effect = "Allow"
actions = ["acm:*"]
resources = ["*"]
}
statement {
sid = "Snapshots"
effect = "Allow"
actions = ["dlm:*"]
resources = ["*"]
}
statement {
sid = "VerificationFunction"
effect = "Allow"
actions = ["lambda:*"]
resources = [
"arn:aws:lambda:${var.aws_region}:${local.account_id}:function:forgejo-backup-verification",
]
}
statement {
sid = "VerificationSchedules"
effect = "Allow"
actions = ["events:*"]
resources = [
"arn:aws:events:${var.aws_region}:${local.account_id}:rule/forgejo-backup-*",
]
}
# CreateLogGroup is not reliable on the log-group ARN before the group exists.
# DescribeLogGroups is a list API. Its resource is log-group::log-stream:, not the group ARN.
statement {
sid = "CreateVerificationLogGroup"
effect = "Allow"
actions = ["logs:CreateLogGroup"]
resources = ["*"]
}
statement {
sid = "DescribeVerificationLogGroups"
effect = "Allow"
actions = ["logs:DescribeLogGroups"]
resources = ["*"]
}
statement {
sid = "VerificationLogs"
effect = "Allow"
actions = [
"logs:DeleteLogGroup",
"logs:DeleteRetentionPolicy",
"logs:DescribeLogGroups",
"logs:ListTagsForResource",
"logs:PutRetentionPolicy",
"logs:TagResource",
"logs:UntagResource",
]
resources = [
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.verification_log_group}",
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.verification_log_group}:*",
]
}
statement {
sid = "VerificationAlarms"
effect = "Allow"
actions = [
"cloudwatch:DeleteAlarms",
"cloudwatch:DescribeAlarms",
"cloudwatch:ListTagsForResource",
"cloudwatch:PutMetricAlarm",
"cloudwatch:TagResource",
"cloudwatch:UntagResource",
]
resources = [
"arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:forgejo-backup-*",
]
}
statement {
sid = "DescribeAlarms"
effect = "Allow"
actions = ["cloudwatch:DescribeAlarms"]
resources = ["*"]
}
statement {
sid = "BackupPrefixParameter"
effect = "Allow"
actions = [
"ssm:AddTagsToResource",
"ssm:DeleteParameter",
"ssm:GetParameter",
"ssm:ListTagsForResource",
"ssm:PutParameter",
"ssm:RemoveTagsFromResource",
]
resources = [
"arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter/forgejo/*",
]
}
# DescribeParameters does not accept a parameter ARN. The provider calls it on *.
statement {
sid = "DescribeBackupParameters"
effect = "Allow"
actions = ["ssm:DescribeParameters"]
resources = ["*"]
}
statement {
sid = "AlertTopicRead"
effect = "Allow"
actions = [
"sns:GetTopicAttributes",
"sns:ListTagsForResource",
]
resources = [local.site_alerts_arn]
}
statement {
sid = "EbsEncryption"
effect = "Allow"
actions = [
"kms:CreateGrant",
"kms:Decrypt",
"kms:DescribeKey",
"kms:GenerateDataKeyWithoutPlaintext",
"kms:ReEncryptFrom",
"kms:ReEncryptTo",
]
resources = ["*"]
condition {
test = "StringEquals"
variable = "kms:ViaService"
values = ["ec2.${var.aws_region}.amazonaws.com"]
}
}
}
data "aws_iam_policy_document" "hcptf_plan_refresh" {
statement {
sid = "RefreshIamRoles"
effect = "Allow"
actions = [
"iam:GetInstanceProfile",
"iam:GetRole",
"iam:GetRolePolicy",
"iam:GetUser",
"iam:GetUserPolicy",
"iam:ListAttachedRolePolicies",
"iam:ListInstanceProfilesForRole",
"iam:ListRolePolicies",
"iam:ListRoleTags",
"iam:ListUserPolicies",
"iam:ListUserTags",
]
resources = [
"arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*",
"arn:aws:iam::${local.account_id}:instance-profile/tf-managed/${local.stack_prefix}*",
"arn:aws:iam::${local.account_id}:user/tf-managed/${local.gcs_user_name}",
"arn:aws:iam::${local.account_id}:role/${local.apply_role}",
"arn:aws:iam::${local.account_id}:role/${local.plan_role}",
]
}
statement {
sid = "RefreshManagedPolicies"
effect = "Allow"
actions = [
"iam:GetPolicy",
"iam:GetPolicyVersion",
]
resources = ["*"]
}
statement {
sid = "RefreshS3"
effect = "Allow"
actions = [
"s3:GetAccelerateConfiguration",
"s3:GetBucketAcl",
"s3:GetBucketCORS",
"s3:GetBucketLocation",
"s3:GetBucketLogging",
"s3:GetBucketNotification",
"s3:GetBucketObjectLockConfiguration",
"s3:GetBucketOwnershipControls",
"s3:GetBucketPolicy",
"s3:GetBucketPolicyStatus",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketRequestPayment",
"s3:GetBucketTagging",
"s3:GetBucketVersioning",
"s3:GetBucketWebsite",
"s3:GetEncryptionConfiguration",
"s3:GetLifecycleConfiguration",
"s3:GetReplicationConfiguration",
"s3:ListBucket",
]
resources = [
"arn:aws:s3:::${local.backup_bucket_name}",
"arn:aws:s3:::${local.replica_bucket_name}",
"arn:aws:s3:::${local.artifacts_bucket_name}",
]
}
# aws_s3_object refresh calls HeadObject and object metadata reads. Scope
# them to the verification package. Backup and replica objects stay unread.
statement {
sid = "RefreshLambdaPackage"
effect = "Allow"
actions = [
"s3:GetObject",
"s3:GetObjectAcl",
"s3:GetObjectAttributes",
"s3:GetObjectTagging",
]
resources = [
"arn:aws:s3:::${local.artifacts_bucket_name}/functions/forgejo-backup-verification.zip",
]
}
statement {
sid = "RefreshEc2"
effect = "Allow"
actions = [
"ec2:DescribeAccountAttributes",
"ec2:DescribeAvailabilityZones",
"ec2:DescribeIamInstanceProfileAssociations",
"ec2:DescribeImages",
"ec2:DescribeInstanceAttribute",
"ec2:DescribeInstanceCreditSpecifications",
"ec2:DescribeInstanceStatus",
"ec2:DescribeInstanceTypes",
"ec2:DescribeInstances",
"ec2:DescribeNetworkInterfaces",
"ec2:DescribeSecurityGroupRules",
"ec2:DescribeSecurityGroups",
"ec2:DescribeSubnets",
"ec2:DescribeTags",
"ec2:DescribeVolumeAttribute",
"ec2:DescribeVolumeStatus",
"ec2:DescribeVolumes",
"ec2:DescribeVpcAttribute",
"ec2:DescribeVpcs",
]
resources = ["*"]
}
statement {
sid = "RefreshLoadBalancer"
effect = "Allow"
actions = [
"elasticloadbalancing:DescribeListenerAttributes",
"elasticloadbalancing:DescribeListeners",
"elasticloadbalancing:DescribeLoadBalancerAttributes",
"elasticloadbalancing:DescribeLoadBalancers",
"elasticloadbalancing:DescribeRules",
"elasticloadbalancing:DescribeTags",
"elasticloadbalancing:DescribeTargetGroupAttributes",
"elasticloadbalancing:DescribeTargetGroups",
"elasticloadbalancing:DescribeTargetHealth",
]
resources = ["*"]
}
statement {
sid = "RefreshCertificate"
effect = "Allow"
actions = [
"acm:DescribeCertificate",
"acm:ListCertificates",
"acm:ListTagsForCertificate",
]
resources = ["*"]
}
statement {
sid = "RefreshLambda"
effect = "Allow"
actions = [
"lambda:GetFunction",
"lambda:GetFunctionCodeSigningConfig",
"lambda:GetFunctionConfiguration",
"lambda:GetPolicy",
"lambda:GetRuntimeManagementConfig",
"lambda:ListTags",
"lambda:ListVersionsByFunction",
]
resources = [
"arn:aws:lambda:${var.aws_region}:${local.account_id}:function:forgejo-backup-verification",
]
}
statement {
sid = "RefreshSchedules"
effect = "Allow"
actions = [
"events:DescribeRule",
"events:ListTagsForResource",
"events:ListTargetsByRule",
]
resources = [
"arn:aws:events:${var.aws_region}:${local.account_id}:rule/forgejo-backup-*",
]
}
statement {
sid = "RefreshLogs"
effect = "Allow"
actions = [
"logs:ListTagsForResource",
]
resources = [
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.verification_log_group}",
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:${local.verification_log_group}:*",
]
}
# DescribeLogGroups is a list API. Its resource is log-group::log-stream:, not the group ARN.
statement {
sid = "DescribeVerificationLogGroups"
effect = "Allow"
actions = ["logs:DescribeLogGroups"]
resources = ["*"]
}
statement {
sid = "RefreshAlarms"
effect = "Allow"
actions = [
"cloudwatch:DescribeAlarms",
"cloudwatch:ListTagsForResource",
]
resources = ["*"]
}
statement {
sid = "RefreshParameter"
effect = "Allow"
actions = [
"ssm:GetParameter",
"ssm:ListTagsForResource",
]
resources = [
"arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter/forgejo/*",
]
}
statement {
sid = "DescribeBackupParameters"
effect = "Allow"
actions = ["ssm:DescribeParameters"]
resources = ["*"]
}
statement {
sid = "RefreshSnapshots"
effect = "Allow"
actions = [
"dlm:GetLifecyclePolicy",
"dlm:ListTagsForResource",
]
resources = ["arn:aws:dlm:${var.aws_region}:${local.account_id}:policy/*"]
}
statement {
sid = "RefreshSns"
effect = "Allow"
actions = [
"sns:GetTopicAttributes",
"sns:ListTagsForResource",
]
resources = [local.site_alerts_arn]
}
}
resource "aws_iam_role" "hcptf_apply" {
name = local.apply_role
assume_role_policy = data.aws_iam_policy_document.hcptf_apply_trust.json
max_session_duration = 3600
tags = {
Owner = "adam@seahavenind.com"
ManagedBy = "terraform"
}
}
resource "aws_iam_role" "hcptf_plan" {
name = local.plan_role
assume_role_policy = data.aws_iam_policy_document.hcptf_plan_trust.json
max_session_duration = 3600
tags = {
Owner = "adam@seahavenind.com"
ManagedBy = "terraform"
}
}
resource "aws_iam_role_policy" "hcptf_scoped_iam" {
name = "scoped-iam-management"
role = aws_iam_role.hcptf_apply.id
policy = data.aws_iam_policy_document.hcptf_scoped_iam.json
}
# Managed, not inline: the enumerated EC2 list plus scoped-iam-management
# exceeds the 10,240-character inline quota. Bootstrap creates this on the
# first apply. Later document edits need that window (CreatePolicyVersion
# is denied on hcptf-forgejo).
resource "aws_iam_policy" "hcptf_apply_services" {
name = "forgejo-services"
path = "/tf-managed/"
description = "Forgejo HCP apply service permissions (PLAT-80)."
policy = data.aws_iam_policy_document.hcptf_apply_services.json
}
resource "aws_iam_role_policy" "hcptf_plan_refresh" {
name = "forgejo-plan-refresh"
role = aws_iam_role.hcptf_plan.id
policy = data.aws_iam_policy_document.hcptf_plan_refresh.json
}
resource "aws_iam_role_policy_attachment" "hcptf_plan_view_only" {
role = aws_iam_role.hcptf_plan.name
policy_arn = "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
}
resource "aws_iam_role_policy_attachments_exclusive" "hcptf_apply" {
role_name = aws_iam_role.hcptf_apply.name
policy_arns = [
aws_iam_policy.hcptf_apply_services.arn,
]
}
resource "aws_iam_role_policy_attachments_exclusive" "hcptf_plan" {
role_name = aws_iam_role.hcptf_plan.name
policy_arns = [
"arn:aws:iam::aws:policy/job-function/ViewOnlyAccess",
]
}