# HCP plan/apply roles for afterhours-shift-manager-prod (PLAT-74 / PLAT-144). # Copy of seahaven-org-baseline/examples/hcptf-workspace-iam/hcp_iam.tf.example # with the afterhours service set. Create, do not import. # # Live seahaven-hcptf-iam-management DenySelfMutation blocks DetachRolePolicy # and PutRolePolicy on hcptf-* (including this role). First-apply sequence: # 1. seahaven-org-baseline scripts/create-hcptf-bootstrap-roles.sh # --account prod --allow-workspace afterhours-shift-manager-prod # 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap / # hcptf-bootstrap-plan (workspace vars, never a project set). # 3. One Manual apply (create roles + scoped inline + boundary + stack, # schedules_enabled=false). # 4. Point TFC_AWS_* back at hcptf-afterhours-shift-manager / # hcptf-afterhours-shift-manager-plan. # 5. Re-run the script without --allow-workspace to pin trust back to # iam-bootstrap-prod only. # Later apply-role IAM edits use the same window. Do not add StringLike # on bootstrap trust. CreatePolicy stays on hcptf-bootstrap only; boundary # document changes after seal also need that window. 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}*", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-${local.stack_name}", ] } } 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}*", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary", "arn:aws:iam::${local.account_id}:policy/seahaven-lambda-execution-boundary-${local.stack_name}", ] } } 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 = "PassExecRolesToCompute" effect = "Allow" actions = ["iam:PassRole"] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*"] condition { test = "StringEquals" variable = "iam:PassedToService" values = ["lambda.amazonaws.com", "ecs-tasks.amazonaws.com", "scheduler.amazonaws.com"] } } statement { sid = "PassHolidaySchedulerRole" effect = "Allow" actions = ["iam:PassRole"] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/afterhours-shift-manager-holiday-scheduler"] condition { test = "StringEquals" variable = "iam:PassedToService" values = ["scheduler.amazonaws.com"] } } # githubdeploy-afterhours-shift-manager lives at /tf-managed/ so # DenySelfMutation (role/githubdeploy-*) does not match. Create without a # permissions boundary; this is not a Lambda execution role. statement { sid = "CreateDeployRole" effect = "Allow" actions = ["iam:CreateRole"] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.deploy_role}"] condition { test = "Null" variable = "iam:PermissionsBoundary" values = ["true"] } } statement { sid = "WriteDeployRoles" effect = "Allow" actions = [ "iam:AttachRolePolicy", "iam:DeleteRole", "iam:DeleteRolePolicy", "iam:DetachRolePolicy", "iam:PutRolePolicy", "iam:TagRole", "iam:UntagRole", "iam:UpdateAssumeRolePolicy", "iam:UpdateRole", "iam:UpdateRoleDescription", ] resources = ["arn:aws:iam::${local.account_id}:role/tf-managed/${local.deploy_role}"] } 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 = "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-*"] } } data "aws_iam_policy_document" "hcptf_apply_services" { statement { sid = "LambdaAll" effect = "Allow" actions = [ "lambda:*", ] resources = [ "arn:aws:lambda:${var.aws_region}:${local.account_id}:function:afterhours-*", ] } statement { sid = "LambdaList" effect = "Allow" actions = [ "lambda:ListFunctions", "lambda:ListLayers", "lambda:GetAccountSettings", ] resources = ["*"] } statement { sid = "EventBridgeRules" effect = "Allow" actions = [ "events:*", ] resources = [ "arn:aws:events:${var.aws_region}:${local.account_id}:rule/afterhours-shift-manager-*", ] } statement { sid = "EventBridgeList" effect = "Allow" actions = ["events:ListRules", "events:ListRuleNamesByTarget"] resources = ["*"] } statement { sid = "CloudWatchLogs" effect = "Allow" actions = [ "logs:CreateLogGroup", "logs:DeleteLogGroup", "logs:PutRetentionPolicy", "logs:DeleteRetentionPolicy", "logs:TagResource", "logs:UntagResource", "logs:ListTagsForResource", "logs:PutMetricFilter", "logs:DeleteMetricFilter", "logs:DescribeMetricFilters", ] resources = [ "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/aws/lambda/afterhours-*", "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/aws/apigateway/afterhours-shift-manager", "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/aws/apigateway/afterhours-shift-manager:*", "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/ecs/afterhours-shift-manager", "arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/ecs/afterhours-shift-manager:*", ] } statement { sid = "CloudWatchLogsDescribe" effect = "Allow" actions = ["logs:DescribeLogGroups"] resources = ["*"] } # CreateStage access_log_settings uses log-delivery APIs. Resource "*" is # required; these actions do not accept a log-group ARN. statement { sid = "ApiGwAccessLogDelivery" effect = "Allow" actions = [ "logs:CreateLogDelivery", "logs:GetLogDelivery", "logs:UpdateLogDelivery", "logs:DeleteLogDelivery", "logs:ListLogDeliveries", "logs:PutResourcePolicy", "logs:DescribeResourcePolicies", ] resources = ["*"] } statement { sid = "StackBuckets" effect = "Allow" actions = [ "s3:*", ] resources = [ "arn:aws:s3:::${local.artifacts_bucket_name}", "arn:aws:s3:::${local.artifacts_bucket_name}/*", ] } statement { sid = "DynamoDBTable" effect = "Allow" actions = [ "dynamodb:*", ] resources = [ "arn:aws:dynamodb:${var.aws_region}:${local.account_id}:table/${local.table_name}", "arn:aws:dynamodb:${var.aws_region}:${local.account_id}:table/${local.table_name}/*", ] } statement { sid = "DynamoDBList" effect = "Allow" actions = ["dynamodb:ListTables"] resources = ["*"] } statement { sid = "HttpApiManage" effect = "Allow" actions = [ "apigateway:*", ] resources = [ "arn:aws:apigateway:${var.aws_region}::/apis", "arn:aws:apigateway:${var.aws_region}::/apis/*", "arn:aws:apigateway:${var.aws_region}::/tags/*", "arn:aws:apigateway:${var.aws_region}::/vpclinks", "arn:aws:apigateway:${var.aws_region}::/vpclinks/*", ] } statement { sid = "AfterhoursSsm" effect = "Allow" actions = [ "ssm:GetParameter", "ssm:GetParameters", "ssm:PutParameter", "ssm:DeleteParameter", "ssm:AddTagsToResource", "ssm:RemoveTagsFromResource", "ssm:ListTagsForResource", ] resources = [ "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/*", ] } statement { sid = "SsmDescribeParameters" effect = "Allow" actions = ["ssm:DescribeParameters"] resources = ["*"] } statement { sid = "SecretsManagerReadAndManage" effect = "Allow" actions = [ "secretsmanager:CreateSecret", "secretsmanager:DeleteSecret", "secretsmanager:DescribeSecret", "secretsmanager:GetResourcePolicy", "secretsmanager:PutResourcePolicy", "secretsmanager:DeleteResourcePolicy", "secretsmanager:TagResource", "secretsmanager:UntagResource", "secretsmanager:UpdateSecret", "secretsmanager:ListSecretVersionIds", ] resources = [ "arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:afterhours-shift-manager/*", ] } statement { sid = "SecretsManagerCreateByName" effect = "Allow" actions = [ "secretsmanager:CreateSecret", ] resources = ["*"] condition { test = "StringLike" variable = "secretsmanager:Name" values = ["afterhours-shift-manager/*"] } } statement { sid = "SecretsManagerList" effect = "Allow" actions = ["secretsmanager:ListSecrets"] resources = ["*"] } statement { sid = "CloudWatchAlarms" effect = "Allow" actions = [ "cloudwatch:PutMetricAlarm", "cloudwatch:DeleteAlarms", "cloudwatch:DescribeAlarms", "cloudwatch:TagResource", "cloudwatch:UntagResource", "cloudwatch:ListTagsForResource", ] resources = [ "arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:Lambda-*-afterhours-*", "arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:DDB-*-afterhours-shifts", "arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:ApiGateway-*", "arn:aws:cloudwatch:${var.aws_region}:${local.account_id}:alarm:ALB-*-afterhours-shift-manager", ] } statement { sid = "CloudWatchDescribeAlarms" effect = "Allow" actions = ["cloudwatch:DescribeAlarms"] resources = ["*"] } statement { sid = "SnsPublishSiteAlerts" effect = "Allow" actions = [ "sns:Publish", "sns:GetTopicAttributes", "sns:ListTagsForResource", ] resources = [local.site_alerts_arn] } statement { sid = "ManageTfManagedBoundary" effect = "Allow" actions = [ "iam:GetPolicy", "iam:GetPolicyVersion", "iam:ListPolicyVersions", "iam:ListPolicyTags", "iam:TagPolicy", "iam:UntagPolicy", ] resources = [ "arn:aws:iam::${local.account_id}:policy/tf-managed/${local.stack_prefix}*", ] } statement { sid = "EventBridgeScheduler" effect = "Allow" actions = [ "scheduler:CreateSchedule", "scheduler:DeleteSchedule", "scheduler:GetSchedule", "scheduler:UpdateSchedule", "scheduler:ListTagsForResource", "scheduler:TagResource", "scheduler:UntagResource", ] resources = [ "arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule/default/holiday-*", "arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule/${local.project}/*", "arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule-group/${local.project}", ] } statement { sid = "EventBridgeSchedulerList" effect = "Allow" actions = [ "scheduler:ListSchedules", "scheduler:ListScheduleGroups", "scheduler:GetScheduleGroup", "scheduler:CreateScheduleGroup", "scheduler:DeleteScheduleGroup", ] resources = ["*"] } } data "aws_iam_policy_document" "hcptf_apply_ecs" { statement { sid = "EcsWorkload" effect = "Allow" actions = [ "ecs:*", ] resources = [ "arn:aws:ecs:${var.aws_region}:${local.account_id}:cluster/${local.project}", "arn:aws:ecs:${var.aws_region}:${local.account_id}:service/${local.project}/${local.project}", "arn:aws:ecs:${var.aws_region}:${local.account_id}:task-definition/${local.project}:*", "arn:aws:ecs:${var.aws_region}:${local.account_id}:task-definition/${local.project}", ] } statement { sid = "EcsAccount" effect = "Allow" actions = [ "ecs:CreateCluster", "ecs:CreateService", "ecs:DeleteService", "ecs:DeregisterTaskDefinition", "ecs:DescribeClusters", "ecs:DescribeServices", "ecs:DescribeTaskDefinition", "ecs:ListClusters", "ecs:ListServices", "ecs:ListTaskDefinitions", "ecs:RegisterTaskDefinition", "ecs:TagResource", "ecs:UntagResource", "ecs:UpdateService", ] resources = ["*"] } statement { sid = "ElbWorkload" effect = "Allow" actions = [ "elasticloadbalancing:*", ] resources = [ "arn:aws:elasticloadbalancing:${var.aws_region}:${local.account_id}:loadbalancer/app/${local.project}/*", "arn:aws:elasticloadbalancing:${var.aws_region}:${local.account_id}:targetgroup/${local.project}-api/*", "arn:aws:elasticloadbalancing:${var.aws_region}:${local.account_id}:listener/app/${local.project}/*", ] } statement { sid = "ElbDescribe" effect = "Allow" actions = [ "elasticloadbalancing:Describe*", "elasticloadbalancing:CreateLoadBalancer", "elasticloadbalancing:CreateTargetGroup", "elasticloadbalancing:CreateListener", "elasticloadbalancing:CreateRule", "elasticloadbalancing:AddTags", "elasticloadbalancing:ModifyLoadBalancerAttributes", "elasticloadbalancing:ModifyTargetGroup", "elasticloadbalancing:ModifyTargetGroupAttributes", "elasticloadbalancing:ModifyListener", "elasticloadbalancing:SetSecurityGroups", "elasticloadbalancing:SetSubnets", ] resources = ["*"] } statement { sid = "EcrRepo" effect = "Allow" actions = [ "ecr:*", ] resources = [ "arn:aws:ecr:${var.aws_region}:${local.account_id}:repository/${local.project}", ] } statement { sid = "EcrAccount" effect = "Allow" actions = [ "ecr:DescribeRepositories", "ecr:GetAuthorizationToken", ] resources = ["*"] } statement { sid = "JobsQueues" effect = "Allow" actions = [ "sqs:*", ] resources = [ "arn:aws:sqs:${var.aws_region}:${local.account_id}:${local.project}-jobs", "arn:aws:sqs:${var.aws_region}:${local.account_id}:${local.project}-jobs-dlq", ] } statement { sid = "VpcSecurityGroups" effect = "Allow" actions = [ "ec2:AssociateRouteTable", "ec2:AttachInternetGateway", "ec2:AuthorizeSecurityGroupEgress", "ec2:AuthorizeSecurityGroupIngress", "ec2:CreateInternetGateway", "ec2:CreateRoute", "ec2:CreateRouteTable", "ec2:CreateSecurityGroup", "ec2:CreateSubnet", "ec2:CreateTags", "ec2:CreateVpc", "ec2:DeleteInternetGateway", "ec2:DeleteRoute", "ec2:DeleteRouteTable", "ec2:DeleteSecurityGroup", "ec2:DeleteSubnet", "ec2:DeleteTags", "ec2:DeleteVpc", "ec2:DescribeAccountAttributes", "ec2:DescribeAvailabilityZones", "ec2:DescribeInternetGateways", "ec2:DescribeNetworkInterfaces", "ec2:DescribeRouteTables", "ec2:DescribeSecurityGroupRules", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeTags", "ec2:DescribeVpcAttribute", "ec2:DescribeVpcs", "ec2:DetachInternetGateway", "ec2:DisassociateRouteTable", "ec2:ModifySubnetAttribute", "ec2:ModifyVpcAttribute", "ec2:RevokeSecurityGroupEgress", "ec2:RevokeSecurityGroupIngress", ] resources = ["*"] } statement { sid = "AcmLookup" effect = "Allow" actions = [ "acm:ListCertificates", "acm:DescribeCertificate", "acm:ListTagsForCertificate", "acm:GetCertificate", ] resources = ["*"] } } data "aws_iam_policy_document" "hcptf_plan_refresh" { statement { sid = "RefreshIamRoles" effect = "Allow" actions = [ "iam:GetRole", "iam:GetRolePolicy", "iam:ListRolePolicies", "iam:ListAttachedRolePolicies", "iam:ListRoleTags", ] resources = [ "arn:aws:iam::${local.account_id}:role/tf-managed/${local.stack_prefix}*", "arn:aws:iam::${local.account_id}:role/tf-managed/${local.deploy_role}", "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 = "RefreshLambda" effect = "Allow" actions = [ "lambda:GetFunction", "lambda:GetFunctionConfiguration", "lambda:GetPolicy", "lambda:GetFunctionCodeSigningConfig", "lambda:GetFunctionConcurrency", "lambda:GetFunctionEventInvokeConfig", "lambda:GetFunctionUrlConfig", "lambda:GetRuntimeManagementConfig", "lambda:GetFunctionRecursionConfig", "lambda:ListTags", "lambda:ListVersionsByFunction", "lambda:ListAliases", ] resources = [ "arn:aws:lambda:${var.aws_region}:${local.account_id}:function:afterhours-*", ] } statement { sid = "RefreshLambdaList" effect = "Allow" actions = [ "lambda:ListFunctions", "lambda:ListLayers", "lambda:GetAccountSettings", ] resources = ["*"] } statement { sid = "RefreshBuckets" effect = "Allow" actions = [ "s3:GetAccelerateConfiguration", "s3:GetAnalyticsConfiguration", "s3:GetBucketAcl", "s3:GetBucketCORS", "s3:GetBucketLifecycleConfiguration", "s3:GetBucketLocation", "s3:GetBucketLogging", "s3:GetBucketNotification", "s3:GetBucketObjectLockConfiguration", "s3:GetBucketOwnershipControls", "s3:GetBucketPolicy", "s3:GetBucketPolicyStatus", "s3:GetBucketPublicAccessBlock", "s3:GetBucketReplication", "s3:GetBucketRequestPayment", "s3:GetBucketTagging", "s3:GetBucketVersioning", "s3:GetBucketWebsite", "s3:GetEncryptionConfiguration", "s3:GetIntelligentTieringConfiguration", "s3:GetInventoryConfiguration", "s3:GetLifecycleConfiguration", "s3:GetMetricsConfiguration", "s3:GetObject", "s3:GetObjectTagging", "s3:GetObjectVersion", "s3:GetReplicationConfiguration", "s3:ListBucket", ] resources = [ "arn:aws:s3:::${local.artifacts_bucket_name}", "arn:aws:s3:::${local.artifacts_bucket_name}/*", ] } statement { sid = "RefreshDynamoDB" effect = "Allow" actions = [ "dynamodb:DescribeTable", "dynamodb:DescribeTimeToLive", "dynamodb:DescribeContinuousBackups", "dynamodb:DescribeKinesisStreamingDestination", "dynamodb:ListTagsOfResource", ] resources = [ "arn:aws:dynamodb:${var.aws_region}:${local.account_id}:table/${local.table_name}", ] } statement { sid = "RefreshEventBridge" effect = "Allow" actions = [ "events:DescribeRule", "events:ListTargetsByRule", "events:ListTagsForResource", ] resources = [ "arn:aws:events:${var.aws_region}:${local.account_id}:rule/afterhours-shift-manager-*", ] } statement { sid = "RefreshLogs" effect = "Allow" actions = [ "logs:DescribeLogGroups", "logs:ListTagsForResource", ] resources = ["*"] } statement { sid = "RefreshHttpApi" effect = "Allow" actions = [ "apigateway:GET", ] resources = [ "arn:aws:apigateway:${var.aws_region}::/apis", "arn:aws:apigateway:${var.aws_region}::/apis/*", "arn:aws:apigateway:${var.aws_region}::/tags/*", ] } statement { sid = "RefreshScheduler" effect = "Allow" actions = [ "scheduler:GetSchedule", "scheduler:ListTagsForResource", ] resources = [ "arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule/default/holiday-*", "arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule/${local.project}/*", "arn:aws:scheduler:${var.aws_region}:${local.account_id}:schedule-group/${local.project}", ] } statement { sid = "RefreshSchedulerList" effect = "Allow" actions = [ "scheduler:ListSchedules", "scheduler:ListScheduleGroups", "scheduler:GetScheduleGroup", ] resources = ["*"] } statement { sid = "RefreshEcs" effect = "Allow" actions = [ "ecs:DescribeClusters", "ecs:DescribeServices", "ecs:DescribeTaskDefinition", "ecs:DescribeTaskSets", "ecs:ListClusters", "ecs:ListServices", "ecs:ListTaskDefinitions", "ecs:ListTagsForResource", ] resources = ["*"] } statement { sid = "RefreshElb" effect = "Allow" actions = [ "elasticloadbalancing:DescribeLoadBalancers", "elasticloadbalancing:DescribeLoadBalancerAttributes", "elasticloadbalancing:DescribeListeners", "elasticloadbalancing:DescribeListenerAttributes", "elasticloadbalancing:DescribeTargetGroups", "elasticloadbalancing:DescribeTargetGroupAttributes", "elasticloadbalancing:DescribeTags", "elasticloadbalancing:DescribeRules", ] resources = ["*"] } statement { sid = "RefreshVpc" effect = "Allow" actions = [ "ec2:DescribeAccountAttributes", "ec2:DescribeAvailabilityZones", "ec2:DescribeInternetGateways", "ec2:DescribeNetworkInterfaces", "ec2:DescribeRouteTables", "ec2:DescribeSecurityGroupRules", "ec2:DescribeSecurityGroups", "ec2:DescribeSubnets", "ec2:DescribeTags", "ec2:DescribeVpcAttribute", "ec2:DescribeVpcs", ] resources = ["*"] } statement { sid = "RefreshEcr" effect = "Allow" actions = [ "ecr:DescribeRepositories", "ecr:DescribeImages", "ecr:GetLifecyclePolicy", "ecr:ListTagsForResource", ] resources = [ "arn:aws:ecr:${var.aws_region}:${local.account_id}:repository/${local.project}", ] } statement { sid = "RefreshSqs" effect = "Allow" actions = [ "sqs:GetQueueAttributes", "sqs:GetQueueUrl", "sqs:ListQueueTags", ] resources = [ "arn:aws:sqs:${var.aws_region}:${local.account_id}:${local.project}-jobs", "arn:aws:sqs:${var.aws_region}:${local.account_id}:${local.project}-jobs-dlq", ] } statement { sid = "RefreshAcm" effect = "Allow" actions = [ "acm:DescribeCertificate", "acm:ListCertificates", "acm:ListTagsForCertificate", "acm:GetCertificate", ] resources = ["*"] } statement { sid = "RefreshSsm" effect = "Allow" actions = [ "ssm:GetParameter", "ssm:GetParameters", "ssm:ListTagsForResource", ] resources = [ "arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/*", ] } statement { sid = "RefreshSsmDescribeParameters" effect = "Allow" actions = ["ssm:DescribeParameters"] resources = ["*"] } statement { sid = "RefreshSecrets" effect = "Allow" actions = [ "secretsmanager:DescribeSecret", "secretsmanager:GetResourcePolicy", "secretsmanager:ListSecretVersionIds", ] resources = [ "arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:afterhours-shift-manager/*", ] } statement { sid = "RefreshSecretsList" effect = "Allow" actions = ["secretsmanager:ListSecrets"] resources = ["*"] } statement { sid = "RefreshAlarms" effect = "Allow" actions = [ "cloudwatch:DescribeAlarms", "cloudwatch:ListTagsForResource", ] resources = ["*"] } 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 } resource "aws_iam_role_policy" "hcptf_apply_services" { name = "afterhours-shift-manager-services" role = aws_iam_role.hcptf_apply.id policy = data.aws_iam_policy_document.hcptf_apply_services.json } # Customer-managed: the apply role already has two inlines (scoped-iam + # services). A third PutRolePolicy exceeds the 10KB combined inline limit # in seahaven-prod. CreatePolicy still needs the hcptf-bootstrap window. resource "aws_iam_policy" "hcptf_apply_ecs" { name = "afterhours-shift-manager-ecs" path = "/tf-managed/" description = "ECS, ALB, ECR, SQS, and VPC permissions for hcptf-afterhours-shift-manager" policy = data.aws_iam_policy_document.hcptf_apply_ecs.json } resource "aws_iam_role_policy" "hcptf_plan_refresh" { name = "afterhours-shift-manager-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_ecs.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", ] }