diff --git a/scripts/test-verify-cloudfront-release.sh b/scripts/test-verify-cloudfront-release.sh index 8e192eae..fcbc6c2d 100755 --- a/scripts/test-verify-cloudfront-release.sh +++ b/scripts/test-verify-cloudfront-release.sh @@ -8,7 +8,7 @@ CURRENT="bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb-2-1" PREVIOUS="aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa-1-1" NEW_HASH="1111111111111111111111111111111111111111111111111111111111111111" OLD_HASH="0000000000000000000000000000000000000000000000000000000000000000" -INDEX_HTML='api.dev.seahaven.com' +INDEX_HTML='' INDEX_HASH="$(printf '%s' "${INDEX_HTML}" | python3 -c 'import hashlib,sys; print(hashlib.sha256(sys.stdin.buffer.read()).hexdigest())')" failures=0 @@ -66,8 +66,13 @@ if [[ "${method}" == "OPTIONS" ]]; then fi if [[ "${url}" == *"/assets/"* ]]; then [[ -n "${dump}" ]] && printf 'HTTP/1.1 200 OK\nCache-Control: public,max-age=31536000,immutable\n\n' > "${dump}" - [[ -n "${output}" ]] && printf 'asset' > "${output}" - [[ -z "${output}" ]] && printf 'asset' + if [[ -f "${state_dir}/asset.js" ]]; then + body="$(cat "${state_dir}/asset.js")" + else + body='const api="https://api.dev.seahaven.com/api";' + fi + [[ -n "${output}" ]] && printf '%s' "${body}" > "${output}" + [[ -z "${output}" ]] && printf '%s' "${body}" exit 0 fi body="$(cat "${state_dir}/index.html")" @@ -244,6 +249,50 @@ AWS rm -rf "${dir}" } +# 6. Hash-matched Deployed release whose JS assets omit the baked API URL. +{ + dir="$(mktemp -d)" + make_stubs "${dir}/bin" + pointer_json "${CURRENT}" "${PREVIOUS}" > "${dir}/pointer.json" + dist_json "Deployed" "/releases/${CURRENT}" > "${dir}/distribution.json" + printf '%s' "${INDEX_HTML}" > "${dir}/index.html" + printf 'const x=1;' > "${dir}/asset.js" + export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}" + export DISTRIBUTION_ID="E2CWLM1AFB964P" EXPECTED_LABEL="${CURRENT}" + export EXPECTED_INDEX_SHA256="${INDEX_HASH}" PREVIOUS_INDEX_SHA256="${OLD_HASH}" + export SITE_URL="https://dev.seahaven.com" SITE_BUCKET="seahaven-shoc-frontend-dev" + export BUDGET=2 INTERVAL=0 + set +e + bash "${VERIFY}" > "${dir}/log.txt" 2>&1 + code=$? + set -e + assert_exit "missing-baked-api-url" 1 "${code}" "${dir}/log.txt" + grep -q "baked dev API URL" "${dir}/log.txt" || { echo "FAIL: missing API URL did not name baked dev API URL" >&2; failures=$((failures + 1)); } + rm -rf "${dir}" +} + +# 7. Hash-matched Deployed release whose JS assets contain the staging API URL. +{ + dir="$(mktemp -d)" + make_stubs "${dir}/bin" + pointer_json "${CURRENT}" "${PREVIOUS}" > "${dir}/pointer.json" + dist_json "Deployed" "/releases/${CURRENT}" > "${dir}/distribution.json" + printf '%s' "${INDEX_HTML}" > "${dir}/index.html" + printf 'const api="https://api.staging.seahaven.com/api";' > "${dir}/asset.js" + export STUB_STATE="${dir}" PATH="${dir}/bin:${PATH}" + export DISTRIBUTION_ID="E2CWLM1AFB964P" EXPECTED_LABEL="${CURRENT}" + export EXPECTED_INDEX_SHA256="${INDEX_HASH}" PREVIOUS_INDEX_SHA256="${OLD_HASH}" + export SITE_URL="https://dev.seahaven.com" SITE_BUCKET="seahaven-shoc-frontend-dev" + export BUDGET=2 INTERVAL=0 + set +e + bash "${VERIFY}" > "${dir}/log.txt" 2>&1 + code=$? + set -e + assert_exit "forbidden-staging-api-url" 1 "${code}" "${dir}/log.txt" + grep -q "forbidden URL api.staging.seahaven.com" "${dir}/log.txt" || { echo "FAIL: staging API URL did not name forbidden URL" >&2; failures=$((failures + 1)); } + rm -rf "${dir}" +} + if [[ "${failures}" -ne 0 ]]; then echo "FAIL: ${failures} verify-cloudfront-release cases failed" >&2 exit 1 diff --git a/scripts/verify-cloudfront-release.sh b/scripts/verify-cloudfront-release.sh index e46ee221..2382c37c 100755 --- a/scripts/verify-cloudfront-release.sh +++ b/scripts/verify-cloudfront-release.sh @@ -129,6 +129,57 @@ if [[ "${last_status}" != "Deployed" || "${last_hash}" != "${EXPECTED_INDEX_SHA2 exit 1 fi +write_asset_paths() { + python3 -c ' +import re, sys +html = open(sys.argv[1], encoding="utf-8").read() +seen = [] +for path in re.findall(r"(?:src|href)=\"(/assets/[^\"]+\.(?:js|css))\"", html): + if path not in seen: + seen.append(path) + print(path) +' "$1" +} + +assert_baked_api_url() { + local tmp="$1" + if [[ ! -s "${tmp}/asset-paths.txt" ]]; then + echo "FAIL: served index.html has no /assets/ URL to check immutable caching." >&2 + exit 1 + fi + : > "${tmp}/assets.txt" + local immutable_ok="no" + local asset_path + while IFS= read -r asset_path; do + curl -fsS --max-time 30 "${SITE_URL}${asset_path}" \ + -o "${tmp}/asset-body" -D "${tmp}/asset.headers" + cat "${tmp}/asset-body" >> "${tmp}/assets.txt" + if [[ "${asset_path}" == *.js && "${immutable_ok}" == "no" ]]; then + if ! grep -qiE 'cache-control:.*immutable' "${tmp}/asset.headers"; then + echo "FAIL: hashed asset is missing Cache-Control immutable." >&2 + exit 1 + fi + immutable_ok="yes" + fi + done < "${tmp}/asset-paths.txt" + if [[ "${immutable_ok}" != "yes" ]]; then + echo "FAIL: served index.html has no hashed JS asset to check immutable caching." >&2 + exit 1 + fi + cat "${tmp}/index.html" "${tmp}/assets.txt" > "${tmp}/served.txt" + local forbidden + for forbidden in api.staging.seahaven.com localhost:5141; do + if grep -Fq "${forbidden}" "${tmp}/served.txt"; then + echo "FAIL: served assets contain forbidden URL ${forbidden}." >&2 + exit 1 + fi + done + if ! grep -Fq "api.dev.seahaven.com" "${tmp}/served.txt"; then + echo "FAIL: served JS assets are missing the baked dev API URL." >&2 + exit 1 + fi +} + tmp="$(mktemp -d)" trap 'rm -rf "${tmp}"' EXIT @@ -139,27 +190,8 @@ if ! grep -qiE 'cache-control:.*no-store' "${tmp}/index.headers"; then echo "FAIL: HTML Cache-Control is missing no-store." >&2 exit 1 fi -for forbidden in api.staging.seahaven.com localhost:5141; do - if grep -Fq "${forbidden}" "${tmp}/index.html"; then - echo "FAIL: served index contains forbidden URL ${forbidden}." >&2 - exit 1 - fi -done -if ! grep -Fq "api.dev.seahaven.com" "${tmp}/index.html"; then - echo "FAIL: served index is missing the dev API URL." >&2 - exit 1 -fi - -asset_path="$(python3 -c 'import re,sys; html=open(sys.argv[1],encoding="utf-8").read(); m=re.search(r"(/assets/[^\"'\'']+)", html); print(m.group(1) if m else "")' "${tmp}/index.html")" -if [[ -z "${asset_path}" ]]; then - echo "FAIL: served index.html has no /assets/ URL to check immutable caching." >&2 - exit 1 -fi -curl -fsS --max-time 30 "${SITE_URL}${asset_path}" -o /dev/null -D "${tmp}/asset.headers" -if ! grep -qiE 'cache-control:.*immutable' "${tmp}/asset.headers"; then - echo "FAIL: hashed asset is missing Cache-Control immutable." >&2 - exit 1 -fi +write_asset_paths "${tmp}/index.html" > "${tmp}/asset-paths.txt" +assert_baked_api_url "${tmp}" cors_code="$(curl -sS --max-time 30 -o /dev/null -D "${tmp}/cors.headers" -w '%{http_code}' -X OPTIONS "${API_URL}" \ -H "Origin: ${SITE_URL}" \