Set up GitHub Actions to authenticate to JFrog Artifactory using OIDC

Set up GitHub Actions to authenticate to JFrog Artifactory using OIDC

2024-08-18 | infrastructure, devops

Why? #

JFrog has some useful GitHub Actions workflows available that authenticate against Artifactory. However, these only configure either the jf CLI tool, or alternatively configure repository access for a particular tool or framework, like Docker registries or Maven libraries.

The Terraform provider for JFrog Artifactory requires an access token, so if you want to automate Terraform operations to JFrog in GitHub Actions, the aforementioned authentication methods are not going to work. It would be possible to create an authentication token in JF and set it up as a GitHub Actions secret, but then we are stuck with a hardcoded credential that may expire, get deleted, or end up being misused. The alternative to this is to set up OIDC authentication between the two tools, in which case a short-lived, temporary access token is given to your workflow each time it runs.

The official JFrog documentation for GH Actions OIDC did not work - provider configuration and specifics of the token exchange request had to be changed, hence the need for this short post. YMMV.

Setup: Configuring JFrog side (with Terraform) #

In order to create this configuration with Terraform, I needed to first manually create an access token with sufficient administrative privileges to create these resources. Once done with this guide, these resources remain in .tf code intended to manage the JF instance.

locals {
  github_org_name = "example-organization"  
}

resource "platform_oidc_configuration" "github_oidc_config" {
  name          = "jf-infra-github-oidc-config"
  description   = "OIDC config for authenticating to GiHub Actions"
  issuer_url    = "https://token.actions.githubusercontent.com/"
  provider_type = "GitHub"
  audience      = "jfrog-github"
}

resource "platform_oidc_identity_mapping" "github_oidc_identity_mapping" {
  name          = "jfrog-infrastructure-repo-gh-oidc-identity-mapping"
  description   = "GitHub OIDC group identity mapping"
  provider_name = platform_oidc_configuration.github_oidc_config.name
  priority      = 10

  claims_json = jsonencode({
    "sub" = "repo:${github_org_name}/jfrog-iac:ref:refs/heads/main",
  })

  token_spec = {
    username   = "jfrog-iac"
    scope      = "applied-permissions/admin"
    audience   = "jfrt@* jfac@* jfmc@* jfmd@* jfevt@* jfxfer@* jflnk@* jfint@* jfwks@*"
    expires_in = 300 # 5 minutes
  }
}

Setup: GitHub Actions flow #

Since you’ll likely want to reuse the authentication steps with different workflows (e.g. plan, apply), it’s configured here as a reusable workflow step in the first file below. The second file shows an example of using the step in another workflow. Note that if you changed the provider name from the example above, you must update the curl command referencing that name.

# note that the filename has to be action.yml, the name you will
# reference in other flows is the name of the enclosing folder
# ./github/workflows/jfrog-oidc-action/action.yaml
name: JFrog OIDC authenticate action
description: Authenticate to JFrog with OIDC

outputs:
  token:
    description: JFrog Artifactory Access Token
    value: ${{ steps.token.outputs.ACCESS_TOKEN }}

runs:
  using: composite
  steps:
    - name: Get ID token
      shell: bash
      run: |
        ID_TOKEN=$(curl -sLS -H "User-Agent: actions/oidc-client" -H "Authorization: Bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" \
          "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=jfrog-github" | jq .value | tr -d '"')
        echo "ID_TOKEN=${ID_TOKEN}" >> $GITHUB_ENV        

    - name: Exchange ID token with access
      shell: bash
      id: token
      env:
        ID_TOKEN: ${{ env.ID_TOKEN }}
        JFROG_URL: "https://your_instance_name.jfrog.io/access/api/v1/oidc/token"
      run: |
        ACCESS_TOKEN=$(curl -XPOST "${JFROG_URL}" -d "{\"grant_type\": \"urn:ietf:params:oauth:grant-type:token-exchange\", \"subject_token_type\":\"urn:ietf:params:oauth:token-type:id_token\", \"subject_token\": \"$ID_TOKEN\", \"provider_name\": \"jf-infra-github-oidc-config\"}" -H "Content-Type: application/json" | jq .access_token | tr -d '"')
        echo "ACCESS_TOKEN=${ACCESS_TOKEN}" >> $GITHUB_OUTPUT        

Using this workflow step in another workflow:

name: TF Apply

on: # up to you
  workflow_dispatch:

permissions: # MUST BE HERE for OIDC to work
  id-token: write
  contents: read

jobs:
  terraform-apply:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v4

      - name: Get JFrog access token
        id: token
        uses: ./.github/workflows/jfrog-oidc-action

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Terraform Init & Validate
        run: terraform init && terraform validate

      - name: Terraform Apply
        env:
          JFROG_ACCESS_TOKEN: ${{ steps.token.outputs.token  }}
        run: |
                    terraform apply -auto-approve