mirror of
https://github.com/Sea-Haven-Industries/seahaven-site.git
synced 2026-09-30 04:13:15 +00:00
feat(ci): deploy the marketing site through the org static caller (PLAT-225) (#69)
* feat(ci): deploy the marketing site through the org static caller (PLAT-225) Prod still ships on merge to main. Exec roles leave this workspace, and the deploy reads the bucket and distribution from SSM. * fix(ci): run the static check after the legacy ci job Both jobs call ci-static, which cancels the other in-progress run on the same ref, so ci-complete never saw both succeed. * fix(ci): address review feedback * fix(ci): address review feedback * fix(ci): address review feedback * fix(ci): address review feedback
This commit is contained in:
parent
f194d65ee5
commit
3a8de4e88b
9 changed files with 237 additions and 541 deletions
59
.github/workflows/ci.yaml
vendored
59
.github/workflows/ci.yaml
vendored
|
|
@ -1,15 +1,70 @@
|
|||
name: CI
|
||||
|
||||
# Parallel static-site and Terraform portions. ci-complete is the check the
|
||||
# CI complete ruleset requires. The ci job remains so main branch protection
|
||||
# still sees ci / ci until this repo is moved onto that ruleset.
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [main]
|
||||
branches: [main, hotfix/**, release/**]
|
||||
merge_group:
|
||||
push:
|
||||
branches: [hotfix/**, release/**]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
if: github.event_name == 'pull_request' && !github.event.pull_request.head.repo.fork
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-autofix.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
permissions:
|
||||
contents: write
|
||||
secrets: inherit
|
||||
with:
|
||||
presets: terraform
|
||||
|
||||
ci:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-static.yaml@9781774f04b824b1182ff41638687f1c01c04361 # v1.0.11
|
||||
needs: autofix
|
||||
if: always() && !cancelled() && (needs.autofix.result == 'skipped' || needs.autofix.outputs.committed != 'true')
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-static.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
with:
|
||||
build-command: "npx @11ty/eleventy"
|
||||
check-dir: "_site"
|
||||
|
||||
# ci-static cancels other in-progress runs of this workflow on the same ref.
|
||||
# Run after the legacy ci job so both calls can finish.
|
||||
static:
|
||||
needs: [autofix, ci]
|
||||
if: always() && !cancelled() && needs.ci.result == 'success' && (needs.autofix.result == 'skipped' || needs.autofix.outputs.committed != 'true')
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-static.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
with:
|
||||
build-command: "npx @11ty/eleventy"
|
||||
check-dir: "_site"
|
||||
|
||||
terraform:
|
||||
needs: autofix
|
||||
if: always() && !cancelled() && (needs.autofix.result == 'skipped' || needs.autofix.outputs.committed != 'true')
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/ci-terraform.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
with:
|
||||
terraform-version: "1.16.0"
|
||||
|
||||
ci-complete:
|
||||
name: ci-complete
|
||||
needs: [autofix, ci, static, terraform]
|
||||
if: always() && !cancelled() && (needs.autofix.result == 'skipped' || needs.autofix.outputs.committed != 'true')
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- name: Require portions
|
||||
env:
|
||||
AUTOFIX: ${{ needs.autofix.result }}
|
||||
CI: ${{ needs.ci.result }}
|
||||
STATIC: ${{ needs.static.result }}
|
||||
TERRAFORM: ${{ needs.terraform.result }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
test "${AUTOFIX}" = success -o "${AUTOFIX}" = skipped
|
||||
test "${CI}" = success
|
||||
test "${STATIC}" = success
|
||||
test "${TERRAFORM}" = success
|
||||
|
|
|
|||
2
.github/workflows/dependency-review.yml
vendored
2
.github/workflows/dependency-review.yml
vendored
|
|
@ -7,7 +7,7 @@ permissions:
|
|||
|
||||
jobs:
|
||||
review:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@9781774f04b824b1182ff41638687f1c01c04361 # v1.0.11
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-dependency-review.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
with:
|
||||
# brace-expansion OOM DoS: no in-range fix (patch only in 5.0.8; @11ty/recursive-copy
|
||||
# pins minimatch <10.0.3). Build-time-only exposure, adjudicated in
|
||||
|
|
|
|||
104
.github/workflows/deploy.yaml
vendored
104
.github/workflows/deploy.yaml
vendored
|
|
@ -1,9 +1,27 @@
|
|||
name: Deploy
|
||||
|
||||
# Static-site CD. The org reusable builds _site/, syncs the bucket root, and
|
||||
# invalidates CloudFront. Terraform owns the bucket and the distribution.
|
||||
# Nothing here creates an HCP run.
|
||||
#
|
||||
# push to main -> prod
|
||||
# weekday cron -> prod (Paychex listings, no commit)
|
||||
# workflow_dispatch -> prod, the main commit only
|
||||
#
|
||||
# Vercel previews branches other than main.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
branches: [main]
|
||||
paths-ignore:
|
||||
# deploy.yaml is included so this transition commit does not start a
|
||||
# prod deploy before the exec role can write the SSM contract.
|
||||
- "terraform/**"
|
||||
- "**/*.md"
|
||||
- ".github/workflows/ci.yaml"
|
||||
- ".github/workflows/deploy.yaml"
|
||||
- ".github/workflows/labeler.yml"
|
||||
- ".github/workflows/dependency-review.yml"
|
||||
schedule:
|
||||
# Weekday morning US Eastern (13:00 UTC). Rebuilds listings from Paychex
|
||||
# without a commit. A failed feed fetch aborts before the S3 sync.
|
||||
|
|
@ -11,72 +29,20 @@ on:
|
|||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
id-token: write # Required for OIDC
|
||||
contents: read # Allows checkout of repo
|
||||
|
||||
# Never cancel a deploy mid-flight: cancelling between the S3 sync and the
|
||||
# CloudFront invalidation (or mid `--delete`) would leave the bucket in a
|
||||
# half-updated state. Queue instead.
|
||||
concurrency:
|
||||
group: deploy-${{ github.ref }}
|
||||
cancel-in-progress: false
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v7.0.1
|
||||
|
||||
- name: Set up Node
|
||||
uses: actions/setup-node@v7
|
||||
with:
|
||||
node-version: "24"
|
||||
cache: npm
|
||||
|
||||
- name: Build site
|
||||
run: |
|
||||
npm ci --ignore-scripts
|
||||
npm run build
|
||||
# Fail closed: never let a silently-empty build reach the --delete sync.
|
||||
test -f _site/index.html
|
||||
test -f _site/contact/index.html
|
||||
test -f _site/404.html
|
||||
count=$(find _site -type f | wc -l)
|
||||
if [ "$count" -lt 40 ]; then
|
||||
echo "::error::build produced only $count files (expected >= 40); aborting deploy"
|
||||
exit 1
|
||||
fi
|
||||
echo "Build OK: $count files."
|
||||
|
||||
- name: Configure AWS credentials using OIDC
|
||||
uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
|
||||
with:
|
||||
role-to-assume: arn:aws:iam::011934824531:role/tf-managed/githubdeploy-seahaven-site
|
||||
aws-region: us-east-1
|
||||
|
||||
- name: Sync build output to S3
|
||||
run: |
|
||||
# 1) Static assets — 1-day browser cache (no filename fingerprinting yet,
|
||||
# so do NOT go immutable). CloudFront /* invalidation below keeps the
|
||||
# edge fresh; this only affects returning visitors' browser cache.
|
||||
aws s3 sync _site/ s3://seahaven-site-prod --no-progress \
|
||||
--exclude "*.html" --exclude "*.xml" --exclude "*.txt" \
|
||||
--cache-control "public, max-age=86400"
|
||||
|
||||
# 2) HTML / sitemap / robots — always revalidate so a deploy is seen immediately.
|
||||
aws s3 sync _site/ s3://seahaven-site-prod --no-progress \
|
||||
--exclude "*" --include "*.html" --include "*.xml" --include "*.txt" \
|
||||
--cache-control "no-cache"
|
||||
|
||||
# 3) Prune files removed from the build. This pass sets no metadata, so
|
||||
# it skips already-uploaded objects (preserving the Cache-Control set
|
||||
# above) and only deletes objects no longer present in _site/.
|
||||
aws s3 sync _site/ s3://seahaven-site-prod --no-progress --delete
|
||||
|
||||
- name: Invalidate CloudFront cache
|
||||
run: |
|
||||
aws cloudfront create-invalidation \
|
||||
--distribution-id E35OCA79OAJ03H \
|
||||
--paths "/*"
|
||||
deploy-prod:
|
||||
name: Deploy site to prod
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/cd-hcp-static.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
secrets: inherit
|
||||
with:
|
||||
environment: prod
|
||||
ssm-prefix: /seahaven-site/deploy
|
||||
output-dir: _site
|
||||
required-paths: _site/index.html,_site/contact/index.html,_site/404.html
|
||||
min-file-count: 40
|
||||
ship-gate: true
|
||||
|
|
|
|||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
|
|
@ -10,4 +10,4 @@ permissions:
|
|||
|
||||
jobs:
|
||||
label:
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@9781774f04b824b1182ff41638687f1c01c04361 # v1.0.11
|
||||
uses: Sea-Haven-Industries/.github/.github/workflows/callable-labeler.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||
|
|
|
|||
|
|
@ -11,7 +11,11 @@ A clean static website for Sea Haven Industries — authored as [Eleventy](https
|
|||
- **Eleventy (11ty)** — thin static build: one base layout + partials, no client framework. Output is plain HTML/CSS/vanilla JS.
|
||||
- **Design system** — CSS custom properties (DM Serif Display + Inter, `--rose: #cc3366`)
|
||||
- **Forms** — Basin (AJAX submission, reCAPTCHA v3, honeypot spam protection)
|
||||
- **Hosting** — S3 + CloudFront, deployed via GitHub Actions OIDC (`npm run build` → sync `_site/`)
|
||||
- **Hosting** — S3 + CloudFront. A merge to `main`, the weekday Paychex cron, and `workflow_dispatch` deploy production through the org static-site workflow. Other branches preview on Vercel (`vercel.json` disables Vercel deploys of `main`).
|
||||
|
||||
## Infrastructure
|
||||
|
||||
HCP Terraform workspace `seahaven-site-prod` applies `terraform/` when `terraform/**` changes on `main`. Speculative plans run on pull requests. The workspace writes `/seahaven-site/deploy/bucket` and `/seahaven-site/deploy/distribution-id`. GitHub Environment `prod` holds `DEPLOY_ROLE_ARN`. Exec roles `hcptf-seahaven-site` and `hcptf-seahaven-site-plan` live in the `seahaven-site-hcptf` stack.
|
||||
|
||||
## Develop
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1,455 +0,0 @@
|
|||
# HCP plan/apply roles imported from seahaven-terraform-substrate (PLAT-146).
|
||||
# Import, do not recreate. Role names stay hcptf-seahaven-site / hcptf-seahaven-site-plan.
|
||||
#
|
||||
# Live seahaven-hcptf-iam-management DenySelfMutation blocks DetachRolePolicy
|
||||
# and PutRolePolicy on hcptf-* (including this role). Import apply sequence:
|
||||
# 1. seahaven-org-baseline scripts/create-hcptf-bootstrap-roles.sh
|
||||
# --account prod --allow-workspace seahaven-site-prod
|
||||
# 2. Point this workspace's TFC_AWS_* at hcptf-bootstrap /
|
||||
# hcptf-bootstrap-plan (workspace vars, never a project set).
|
||||
# 3. One Manual apply (import + detach seahaven-hcptf-iam-management +
|
||||
# put scoped inline).
|
||||
# 4. Point TFC_AWS_* back at hcptf-seahaven-site / hcptf-seahaven-site-plan.
|
||||
# 5. Re-run the script without --allow-workspace to pin trust back to
|
||||
# iam-bootstrap-prod only.
|
||||
# This stack has no Lambda execution-role boundary pin.
|
||||
|
||||
import {
|
||||
to = aws_iam_role.hcptf_apply
|
||||
id = "hcptf-seahaven-site"
|
||||
}
|
||||
|
||||
import {
|
||||
to = aws_iam_role.hcptf_plan
|
||||
id = "hcptf-seahaven-site-plan"
|
||||
}
|
||||
|
||||
import {
|
||||
to = aws_iam_role_policy.hcptf_apply_services
|
||||
id = "hcptf-seahaven-site:seahaven-site-services"
|
||||
}
|
||||
|
||||
import {
|
||||
to = aws_iam_role_policy.hcptf_plan_refresh
|
||||
id = "hcptf-seahaven-site-plan:seahaven-site-plan-refresh"
|
||||
}
|
||||
|
||||
import {
|
||||
to = aws_iam_role_policy_attachments_exclusive.hcptf_apply
|
||||
id = "hcptf-seahaven-site"
|
||||
}
|
||||
|
||||
import {
|
||||
to = aws_iam_role_policy_attachment.hcptf_plan_viewonly
|
||||
id = "hcptf-seahaven-site-plan/arn:aws:iam::aws:policy/job-function/ViewOnlyAccess"
|
||||
}
|
||||
|
||||
import {
|
||||
to = aws_iam_role_policy_attachments_exclusive.hcptf_plan
|
||||
id = "hcptf-seahaven-site-plan"
|
||||
}
|
||||
|
||||
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:seahaven-prod:workspace:seahaven-site-prod: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:seahaven-prod:workspace:seahaven-site-prod:run_phase:plan",
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "hcptf_scoped_iam" {
|
||||
statement {
|
||||
sid = "DenyCreatePolicy"
|
||||
effect = "Deny"
|
||||
actions = ["iam:CreatePolicy", "iam:CreatePolicyVersion"]
|
||||
resources = ["*"]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "WriteDeployRoles"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"iam:AttachRolePolicy",
|
||||
"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/githubdeploy-seahaven-site"]
|
||||
}
|
||||
|
||||
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-*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "hcptf_apply_services" {
|
||||
name = "seahaven-site-services"
|
||||
role = aws_iam_role.hcptf_apply.id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Action = [
|
||||
"s3:*",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:s3:::seahaven-site-prod",
|
||||
"arn:aws:s3:::seahaven-site-prod/*",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "OriginBucket"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"iam:GetOpenIDConnectProvider",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:iam::${local.account_id}:oidc-provider/token.actions.githubusercontent.com",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "ReadGithubOidcProvider"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"cloudfront:*",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "CloudFrontManage"
|
||||
},
|
||||
{
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:RequestTag/Project" = "seahaven-site"
|
||||
}
|
||||
}
|
||||
Action = [
|
||||
"acm:RequestCertificate",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "AcmCreate"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"acm:ListCertificates",
|
||||
"acm:ListTagsForCertificate",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "AcmList"
|
||||
},
|
||||
{
|
||||
Condition = {
|
||||
StringEquals = {
|
||||
"aws:ResourceTag/Project" = "seahaven-site"
|
||||
}
|
||||
}
|
||||
Action = [
|
||||
"acm:DescribeCertificate",
|
||||
"acm:GetCertificate",
|
||||
"acm:DeleteCertificate",
|
||||
"acm:AddTagsToCertificate",
|
||||
"acm:RemoveTagsFromCertificate",
|
||||
"acm:RenewCertificate",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "AcmManageTagged"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:ssm:us-east-1:${local.account_id}:parameter/seahaven/waf/app-web-acl-arn",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "ReadAppWebAclSsm"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"wafv2:GetWebACL",
|
||||
"wafv2:GetWebACLForResource",
|
||||
"wafv2:ListWebACLs",
|
||||
"wafv2:ListResourcesForWebACL",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "ReadWafWebAcl"
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "hcptf_plan_refresh" {
|
||||
name = "seahaven-site-plan-refresh"
|
||||
role = aws_iam_role.hcptf_plan.id
|
||||
policy = jsonencode({
|
||||
Version = "2012-10-17"
|
||||
Statement = [
|
||||
{
|
||||
Action = [
|
||||
"iam:GetRole",
|
||||
"iam:GetRolePolicy",
|
||||
"iam:ListRolePolicies",
|
||||
"iam:ListAttachedRolePolicies",
|
||||
"iam:ListRoleTags",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:iam::${local.account_id}:role/tf-managed/githubdeploy-seahaven-site",
|
||||
"arn:aws:iam::${local.account_id}:role/hcptf-seahaven-site",
|
||||
"arn:aws:iam::${local.account_id}:role/hcptf-seahaven-site-plan",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshDeployRole"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"iam:GetOpenIDConnectProvider",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:iam::${local.account_id}:oidc-provider/token.actions.githubusercontent.com",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshGithubOidcProvider"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"iam:GetPolicy",
|
||||
"iam:GetPolicyVersion",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshManagedPolicies"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"s3:Get*",
|
||||
"s3:ListBucket",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:s3:::seahaven-site-prod",
|
||||
"arn:aws:s3:::seahaven-site-prod/*",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshOriginBucket"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"cloudfront:Get*",
|
||||
"cloudfront:List*",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshCloudFront"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"cloudfront:DescribeFunction",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:cloudfront::${local.account_id}:function/seahaven-site-prod-directory-index",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshCloudFrontFunction"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"acm:DescribeCertificate",
|
||||
"acm:ListCertificates",
|
||||
"acm:ListTagsForCertificate",
|
||||
"acm:GetCertificate",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshAcm"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters",
|
||||
]
|
||||
Resource = [
|
||||
"arn:aws:ssm:us-east-1:${local.account_id}:parameter/seahaven/waf/app-web-acl-arn",
|
||||
]
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshAppWebAclSsm"
|
||||
},
|
||||
{
|
||||
Action = [
|
||||
"wafv2:GetWebACL",
|
||||
"wafv2:ListWebACLs",
|
||||
]
|
||||
Resource = "*"
|
||||
Effect = "Allow"
|
||||
Sid = "RefreshWafWebAcl"
|
||||
},
|
||||
]
|
||||
})
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "hcptf_apply" {
|
||||
name = "hcptf-seahaven-site"
|
||||
assume_role_policy = data.aws_iam_policy_document.hcptf_apply_trust.json
|
||||
max_session_duration = 3600
|
||||
|
||||
tags = {
|
||||
Project = "seahaven-site"
|
||||
Owner = "adam@seahavenind.com"
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
|
||||
# Empty exclusive set keeps seahaven-hcptf-iam-management detached.
|
||||
resource "aws_iam_role_policy_attachments_exclusive" "hcptf_apply" {
|
||||
role_name = aws_iam_role.hcptf_apply.name
|
||||
policy_arns = []
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "hcptf_plan" {
|
||||
name = "hcptf-seahaven-site-plan"
|
||||
assume_role_policy = data.aws_iam_policy_document.hcptf_plan_trust.json
|
||||
max_session_duration = 3600
|
||||
|
||||
tags = {
|
||||
Project = "seahaven-site"
|
||||
Owner = "adam@seahavenind.com"
|
||||
ManagedBy = "terraform"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy_attachment" "hcptf_plan_viewonly" {
|
||||
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_plan" {
|
||||
role_name = aws_iam_role.hcptf_plan.name
|
||||
policy_arns = [
|
||||
aws_iam_role_policy_attachment.hcptf_plan_viewonly.policy_arn,
|
||||
]
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -1,5 +1,8 @@
|
|||
data "aws_iam_policy_document" "github_deploy_assume" {
|
||||
# Legacy branch trust. Remove after one deploy through cd-hcp-static has
|
||||
# succeeded. The branch subject stays until EnvironmentProd is applied.
|
||||
statement {
|
||||
sid = "LegacyBranch"
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRoleWithWebIdentity"]
|
||||
|
||||
|
|
@ -20,25 +23,56 @@ data "aws_iam_policy_document" "github_deploy_assume" {
|
|||
values = ["repo:${var.github_repo}:ref:refs/heads/${var.github_deploy_branch}"]
|
||||
}
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "EnvironmentProd"
|
||||
effect = "Allow"
|
||||
actions = ["sts:AssumeRoleWithWebIdentity"]
|
||||
|
||||
principals {
|
||||
type = "Federated"
|
||||
identifiers = [data.aws_iam_openid_connect_provider.github.arn]
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "token.actions.githubusercontent.com:aud"
|
||||
values = ["sts.amazonaws.com"]
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "StringEquals"
|
||||
variable = "token.actions.githubusercontent.com:sub"
|
||||
values = ["repo:${var.github_repo}:environment:prod"]
|
||||
}
|
||||
|
||||
condition {
|
||||
test = "StringLike"
|
||||
variable = "token.actions.githubusercontent.com:job_workflow_ref"
|
||||
values = ["Sea-Haven-Industries/.github/.github/workflows/cd-hcp-static.yaml@*"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_policy" "github_deploy_boundary" {
|
||||
name = "seahaven-site-githubdeploy-boundary"
|
||||
path = "/tf-managed/"
|
||||
description = "Permissions boundary for githubdeploy-seahaven-site"
|
||||
policy = data.aws_iam_policy_document.github_deploy.json
|
||||
|
||||
# Request tag the hcptf apply role requires before it may CreatePolicy.
|
||||
tags = {
|
||||
BoundaryFor = "githubdeploy-seahaven-site"
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role" "github_deploy" {
|
||||
name = local.deploy_role
|
||||
path = "/tf-managed/"
|
||||
description = "GitHub Actions content-deploy role for ${var.github_repo}@${var.github_deploy_branch}"
|
||||
description = "GitHub Actions content-deploy role for ${var.github_repo} Environment prod"
|
||||
assume_role_policy = data.aws_iam_policy_document.github_deploy_assume.json
|
||||
permissions_boundary = aws_iam_policy.github_deploy_boundary.arn
|
||||
max_session_duration = 3600
|
||||
|
||||
# Not a Lambda execution role. Config omits permissions_boundary so a later
|
||||
# apply will not PutRolePermissionsBoundary the Lambda ceiling back. Live still
|
||||
# has seahaven-lambda-execution-boundary; omitting without ignore_changes would
|
||||
# plan DeleteRolePermissionsBoundary, which hcptf-seahaven-site is denied
|
||||
# (DenyBoundaryTampering). Ignore the attribute so this apply does not touch
|
||||
# the ceiling. An administrator deletes the live attachment, then a follow-up
|
||||
# drops this lifecycle after refresh-only updates state to null.
|
||||
lifecycle {
|
||||
ignore_changes = [permissions_boundary]
|
||||
}
|
||||
}
|
||||
|
||||
data "aws_iam_policy_document" "github_deploy" {
|
||||
|
|
@ -70,10 +104,21 @@ data "aws_iam_policy_document" "github_deploy" {
|
|||
effect = "Allow"
|
||||
actions = [
|
||||
"cloudfront:CreateInvalidation",
|
||||
"cloudfront:GetDistribution",
|
||||
"cloudfront:GetInvalidation",
|
||||
]
|
||||
resources = [aws_cloudfront_distribution.site.arn]
|
||||
}
|
||||
|
||||
statement {
|
||||
sid = "ReadDeployContract"
|
||||
effect = "Allow"
|
||||
actions = [
|
||||
"ssm:GetParameter",
|
||||
"ssm:GetParameters",
|
||||
]
|
||||
resources = ["arn:aws:ssm:${var.aws_region}:${local.account_id}:parameter/seahaven-site/deploy/*"]
|
||||
}
|
||||
}
|
||||
|
||||
resource "aws_iam_role_policy" "github_deploy" {
|
||||
|
|
|
|||
67
terraform/removed.tf
Normal file
67
terraform/removed.tf
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# hcptf-seahaven-site and hcptf-seahaven-site-plan moved to the
|
||||
# seahaven-site-hcptf stack in seahaven-org-baseline (PLAT-225).
|
||||
# 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_apply_services
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy.hcptf_scoped_iam
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy.hcptf_plan_refresh
|
||||
|
||||
lifecycle {
|
||||
destroy = false
|
||||
}
|
||||
}
|
||||
|
||||
removed {
|
||||
from = aws_iam_role_policy_attachment.hcptf_plan_viewonly
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
14
terraform/ssm.tf
Normal file
14
terraform/ssm.tf
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Names the static-site deploy workflow reads. Terraform does not manage the
|
||||
# objects in the bucket, so a content deploy does not change this plan.
|
||||
|
||||
resource "aws_ssm_parameter" "deploy_bucket" {
|
||||
name = "/seahaven-site/deploy/bucket"
|
||||
type = "String"
|
||||
value = aws_s3_bucket.origin.bucket
|
||||
}
|
||||
|
||||
resource "aws_ssm_parameter" "deploy_distribution_id" {
|
||||
name = "/seahaven-site/deploy/distribution-id"
|
||||
type = "String"
|
||||
value = aws_cloudfront_distribution.site.id
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue