diff --git a/.github/workflows/cdk-diff.yml b/.github/workflows/cdk-diff.yml new file mode 100644 index 00000000..86c5cc6b --- /dev/null +++ b/.github/workflows/cdk-diff.yml @@ -0,0 +1,70 @@ +name: cdk-diff + +on: + pull_request: + branches: [mainline] + paths: + - 'lib/**' + - 'smithy/**' + - '.github/workflows/cdk-diff.yml' + - 'package.json' + - 'package-lock.json' + +concurrency: + group: cdk-diff-${{ github.event.pull_request.number }} + cancel-in-progress: true + +env: + AWS_REGION: "us-west-2" + STAGE: "prod" + +jobs: + diff: + runs-on: ubuntu-latest + environment: prod + permissions: + contents: read + id-token: write + pull-requests: write + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + distribution: corretto + java-version: 17 + cache: gradle + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build Smithy model + working-directory: smithy + run: ./gradlew smithyBuild + + - name: Build TypeScript + run: npm run build + + - uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ secrets.CDK_DEPLOY_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: CDK Synthesize + run: npx cdk synth --all --output build/cdk.out + + - name: CDK Diff + uses: corymhall/cdk-diff-action@v2 + with: + githubToken: ${{ secrets.GITHUB_TOKEN }} + cdkOutDir: build/cdk.out diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 00000000..bac5c456 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,89 @@ +name: deploy + +on: + push: + branches: [mainline] + workflow_dispatch: + +concurrency: + group: deploy-${{ github.ref }} + cancel-in-progress: false + +env: + AWS_REGION: "us-west-2" + STAGE: "prod" + +jobs: + build: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-java@v5 + with: + distribution: corretto + java-version: 17 + cache: gradle + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - name: Build Smithy model + working-directory: smithy + run: ./gradlew smithyBuild + + - name: Build TypeScript + run: npm run build + + - uses: docker/setup-qemu-action@v3 + with: + platforms: arm64 + + - name: CDK synth + run: npx cdk synth --all --output build/cdk.out + + - uses: actions/upload-artifact@v6 + with: + name: cdk-out + path: build/cdk.out + retention-days: 1 + + deploy: + needs: build + runs-on: ubuntu-latest + environment: prod + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-node@v6 + with: + node-version: 24 + cache: npm + + - name: Install dependencies + run: npm ci + + - uses: actions/download-artifact@v7 + with: + name: cdk-out + path: build/cdk.out + + - uses: aws-actions/configure-aws-credentials@v6 + with: + role-to-assume: ${{ secrets.CDK_DEPLOY_ROLE_ARN }} + aws-region: ${{ env.AWS_REGION }} + + - name: Deploy stacks + run: npx cdk deploy *-${{ env.STAGE }} --app build/cdk.out --require-approval never diff --git a/.gitignore b/.gitignore index b73d6ae0..99a0b396 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ cdk.out *.js !.prettierrc.js +smithy/.gradle/ smithy/build build/ __pycache__ diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 8e7795a3..383fd857 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -1352,9 +1352,9 @@ ] }, "node_modules/@tailwindcss/node": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.0.tgz", - "integrity": "sha512-Yv+fn/o2OmL5fh/Ir62VXItdShnUxfpkMA4Y7jdeC8O81WPB8Kf6TT6GSHvnqgSwDzlB5iT7kDpeXxLsUS0T6Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.2.1.tgz", + "integrity": "sha512-jlx6sLk4EOwO6hHe1oCGm1Q4AN/s0rSrTTPBGPM0/RQ6Uylwq17FuU8IeJJKEjtc6K6O07zsvP+gDO6MMWo7pg==", "license": "MIT", "dependencies": { "@jridgewell/remapping": "^2.3.5", @@ -1363,36 +1363,36 @@ "lightningcss": "1.31.1", "magic-string": "^0.30.21", "source-map-js": "^1.2.1", - "tailwindcss": "4.2.0" + "tailwindcss": "4.2.1" } }, "node_modules/@tailwindcss/oxide": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.0.tgz", - "integrity": "sha512-AZqQzADaj742oqn2xjl5JbIOzZB/DGCYF/7bpvhA8KvjUj9HJkag6bBuwZvH1ps6dfgxNHyuJVlzSr2VpMgdTQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.2.1.tgz", + "integrity": "sha512-yv9jeEFWnjKCI6/T3Oq50yQEOqmpmpfzG1hcZsAOaXFQPfzWprWrlHSdGPEF3WQTi8zu8ohC9Mh9J470nT5pUw==", "license": "MIT", "engines": { "node": ">= 20" }, "optionalDependencies": { - "@tailwindcss/oxide-android-arm64": "4.2.0", - "@tailwindcss/oxide-darwin-arm64": "4.2.0", - "@tailwindcss/oxide-darwin-x64": "4.2.0", - "@tailwindcss/oxide-freebsd-x64": "4.2.0", - "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.0", - "@tailwindcss/oxide-linux-arm64-gnu": "4.2.0", - "@tailwindcss/oxide-linux-arm64-musl": "4.2.0", - "@tailwindcss/oxide-linux-x64-gnu": "4.2.0", - "@tailwindcss/oxide-linux-x64-musl": "4.2.0", - "@tailwindcss/oxide-wasm32-wasi": "4.2.0", - "@tailwindcss/oxide-win32-arm64-msvc": "4.2.0", - "@tailwindcss/oxide-win32-x64-msvc": "4.2.0" + "@tailwindcss/oxide-android-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-arm64": "4.2.1", + "@tailwindcss/oxide-darwin-x64": "4.2.1", + "@tailwindcss/oxide-freebsd-x64": "4.2.1", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.2.1", + "@tailwindcss/oxide-linux-arm64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-arm64-musl": "4.2.1", + "@tailwindcss/oxide-linux-x64-gnu": "4.2.1", + "@tailwindcss/oxide-linux-x64-musl": "4.2.1", + "@tailwindcss/oxide-wasm32-wasi": "4.2.1", + "@tailwindcss/oxide-win32-arm64-msvc": "4.2.1", + "@tailwindcss/oxide-win32-x64-msvc": "4.2.1" } }, "node_modules/@tailwindcss/oxide-android-arm64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.0.tgz", - "integrity": "sha512-F0QkHAVaW/JNBWl4CEKWdZ9PMb0khw5DCELAOnu+RtjAfx5Zgw+gqCHFvqg3AirU1IAd181fwOtJQ5I8Yx5wtw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.2.1.tgz", + "integrity": "sha512-eZ7G1Zm5EC8OOKaesIKuw77jw++QJ2lL9N+dDpdQiAB/c/B2wDh0QPFHbkBVrXnwNugvrbJFk1gK2SsVjwWReg==", "cpu": [ "arm64" ], @@ -1406,9 +1406,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-arm64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.0.tgz", - "integrity": "sha512-I0QylkXsBsJMZ4nkUNSR04p6+UptjcwhcVo3Zu828ikiEqHjVmQL9RuQ6uT/cVIiKpvtVA25msu/eRV97JeNSA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.2.1.tgz", + "integrity": "sha512-q/LHkOstoJ7pI1J0q6djesLzRvQSIfEto148ppAd+BVQK0JYjQIFSK3JgYZJa+Yzi0DDa52ZsQx2rqytBnf8Hw==", "cpu": [ "arm64" ], @@ -1422,9 +1422,9 @@ } }, "node_modules/@tailwindcss/oxide-darwin-x64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.0.tgz", - "integrity": "sha512-6TmQIn4p09PBrmnkvbYQ0wbZhLtbaksCDx7Y7R3FYYx0yxNA7xg5KP7dowmQ3d2JVdabIHvs3Hx4K3d5uCf8xg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.2.1.tgz", + "integrity": "sha512-/f/ozlaXGY6QLbpvd/kFTro2l18f7dHKpB+ieXz+Cijl4Mt9AI2rTrpq7V+t04nK+j9XBQHnSMdeQRhbGyt6fw==", "cpu": [ "x64" ], @@ -1438,9 +1438,9 @@ } }, "node_modules/@tailwindcss/oxide-freebsd-x64": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.0.tgz", - "integrity": "sha512-qBudxDvAa2QwGlq9y7VIzhTvp2mLJ6nD/G8/tI70DCDoneaUeLWBJaPcbfzqRIWraj+o969aDQKvKW9dvkUizw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.2.1.tgz", + "integrity": "sha512-5e/AkgYJT/cpbkys/OU2Ei2jdETCLlifwm7ogMC7/hksI2fC3iiq6OcXwjibcIjPung0kRtR3TxEITkqgn0TcA==", "cpu": [ "x64" ], @@ -1454,9 +1454,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.0.tgz", - "integrity": "sha512-7XKkitpy5NIjFZNUQPeUyNJNJn1CJeV7rmMR+exHfTuOsg8rxIO9eNV5TSEnqRcaOK77zQpsyUkBWmPy8FgdSg==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.2.1.tgz", + "integrity": "sha512-Uny1EcVTTmerCKt/1ZuKTkb0x8ZaiuYucg2/kImO5A5Y/kBz41/+j0gxUZl+hTF3xkWpDmHX+TaWhOtba2Fyuw==", "cpu": [ "arm" ], @@ -1470,9 +1470,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.0.tgz", - "integrity": "sha512-Mff5a5Q3WoQR01pGU1gr29hHM1N93xYrKkGXfPw/aRtK4bOc331Ho4Tgfsm5WDGvpevqMpdlkCojT3qlCQbCpA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.2.1.tgz", + "integrity": "sha512-CTrwomI+c7n6aSSQlsPL0roRiNMDQ/YzMD9EjcR+H4f0I1SQ8QqIuPnsVp7QgMkC1Qi8rtkekLkOFjo7OlEFRQ==", "cpu": [ "arm64" ], @@ -1486,9 +1486,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-arm64-musl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.0.tgz", - "integrity": "sha512-XKcSStleEVnbH6W/9DHzZv1YhjE4eSS6zOu2eRtYAIh7aV4o3vIBs+t/B15xlqoxt6ef/0uiqJVB6hkHjWD/0A==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.2.1.tgz", + "integrity": "sha512-WZA0CHRL/SP1TRbA5mp9htsppSEkWuQ4KsSUumYQnyl8ZdT39ntwqmz4IUHGN6p4XdSlYfJwM4rRzZLShHsGAQ==", "cpu": [ "arm64" ], @@ -1502,9 +1502,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-gnu": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.0.tgz", - "integrity": "sha512-/hlXCBqn9K6fi7eAM0RsobHwJYa5V/xzWspVTzxnX+Ft9v6n+30Pz8+RxCn7sQL/vRHHLS30iQPrHQunu6/vJA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.2.1.tgz", + "integrity": "sha512-qMFzxI2YlBOLW5PhblzuSWlWfwLHaneBE0xHzLrBgNtqN6mWfs+qYbhryGSXQjFYB1Dzf5w+LN5qbUTPhW7Y5g==", "cpu": [ "x64" ], @@ -1518,9 +1518,9 @@ } }, "node_modules/@tailwindcss/oxide-linux-x64-musl": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.0.tgz", - "integrity": "sha512-lKUaygq4G7sWkhQbfdRRBkaq4LY39IriqBQ+Gk6l5nKq6Ay2M2ZZb1tlIyRNgZKS8cbErTwuYSor0IIULC0SHw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.2.1.tgz", + "integrity": "sha512-5r1X2FKnCMUPlXTWRYpHdPYUY6a1Ar/t7P24OuiEdEOmms5lyqjDRvVY1yy9Rmioh+AunQ0rWiOTPE8F9A3v5g==", "cpu": [ "x64" ], @@ -1534,9 +1534,9 @@ } }, "node_modules/@tailwindcss/oxide-wasm32-wasi": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.0.tgz", - "integrity": "sha512-xuDjhAsFdUuFP5W9Ze4k/o4AskUtI8bcAGU4puTYprr89QaYFmhYOPfP+d1pH+k9ets6RoE23BXZM1X1jJqoyw==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.2.1.tgz", + "integrity": "sha512-MGFB5cVPvshR85MTJkEvqDUnuNoysrsRxd6vnk1Lf2tbiqNlXpHYZqkqOQalydienEWOHHFyyuTSYRsLfxFJ2Q==", "bundleDependencies": [ "@napi-rs/wasm-runtime", "@emnapi/core", @@ -1621,9 +1621,9 @@ "optional": true }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.0.tgz", - "integrity": "sha512-2UU/15y1sWDEDNJXxEIrfWKC2Yb4YgIW5Xz2fKFqGzFWfoMHWFlfa1EJlGO2Xzjkq/tvSarh9ZTjvbxqWvLLXA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", + "integrity": "sha512-YlUEHRHBGnCMh4Nj4GnqQyBtsshUPdiNroZj8VPkvTZSoHsilRCwXcVKnG9kyi0ZFAS/3u+qKHBdDc81SADTRA==", "cpu": [ "arm64" ], @@ -1637,9 +1637,9 @@ } }, "node_modules/@tailwindcss/oxide-win32-x64-msvc": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.0.tgz", - "integrity": "sha512-CrFadmFoc+z76EV6LPG1jx6XceDsaCG3lFhyLNo/bV9ByPrE+FnBPckXQVP4XRkN76h3Fjt/a+5Er/oA/nCBvQ==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.2.1.tgz", + "integrity": "sha512-rbO34G5sMWWyrN/idLeVxAZgAKWrn5LiR3/I90Q9MkA67s6T1oB0xtTe+0heoBvHSpbU9Mk7i6uwJnpo4u21XQ==", "cpu": [ "x64" ], @@ -1653,14 +1653,14 @@ } }, "node_modules/@tailwindcss/vite": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.0.tgz", - "integrity": "sha512-da9mFCaHpoOgtQiWtDGIikTrSpUFBtIZCG3jy/u2BGV+l/X1/pbxzmIUxNt6JWm19N3WtGi4KlJdSH/Si83WOA==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.2.1.tgz", + "integrity": "sha512-TBf2sJjYeb28jD2U/OhwdW0bbOsxkWPwQ7SrqGf9sVcoYwZj7rkXljroBO9wKBut9XnmQLXanuDUeqQK0lGg/w==", "license": "MIT", "dependencies": { - "@tailwindcss/node": "4.2.0", - "@tailwindcss/oxide": "4.2.0", - "tailwindcss": "4.2.0" + "@tailwindcss/node": "4.2.1", + "@tailwindcss/oxide": "4.2.1", + "tailwindcss": "4.2.1" }, "peerDependencies": { "vite": "^5.2.0 || ^6 || ^7" @@ -3638,9 +3638,9 @@ } }, "node_modules/tailwindcss": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.0.tgz", - "integrity": "sha512-yYzTZ4++b7fNYxFfpnberEEKu43w44aqDMNM9MHMmcKuCH7lL8jJ4yJ7LGHv7rSwiqM0nkiobF9I6cLlpS2P7Q==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.2.1.tgz", + "integrity": "sha512-/tBrSQ36vCleJkAOsy9kbNTgaxvGbyOamC30PRePTQe/o1MFwEKHQk4Cn7BNGaPtjp+PuUrByJehM1hgxfq4sw==", "license": "MIT" }, "node_modules/tapable": { diff --git a/lib/app.ts b/lib/app.ts index 13fabace..11249188 100644 --- a/lib/app.ts +++ b/lib/app.ts @@ -1,11 +1,34 @@ #!/usr/bin/env node import {App} from "aws-cdk-lib"; -import {ACCOUNT_ID, REGION} from "./config"; -import {PipelineStack} from "./stacks/pipeline"; +import {ACCOUNT_ID, REGION, StageType} from "./config"; +import {GitHubOidcStack} from "./stacks/github-oidc"; +import {MonitoringStack} from "./stacks/monitoring"; +import {ServiceStack} from "./stacks/service"; const app = new App(); -new PipelineStack(app, "Pipeline", {env: {account: ACCOUNT_ID, region: REGION}}); +const env = {account: ACCOUNT_ID, region: REGION}; + +new GitHubOidcStack(app, "GitHubOidcStack", { + env, + githubOrg: "layertwo", + githubRepo: "ffsync", +}); + +[StageType.PROD].forEach((stageType) => { + const serviceStack = new ServiceStack(app, `Service-${stageType.toLowerCase()}`, { + env, + stageType, + }); + + new MonitoringStack(app, `Monitoring-${stageType.toLowerCase()}`, { + env, + stageType, + storageApi: serviceStack.storageApi, + storageHandler: serviceStack.storageHandler, + storageTable: serviceStack.storageTable, + }); +}); app.synth(); diff --git a/lib/config.ts b/lib/config.ts index 1d7a90c7..c6ec29a0 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -3,12 +3,7 @@ export const REGION = "us-west-2"; export enum StageType { PROD = "prod", - BETA = "beta", } export const BASE_DOMAIN = "ffsync.layertwo.dev"; export const HOSTED_ZONE_ID = "Z0557162T90BYTET3VI9"; - -// renovate: datasource=github-releases depName=smithy-lang/smithy -export const SMITHY_LANG_VER = "1.67.0"; -export const SMITHY_DOWNLOAD_URL = `https://github.com/smithy-lang/smithy/releases/download/${SMITHY_LANG_VER}/smithy-cli-linux-aarch64.zip`; diff --git a/lib/stacks/github-oidc.ts b/lib/stacks/github-oidc.ts new file mode 100644 index 00000000..dea00438 --- /dev/null +++ b/lib/stacks/github-oidc.ts @@ -0,0 +1,90 @@ +import {Construct} from "constructs"; + +import {CfnOutput, Duration, Stack, StackProps} from "aws-cdk-lib"; +import { + Effect, + OpenIdConnectProvider, + PolicyStatement, + Role, + WebIdentityPrincipal, +} from "aws-cdk-lib/aws-iam"; + +export interface GitHubOidcStackProps extends StackProps { + readonly githubOrg: string; + readonly githubRepo: string; + readonly githubBranch?: string; + readonly githubEnvironment?: string; +} + +// Reference: https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws +export class GitHubOidcStack extends Stack { + public readonly role: Role; + + constructor(scope: Construct, id: string, props: GitHubOidcStackProps) { + super(scope, id, props); + + // Create or reference the GitHub OIDC provider + const githubProvider = new OpenIdConnectProvider(this, "GitHubOidcProvider", { + url: "https://token.actions.githubusercontent.com", + clientIds: ["sts.amazonaws.com"], + }); + + // Build the subject claim for the trust policy + let subjectClaim = `repo:${props.githubOrg}/${props.githubRepo}:`; + + if (props.githubEnvironment) { + subjectClaim += `environment:${props.githubEnvironment}`; + } else if (props.githubBranch) { + subjectClaim += `ref:refs/heads/${props.githubBranch}`; + } else { + // Allow any branch/environment + subjectClaim += "*"; + } + + // Create IAM role that GitHub Actions can assume + this.role = new Role(this, "GitHubActionsRole", { + roleName: "GitHubActionsDeployRole", + assumedBy: new WebIdentityPrincipal(githubProvider.openIdConnectProviderArn, { + StringEquals: { + "token.actions.githubusercontent.com:aud": "sts.amazonaws.com", + }, + StringLike: { + "token.actions.githubusercontent.com:sub": subjectClaim, + }, + }), + maxSessionDuration: Duration.hours(1), + }); + + this.role.addToPolicy( + new PolicyStatement({ + effect: Effect.ALLOW, + actions: ["sts:AssumeRole"], + resources: ["*"], + conditions: { + StringEquals: { + "iam:ResourceTag/aws-cdk:bootstrap-role": [ + "deploy", + "file-publishing", + "image-publishing", + "lookup", + ], + }, + }, + }), + ); + + // Output the role ARN for use in GitHub secrets + new CfnOutput(this, "GitHubActionsRoleArn", { + value: this.role.roleArn, + description: + "ARN of the IAM role for GitHub Actions (add to GitHub secrets as AWS_ROLE_ARN)", + exportName: "GitHubActionsRoleArn", + }); + + // Output the OIDC provider ARN + new CfnOutput(this, "GitHubOidcProviderArn", { + value: githubProvider.openIdConnectProviderArn, + description: "ARN of the GitHub OIDC provider", + }); + } +} diff --git a/lib/stacks/pipeline.ts b/lib/stacks/pipeline.ts deleted file mode 100644 index e9668bd6..00000000 --- a/lib/stacks/pipeline.ts +++ /dev/null @@ -1,92 +0,0 @@ -import {Construct} from "constructs"; - -import {Stack, StackProps, Stage, StageProps} from "aws-cdk-lib"; -import {ComputeType, LinuxArmBuildImage} from "aws-cdk-lib/aws-codebuild"; -import {PipelineType} from "aws-cdk-lib/aws-codepipeline"; -import * as pipelines from "aws-cdk-lib/pipelines"; - -import {ACCOUNT_ID, REGION, SMITHY_DOWNLOAD_URL, StageType} from "../config"; -import {MonitoringStack} from "./monitoring"; -import {ServiceStack} from "./service"; - -const CONNECTION_ARN = - "arn:aws:codeconnections:us-west-2:830583812777:connection/148a4c06-c9f6-4c38-ae99-2e18395e4822"; - -export class PipelineStack extends Stack { - constructor(scope: Construct, id: string, props?: StackProps) { - super(scope, id, props); - - const pipeline = new pipelines.CodePipeline(this, "Pipeline", { - pipelineType: PipelineType.V2, - synth: new pipelines.ShellStep("Synth", { - input: pipelines.CodePipelineSource.connection("layertwo/ffsync", "mainline", { - connectionArn: CONNECTION_ARN, - }), - installCommands: [ - "mkdir -p smithy-install/smithy", - `curl -L ${SMITHY_DOWNLOAD_URL} -o smithy-install/smithy-cli-linux-aarch64.zip`, - "unzip -qo smithy-install/smithy-cli-linux-aarch64.zip -d smithy-install", - "mv smithy-install/smithy-cli-linux-aarch64/* smithy-install/smithy", - "sudo smithy-install/smithy/install", - ], - commands: [ - "npm ci", - "npx smithy build -c smithy/smithy-build.json smithy", - "npm run build", - "npx cdk synth", - ], - primaryOutputDirectory: "build/cdk.out", - }), - selfMutation: true, - codeBuildDefaults: { - buildEnvironment: { - buildImage: LinuxArmBuildImage.AMAZON_LINUX_2_STANDARD_3_0, - computeType: ComputeType.SMALL, - }, - }, - }); - - pipeline.addStage( - new LogicalStage(this, "Beta", { - env: { - account: ACCOUNT_ID, - region: REGION, - }, - stageType: StageType.BETA, - }), - ); - - pipeline.addStage( - new LogicalStage(this, "Prod", { - env: { - account: ACCOUNT_ID, - region: REGION, - }, - stageType: StageType.PROD, - }), - ); - } -} - -export interface LogicalStageProps extends StageProps { - stageType: StageType; -} - -export class LogicalStage extends Stage { - constructor(scope: Construct, id: string, props: LogicalStageProps) { - super(scope, id, props); - - const serviceStack = new ServiceStack(this, `ServiceStack`, { - env: props.env, - stageType: props.stageType, - }); - - new MonitoringStack(this, `MonitoringStack`, { - env: props.env, - stageType: props.stageType, - storageApi: serviceStack.storageApi, - storageHandler: serviceStack.storageHandler, - storageTable: serviceStack.storageTable, - }); - } -} diff --git a/lib/stacks/service.ts b/lib/stacks/service.ts index 5e9907fa..c95fbf59 100644 --- a/lib/stacks/service.ts +++ b/lib/stacks/service.ts @@ -106,10 +106,7 @@ export class ServiceStack extends Stack { pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true, }, - removalPolicy: - this.props.stageType === StageType.PROD - ? RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE - : RemovalPolicy.DESTROY, + removalPolicy: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, }); // Add GSI for efficient user collection queries @@ -141,10 +138,7 @@ export class ServiceStack extends Stack { pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true, }, - removalPolicy: - this.props.stageType === StageType.PROD - ? RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE - : RemovalPolicy.DESTROY, + removalPolicy: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, }); } @@ -161,10 +155,7 @@ export class ServiceStack extends Stack { pointInTimeRecoverySpecification: { pointInTimeRecoveryEnabled: true, }, - removalPolicy: - this.props.stageType === StageType.PROD - ? RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE - : RemovalPolicy.DESTROY, + removalPolicy: RemovalPolicy.RETAIN_ON_UPDATE_OR_DELETE, }); return table; diff --git a/smithy/build.gradle.kts b/smithy/build.gradle.kts new file mode 100644 index 00000000..d6064bc6 --- /dev/null +++ b/smithy/build.gradle.kts @@ -0,0 +1,20 @@ +plugins { + `java-library` + id("software.amazon.smithy.gradle.smithy-jar") version "1.3.0" +} + +repositories { + mavenCentral() +} + +smithy { + outputDirectory.set(file("../build/smithy")) +} + +dependencies { + smithyBuild("software.amazon.smithy:smithy-aws-traits:1.67.0") + smithyBuild("software.amazon.smithy:smithy-aws-apigateway-traits:1.67.0") + smithyBuild("software.amazon.smithy:smithy-validation-model:1.67.0") + smithyBuild("software.amazon.smithy:smithy-openapi:1.67.0") + smithyBuild("software.amazon.smithy:smithy-aws-apigateway-openapi:1.67.0") +} diff --git a/smithy/gradle/wrapper/gradle-wrapper.jar b/smithy/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..61285a65 Binary files /dev/null and b/smithy/gradle/wrapper/gradle-wrapper.jar differ diff --git a/smithy/gradle/wrapper/gradle-wrapper.properties b/smithy/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..cea7a793 --- /dev/null +++ b/smithy/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,7 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip +networkTimeout=10000 +validateDistributionUrl=true +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/smithy/gradlew b/smithy/gradlew new file mode 100755 index 00000000..adff685a --- /dev/null +++ b/smithy/gradlew @@ -0,0 +1,248 @@ +#!/bin/sh + +# +# Copyright © 2015 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# SPDX-License-Identifier: Apache-2.0 +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/smithy/gradlew.bat b/smithy/gradlew.bat new file mode 100644 index 00000000..e509b2dd --- /dev/null +++ b/smithy/gradlew.bat @@ -0,0 +1,93 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 + +goto fail + +:execute +@rem Setup the command line + + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/smithy/settings.gradle.kts b/smithy/settings.gradle.kts new file mode 100644 index 00000000..6a03fc94 --- /dev/null +++ b/smithy/settings.gradle.kts @@ -0,0 +1,8 @@ +rootProject.name = "ffsync-smithy" + +pluginManagement { + repositories { + gradlePluginPortal() + mavenCentral() + } +} diff --git a/smithy/smithy-build.json b/smithy/smithy-build.json index ac4ae033..06eb1db1 100644 --- a/smithy/smithy-build.json +++ b/smithy/smithy-build.json @@ -1,15 +1,6 @@ { "version": "1.0", "sources": ["models"], - "maven": { - "dependencies": [ - "software.amazon.smithy:smithy-aws-traits:1.67.0", - "software.amazon.smithy:smithy-aws-apigateway-traits:1.67.0", - "software.amazon.smithy:smithy-validation-model:1.67.0", - "software.amazon.smithy:smithy-openapi:1.67.0", - "software.amazon.smithy:smithy-aws-apigateway-openapi:1.67.0" - ] - }, "projections": { "storage": { "plugins": { diff --git a/tsconfig.json b/tsconfig.json index 9e5f759c..f4496ecd 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -26,6 +26,7 @@ }, "exclude": [ "node_modules", - "cdk.out" + "cdk.out", + "frontend" ] }