Skip to content

[Feature]: Gate manual dev3 deploys behind a GitHub Environment (with fork-PR support) #2977

Description

@cqnykamp

Before submitting

  • I searched existing issues and did not find a duplicate request.

Affected area

Developer experience / tooling

Layer

Infrastructure / CI

Problem

After #2976, dev-deploy.yml auto-deploys pushes to both main and infra/* branches, with the dev3 IAM role's OIDC trust scoped to repo:Doenet/DoenetApps:ref:refs/heads/main + repo:Doenet/DoenetApps:ref:refs/heads/infra/*. That works for infra branches owned by maintainers, but two gaps remain:

  1. No support for fork PRs. Most of our PRs come from contributor forks, and those branches are unreachable from dev-deploy.yml — the OIDC trust doesn't cover the fork's ref, and the reusable workflows' actions/checkout only checks out refs from the upstream repo (no repository: argument). So the common case — "try this PR on dev3 before merging" — is not actually supported.
  2. No reviewer gate on the manual path. Any push to infra/* auto-deploys immediately. There's no built-in way to require human approval before a not-yet-reviewed branch executes code on dev3. For a maintainer-owned infra/* branch this is usually fine; for any path that eventually deploys fork-PR code, it isn't.

Proposed solution

Adopt the standard GitHub-recommended pattern for deploying fork-PR code from a privileged context:

  1. Replace the infra/* push trigger with workflow_dispatch carrying a pr_number input. A resolve step runs gh pr view to surface the fork's repo, head ref, and head SHA at dispatch time (frozen — later pushes to the PR aren't deployed by the same run). Push-to-main auto-deploy stays as-is.

  2. GitHub Environment dev3 with required reviewers (1–2 maintainers). Each deploy job declares environment: dev3 only on the workflow_dispatch path; the push-to-main auto-deploy stays unenvironment'd. The run pauses on "Waiting for review" before any AWS calls.

  3. Tighten the dev3 OIDC trust in dev3-doenet-repositories.params to exactly two subjects:

    • repo:Doenet/DoenetApps:ref:refs/heads/main — push-to-main auto path
    • repo:Doenet/DoenetApps:environment:dev3 — env-approved manual dispatch

    This drops the infra/* ref trust added in feat(infra): allow dev deploys from infra/* branches #2976 in favor of the more general env-scoped trust.

  4. Thread a deploy_repo input through to each reusable-deploy-*.yml's actions/checkout so the fork's code can actually be checked out (repository: \${{ inputs.deploy_repo }}).

End state: any contributor (fork or maintainer) opens a PR; a maintainer dispatches with pr_number=N, approves the environment gate, and the PR's code deploys to dev3 with full OIDC + ECR + ECS plumbing. Prod is untouched. The infra/* shortcut from #2976 is no longer needed because env-gated manual dispatch covers it.

A full plan with file-by-file changes and verification steps was drafted (in offline notes); happy to attach when this is picked up.

Alternatives considered

  • `pull_request_target` + env gate. Equivalent security model. Slightly nicer UX (no PR-number paste) but easier to misconfigure into a known-insecure pattern; explicit `workflow_dispatch` is more deliberate.
  • Comment-driven (`/deploy-dev3` bot). Kubernetes-style. Needs a bot listener and more wiring; environment reviewer UI is built-in and audited.
  • `github.actor` allowlist in YAML. Simpler but bypasses GitHub's audit log for approvals; allowlist drift lives in code.
  • Two environments (`dev3-auto` + `dev3-manual`). Uniform but doubles env configuration for no real gain; one env on the manual path only is the lighter shape.
  • Keep feat(infra): allow dev deploys from infra/* branches #2976's `infra/*` push trigger alongside the env-gated dispatch. Possible but redundant — a maintainer with the env-gate available has no reason to also need an unapproved `infra/*` shortcut.

Additional context

Security notes that came out of the design review (capture them when this is picked up):

  • dev3 must remain a separate trust boundary from prod (separate AWS account already, IAM resource scoping by `${EnvironmentName}*`). Verify the ECS task role in `service-taskrole-includes.yml` is similarly scoped.
  • Disable "Allow administrators to bypass configured protection rules" on the `dev3` environment.
  • The `resolve` job must run before the env gate so the deployed SHA is frozen at dispatch — not pulled live after approval.
  • Reviewers must inspect `Dockerfile`, `package.json`, lockfile diffs, postinstall scripts before approving. Treat dev3 as "fork code may execute here."
  • Add a notification on `github-actions-dev3` AssumeRoleWithWebIdentity from a non-`main` subject, so any env-approved dispatch is visible.

Industry references for the pattern: Kubernetes `/ok-to-test`, CNCF projects (Istio, Envoy), GitHub's own "Using environments for deployment" + "Security hardening for GitHub Actions" docs.

Metadata

Metadata

Assignees

No one assigned

    Fields

    Priority

    Low

    Effort

    Low

    Projects

    Status
    No status

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions