Environments #
GitHub’s Environments are closely tied to “Deployments”. So much so I thought “huh this seems useless (for me)” and moved on. Perhaps I held this belief after seeing the 58 instances of “deployment” on the page describing “Environments” (which has 118 hits).
But, as I learned later Environments are actually quite useful outside of the world of Deployments, and inside the world of Security. That’s because:
You can configure environments with protection rules and secrets. When a workflow job references an environment, the job won’t start until all of the environment’s protection rules pass. A job also cannot access secrets that are defined in an environment until all the deployment protection rules pass.
It may not be well-known that GitHub Secrets are barely secure. Of course the pages on “About Secrets” and “Using Secrets in GitHub Actions” don’t mention it, but “Security hardening for GitHub Actions” (what, you don’t cross-search all your favorite technologies with “-hardening”) does:
Any user with write access to your repository has read access to all secrets configured in your repository. Therefore, you should ensure that the credentials being used within workflows have the least privileges required.
… has read access to all secrets?! (way to bury the lede there!)
For Security #
Environments can be used to securely store secrets knowing that even users with write access can’t (easily - depending on the overall security enforced by the repo’s branch/tag protections) access them. For security-purposes, they work like this:
- You make a GitHub Environment, let’s call it
main
- You configure the “deployment” protection rules to restrict the “deployment” to just the
main
branch- “deployment” is a (repeated) red herring
- You store your precious secrets as Environment Secrets
Then, in your workflow you just need to use environment: main
.
If the ref that the workflow is triggered from is main
the workflow proceeds and can access the secrets.
Otherwise, it gets rejected with a mostly helpful message.
When configured this way, along with common branch rulesets like requiring a PR and requiring code review, we know:
- The workflow definition came from the default branch, which should be trustable
- The workflow run is running against the default branch, which should be trustable
Trust is Security’s best frienemy.
Why “Sometimes”? #
This is useful for situations where the triggering ref is main
- events like status
, or issues
, or workflow_dispatch
(when the ref
is main
)
(See GITHUB_REF
in the docs)
However that means that triggers like pull_request
will almost never match (unless you’re opening PRs where main
is the head branch - dude WTF?).
However however a clever GitHub aficionado would point out the existence of pull_request_target
.
An event whose sole purpose is to trigger on Pull Request events, but with the github.ref
of the base branch!
So using pull_request_target
means we CAN combine Environments with Pull Request events!
However however however the triggering branch is still the pull request base, so the environment is rejected. (You get NOTHING! You LOSE! GOOD DAY SIR!)
(I have an open ticket about this but I wouldn’t hold my breath)
While I’m piling on GitHub, even if this “bug” was fixed, the lack of event symmetry means that
pull_request_review
and pull_request_review_comment
events would still be left out by the roadside,
(forever) waiting for a _target
equivalent.
Workaround(s) #
If your workflow is inherently safe for anyone to be able to trigger arbitrarily, one workaround is to use workflow_dispatch
.
you can combine pull_request
and workflow_dispatch
triggers, like so:
on:
pull_request: ...
workflow_dispatch:
inputs:
pull-number:
type: number
required: true
jobs:
trigger-job:
if: github.event_name == 'pull_request'
name: Trigger the workflow
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- run: gh workflow run <filename> --ref main -F pull-number=${{ github.event.pull_request.number }}
env:
GH_TOKEN: ${{ github.token }}
GH_REPO: ${{ github.repository }}
the-job:
if: github.event_name == 'workflow_dispatch'
name: Name
runs-on: ubuntu-latest
environment: main
steps: ...
It’s a hack, and not a very pretty or nice one. But it works in a pinch
There are undoubtedly other workarounds, but that’s all the simple ones I’m aware of.