Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow creating a trust relationship with Github Actions OIDC provider to retrieve an uaa access token using a Github id_token #2838

Closed
vchrisb opened this issue Apr 18, 2024 · 12 comments · Fixed by #3219

Comments

@vchrisb
Copy link

vchrisb commented Apr 18, 2024

What version of UAA are you running?

  • 76.26.0
  • 77.1.0

How are you deploying the UAA?

I am deploying the UAA

  • as part of a commercial Cloud Foundry distribution

What did you do?

JWT Bearer Token Grant by adding the Github OIDC Provider:

I added the Github OIDC provider using non existing credentials and used the repository_owner claim as the user_name:

uaa curl /identity-providers -X POST -H "Content-Type: application/json" -d '{"type": "oidc1.0", "name": "Github", "originKey": "github", "config": {"discoveryUrl": "https://token.actions.githubusercontent.com/.well-known/openid-configuration", "scopes": ["read:user", "user:email"], "linkText": "Login with Github", "showLinkText": false, "addShadowUserOnLogin": true, "clientAuthInBody": true, "relyingPartyId": "uaa", "relyingPartySecret": "uaa", "addShadowUserOnLogin": true, "attributeMappings" : {"given_name": "repository_owner", "family_name": "repository_owner_id", "user_name": "repository_owner"}}}'

The sub can't be used for the user_name, as it includes unsupported characters like / and :. I used the repository_owner claim for the user_name.

UAA client required for authentication:

uaa curl /oauth/clients -X POST -H "Content-Type: application/json" -d '{"client_id" : "jwt-bearer-client", "client_secret" : "secret", "access_token_validity": 1800,  "authorities" : [ "uaa.resource" ], "authorized_grant_types" : [ "urn:ietf:params:oauth:grant-type:jwt-bearer" ], "scope": ["openid", "cloud_controller.read"], "allowedproviders" : [ "github" ], "name" : "JWT Bearer Client"}'

Finally the request to get an access_token (uaa has to be used for requesting the id_token)

curl -u 'jwt-bearer-client:secret' -d 'grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<github id token>' https://uaa.sys.domain.com/oauth/token

The resulting access_token:

{
  "jti": "a6bfcbc1e851444196ce76779567cbed",
  "sub": "c8068c6a-8e21-408f-9532-0f3a413f988e",
  "scope": [
    "openid",
    "cloud_controller.read"
  ],
  "client_id": "jwt-bearer-client",
  "cid": "jwt-bearer-client",
  "azp": "jwt-bearer-client",
  "grant_type": "urn:ietf:params:oauth:grant-type:jwt-bearer",
  "user_id": "c8068c6a-8e21-408f-9532-0f3a413f988e",
  "origin": "github",
  "user_name": "vchrisb",
  "email": "[email protected]",
  "rev_sig": "7ec0c4f2",
  "iat": 1713450185,
  "exp": 1713493385,
  "iss": "https://uaa.sys.domain.com/oauth/token",
  "zid": "uaa",
  "aud": [
    "cloud_controller",
    "openid",
    "jwt-bearer-client"
  ]
}

Example Github workflow to retrieve the id_token using a custom github action vchrisb/setup-cf:

name: CI
permissions:
  id-token: write # This is required for requesting the JWT
  contents: read  # This is required for actions/checkout

on:
  push:

jobs:
  deploy:
    name: password
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v4
    - name: setup cf cli
      uses: vchrisb/[email protected]
      with:
        api: ${{ secrets.CF_API }}
        client_id: ${{ secrets.CF_CLIENT_ID }}
        client_secret: ${{ secrets.CF_CLIENT_SECRET }}
        grant_type: jwt-bearer
        org: test
        space: dev
    - name: access cloud foundry api
      run: cf push --strategy rolling

It is possible to get an uaa access_token using the JWT Bearer Token Grant, but this has limitations:

  • adding the identity provider requires fake credentials
  • the sub, or combination of claims, can't be used for the user_name
  • a client_secret is required

client_credentials Grant:

I also tried using the client_credentials Grant with private_key_jwt to retrieve an UAA access_token by providing a Github id_token.
For that I created a UAA client, which name matched the sub claim of the id_token. I configured the client's jwks_uri with the one of Github. An authentication does fail, because the iss claim does not include the client_id, which is required by the specs for this flow.

Conversation on that in UAA Slack.

What did you expect to see? What goal are you trying to achieve with the UAA?

I would like to be able to access the cloud foundry api from a Github workflow using temporary credentials.

Background

From Github Security hardening with OpenID Connect:

GitHub Actions workflows are often designed to access a cloud provider (such as AWS, Azure, GCP, or HashiCorp Vault) in order to deploy software or use the cloud's services. Before the workflow can access these resources, it will supply credentials, such as a password or token, to the cloud provider. These credentials are usually stored as a secret in GitHub, and the workflow presents this secret to the cloud provider every time it runs.

However, using hardcoded secrets requires you to create credentials in the cloud provider and then duplicate them in GitHub as a secret.

With OpenID Connect (OIDC), you can take a different approach by configuring your workflow to request a short-lived access token directly from the cloud provider. Your cloud provider also needs to support OIDC on their end, and you must configure a trust relationship that controls which workflows are able to request the access tokens. Providers that currently support OIDC include Amazon Web Services, Azure, Google Cloud Platform, and HashiCorp Vault, among others.

Github Actions does provide an id_token to workflows with following relevant claims from Understanding the OIDC token:

  • sub, e.g. repo:octo-org/octo-repo:environment:prod, the template used to generate the sub value can be configured
  • iss is always https://token.actions.githubusercontent.com
  • aud can be configured when requesting the id_token in the workflow

Example Job to retrieve the id_token:

  job:
    environment: Production
    runs-on: ubuntu-latest
    steps:
    - name: Install OIDC Client from Core Package
      run: npm install @actions/[email protected] @actions/http-client
    - name: Get Id Token
      uses: actions/github-script@v7
      id: idtoken
      with:
        script: |
          const coredemo = require('@actions/core')
          let id_token = await coredemo.getIDToken('https://uaa.sys.domain.com/oauth/token')
          coredemo.setOutput('id_token', id_token)

The Github OIDC provider is solely exposing the JSON web key (JWK) endpoint: https://token.actions.githubusercontent.com/.well-known/jwks to be used to validate the Github id_token.

Github OIDC openid-configuration

The Github documentation does have examples how to create a trust relation with AWS, Azure, GCP and Vault.

(The Github actions OIDC provider is not the same as the Oauth2 provider for Github Oauth2 Apps.)

Ask

I want to use a short live token to interact with the cloud foundry api.
For that, I would like to be able to create a trust relation from UAA to Github, (and other CI systems like GitLab) to use the id_token provided in the workflow to retrieve an UAA client access_token.
The UAA client might be created using the sub claim of the id_token or a pre created client might be pattern matched against the sub.
The client will be used to assign a cloud foundry space role to it.

@cf-gitbot
Copy link

We have created an issue in Pivotal Tracker to manage this:

https://www.pivotaltracker.com/story/show/187454040

The labels on this github issue will be updated when the story is started.

@vchrisb vchrisb changed the title Allow creating a trust relationship with Github OIDC provider to retrieve an uaa access token using a Github id_token Allow creating a trust relationship with Github Actions OIDC provider to retrieve an uaa access token using a Github id_token Apr 18, 2024
@strehle
Copy link
Member

strehle commented Apr 18, 2024

Ok, thank you. I know understand what you want and I have a plan in my mind howto solve it... the Prio is currently so that I want solve PR 2813 and then I can start to create some PRs for this fix. I think I will solve this issue with more than one PR, because the issue now exists in some parts, but finally the client credential flow should work and JWT bearer as well

@strehle
Copy link
Member

strehle commented Apr 18, 2024

@peterhaochen47 , FYI

@vchrisb
Copy link
Author

vchrisb commented Apr 18, 2024

thank you @strehle
I did some more testing and had success with jwt bearer grant.

I updated the initial issue with the findings

@peterhaochen47
Copy link
Member

peterhaochen47 commented Apr 19, 2024

Ok cool, seems like there's at least a workaround / a hack.

We'll keep the issue open and in mind but likely won't pursue a fix immediately, because being compatible with a non-standard OIDC is not on our current roadmap. Nevertheless, your issue will help others & help us gather data point about the demand for this.

@vchrisb
Copy link
Author

vchrisb commented Apr 19, 2024

My last comment might have been misleading.

For the jwt bearer token grant that is probably right, but the original ask is to support a trust relation.
To use the jwt bearer token grant for this problem, is already a workaround by itself.

AWS, Azure, GCP and Vault do support a trust with amongst others GitHub and Gitlab.
From a bit more research, I think they actually use OIDC Token Exchange RFC 8693?

Here is the original github roadmap issue: github/roadmap#376

@damzog
Copy link

damzog commented Jun 6, 2024

Hi @vchrisb great stuff! I did not fully get it yet but it seems this requirement matches very much with one of our own: We have Azure Entra as Identity provider and would like to enable API developers to take an Azure Entra Bearer token from there clients and then use this token to execute API calls to a Cloudfoundry instance using UAA (knowing the same identities) on behalf the identy from the Azure token.

@damzog
Copy link

damzog commented Jun 14, 2024

Hi @vchrisb , I have been able to use this pattern to create a web app offering SSO with Microsoft Entra which exchanges the Azure id-token to a UAA issued access-token and uses it to call the CF Api with it.

In this scenario it is actually ok to have a uaa client that is used be the server side part of the app to do the token exchange. It is a security gain in any case.

Now in your case I was wondering: Wouldn't it make sense to put a reverse proxy between your github runner and the cf api that actually does the token exchange using a uaa client?

@strehle strehle linked a pull request Jan 6, 2025 that will close this issue
@vchrisb
Copy link
Author

vchrisb commented Jan 17, 2025

Thank you @strehle for the new feature. 💯
I looked at the PR but I was not able to figure out how to use this for the purpose explained in this issue.

@strehle
Copy link
Member

strehle commented Jan 17, 2025

Thank you @strehle for the new feature. 💯 I looked at the PR but I was not able to figure out how to use this for the purpose explained in this issue.

2 parts to benefit.

  1. you use the token from github as user token and for client authentication, because in your example you showed parts

client_id: ${{ secrets.CF_CLIENT_ID }} client_secret: ${{ secrets.CF_CLIENT_SECRET }} grant_type: jwt-bearer

Using a secret in github action is that what we should not do or better what we want to remove. github actions provide a token in order to omit secret in the github repo.

  1. if you use the token from github for the simple client_credentials flow, then you dont have to have a user / fake user, because in id_token from github there is no real user information but more the information about the repo . See example https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token . The token from the example only has the "actor": "octocat", claim, which could be used for a user-mapping... but I would use it for client_credentials only and in push command I would use the client. Cloud Controller roles can be setup for a client, e.g. https://docs.cloudfoundry.org/adminguide/cli-user-management.html -> https://cli.cloudfoundry.org/en-US/v6/set-space-role.html , means cf set-space-role USERNAME ORGANIZATION-NAME SPACE-NAME ROLE is used for users, but USERNAME can be a clientid/clientname if you add --client in the commands

Missing is the support in cf-cli; cloudfoundry/cli#3368

Finally. If you prefer to use a user in CF then I recommend to enhance the cf client with jwt-bearer, e.g.

uaac client update cf --authorized_grant_types "password refresh_token urn:ietf:params:oauth:grant-type:jwt-bearer"

because then you can do your jwt brearer grant type with cf client and empty secret, same what cf login current does with the password grant.
If we clarify if jwt bearer can be setup with an empty secret, e.g. #3232 then your setup would be easier.

If you want go with clients then you can do this with the new feature. You can decide if you want see a github action as technical object or a user

@vchrisb
Copy link
Author

vchrisb commented Jan 17, 2025

sorry, I did not yet get it. :/
I don't understand what new functionality actually was added via the PRs.

  1. you use the token from github as user token and for client authentication, because in your example you showed parts

client_id: ${{ secrets.CF_CLIENT_ID }} client_secret: ${{ secrets.CF_CLIENT_SECRET }} grant_type: jwt-bearer

Using a secret in github action is that what we should not do or better what we want to remove. github actions provide a token in order to omit secret in the github repo.

The desire is to not require any client secret, but these are currently required for grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer. My understanding ist that #3232 would allow to use jwt-bearer without the additional client secret?

  1. if you use the token from github for the simple client_credentials flow, then you dont have to have a user / fake user, because in id_token from github there is no real user information but more the information about the repo . See example https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token . The token from the example only has the "actor": "octocat", claim, which could be used for a user-mapping... but I would use it for client_credentials only and in push command I would use the client. Cloud Controller roles can be setup for a client, e.g. https://docs.cloudfoundry.org/adminguide/cli-user-management.html -> https://cli.cloudfoundry.org/en-US/v6/set-space-role.html , means cf set-space-role USERNAME ORGANIZATION-NAME SPACE-NAME ROLE is used for users, but USERNAME can be a clientid/clientname if you add --client in the commands

Missing is the support in cf-cli; cloudfoundry/cli#3368

Can you please give an example, how I could create a client, to be used with client_credentials, with the example id token from:
https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token

The sub is the claim needed to be mapped to an uaa client. In the example it is repo:octo-org/octo-repo:environment:prod.

Finally. If you prefer to use a user in CF then I recommend to enhance the cf client with jwt-bearer, e.g.

uaac client update cf --authorized_grant_types "password refresh_token urn:ietf:params:oauth:grant-type:jwt-bearer"

because then you can do your jwt brearer grant type with cf client and empty secret, same what cf login current does with the password grant. If we clarify if jwt bearer can be setup with an empty secret, e.g. #3232 then your setup would be easier.

If you want go with clients then you can do this with the new feature. You can decide if you want see a github action as technical object or a user

What I like about jwt-bearer flow is, that the user is created dynamically. But similarly to above, the sub (repo:octo-org/octo-repo:environment:prod), with currently unsupported characters, needs to be used/mapped for the user_name.

Thank you.

@strehle
Copy link
Member

strehle commented Jan 17, 2025

May we should move to slack, but first about JWT-Bearer. It is fine to use it. About sub. You can use other token claims to map to user_name, so actor is the claim you should use. e.g. https://cloudfoundry.github.io/uaa/version/77.24.0/index.html#oauth-oidc ->

"attributeMappings" : { "user_name" : "actor"}

should solve it for you.

Then you have client authentication. If you have JWT bearer already, then I recommend to use client cf and empty secret .

For the setup of mapping a token from https://docs.github.com/en/actions/security-for-github-actions/security-hardening-your-deployments/about-security-hardening-with-openid-connect#understanding-the-oidc-token to get a client. I will do it later, because then I can reference a doc in https://cloudfoundry.github.io/uaa/version/77.25.0/index.html#change-client-jwt (which is not yet there)

My understanding ist that #3232 would allow to use jwt-bearer without the additional client secret?

Not completly but you should be able to re-use the cf client (standard oauth client in cloudfoundry to fullfill the cf login xxx)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Development

Successfully merging a pull request may close this issue.

5 participants