mirror of
https://github.com/Sea-Haven-Industries/payments-dashboard.git
synced 2026-10-02 06:13:25 +00:00
refactor(iam): forget in-repo HCP exec roles (#120)
* refactor(iam): forget in-repo HCP exec roles Org-baseline owns the apply and plan roles, so this workspace can assume them without a bootstrap window. Prod state forgets the old addresses without destroying the live roles. * ci(terraform): pin the isolation check to v1.0.21 The pre-release pin cloned the private .github repo with the caller token and the Terraform job failed. v1.0.21 loads the checker from the workflow commit.
This commit is contained in:
parent
30a1737345
commit
7c2c806339
11 changed files with 124 additions and 1133 deletions
31
.github/workflows/ci.yaml
vendored
31
.github/workflows/ci.yaml
vendored
|
|
@ -31,29 +31,14 @@ jobs:
|
|||
|
||||
terraform:
|
||||
name: Terraform
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 15
|
||||
defaults:
|
||||
run:
|
||||
working-directory: terraform
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
|
||||
with:
|
||||
terraform_version: "1.16.0"
|
||||
terraform_wrapper: false
|
||||
|
||||
- name: Terraform fmt
|
||||
run: terraform fmt -check -recursive
|
||||
|
||||
- name: Terraform init
|
||||
run: terraform init -backend=false
|
||||
|
||||
- name: Terraform validate
|
||||
run: terraform validate
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-terraform.yaml@47185fa602dffddb8297db5f3525d7c9bc05d7cd # v1.0.21
|
||||
with:
|
||||
terraform-version: "1.16.0"
|
||||
working-directory: terraform
|
||||
app-paths: |
|
||||
src/
|
||||
package.json
|
||||
package-lock.json
|
||||
|
||||
ci:
|
||||
name: ci / ci
|
||||
|
|
|
|||
29
SETUP.md
29
SETUP.md
|
|
@ -26,21 +26,24 @@ copies of the prod secrets.
|
|||
|
||||
## 2. HCP Terraform and GitHub Environments
|
||||
|
||||
Prod already applied. A new workspace (dev) uses one bootstrap window:
|
||||
`hcptf-payments-dashboard`, `hcptf-payments-dashboard-plan`, and
|
||||
`payments-dashboard-lambda-boundary` live in org-baseline stack
|
||||
`seahaven-hcptf`, one pair per account. This repo does not create them.
|
||||
Prod state already has the old copies. `terraform/removed.tf` forgets those
|
||||
addresses and does not destroy them.
|
||||
|
||||
1. Tag the workspace `app:payments-dashboard`. Working directory `terraform`.
|
||||
VCS on `main`. Trigger prefix `terraform/**`. Speculative plans on.
|
||||
Set `environment`, `schedules_enabled=false`, and `boa_base_url`.
|
||||
No project-level variable set.
|
||||
2. From `seahaven-org-baseline`:
|
||||
`scripts/create-hcptf-bootstrap-roles.sh --account <dev|prod> --allow-workspace payments-dashboard-<env>`
|
||||
3. Point that workspace's `TFC_AWS_APPLY_ROLE_ARN` / `TFC_AWS_PLAN_ROLE_ARN` at
|
||||
`hcptf-bootstrap` / `hcptf-bootstrap-plan`. Set `TFC_AWS_PROVIDER_AUTH=true`.
|
||||
4. One manual apply. This creates the scoped `hcptf-*` roles, the Lambda
|
||||
boundary, VPC/NAT, and the rest of the stack.
|
||||
5. Retarget `TFC_AWS_*` to `hcptf-payments-dashboard` /
|
||||
`hcptf-payments-dashboard-plan`. Re-run the create script with no
|
||||
`--allow-workspace`.
|
||||
2. Point `TFC_AWS_APPLY_ROLE_ARN` / `TFC_AWS_PLAN_ROLE_ARN` at
|
||||
`hcptf-payments-dashboard` / `hcptf-payments-dashboard-plan` in that
|
||||
account. Set `TFC_AWS_PROVIDER_AUTH=true`.
|
||||
3. Apply only after `seahaven-hcptf` has created those roles and
|
||||
`arn:aws:iam::<account>:policy/tf-managed/payments-dashboard-lambda-boundary`.
|
||||
|
||||
Prod roles already exist. A dev apply waits until the same names exist in
|
||||
`710827005802`.
|
||||
|
||||
GitHub Environment `dev`: no reviewers. `DEPLOY_ROLE_ARN` is the dev
|
||||
`github_deploy_role_arn`. Environment `prod`: reviewers, branch policy `main`
|
||||
|
|
@ -66,8 +69,10 @@ until cutover.
|
|||
|
||||
Avoid weekday 9am ET and the 16:00/19:00/22:00 UTC intraday slots.
|
||||
|
||||
1. Merge this repo's PR (SAM CD is gone). First HCP apply is the bootstrap
|
||||
window above with `schedules_enabled=false`.
|
||||
1. Prod infrastructure is already applied. Exec roles stay
|
||||
`hcptf-payments-dashboard` and `hcptf-payments-dashboard-plan`, owned by
|
||||
org-baseline stack `seahaven-hcptf`. `schedules_enabled` stays false until
|
||||
cutover.
|
||||
2. Copy DynamoDB `PaymentsDashboard` mgmt → prod. Verify item counts for
|
||||
`payment#`, `boa_recon#`, and `boa_balance#`. Do not copy
|
||||
`seahaven-payments-boa-raw-*`.
|
||||
|
|
|
|||
|
|
@ -52,7 +52,6 @@ resource "aws_apigatewayv2_stage" "default" {
|
|||
depends_on = [
|
||||
aws_apigatewayv2_route.slack_events,
|
||||
aws_apigatewayv2_route.expense_events,
|
||||
aws_iam_role_policy.hcptf_apply_services,
|
||||
]
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,915 +0,0 @@
|
|||
# HCP plan/apply roles for payments-dashboard-prod (PLAT-79 / PLAT-144).
|
||||
# Copy of seahaven-org-baseline/examples/hcptf-workspace-iam/hcp_iam.tf.example
|
||||
# with the payments-dashboard 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 payments-dashboard-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-payments-dashboard /
|
||||
# hcptf-payments-dashboard-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 = "PassExecRolesToLambda"
|
||||
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"]
|
||||
}
|
||||
}
|
||||
|
||||
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" {
|
||||
# checkov:skip=CKV_AWS_111: List/describe, HTTP API log delivery, and VPC/NAT lifecycle APIs require Resource=*. Function, bucket, table, queue, secret, alarm, and SSM writes are ARN-prefixed.
|
||||
statement {
|
||||
sid = "LambdaAll"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"lambda:*",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:lambda:${var.aws_region}:${local.account_id}:function:payments-*",
|
||||
]
|
||||
}
|
||||
|
||||
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/${local.project}-*",
|
||||
]
|
||||
}
|
||||
|
||||
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/payments-*",
|
||||
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/aws/apigateway/${local.project}",
|
||||
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/aws/apigateway/${local.project}:*",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "CloudWatchLogsDescribe"
|
||||
effect = "Allow"
|
||||
actions = ["logs:DescribeLogGroups"]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
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}/*",
|
||||
"arn:aws:s3:::${local.csv_bucket_name}",
|
||||
"arn:aws:s3:::${local.csv_bucket_name}/*",
|
||||
"arn:aws:s3:::${local.boa_raw_bucket_name}",
|
||||
"arn:aws:s3:::${local.boa_raw_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 = "SqsDlq"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"sqs:*",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:sqs:${var.aws_region}:${local.account_id}:payments-processPaymentCsv-async-dlq",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "SqsList"
|
||||
effect = "Allow"
|
||||
actions = ["sqs:ListQueues"]
|
||||
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 = "PaymentsSsm"
|
||||
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}/*",
|
||||
"arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.dynamodb_cmk_ssm}",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "SsmDescribeParameters"
|
||||
effect = "Allow"
|
||||
actions = ["ssm:DescribeParameters"]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "SecretsManagerRead"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"secretsmanager:DescribeSecret",
|
||||
"secretsmanager:GetResourcePolicy",
|
||||
"secretsmanager:ListSecretVersionIds",
|
||||
"secretsmanager:TagResource",
|
||||
"secretsmanager:UntagResource",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:secretsmanager:${var.aws_region}:${local.account_id}:secret:payments-dashboard/*",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "SecretsManagerList"
|
||||
effect = "Allow"
|
||||
actions = ["secretsmanager:ListSecrets"]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "KmsTableCmk"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"kms:DescribeKey",
|
||||
"kms:GetKeyPolicy",
|
||||
"kms:ListResourceTags",
|
||||
"kms:CreateGrant",
|
||||
"kms:ListGrants",
|
||||
"kms:RetireGrant",
|
||||
"kms:Encrypt",
|
||||
"kms:Decrypt",
|
||||
"kms:GenerateDataKey",
|
||||
"kms:GenerateDataKeyWithoutPlaintext",
|
||||
]
|
||||
resources = [local.dynamodb_cmk_arn]
|
||||
}
|
||||
|
||||
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:payments-*",
|
||||
]
|
||||
}
|
||||
|
||||
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 = "Ec2VpcManagement"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ec2:AllocateAddress",
|
||||
"ec2:AssociateRouteTable",
|
||||
"ec2:AttachInternetGateway",
|
||||
"ec2:AuthorizeSecurityGroupEgress",
|
||||
"ec2:AuthorizeSecurityGroupIngress",
|
||||
"ec2:CreateInternetGateway",
|
||||
"ec2:CreateNatGateway",
|
||||
"ec2:CreateRoute",
|
||||
"ec2:CreateRouteTable",
|
||||
"ec2:CreateSecurityGroup",
|
||||
"ec2:CreateSubnet",
|
||||
"ec2:CreateVpc",
|
||||
"ec2:CreateVpcEndpoint",
|
||||
"ec2:CreateTags",
|
||||
"ec2:DeleteInternetGateway",
|
||||
"ec2:DeleteNatGateway",
|
||||
"ec2:DeleteRoute",
|
||||
"ec2:DeleteRouteTable",
|
||||
"ec2:DeleteSecurityGroup",
|
||||
"ec2:DeleteSubnet",
|
||||
"ec2:DeleteVpc",
|
||||
"ec2:DeleteVpcEndpoints",
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeAddresses",
|
||||
"ec2:DescribeAddressesAttribute",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeInternetGateways",
|
||||
"ec2:DescribeNatGateways",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribeRouteTables",
|
||||
"ec2:DescribeSecurityGroupRules",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeVpcAttribute",
|
||||
"ec2:DescribeVpcEndpoints",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DescribePrefixLists",
|
||||
"ec2:DetachInternetGateway",
|
||||
"ec2:DisassociateAddress",
|
||||
"ec2:DisassociateRouteTable",
|
||||
"ec2:ModifySubnetAttribute",
|
||||
"ec2:ModifyVpcAttribute",
|
||||
"ec2:ModifyVpcEndpoint",
|
||||
"ec2:ReleaseAddress",
|
||||
"ec2:RevokeSecurityGroupEgress",
|
||||
"ec2:RevokeSecurityGroupIngress",
|
||||
"ec2:UpdateSecurityGroupRuleDescriptionsEgress",
|
||||
"ec2:UpdateSecurityGroupRuleDescriptionsIngress",
|
||||
]
|
||||
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:payments-*",
|
||||
]
|
||||
}
|
||||
|
||||
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}/*",
|
||||
"arn:aws:s3:::${local.csv_bucket_name}",
|
||||
"arn:aws:s3:::${local.csv_bucket_name}/*",
|
||||
"arn:aws:s3:::${local.boa_raw_bucket_name}",
|
||||
"arn:aws:s3:::${local.boa_raw_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/${local.project}-*",
|
||||
]
|
||||
}
|
||||
|
||||
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 = "RefreshSsm"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters",
|
||||
"ssm:ListTagsForResource",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.ssm_prefix}/*",
|
||||
"arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter${local.dynamodb_cmk_ssm}",
|
||||
]
|
||||
}
|
||||
|
||||
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:payments-dashboard/*",
|
||||
]
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "RefreshSqs"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"sqs:GetQueueAttributes",
|
||||
"sqs:GetQueueUrl",
|
||||
"sqs:ListQueueTags",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:sqs:${var.aws_region}:${local.account_id}:payments-processPaymentCsv-async-dlq",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "RefreshKms"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"kms:DescribeKey",
|
||||
"kms:GetKeyPolicy",
|
||||
"kms:ListResourceTags",
|
||||
"kms:CreateGrant",
|
||||
"kms:ListGrants",
|
||||
]
|
||||
resources = [local.dynamodb_cmk_arn]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "RefreshEc2"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ec2:DescribeAccountAttributes",
|
||||
"ec2:DescribeAddresses",
|
||||
"ec2:DescribeAddressesAttribute",
|
||||
"ec2:DescribeAvailabilityZones",
|
||||
"ec2:DescribeInternetGateways",
|
||||
"ec2:DescribeNatGateways",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DescribeRouteTables",
|
||||
"ec2:DescribeSecurityGroupRules",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeTags",
|
||||
"ec2:DescribeVpcAttribute",
|
||||
"ec2:DescribeVpcEndpoints",
|
||||
"ec2:DescribeVpcs",
|
||||
"ec2:DescribePrefixLists",
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
}
|
||||
|
||||
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" {
|
||||
# checkov:skip=CKV_AWS_111: List/describe, HTTP API log delivery, and VPC/NAT lifecycle APIs require Resource=*. Function, bucket, table, queue, secret, alarm, and SSM writes are ARN-prefixed.
|
||||
name = "payments-dashboard-services"
|
||||
role = aws_iam_role.hcptf_apply.id
|
||||
policy = data.aws_iam_policy_document.hcptf_apply_services.json
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "hcptf_plan_refresh" {
|
||||
# checkov:skip=CKV_AWS_107: ViewOnlyAccess plus this sidecar is the org HCP plan-role pattern (PLAT-144 / afterhours). Sidecar Get* is scoped to this stack's roles, buckets, table, queues, functions, and parameters. It does not add iam:CreateAccessKey, secretsmanager:GetSecretValue, or ssm:GetParameter on *.
|
||||
name = "payments-dashboard-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 = []
|
||||
}
|
||||
|
||||
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",
|
||||
]
|
||||
}
|
||||
|
|
@ -147,7 +147,7 @@ resource "aws_iam_role" "lambda" {
|
|||
path = "/tf-managed/"
|
||||
description = "Lambda execution role for ${each.value.function_name}"
|
||||
assume_role_policy = data.aws_iam_policy_document.lambda_assume.json
|
||||
permissions_boundary = aws_iam_policy.lambda_boundary.arn
|
||||
permissions_boundary = local.lambda_boundary_arn
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "lambda" {
|
||||
|
|
|
|||
|
|
@ -1,147 +0,0 @@
|
|||
# Per-workload Lambda permissions boundary. Created on the first (bootstrap)
|
||||
# apply. The scoped apply role denies iam:CreatePolicy / CreatePolicyVersion,
|
||||
# so later edits to this document need the hcptf-bootstrap window.
|
||||
|
||||
data "aws_iam_policy_document" "lambda_boundary" {
|
||||
# checkov:skip=CKV_AWS_111: AWS requires Resource=* for logs:DescribeLogGroups, xray Put*, and EC2 ENI lifecycle used by VPC Lambdas. Secrets, table, CMK, buckets, DLQ, and invoke are ARN-pinned.
|
||||
statement {
|
||||
sid = "CloudWatchLogsWrite"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"logs:CreateLogGroup",
|
||||
"logs:CreateLogStream",
|
||||
"logs:PutLogEvents",
|
||||
"logs:DescribeLogStreams",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:logs:${var.aws_region}:${local.account_id}:log-group:/aws/lambda*",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "CloudWatchLogsDescribe"
|
||||
effect = "Allow"
|
||||
actions = ["logs:DescribeLogGroups"]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "XRay"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"xray:PutTraceSegments",
|
||||
"xray:PutTelemetryRecords",
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "Ec2Eni"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ec2:CreateNetworkInterface",
|
||||
"ec2:DescribeNetworkInterfaces",
|
||||
"ec2:DeleteNetworkInterface",
|
||||
"ec2:DescribeSubnets",
|
||||
"ec2:DescribeSecurityGroups",
|
||||
"ec2:DescribeVpcs",
|
||||
]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "PaymentsSecrets"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"secretsmanager:GetSecretValue",
|
||||
]
|
||||
resources = [for arn in local.secret_arns : arn]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "PaymentsDynamoDB"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"dynamodb:GetItem",
|
||||
"dynamodb:PutItem",
|
||||
"dynamodb:UpdateItem",
|
||||
"dynamodb:DeleteItem",
|
||||
"dynamodb:Query",
|
||||
"dynamodb:Scan",
|
||||
"dynamodb:BatchGetItem",
|
||||
"dynamodb:BatchWriteItem",
|
||||
"dynamodb:DescribeTable",
|
||||
"dynamodb:ConditionCheckItem",
|
||||
]
|
||||
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 = "PaymentsCmk"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"kms:Decrypt",
|
||||
"kms:GenerateDataKey",
|
||||
"kms:DescribeKey",
|
||||
]
|
||||
resources = [local.dynamodb_cmk_arn]
|
||||
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "kms:ViaService"
|
||||
values = ["dynamodb.${var.aws_region}.amazonaws.com"]
|
||||
}
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "PaymentsCsvRead"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"s3:GetObject",
|
||||
"s3:GetObjectVersion",
|
||||
]
|
||||
resources = ["arn:aws:s3:::${local.csv_bucket_name}/*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "PaymentsBoaRawPut"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"s3:PutObject",
|
||||
]
|
||||
resources = ["arn:aws:s3:::${local.boa_raw_bucket_name}/*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "PaymentsDlqSend"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"sqs:SendMessage",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:sqs:${var.aws_region}:${local.account_id}:payments-processPaymentCsv-async-dlq",
|
||||
]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "PaymentsInvokeExpenseProcessor"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"lambda:InvokeFunction",
|
||||
]
|
||||
resources = [
|
||||
"arn:aws:lambda:${var.aws_region}:${local.account_id}:function:payments-expenseProcessor",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "lambda_boundary" {
|
||||
# checkov:skip=CKV_AWS_111: AWS requires Resource=* for logs:DescribeLogGroups, xray Put*, and EC2 ENI lifecycle used by VPC Lambdas. Secrets, table, CMK, buckets, DLQ, and invoke are ARN-pinned.
|
||||
name = "payments-dashboard-lambda-boundary"
|
||||
path = "/tf-managed/"
|
||||
description = "Per-workload Lambda permissions boundary for payments-dashboard (PLAT-79)."
|
||||
policy = data.aws_iam_policy_document.lambda_boundary.json
|
||||
}
|
||||
|
|
@ -6,11 +6,9 @@ locals {
|
|||
|
||||
hcp_project = "seahaven-${var.environment}"
|
||||
hcp_workspace = "${local.project}-${var.environment}"
|
||||
apply_role = "hcptf-payments-dashboard"
|
||||
plan_role = "hcptf-payments-dashboard-plan"
|
||||
deploy_role = "githubdeploy-payments-dashboard"
|
||||
stack_name = local.project
|
||||
stack_prefix = "payments-dashboard-"
|
||||
# Owned by org-baseline stack seahaven-hcptf. This configuration only references it.
|
||||
lambda_boundary_arn = "arn:aws:iam::${local.account_id}:policy/tf-managed/payments-dashboard-lambda-boundary"
|
||||
|
||||
artifacts_bucket_name = "payments-dashboard-artifacts-${local.account_id}"
|
||||
csv_bucket_name = "seahaven-payments-csv-${local.account_id}"
|
||||
|
|
@ -41,9 +39,8 @@ locals {
|
|||
}
|
||||
dynamodb_cmk_arn = local.dynamodb_cmk_arns[var.environment]
|
||||
|
||||
# Exact ARNs (ticket rule). Hardcoded so the first plan can run as
|
||||
# hcptf-bootstrap-plan, which cannot ssm:GetParameter / DescribeSecret.
|
||||
# Dev values are shells created for this workspace. They are not prod secrets.
|
||||
# Exact ARNs (ticket rule). Dev values are shells created for this workspace.
|
||||
# They are not prod secrets.
|
||||
secret_arns_by_env = {
|
||||
prod = {
|
||||
"payments-dashboard/slack-bot-token" = "arn:aws:secretsmanager:us-east-1:011934824531:secret:payments-dashboard/slack-bot-token-0pAM3S"
|
||||
|
|
|
|||
|
|
@ -42,13 +42,3 @@ output "artifacts_bucket_name" {
|
|||
description = "Lambda artifacts bucket. deploy.yaml uploads functions/<name>/<sha>.zip."
|
||||
value = aws_s3_bucket.artifacts.id
|
||||
}
|
||||
|
||||
output "hcptf_apply_role_arn" {
|
||||
description = "HCP apply role ARN. Set TFC_AWS_APPLY_ROLE_ARN after the bootstrap window."
|
||||
value = aws_iam_role.hcptf_apply.arn
|
||||
}
|
||||
|
||||
output "hcptf_plan_role_arn" {
|
||||
description = "HCP plan role ARN. Set TFC_AWS_PLAN_ROLE_ARN after the bootstrap window."
|
||||
value = aws_iam_role.hcptf_plan.arn
|
||||
}
|
||||
|
|
|
|||
75
terraform/removed.tf
Normal file
75
terraform/removed.tf
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
# hcptf-payments-dashboard, hcptf-payments-dashboard-plan, and the Lambda
|
||||
# boundary moved to the seahaven-hcptf stack in seahaven-org-baseline.
|
||||
# Prod state already contains them. Forget them here. Do not delete the live roles.
|
||||
|
||||
removed {
|
||||
from = aws_iam_role.hcptf_apply
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role.hcptf_plan
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy.hcptf_scoped_iam
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy.hcptf_apply_services
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy.hcptf_plan_refresh
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy_attachment.hcptf_plan_view_only
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy_attachments_exclusive.hcptf_apply
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy_attachments_exclusive.hcptf_plan
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_policy.lambda_boundary
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +1 @@
|
|||
# Secret values stay in Secrets Manager. ARNs are pinned in locals.tf so the
|
||||
# first bootstrap-plan does not need secretsmanager:DescribeSecret.
|
||||
# Secret values stay in Secrets Manager. ARNs are pinned in locals.tf.
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { fileURLToPath } from "node:url";
|
|||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const TERRAFORM = join(ROOT, "terraform");
|
||||
const lambdaTf = readFileSync(join(TERRAFORM, "lambda.tf"), "utf8");
|
||||
const hcpIam = readFileSync(join(TERRAFORM, "hcp_iam.tf"), "utf8");
|
||||
const removed = readFileSync(join(TERRAFORM, "removed.tf"), "utf8");
|
||||
const deploy = readFileSync(join(ROOT, ".github", "workflows", "deploy.yaml"), "utf8");
|
||||
const ci = readFileSync(join(ROOT, ".github", "workflows", "ci.yaml"), "utf8");
|
||||
const locals = readFileSync(join(TERRAFORM, "locals.tf"), "utf8");
|
||||
|
|
@ -43,11 +43,18 @@ describe("HCP Terraform seam (PLAT-79)", () => {
|
|||
assert.match(variables, /contains\(\["dev", "prod"\], var\.environment\)/);
|
||||
});
|
||||
|
||||
it("declares in-repo hcptf roles", () => {
|
||||
assert.match(locals, /apply_role\s+= "hcptf-payments-dashboard"/);
|
||||
assert.match(locals, /plan_role\s+= "hcptf-payments-dashboard-plan"/);
|
||||
assert.match(hcpIam, /hcptf_apply/);
|
||||
assert.match(hcpIam, /DenyCreatePolicy/);
|
||||
it("does not declare in-repo hcptf roles", () => {
|
||||
assert.equal(existsSync(join(TERRAFORM, "hcp_iam.tf")), false);
|
||||
assert.equal(existsSync(join(TERRAFORM, "lambda_boundary.tf")), false);
|
||||
assert.match(lambdaTf, /permissions_boundary\s+=\s+local\.lambda_boundary_arn/);
|
||||
assert.match(
|
||||
locals,
|
||||
/arn:aws:iam::\$\{local\.account_id\}:policy\/tf-managed\/payments-dashboard-lambda-boundary/,
|
||||
);
|
||||
assert.match(removed, /from = aws_iam_role\.hcptf_apply/);
|
||||
assert.match(removed, /from = aws_iam_policy\.lambda_boundary/);
|
||||
assert.match(removed, /destroy\s+=\s+false/);
|
||||
assert.doesNotMatch(locals, /apply_role/);
|
||||
});
|
||||
|
||||
it("calls the Lambda zip reusable for dev and prod", () => {
|
||||
|
|
@ -64,13 +71,14 @@ describe("HCP Terraform seam (PLAT-79)", () => {
|
|||
assert.match(deploy, /function-keys: process_csv,slack_app_home,fetch_boa,expense_receiver,expense_processor/);
|
||||
});
|
||||
|
||||
it("runs npm test and terraform validate behind ci / ci", () => {
|
||||
it("runs npm test and the Terraform callable behind ci / ci", () => {
|
||||
assert.doesNotMatch(ci, /ci-typescript-cdk/);
|
||||
assert.doesNotMatch(ci, /run-sam-validate/);
|
||||
assert.match(ci, /npm test/);
|
||||
assert.match(ci, /terraform fmt -check/);
|
||||
assert.match(ci, /terraform init -backend=false/);
|
||||
assert.match(ci, /terraform validate/);
|
||||
assert.match(ci, /ci-terraform\.yaml@47185fa602dffddb8297db5f3525d7c9bc05d7cd/);
|
||||
assert.match(ci, /src\//);
|
||||
assert.match(ci, /package\.json/);
|
||||
assert.match(ci, /package-lock\.json/);
|
||||
assert.match(ci, /name: ci \/ ci/);
|
||||
});
|
||||
|
||||
|
|
@ -101,9 +109,4 @@ describe("HCP Terraform seam (PLAT-79)", () => {
|
|||
assert.doesNotMatch(variables, /github_deploy_branch/);
|
||||
});
|
||||
|
||||
it("includes provider-6 S3 Get* needed for refresh", () => {
|
||||
assert.match(hcpIam, /s3:GetLifecycleConfiguration/);
|
||||
assert.match(hcpIam, /s3:GetReplicationConfiguration/);
|
||||
assert.match(hcpIam, /s3:GetBucketReplication/);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue