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
|
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:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main]
|
branches: [main, hotfix/**, release/**]
|
||||||
merge_group:
|
merge_group:
|
||||||
|
push:
|
||||||
|
branches: [hotfix/**, release/**]
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|
||||||
jobs:
|
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:
|
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:
|
with:
|
||||||
build-command: "npx @11ty/eleventy"
|
build-command: "npx @11ty/eleventy"
|
||||||
check-dir: "_site"
|
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:
|
jobs:
|
||||||
review:
|
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:
|
with:
|
||||||
# brace-expansion OOM DoS: no in-range fix (patch only in 5.0.8; @11ty/recursive-copy
|
# 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
|
# pins minimatch <10.0.3). Build-time-only exposure, adjudicated in
|
||||||
|
|
|
||||||
102
.github/workflows/deploy.yaml
vendored
102
.github/workflows/deploy.yaml
vendored
|
|
@ -1,9 +1,27 @@
|
||||||
name: Deploy
|
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:
|
on:
|
||||||
push:
|
push:
|
||||||
branches:
|
branches: [main]
|
||||||
- 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:
|
schedule:
|
||||||
# Weekday morning US Eastern (13:00 UTC). Rebuilds listings from Paychex
|
# Weekday morning US Eastern (13:00 UTC). Rebuilds listings from Paychex
|
||||||
# without a commit. A failed feed fetch aborts before the S3 sync.
|
# without a commit. A failed feed fetch aborts before the S3 sync.
|
||||||
|
|
@ -11,72 +29,20 @@ on:
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions:
|
permissions:
|
||||||
id-token: write # Required for OIDC
|
contents: read
|
||||||
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
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
deploy:
|
deploy-prod:
|
||||||
runs-on: ubuntu-latest
|
name: Deploy site to prod
|
||||||
|
uses: Sea-Haven-Industries/.github/.github/workflows/cd-hcp-static.yaml@0a1010e63248c9ca9f042c870eb2c579ba6b9455 # v1.0.19
|
||||||
steps:
|
permissions:
|
||||||
- name: Checkout repository
|
contents: read
|
||||||
uses: actions/checkout@v7.0.1
|
id-token: write
|
||||||
|
secrets: inherit
|
||||||
- name: Set up Node
|
|
||||||
uses: actions/setup-node@v7
|
|
||||||
with:
|
with:
|
||||||
node-version: "24"
|
environment: prod
|
||||||
cache: npm
|
ssm-prefix: /seahaven-site/deploy
|
||||||
|
output-dir: _site
|
||||||
- name: Build site
|
required-paths: _site/index.html,_site/contact/index.html,_site/404.html
|
||||||
run: |
|
min-file-count: 40
|
||||||
npm ci --ignore-scripts
|
ship-gate: true
|
||||||
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 "/*"
|
|
||||||
|
|
|
||||||
2
.github/workflows/labeler.yml
vendored
2
.github/workflows/labeler.yml
vendored
|
|
@ -10,4 +10,4 @@ permissions:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
label:
|
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.
|
- **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`)
|
- **Design system** — CSS custom properties (DM Serif Display + Inter, `--rose: #cc3366`)
|
||||||
- **Forms** — Basin (AJAX submission, reCAPTCHA v3, honeypot spam protection)
|
- **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
|
## Develop
|
||||||
```bash
|
```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" {
|
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 {
|
statement {
|
||||||
|
sid = "LegacyBranch"
|
||||||
effect = "Allow"
|
effect = "Allow"
|
||||||
actions = ["sts:AssumeRoleWithWebIdentity"]
|
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}"]
|
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" {
|
resource "aws_iam_role" "github_deploy" {
|
||||||
name = local.deploy_role
|
name = local.deploy_role
|
||||||
path = "/tf-managed/"
|
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
|
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
|
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" {
|
data "aws_iam_policy_document" "github_deploy" {
|
||||||
|
|
@ -70,10 +104,21 @@ data "aws_iam_policy_document" "github_deploy" {
|
||||||
effect = "Allow"
|
effect = "Allow"
|
||||||
actions = [
|
actions = [
|
||||||
"cloudfront:CreateInvalidation",
|
"cloudfront:CreateInvalidation",
|
||||||
|
"cloudfront:GetDistribution",
|
||||||
"cloudfront:GetInvalidation",
|
"cloudfront:GetInvalidation",
|
||||||
]
|
]
|
||||||
resources = [aws_cloudfront_distribution.site.arn]
|
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" {
|
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