In this post, I describe how to build secure GitHub Actions workflows bypull_request_target
event instead ofpull_request
event.
This post is based on my post written in Japanese.pull_request_target で GitHub Actions の改竄を防ぐ
GitHub Actions is one of the most popular CI platform.
GitHub Actions is powerful, but has a security concern that workflow files.github/workflows/*.yaml
can be tampered and malicious codes can be executed with secrets and permissions in CI.
To solve the issue, I propose using GitHub Actions'pull_request_target event instead ofpull_request
event.
Note that in this post I talk about the enterprise software development on private repositories rather than OSS activities on public repositories, and I assume pull requests aren't sent from Fork repositories.
Before usingpull_request_target
Before usingpull_request_target
, you should utilize GitHub features such asBranch protection rules,code owners, andOIDC, and so on for security.
In this post I assume you are utilizing them properly already. Usingpull_request_target
is a more advanced topic.
What and Why pull_request_target?
pull_request_target is one of the events triggering GitHub Actions workflows.
One of the differences between pull_request_target and pull_request is that pull_request_target triggers workflows based on the latest commit of the pull request's base branch.
Even if workflow files are modified or deleted on feature branches, workflows on the default branch aren't affected so you can prevent malicious code from being executed in CI without code review.
Example of pull_request_target
If you aren't familiar with pull_request_target and you can't understand how it prevents tampering, please add the following workflow to your repository's default branch.
name:teston:pull_request_target:# Use pull_request_targetbranches:[main]jobs:test:runs-on:ubuntu-lateststeps:-run:echo "$EVENT"env:EVENT:${{toJSON(github)}}
Then please modify the workflow file and create a pull request to the default branch.
The workflow would be run based on the workflow file of the base branch and your modification wouldn't affect to the workflow run.
And even if you remove the workflow from the feature branch, the workflow would be run.
So malicious codes can't be run in CI unless they are merged into the default branch.
This is one of the diffrences between pull_request and pull_request_target.
Don't execute actions and scripts of feature branches
You shouldn't execute actions and scripts of feature branches because they can be tampered.
If you want to execute them, you should get them from safe other repositories or branches such as the default branch.
Secure OIDC Settings
To access Cloud Providers such as AWS and Google Cloud, you should use OIDC rather than secrets in terms of security.
GitHub supports various OIDC claims.
- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token
- https://token.actions.githubusercontent.com/.well-known/openid-configuration
You can prevent malicious authentication to OIDC with the following claims.
- repo
- event_name
- base_ref
- ref
If you want to allow the authentication only on the specific workflows, you can use the claimworkflow
too.
I describe OIDC settings on AWS and Google Cloud.
AWS
You create two IAM Roles.
- IAM Role for the default branch can create, read, update, and delete resources
- IAM Role for pull requests can read resources
You can restrict the authentication to those IAM Roles by the following IAM Role's trust policy.
For the default branch
"Condition":{"StringEquals":{"token.actions.githubusercontent.com:aud":"sts.amazonaws.com","token.actions.githubusercontent.com:sub":"repo:octo-org/octo-repo:event_name:push:base_ref::ref:refs/heads/main"}}
For pull_request_target
"Condition":{"StringEquals":{"token.actions.githubusercontent.com:aud":"sts.amazonaws.com"},"StringLike":{"token.actions.githubusercontent.com:sub":"repo:octo-org/octo-repo:event_name:pull_request_target:base_ref:main:*"}}
Then you can prevent the following attacks.
- Assume the IAM Role for the default branch on pull request
- This is impossible because only push event to the default branch is allowed
- Assume the IAM Role for pull requests by running malicious workflows with pull_request event
- This is impossible because only pull_request_target event is allowed
- Assume the IAM Role for pull requests by adding malicious workflows to any feature branches and sending pull requests with pull_request_target event to the branches
- This is impossible because base_ref must be main
In case of AWS, you need toset the customization template for an OIDC subject claim for the GitHub repository.
Otherwise, the authentication would fail.
gh api\--method PUT\-H"Accept: application/vnd.github+json"\-H"X-GitHub-Api-Version: 2022-11-28"\"/repos/$REPO/actions/oidc/customization/sub"\-Fuse_default=false\-f"include_claim_keys[]=repo"\-f"include_claim_keys[]=event_name"\-f"include_claim_keys[]=base_ref"\-f"include_claim_keys[]=ref"
Google Cloud
- https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/configuring-openid-connect-in-google-cloud-platform
- https://cloud.google.com/iam/docs/workload-identity-federation
You create two Service Accounts.
- Service Account for the default branch can create, read, update, and delete resources
- Service Account for pull requests can read resources
You can restrict the authentication to those Service Accounts by the following Attribute mappings and Attribute conditions.
Attribute mapping
attribute.repository = assertion.repositoryattribute.event_name = assertion.event_nameattribute.base_ref = assertion.base_refattribute.ref = assertion.refattribute.workflow = assertion.workflow
Attribute conditions
For CI on Pull Request
attribute.repository == "kouzoh/microservices-terraform" && attribute.event_name == "pull_request_target" && attribute.base_ref == "master"
For CI on the default branch
attribute.repository == "octo-org/octo-repo" && attribute.event_name == "push" && attribute.ref == "refs/heads/main"
Then you can prevent attacks same with AWS.
Unlike AWS, you don't have toset the customization template for an OIDC subject claim for the repository.
Secret Management
To access secrets securely in CI, you should manage them in secrets management services such asAWS Secrets Manager andGoogle Secret Manager and access them via OIDC so that you can restrict access to them with OIDC claims.
GitHub's Environment Secrets can also restrict the access but it supports only the restriction based on branches, so malicious workflows can access secrets for pull request CI.
As I described in the previous section, OIDC supports more flexible restrictions, so they are better than GitHub Secrets in terms of security.
Modify workflows for pull_request_target
The GitHub Actionsbuilt in environment variables andContext of pull_request_target event are different from those of pull_request event.
For example, the following environment variables and context are different.
- event_name, GITHUB_EVENT_NAME
- ref, GITHUB_REF
- sha, GITHUB_SHA
- ref_name, GITHUB_REF_NAME
You may need to fix scripts and actions so that they work well on pull_request_target events.
For example, if you usetfcmt andgithub-comment, which are my OSS, you need to set the merge commit hash to the environment variablesTFCMT_SHA
andGH_COMMENT_SHA1
.
You also need to check if third party actions support the pull_request_target event.
Checkout merge commits
To checkout the merged commit withactions/checkout on pull_request_target event, you need toget the pull request by GitHub API and set the merge commit hash toactions/checkout
inputref
.
-uses:actions/github-script@v6id:prwith:script:|const { data: pullRequest } = await github.rest.pulls.get({...context.repo,pull_number: context.payload.pull_request.number,});return pullRequest-uses:actions/checkout@v4with:ref:${{fromJSON(steps.pr.outputs.result).merge_commit_sha}}
I createda small action for this.
-uses:suzuki-shunsuke/get-pr-action@v0.1.0id:pr-uses:actions/checkout@v4with:ref:${{steps.get-pr.outputs.merge_commit_sha}}
It is useless to call the GitHub API to get the merge commit hash everytime you runactions/checkout
, so it's good to get the merge commit hash in one job and pass the merge commit hash by the job's output.
jobs:get-pr:outputs:merge_commit_sha:${{steps.prs.outputs.merge_commit_sha}}runs-on:ubuntu-lateststeps:-uses:suzuki-shunsuke/get-pr-action@v0.1.0id:prfoo:runs-on:ubuntu-latestneeds:-get-prsteps:-uses:actions/checkout@v4with:ref:${{needs.get-pr.outputs.merge_commit_sha}}
Note that the context value${{github.event.pull_request.merge_commit_sha}}
isn't the latest merge commit hash.
Test of workflow changes
One of the drawbacks of pull_request_target is that it's difficult to test changes of GitHub Actions workflows in CI because changes aren't reflected until they are merged to the default branch.
Especially, if pull requests by Renovate are merged automatically, workflows may be broken suddenly.
To solve the issue, maybe you can run workflows with test files when workflow files are modified.
By separating workflows as reusable workflows, maybe you can test workflow changes with test inputs.
About Renovate, disabling auto-merge of actions updates is also one of options.
Conclusion
In this post, I described how to build secure GitHub Actions workflows bypull_request_target
event instead ofpull_request
event.
Usingpull_request_target
, you can prevent malicious codes from being executed in CI.
And by managing secrets in secrets management services such asAWS Secrets Manager andGoogle Secret Manager and access them via OIDC, you can restrict the access to secrets securely.
To migratepull_request
topull_request_target
, several modifications are needed.
Andpull_request_target
has a drawback that it's difficult to test changes of workflows, so it's good to introducepull_request_target
to repositories that require strong permissions in CI.
For example, a Terraform Monorepo tends to require strong permissions for CI, so it's good to introducepull_request_target
to it.
Top comments(0)
For further actions, you may consider blocking this person and/orreporting abuse