#!/usr/bin/env bash ############################################################################### # setup_procurement_api_domain.sh # # One-time (idempotent) wiring for the procurement-api custom domain # (procurement-api.seahaven.com). CROSS-ACCOUNT: the API + ACM cert live in # seahaven-prod (011934824531), but the seahaven.com public zone lives in the # mgmt account (328440206208), so the cert's DNS-validation record and the # final A-alias are added to the mgmt zone. # # Order of operations: # 1. ./setup_procurement_api_domain.sh cert # - requests (or reuses) the ACM cert in prod, us-east-1 # - adds its DNS-validation CNAME to the mgmt seahaven.com zone # - waits for ISSUED, then writes the cert ARN to prod SSM # (/procurement-api/custom-domain/certificate-arn) # 2. deploy the procurement-api stack (cdk deploy procurement-api) -- it # reads the SSM param and creates the API Gateway DomainName + mapping # 3. ./setup_procurement_api_domain.sh alias # - reads the stack's regional alias target from the outputs # - adds the A-alias (procurement-api.seahaven.com -> API GW) to the # mgmt zone # # Requires SSO sessions for BOTH profiles (prod for ACM/SSM, mgmt for Route53). ############################################################################### set -euo pipefail DOMAIN="procurement-api.seahaven.com" REGION="us-east-1" PROD_PROFILE="${PROD_PROFILE:-seahaven-prod}" MGMT_PROFILE="${MGMT_PROFILE:-seahaven-mgmt}" PROD_ACCOUNT="011934824531" MGMT_ACCOUNT="328440206208" ZONE_ID="Z06652411XKH89KTZD3XA" # seahaven.com public zone, in the mgmt account SSM_PARAM="/procurement-api/custom-domain/certificate-arn" STACK_NAME="procurement-api" _verify_account() { local profile="$1" expected="$2" local got got="$(aws sts get-caller-identity --profile "${profile}" --query Account --output text)" if [[ "${got}" != "${expected}" ]]; then echo "ERROR: profile ${profile} resolves to ${got}, expected ${expected}. Aborting." >&2 exit 1 fi } cmd_cert() { _verify_account "${PROD_PROFILE}" "${PROD_ACCOUNT}" _verify_account "${MGMT_PROFILE}" "${MGMT_ACCOUNT}" echo "==> Finding or requesting ACM cert for ${DOMAIN} in ${PROD_ACCOUNT}/${REGION}" local cert_arn cert_arn="$(aws acm list-certificates --profile "${PROD_PROFILE}" --region "${REGION}" \ --query "CertificateSummaryList[?DomainName=='${DOMAIN}'].CertificateArn | [0]" --output text)" if [[ "${cert_arn}" == "None" || -z "${cert_arn}" ]]; then cert_arn="$(aws acm request-certificate --profile "${PROD_PROFILE}" --region "${REGION}" \ --domain-name "${DOMAIN}" --validation-method DNS \ --query CertificateArn --output text)" echo " requested ${cert_arn}; waiting for the validation record to populate..." sleep 8 else echo " reusing existing ${cert_arn}" fi echo "==> Reading the DNS-validation record" local rec_name rec_value rec_name="$(aws acm describe-certificate --profile "${PROD_PROFILE}" --region "${REGION}" \ --certificate-arn "${cert_arn}" \ --query "Certificate.DomainValidationOptions[0].ResourceRecord.Name" --output text)" rec_value="$(aws acm describe-certificate --profile "${PROD_PROFILE}" --region "${REGION}" \ --certificate-arn "${cert_arn}" \ --query "Certificate.DomainValidationOptions[0].ResourceRecord.Value" --output text)" echo "==> Upserting validation CNAME in the mgmt seahaven.com zone" aws route53 change-resource-record-sets --profile "${MGMT_PROFILE}" \ --hosted-zone-id "${ZONE_ID}" --change-batch "$(cat </dev/null echo "==> Waiting for cert to reach ISSUED (can take a few minutes)" aws acm wait certificate-validated --profile "${PROD_PROFILE}" --region "${REGION}" \ --certificate-arn "${cert_arn}" echo "==> Writing cert ARN to prod SSM ${SSM_PARAM}" aws ssm put-parameter --profile "${PROD_PROFILE}" --region "${REGION}" \ --name "${SSM_PARAM}" --type String --overwrite --value "${cert_arn}" >/dev/null echo "OK: cert ISSUED and SSM param set. Now: cdk deploy ${STACK_NAME}, then '$0 alias'." } cmd_alias() { _verify_account "${PROD_PROFILE}" "${PROD_ACCOUNT}" _verify_account "${MGMT_PROFILE}" "${MGMT_ACCOUNT}" echo "==> Reading regional alias target from the ${STACK_NAME} stack outputs" local target zone target="$(aws cloudformation describe-stacks --profile "${PROD_PROFILE}" --region "${REGION}" \ --stack-name "${STACK_NAME}" \ --query "Stacks[0].Outputs[?OutputKey=='ProcurementApiAliasTarget'].OutputValue | [0]" --output text)" zone="$(aws cloudformation describe-stacks --profile "${PROD_PROFILE}" --region "${REGION}" \ --stack-name "${STACK_NAME}" \ --query "Stacks[0].Outputs[?OutputKey=='ProcurementApiAliasHostedZoneId'].OutputValue | [0]" --output text)" if [[ -z "${target}" || "${target}" == "None" ]]; then echo "ERROR: no alias target output; deploy the stack first." >&2 exit 1 fi echo "==> Upserting A-alias ${DOMAIN} -> ${target} in the mgmt zone" aws route53 change-resource-record-sets --profile "${MGMT_PROFILE}" \ --hosted-zone-id "${ZONE_ID}" --change-batch "$(cat </dev/null echo "OK: A-alias set. Verify: curl -sS -o /dev/null -w '%{http_code}\\n' https://${DOMAIN}/docs (expect 401 without a token)." } case "${1:-}" in cert) cmd_cert ;; alias) cmd_alias ;; *) echo "usage: $0 {cert|alias}" >&2; exit 2 ;; esac