├── .github └── dependabot.yaml ├── Dockerfile ├── LICENSE ├── README.md ├── action.yml ├── generate_jwt.rb ├── get-installation-access-token.sh └── release.sh /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "docker" 9 | directory: "/" 10 | schedule: 11 | interval: "weekly" 12 | groups: 13 | docker: 14 | patterns: 15 | - '*' 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ruby:alpine3.12 2 | WORKDIR /action 3 | RUN gem install jwt && \ 4 | apk add jq && \ 5 | apk add curl 6 | COPY generate_jwt.rb get-installation-access-token.sh ./ 7 | ENTRYPOINT ["/action/get-installation-access-token.sh"] 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 NAV IT 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub App installation access token generator 2 | 3 | GitHub Action that can be used to generate an installation access token for a GitHub App. This token can for instance be used to clone repos, given the GitHub App has sufficient permissions to do so. 4 | 5 | ## Usage 6 | 7 | ```yaml 8 | name: Checkout repos 9 | on: push 10 | jobs: 11 | checkout: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/checkout@v4 15 | 16 | - uses: navikt/github-app-token-generator@v1 17 | id: get-token 18 | with: 19 | private-key: "${{ secrets.PRIVATE_KEY }}" 20 | app-id: "${{ secrets.APP_ID }}" 21 | 22 | - name: Check out an other repo 23 | uses: actions/checkout@v4 24 | with: 25 | repository: owner/repo 26 | token: ${{ steps.get-token.outputs.token }} 27 | ``` 28 | 29 | ## Requirements 30 | 31 | The action needs two input parameters, `private-key` and `app-id`. To get these, simply create a GitHub App. The private key can be generated and downloaded, and should be added to the repos as a secret. Known supported private key formats are 32 | * PKCS#1 RSAPrivateKey** (PEM header: BEGIN RSA PRIVATE KEY) 33 | * PKCS#8 PrivateKeyInfo* (PEM header: BEGIN PRIVATE KEY) 34 | 35 | The installation ID that is used during the creation of the access token is created against the repo running the action. If you need to create the installation ID for a different repo you can use the `repo` input: 36 | 37 | ```yaml 38 | uses: navikt/github-app-token-generator@v1 39 | id: get-token 40 | with: 41 | private-key: "${{ secrets.PRIVATE_KEY }}" 42 | app-id: "${{ secrets.APP_ID }}" 43 | repo: some/repo 44 | ``` 45 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: GitHub App installation access token generator 2 | description: Generate an installation access token for a GitHub App that can be for instance used to clone other repositories 3 | inputs: 4 | private-key: 5 | description: Private key for the GitHub App 6 | required: true 7 | app-id: 8 | description: GitHub App ID 9 | required: true 10 | repo: 11 | description: Override GITHUB_REPOSITORY when creating the installation ID 12 | required: false 13 | outputs: 14 | token: 15 | description: Installation access token for the GitHub App 16 | runs: 17 | using: docker 18 | image: Dockerfile 19 | args: 20 | - ${{ inputs.private-key }} 21 | - ${{ inputs.app-id }} 22 | author: Christer Edvartsen 23 | branding: 24 | icon: unlock 25 | color: red 26 | -------------------------------------------------------------------------------- /generate_jwt.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require 'openssl' 4 | require 'jwt' 5 | 6 | private_key = ENV.fetch('PRIVATE_KEY') 7 | app_id = ENV.fetch('APP_ID') 8 | 9 | payload = { 10 | iat: Time.now.to_i, 11 | exp: Time.now.to_i + (10 * 60), 12 | iss: app_id 13 | } 14 | 15 | puts JWT.encode(payload, OpenSSL::PKey::RSA.new(private_key), 'RS256') 16 | -------------------------------------------------------------------------------- /get-installation-access-token.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh -e 2 | 3 | export PRIVATE_KEY="$1" 4 | export APP_ID="$2" 5 | 6 | if [ -z "$PRIVATE_KEY" ]; then 7 | echo "Private key not specified." 8 | echo "Usage: ${0} " 9 | exit 1 10 | fi 11 | 12 | if [ -z "$APP_ID" ]; then 13 | echo "Application ID not specified." 14 | echo "Usage: ${0} " 15 | exit 1 16 | fi 17 | 18 | repo=${GITHUB_REPOSITORY:?Missing required GITHUB_REPOSITORY environment variable} 19 | 20 | [ -n "$INPUT_REPO" ] && repo="$INPUT_REPO" 21 | 22 | jwt=$(ruby "$(dirname "$0")"/generate_jwt.rb) 23 | response=$(curl -s -H "Authorization: Bearer ${jwt}" -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/${repo}/installation") 24 | installation_id=$(echo "$response" | jq -r .id) 25 | 26 | if [ "$installation_id" = "null" ]; then 27 | echo "Unable to get installation ID. Is the GitHub App installed on ${repo}?" 28 | echo "$response" | jq -r .message 29 | exit 1 30 | fi 31 | 32 | token=$(curl -s -X POST \ 33 | -H "Authorization: Bearer ${jwt}" \ 34 | -H "Accept: application/vnd.github.v3+json" \ 35 | https://api.github.com/app/installations/"${installation_id}"/access_tokens | jq -r .token) 36 | 37 | if [ "$token" = "null" ]; then 38 | echo "Unable to generate installation access token" 39 | exit 1 40 | fi 41 | 42 | echo "token=${token}" >> "$GITHUB_OUTPUT" 43 | -------------------------------------------------------------------------------- /release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | if [[ "$#" -ne 1 ]] || [[ ! $1 =~ ^v([0-9]+)\.([0-9]+)\.([0-9]+)$ ]]; then 3 | echo "Usage: $0 vX.Y.Z" 4 | exit 1 5 | fi 6 | 7 | branch=$(git branch --show-current) 8 | 9 | if [[ "$branch" != "main" ]]; then 10 | echo "You're in the \"${branch}\" branch, you need to switch to main" 11 | exit 1 12 | fi 13 | 14 | major=v${BASH_REMATCH[1]} 15 | minor=${major}.${BASH_REMATCH[2]} 16 | patch=${minor}.${BASH_REMATCH[3]} 17 | 18 | echo "HEAD is $(git log -1 --pretty="%h: %B")" 19 | read -p "You are about to create the following tags: ${major}, ${minor} and ${patch}, continue? [y/n] " -r -n 1 20 | echo 21 | 22 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then 23 | echo "Aborting..." 24 | exit 1 25 | fi 26 | 27 | echo "Creating tag ${major}" 28 | git tag -f ${major} 29 | 30 | echo "Creating tag ${minor}" 31 | git tag -f ${minor} 32 | 33 | echo "Creating tag ${patch}" 34 | git tag -f ${patch} 35 | 36 | read -p "Tags created, push? [y/n] " -r -n 1 37 | echo 38 | 39 | if [[ ! $REPLY =~ ^[Yy]$ ]]; then 40 | echo "Aborting..." 41 | exit 1 42 | fi 43 | 44 | git push 45 | git push --tags -f 46 | --------------------------------------------------------------------------------