-
Notifications
You must be signed in to change notification settings - Fork 524
ci(repo): add the cli-artifacts S3 bucket and publisher role stack #6690
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
avallete
wants to merge
4
commits into
develop
Choose a base branch
from
avallete/vigilant-fermi-d4khh7
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+285
−0
Open
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
fedb4d4
ci(repo): add the cli-artifacts S3 bucket and publisher role stack
avallete 368bcf2
ci(repo): harden the cli-artifacts stack deploy
avallete 1b7f256
ci(repo): tighten the cli-artifacts stack after review
avallete 7071a19
docs(repo): state the branch-scoped trust of the publisher role
avallete File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| name: CLI Artifacts Infra | ||
|
|
||
| # Deploys infra/cli-artifacts/template.yaml into the cli-artifacts-prod AWS account: the | ||
| # public-read S3 bucket that mirrors slim native artifacts and the PutObject-only role the | ||
| # mirror-slim-image workflow assumes. Only develop can assume github-deploy in that account, so | ||
| # pull requests lint the template and never touch AWS. Details: infra/cli-artifacts/README.md. | ||
|
|
||
| on: | ||
| pull_request: | ||
| paths: | ||
| - infra/cli-artifacts/** | ||
| - .github/workflows/cli-artifacts-infra.yml | ||
| push: | ||
| branches: | ||
| - develop | ||
| paths: | ||
| - infra/cli-artifacts/** | ||
| - .github/workflows/cli-artifacts-infra.yml | ||
| workflow_dispatch: | ||
|
|
||
| permissions: {} | ||
|
|
||
| # Lint runs are per pull request or ref; deploys serialize on their own group below. | ||
| concurrency: | ||
| group: cli-artifacts-infra-${{ github.event.pull_request.number || github.ref }} | ||
| cancel-in-progress: ${{ github.event_name == 'pull_request' }} | ||
|
|
||
| jobs: | ||
| lint: | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 10 | ||
| permissions: | ||
| contents: read | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Install cfn-lint | ||
| run: | | ||
| set -euo pipefail | ||
| python3 -m pip install --user --quiet "cfn-lint==1.57.0" | ||
| echo "${HOME}/.local/bin" >> "$GITHUB_PATH" | ||
|
|
||
| - name: Lint template | ||
| run: cfn-lint infra/cli-artifacts/template.yaml | ||
|
|
||
| deploy: | ||
| needs: lint | ||
| if: github.event_name != 'pull_request' && github.ref == 'refs/heads/develop' && vars.CLI_ARTIFACTS_AWS_ACCOUNT_ID != '' | ||
| runs-on: ubuntu-latest | ||
| timeout-minutes: 20 | ||
| concurrency: | ||
| group: cli-artifacts-infra-deploy | ||
| cancel-in-progress: false | ||
| permissions: | ||
| contents: read | ||
| id-token: write | ||
| env: | ||
| # Passed explicitly on every deploy: `cloudformation deploy` keeps an existing stack's | ||
| # previous parameter values unless they are overridden, so template defaults alone | ||
| # would never update a live stack. | ||
| BUCKET_NAME: supabase-cli-artifacts | ||
| TRUSTED_SUBJECT: repo:supabase/cli:ref:refs/heads/develop | ||
| steps: | ||
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| - name: Configure aws credentials | ||
| uses: aws-actions/configure-aws-credentials@cbe3b392738ccf3f987d68400dafcf4b0624a56c # v6.2.4 | ||
| with: | ||
| role-to-assume: arn:aws:iam::${{ vars.CLI_ARTIFACTS_AWS_ACCOUNT_ID }}:role/github-deploy | ||
| aws-region: us-east-1 | ||
|
|
||
| - name: Clear a failed initial create | ||
| # A create that rolled back leaves the stack in ROLLBACK_COMPLETE, which cannot be | ||
| # updated; it holds no resources, so deleting it is the only way to retry. | ||
| run: | | ||
| set -euo pipefail | ||
| status="$(aws cloudformation describe-stacks --stack-name cli-artifacts \ | ||
| --query "Stacks[0].StackStatus" --output text 2>/dev/null || echo NONE)" | ||
| echo "current stack status: ${status}" | ||
| if [ "${status}" = "ROLLBACK_COMPLETE" ]; then | ||
| aws cloudformation delete-stack --stack-name cli-artifacts | ||
| aws cloudformation wait stack-delete-complete --stack-name cli-artifacts | ||
| fi | ||
|
|
||
| - name: Deploy stack | ||
| run: | | ||
| set -euo pipefail | ||
| aws cloudformation deploy \ | ||
| --stack-name cli-artifacts \ | ||
| --template-file infra/cli-artifacts/template.yaml \ | ||
| --capabilities CAPABILITY_NAMED_IAM \ | ||
| --no-fail-on-empty-changeset \ | ||
| --parameter-overrides \ | ||
| "BucketName=${BUCKET_NAME}" \ | ||
| "TrustedSubject=${TRUSTED_SUBJECT}" | ||
|
|
||
| - name: Show stack events on failure | ||
| if: failure() | ||
| run: | | ||
| set -euo pipefail | ||
| aws cloudformation describe-stack-events \ | ||
| --stack-name cli-artifacts \ | ||
| --query "StackEvents[?contains(ResourceStatus, 'FAILED')].[LogicalResourceId,ResourceStatus,ResourceStatusReason]" \ | ||
| --output table || true | ||
|
|
||
| - name: Show stack outputs | ||
| run: | | ||
| set -euo pipefail | ||
| aws cloudformation describe-stacks \ | ||
| --stack-name cli-artifacts \ | ||
| --query "Stacks[0].Outputs" \ | ||
| --output table | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,54 @@ | ||
| # cli-artifacts | ||
|
|
||
| CloudFormation stack for the `cli-artifacts-prod` AWS account (declared in | ||
| `supabase/aws-org-root`). It owns the public-read S3 mirror of slim native artifacts that the | ||
| CLI downloads when registry blob CDNs or GitHub release assets are blocked, for example inside | ||
| agent sandboxes that allow `*.amazonaws.com`. | ||
|
|
||
| ## What the stack owns | ||
|
|
||
| - `AWS::S3::Bucket` `supabase-cli-artifacts`: anonymous `GetObject` through the bucket policy, | ||
| TLS required, no `ListBucket`, ACLs blocked, versioning with 30-day noncurrent expiry so a bad | ||
| same-version overwrite can be recovered. | ||
| - `AWS::IAM::Role` `slim-artifacts-publisher`: trusts the account's GitHub OIDC provider for | ||
| `repo:supabase/cli:ref:refs/heads/develop` only (exact match) and can do nothing but | ||
| `s3:PutObject` and `s3:AbortMultipartUpload` on the bucket's objects. The slim native mirror job in `.github/workflows/mirror-slim-image.yml` | ||
| assumes it per release once that upload step ships. | ||
|
|
||
| Objects are addressed as | ||
|
|
||
| ``` | ||
| https://supabase-cli-artifacts.s3.us-east-1.amazonaws.com/<service>/<version>/<service>-<version>-<target>.tar.zst | ||
| https://supabase-cli-artifacts.s3.us-east-1.amazonaws.com/<service>/<version>/<service>-<version>-<target>.manifest.json | ||
| https://supabase-cli-artifacts.s3.us-east-1.amazonaws.com/<service>/<version>/<service>-<version>-<target>.SHA256SUMS | ||
| ``` | ||
|
|
||
| which keeps the GitHub release asset names, so the `SHA256SUMS` lines match the archive name | ||
| without rewriting. | ||
|
|
||
| ## How it deploys | ||
|
|
||
| `.github/workflows/cli-artifacts-infra.yml` runs `cfn-lint` on pull requests that touch this | ||
| directory or the workflow, and deploys the stack on pushes to `develop` with the account's | ||
| `github-deploy` role. Every other ref can only | ||
| assume the read-only `github-preview` role, which cannot create change sets, so pull requests do | ||
| not preview against AWS. The deploy job is skipped until the `CLI_ARTIFACTS_AWS_ACCOUNT_ID` | ||
| repository variable is set. | ||
|
|
||
| Two roles on purpose: `github-deploy` is account admin and only ever applies this template; | ||
| the per-release upload runs as the bucket-scoped publisher role. | ||
|
|
||
| The trust is branch-scoped, not workflow-scoped: any job on `develop` with `id-token: write` | ||
| can assume the publisher role. IAM evaluates only the `sub` and `aud` claims of the GitHub | ||
| token, and narrowing `sub` to one workflow would require a repository-wide subject template | ||
| that the org-managed `github-deploy` and `github-preview` trusts do not accept. The controls | ||
| are the protected branch, the write-only permission set, the one-hour session cap, and bucket | ||
| versioning. | ||
|
|
||
| ## Changing the trusted branch | ||
|
|
||
| Change `TRUSTED_SUBJECT` in the deploy job of the workflow (for example to a release branch) | ||
| and merge; the workflow passes it as the `TrustedSubject` parameter on every deploy. The | ||
| subject must be the plain `repo:<owner>/<repo>:ref:refs/heads/<branch>` form that | ||
| `supabase/cli` currently emits; repositories created or renamed after 2026-07-15 emit immutable | ||
| `owner@<id>/repo@<id>` subjects instead. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,111 @@ | ||
| AWSTemplateFormatVersion: "2010-09-09" | ||
| Description: >- | ||
| Public-read S3 mirror for slim native artifacts consumed by the Supabase CLI, plus the | ||
| bucket-scoped publisher role assumed by the mirror-slim-image workflow. | ||
|
|
||
| Parameters: | ||
| BucketName: | ||
| Type: String | ||
| Default: supabase-cli-artifacts | ||
| Description: >- | ||
| Globally unique bucket name; also the host label in the public URL, so dots are not | ||
| allowed (they break TLS on the virtual-hosted endpoint). | ||
| AllowedPattern: "^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$" | ||
| TrustedSubject: | ||
| Type: String | ||
| Default: repo:supabase/cli:ref:refs/heads/develop | ||
| Description: Exact GitHub Actions OIDC subject allowed to assume the publisher role. | ||
| AllowedPattern: "^repo:[A-Za-z0-9._@-]+/[A-Za-z0-9._@-]+:ref:refs/heads/[A-Za-z0-9._/-]+$" | ||
|
|
||
| Resources: | ||
| ArtifactBucket: | ||
| Type: AWS::S3::Bucket | ||
| DeletionPolicy: Retain | ||
| UpdateReplacePolicy: Retain | ||
| # Objects are meant to be public through the bucket policy; only ACL-based public | ||
| # access stays blocked. Listing is never granted, so keys must be known in advance. | ||
| Properties: | ||
| BucketName: !Ref BucketName | ||
|
avallete marked this conversation as resolved.
|
||
| PublicAccessBlockConfiguration: | ||
| BlockPublicAcls: true | ||
| IgnorePublicAcls: true | ||
| BlockPublicPolicy: false | ||
| RestrictPublicBuckets: false | ||
| OwnershipControls: | ||
| Rules: | ||
| - ObjectOwnership: BucketOwnerEnforced | ||
| BucketEncryption: | ||
| ServerSideEncryptionConfiguration: | ||
| - ServerSideEncryptionByDefault: | ||
| SSEAlgorithm: AES256 | ||
| VersioningConfiguration: | ||
| Status: Enabled | ||
| LifecycleConfiguration: | ||
| Rules: | ||
| - Id: expire-noncurrent-and-abort-multipart | ||
| Status: Enabled | ||
| NoncurrentVersionExpiration: | ||
| NoncurrentDays: 30 | ||
| AbortIncompleteMultipartUpload: | ||
| DaysAfterInitiation: 7 | ||
|
|
||
| ArtifactBucketPolicy: | ||
| Type: AWS::S3::BucketPolicy | ||
| Properties: | ||
| Bucket: !Ref ArtifactBucket | ||
| PolicyDocument: | ||
| Version: "2012-10-17" | ||
| Statement: | ||
| - Sid: PublicReadObjects | ||
| Effect: Allow | ||
| Principal: "*" | ||
| Action: s3:GetObject | ||
| Resource: !Sub "${ArtifactBucket.Arn}/*" | ||
|
avallete marked this conversation as resolved.
|
||
| - Sid: DenyInsecureTransport | ||
| Effect: Deny | ||
| Principal: "*" | ||
| Action: s3:* | ||
| Resource: | ||
| - !GetAtt ArtifactBucket.Arn | ||
| - !Sub "${ArtifactBucket.Arn}/*" | ||
| Condition: | ||
| Bool: | ||
| aws:SecureTransport: "false" | ||
|
|
||
| PublisherRole: | ||
| Type: AWS::IAM::Role | ||
| Properties: | ||
| RoleName: slim-artifacts-publisher | ||
| Description: Uploads slim native artifacts from the supabase/cli mirror workflow. | ||
| MaxSessionDuration: 3600 | ||
| AssumeRolePolicyDocument: | ||
| Version: "2012-10-17" | ||
| Statement: | ||
| - Effect: Allow | ||
| Principal: | ||
| Federated: !Sub arn:${AWS::Partition}:iam::${AWS::AccountId}:oidc-provider/token.actions.githubusercontent.com | ||
| Action: sts:AssumeRoleWithWebIdentity | ||
| Condition: | ||
| StringEquals: | ||
| token.actions.githubusercontent.com:aud: sts.amazonaws.com | ||
| token.actions.githubusercontent.com:sub: !Ref TrustedSubject | ||
|
avallete marked this conversation as resolved.
|
||
| Policies: | ||
| - PolicyName: put-artifact-objects | ||
| PolicyDocument: | ||
| Version: "2012-10-17" | ||
| Statement: | ||
| # AbortMultipartUpload lets a failed `aws s3 cp` clean up its own parts; it | ||
| # grants no read, list, or delete on completed objects. | ||
| - Effect: Allow | ||
| Action: | ||
| - s3:PutObject | ||
|
avallete marked this conversation as resolved.
|
||
| - s3:AbortMultipartUpload | ||
| Resource: !Sub "${ArtifactBucket.Arn}/*" | ||
|
avallete marked this conversation as resolved.
|
||
|
|
||
| Outputs: | ||
| BucketName: | ||
| Value: !Ref ArtifactBucket | ||
| BucketUrl: | ||
| Value: !Sub "https://${ArtifactBucket}.s3.${AWS::Region}.amazonaws.com" | ||
| PublisherRoleArn: | ||
| Value: !GetAtt PublisherRole.Arn | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.