While I was looking at the Ardalis.GuardClauses repo, I noticed that the build process was failing. Talking with Steve, I noticed that the build was failing on pull requests from forks. Being an open source project and wanting to have others contribute, this is a problem.
Problem: Writing code coverage results to the PR
Part of the build process is writing code coverage results to the pull request. The build uses marocchino/sticky-pull-request-comment to keep the code coverage to one comment in the pull request. However, the error message - as seen in this run is:
Error: Resource not accessible by integration
That “not accessible” made me think it was a permissions issue. I needed to explore further.
Check the GITHUB_TOKEN
permissions
Knowing that the action uses the GITHUB_TOKEN
by default, I needed to look at the permissions set under the “Set up job” step.
In order to write to the pull request, there should be write permissions. Let’s look at this further.
Looking at GITHUB_TOKEN
permissions issues
GitHub had announced changes to the default behavior of GITHUB_TOKEN
in workflows in February 2023. With this being an existing repo, I don’t think this is a problem.
I ended up creating a test PR to help test possible solutions. We tried looking at the permissions
attribute in the YML at the workflow level and at the job level. You can read more about assigning permissions to jobs. However, this still didn’t solve our problem with the forks from new contributors.
Steve opened an issue on the sticky-pull-request-comment action, and marocchino gave us some hints that led me to explore a bit further.
Solution: Separating the reads and the writes
I did some further reading and came across this post: Keeping your GitHub Actions and workflows secure Part 1: Preventing pwn requests. In the post, the GitHub Security Lab explains that you can use the workflow_run
trigger for building untrusted code and writing to a PR. This breaks the build into two workflows:
- A read-only repo token that runs the build and tests
- A write-access
workflow_run
trigger that runs after the first one does and writes the code coverage to the PR
What does this look like?
The commenting workflow does need access to some of the files created by the first workflow, so I’ve added upload and download artifacts and am using the dawidd6/action-download-artifact action to get the artifacts from another workflow.
I added an if
expression to only run that job when it is a pull request situation. When pushing to main
, that workflow will appear as skipped.
build.yml
name: .NET Core
on:
workflow_dispatch:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: "7.x"
- name: Install dependencies
run: dotnet restore
- name: Build
id: build
run: dotnet build --configuration Release --no-restore
# See https://josh-ops.com/posts/github-code-coverage/
# Add coverlet.collector nuget package to test project - 'dotnet add <TestProject.cspoj> package coverlet
- name: Test
id: test
if: steps.build.outcome == 'success'
run: dotnet test --no-restore --verbosity normal --collect:"XPlat Code Coverage" --logger trx --results-directory coverage
# Copy code coverage always - regardless of test failures or successes
- name: Copy Coverage To Predictable Location
if: success() || failure()
run: cp coverage/*/coverage.cobertura.xml coverage/coverage.cobertura.xml
- name: Code Coverage Summary Report
uses: irongut/CodeCoverageSummary@v1.3.0
if: success() || failure()
with:
filename: coverage/coverage.cobertura.xml
badge: true
format: "markdown"
output: "both"
comment-on-pr.yml
name: Comment on the Pull Request
# read-write repo token
# See: https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
on:
workflow_run:
workflows: [".NET Core"]
types:
- completed
jobs:
comment:
runs-on: ubuntu-latest
# Only comment on the PR if this is a PR event
if: github.event.workflow_run.event == 'pull_request'
steps:
- name: Get the PR Number artifact
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
workflow_conclusion: ""
name: pr-number
- name: Read PR Number into GitHub environment variables
run: echo "PR_NUMBER=$(cat pr-number.txt)" >> $GITHUB_ENV
- name: Confirm the PR Number (Debugging)
run: echo $PR_NUMBER
- name: Get the code coverage results file
uses: dawidd6/action-download-artifact@v2
with:
workflow: ${{ github.event.workflow_run.workflow_id }}
workflow_conclusion: ""
name: code-coverage-results
- name: Add Coverage PR Comment
uses: marocchino/sticky-pull-request-comment@v2
with:
number: ${{ env.PR_NUMBER }}
recreate: true
path: code-coverage-results.md
Conclusion
Remember, it is never “simply a permissions issue”. If you ever think that, the permissions will remind you otherwise. When you see Error: Resource not accessible by integration
, consider these things:
- Does your
GITHUB_TOKEN
have the write permissions where it is needed? - Do you need to isolate your actions into separate workflows to grant only the permissions needed for the steps?
Thanks to the GitHub Security Lab, we have the guidance on how to separate our workflows using their example as a guide.