How to push to protected branches in a GitHub Action

I’m in this same boat here. Forced to choose between locking down my branch or automation. Frustrating.

6 Likes

+1 For having an option to allow GitHub actions to bypass branch protection.

I was able to create a workflow that temporarily disables the branch protection and then enables it again. This works fine if you don’t have multiple pull requests merged to master at the same time. And of course this means there is no branch protection for a few seconds.

The workflow

  1. Removes branch protection from master
  2. Runs npm version to increment the release number and create a tag
  3. Pushes the version number change to master and the tag
  4. Enables the branch protection for master again.
  5. Builds and publishes the library to NPM
name: Publish Release

on:
  push:
    branches: [ master ]

jobs:
  build-publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - name: Setup GIT
        run: |
          git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
          git config user.name "$GITHUB_ACTOR"
      - name: Setup Node.js
        uses: actions/setup-node@v1
        with:
          node-version: 10
          registry-url: https://npm.pkg.github.com/
          scope: '@YOUR_ORG_HERE'
      - name: Branch protection OFF
        uses: octokit/request-action@v2.x
        with:
          route: PUT /repos/:repository/branches/master/protection
          repository: ${{ github.repository }}
          required_status_checks: |
            null
          enforce_admins: |
            null
          required_pull_request_reviews: |
            null
          restrictions: | 
            null 
        env:
          GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_REPO_ADMIN_CI_TOKEN }}
      - name: Versioning
        run: |
          npm version minor -m "chore(release): %s"
          git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY"
          git push "https://$GITHUB_ACTOR:$GITHUB_TOKEN@github.com/$GITHUB_REPOSITORY" --tags
        env:
          NODE_AUTH_TOKEN: ${{secrets.GH_PACKAGES_TOKEN}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
      - name: Branch protection ON
        uses: octokit/request-action@v2.x
        with:
          route: PUT /repos/:repository/branches/master/protection
          repository: ${{ github.repository }}
          mediaType: |
            previews: 
              - luke-cage
          required_status_checks: |
            strict: true
            contexts:
              - build
          enforce_admins: |
            null
          required_pull_request_reviews: |
            dismiss_stale_reviews: true
            required_approving_review_count: 1
          restrictions: | 
            null 
        env:
          GITHUB_TOKEN: ${{ secrets.GH_ACTIONS_REPO_ADMIN_CI_TOKEN }}
      - name: Build and Publish
        run: |
          npm ci
          npm run build -- --prod
          npm publish
        env:
          NODE_AUTH_TOKEN: ${{secrets.GH_PACKAGES_TOKEN}}
          GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
7 Likes

It would be great if the github actions could bypass the branch protection policies for us, too. We would like to automatically add the latest changelog from LTS branches to the master branch’s changelog file on new LTS releases.

2 Likes

Plus one for asking this feature support

1 Like

on:
push:
branches:
- master
jobs:
publish-gpr:
if: “!contains(github.event.head_commit.author.name, ‘GITHUBACTION’)”
runs-on: ubuntu-latest
steps:

- name: Set up git for commits
run: |
git config user.name “GITHUBACTION”
timeout-minutes: 1
- name: Push
run: git push --follow-tags --no-verify origin HEAD:master
timeout-minutes: 1
empty-job:
# if no jobs run, github action considers it a test failure – which seems like a bug
# this makes it so the top-level if statement for the job does not give status failure.
runs-on: ubuntu-latest
if: success()
steps:
- name: Meep
run: |
echo “This job will always succeed. If no jobs run, we still want the workflow to give status success.”

Am I right that these are the workaround options that we have?

  1. Create an admin Personal Access Token, add to githubsecrets, and have the CI use that, and disable the “Include administrators” option under the protected branch setting

  2. Not require approvals before merging to the protected branch

  3. Have the CI create a PR which a human must manually approve and merge

3 Likes

Hey guys, we just spent a good time trying to work this out, posting our solution:

  • Don’t enforce branch protection on admin.
  • Create dedicated system-admin user and set him as admin on the protected repo.
  • Create a personal access token from system-admin user and store it as ADMIN_TOKEN in the repo secrets.
  • User actions/checkout@v2 with the token we created.
  • Configure git name and email

Example:

jobs:
  publish:
    name: Publish
    runs-on: ubuntu-latest
    steps:
        - name: checkout
          uses: actions/checkout@v2
          with:
            token: ${{ secrets.ADMIN_TOKEN }}

        - name: Configure CI Git User
          run: |
            git config --global user.name '@system-admin'
            git config --global user.email 'system-admin@users.noreply.github.com'

        - name: Do git actions
          run: |
            touch test.temp
            git commit -am "Hey, I'm pushing from the CI! :)"
            git push
16 Likes

Hey @nirsky do you know if you need a dedicated system-admin if you are part of on organization? I’m wondering if I can just have org matinees create an admin token? It doesn’t look like the username and email on your GitHub user actually means anything right?

Hey @renamari, you can definitely use the org maintainers token, no problem with that, the system-admin was just an example.
The reason we created a dedicated user is because we use the same accounts for our personal GitHub, which means the token could be used to access our own personal repos…

Since you’re already providing the admin token, you might as well merge with the admin token. This would enable anyone to change the workflow as mentioned here.

Gitlab solves this by allowing secrets to be marked protected and only usable by protected branches.

7 Likes

Thank you! I was missing using personal access token for checkout step, happy you pointed it out

1 Like

Seems like tying the perms on the action to the perms on the author of a commit could provide the automation power we want here without the security vulnerability. No?

Wow you saved me hours of debugging! ty!

So… there’s still no good way to do this without creating and managing a bot user just for this purpose, right?

It’s pretty absurd that Github still has no way to directly handle what should be a really straightforward, really common use case like this.

4 Likes

For Team and Enterprise plans, the “system-admin” user doesn’t necessarily need admin access:

"12. Optionally, if your repository is owned by an organization using GitHub Team or GitHub Enterprise Cloud, enable branch restrictions.

However, as an Owner (=full Admin) I cannot prevent myself from making stupid mistakes and am still able to push to a protected branch :frowning:

This approach oddly doesn’t work for us. We already had a bot user for various GitHub activities and when we enforce the following:

… but add our non-admin bot user here:

We still get the following when attempting to merge into this protected branch:

git push origin stage
remote: error: GH006: Protected branch update failed for refs/heads/stage.        
remote: error: At least 1 approving review is required by reviewers with write access. 

We definitely clone the repository using the bot user’s token, too:

      - name: Check Out Chart
        id: chart_checkout
        uses: actions/checkout@v2
        with:
          repository: ${{ env.HELM_CHART_REPO }}
          token: ${{ secrets.BUILD_SERVICE_PAT }}
          ref: master
          path: ${{ github.workspace }}/${{ env.CHART_DIRECTORY }}
          fetch-depth: 0

Anybody have any ideas why this feature is not working in this scenario? I am pretty puzzled and don’t really want to give our build robot admin controls.

Ugh, I’m trying to migrate away from Bitbucket, but this is just painful, almost to the extent I may have to stick with Bitbucket or pay a load of dollars for gitlab.

I seriously need the workflows to push to protected branches without having to use a PAT and then cause it to go around in a circle.

When we’re working on lots of small tickets we can just push/pr to our dev branch and let a workflow handle the merging into a QA branch which is then deployed for testing.
When features need testing we can PR into the QA branch so they get deployed for testing. If it’s not ready for our next release then its no big deal because it’s not in out dev branch.

1 Like

I think I found at least a working solution to the problem, although a real solution by design would be beneficial.

First, you need to create a bot with a PAT. Give it sufficient permissions. For me this was only public_repo. Then in the workflow that will do the push action, exclude the user (our bot is called nextcloud-cookbook-bot). This can be achieved by a simple check on the complete workflow:

on:
    push:
jobs:
    deploy:
        if: github.actor != 'nextcloud-cookbook-bot'
        name: Deploy codebase
        # ...
        steps:
            - ...

Then you can register a new remote in the git worker (replace USER and TOKEN with the login credentials for the bot and OWNER and REPO with the corresponding names of the repo):

git remote add NAME https://USER:TOKEN@github.com/OWNER/REPO.git

The current version of the actions/checkout (v2 is used here) is setting an extraheader to authenticate with the GITHUB_TOKEN when accessing the https interface. This will override the user/token given in the URL. So, for each push, you need to manually disable the GITHUB_TOKEN via CLI options. See here:

git push -c "http.https://github.com/.extraheader=" NAME ...

With these changes I tried in a sandbox repo and it worked. The real repo is just set up today, so I have not checked if everything is working there yet but it should unless I did some serious bug here.

I have to admit I had a typo in this. The ordering seems important :grinning_face_with_smiling_eyes:. Correct would be

git -c "http.https://github.com/.extraheader=" push NAME ...