How to use GitHub OIDC to provide a JWT token for the Terraform Vault provider?

I want to use GitHub Actions to use the Terraform Vault Provider to apply changes to my Vault instance. In order to do this, I need some sort of Vault authentication method. I have set up a JWT auth method in vault:

$ vault read auth/jwt/role/github-admin-role
Key                        Value
---                        -----
allowed_redirect_uris      <nil>
bound_audiences            [https://github.com/bby-corp]
bound_claims               map[]
bound_claims_type          glob
bound_subject              repo:bby-corp/atat-vault-terraform:pull_request
...

Then I define the terraform provider like:

provider "vault" {
  address = "https://vault.company.com"
  auth_login_jwt {
    role = "github-admin-role"
  }
}

So far so good. In my GitHub action pipeline, I can successfully grab a JWT token and log in using curl:

     - name: Set JWT Auth Token
        id: auth-token
        run: |
            export TOKEN=$(curl -sSL -H "Authorization: bearer $ACTIONS_ID_TOKEN_REQUEST_TOKEN" "$ACTIONS_ID_TOKEN_REQUEST_URL")
            echo $TOKEN | jq .value | base64
            echo "token=$(echo $TOKEN)" >> $GITHUB_OUTPUT

      - name: Troubleshoot Token
        run: |
            export TOKEN_JSON=$(echo $GITHUB_OIDC_TOKEN | jq "{ jwt: .value, role: \"$VAULT_ROLE\" }")
            echo $TOKEN_JSON | jq -r '.jwt | split(".") | .[1] | @base64d' | jq
            echo $TOKEN_JSON | curl -sSLf -X PUT -H "Content-Type: application/json" --data @- $VAULT_URL/v1/auth/jwt/login
        env:
          VAULT_ROLE: "github-admin-role"
          VAULT_URL: "https://vault.company.com"
          GITHUB_OIDC_TOKEN: ${{ steps.auth-token.outputs.token }}

This succeeds. The echo of the token in the “set token” step gives a seemlingly good looking base64 token. The more complex jq breakdown in the “troubleshoot” step gives a valid json that holds all the token’s claims, as expected, eg:

{
  "jti": "4b8a5aa4-0fd4-4f57-b31c-81cbf42f90b1",
  "sub": "repo:company/vault-terraform:pull_request",
  "aud": "https://github.com/company",
  ...
}

The curl command gives the following response:

{"request_id":"2692e08e-8445-1c3f-0c0c-486b253e8f18","lease_id":"","renewable":false,"lease_duration":0,"data":null,"wrap_info":null,"warnings":["TTL of \"768h\" exceeded the effective max_ttl of \"1m40s\"; TTL value is capped accordingly"],"auth":{"client_token":"hvs.CAESIF2RcSztn688v8iMo-okYYYu9-4mM4cxFD-PyN0XM1CSGh4KHGh2cy5YckNGamJsSHI1bGtiekNRbmVDdjMyV3M","accessor":"Drl6JWqS71GwzNIT4uvjMuuB","policies":["admin","default"],"token_policies":["admin","default"],"metadata":{"role":"github-admin-role"},"lease_duration":100,"renewable":true,"entity_id":"17689fc9-2c60-fb13-5c3e-3b725976e5c1","token_type":"service","orphan":true,"mfa_requirement":null,"num_uses":0}}

So far so good! Login succeeds and I get a token with the desired role. But then I go to run a terraform plan in a follow on step and this is what happens:

      - name: Plan Terraform
        run: |
          export TERRAFORM_VAULT_AUTH_JWT=$(echo $GITHUB_OIDC_TOKEN | jq .value)
          TF_LOG=debug terraform plan -input=false -no-color -out=tfplan
        env:
          GITHUB_OIDC_TOKEN: ${{ steps.auth-token.outputs.token }}

Error message:

Error: Error making API request.

URL: PUT https://vault.company.com/v1/auth/jwt/login
Code: 400. Errors:

* error validating token: error verifying token signature: oidc: malformed jwt: illegal base64 data at input byte 0

I try to correct this by encoding the token that I am saving to the ENV variable in base64:

          export TERRAFORM_VAULT_AUTH_JWT=$(echo $GITHUB_OIDC_TOKEN | jq .value | base64)

and I get

* error validating token: error verifying token signature: oidc: malformed jwt: square/go-jose: compact JWS format must have three parts

I am doing something wrong with the handling of the JWT token when I pass it to the provider (via env variable), but I cannot figure out what. The same post to the same URL with the “same” data is yielding a successful login, when I create my own JSON value with jq in the shell.

Does anyone have any idea what the terraform vault provider could be doing that is messing me up here? Or at the very least, does anyone know where I can look in the code base to try to figure that out?

You need to add the -r option to jq, or the output will include surrounding double-quotes which are not part of the JWT syntax.

1 Like

Oh my good golly, this is why you post questions instead of flailing for hours. Thanks for the help, this got it fixed.