RSS

Propagating OAuth2 tokens made easier

oauth2-refresh-controller release announcement

We are thrilled to announce the release of oauth2-refresh-controller v1.0.1 component. Modifying deployments to include OAuth2 secrets is now thing of the past. Inject and refresh tokens automatically with the oauth2-refresh-controller.

What is OAuth2

OAuth2 is the industry-standard protocol for authorization. It defines workflows on how to authorize user’s access to protected resources.

At the end of a workflow - such as verifying the application has access to the requested resources, that the secret provided is correct, … - the user obtains a piece of secret data called access token. This token is then passed around whenever access to protected resources is requested.

oauth-flow-comparison (source https://blog.oauth.io/introduction-oauth2-flow-diagrams/)

At CERN, the Authorization Service already provides ways to integrate services and applications. On the other side, a variety of services is able to consume access tokens - including the EOS storage service which we’ll talk about in this post.

Introducing oauth2-refresh-controller

oauth2-refresh-controller is a Kubernetes controller for injecting OAuth2 access tokens into Pods, and then their subsequent rotation using refresh tokens. It is deployed as an opt-in feature in the upcoming v1.26 cluster templates at CERN.

How do I use this?

Create a secret containing the OAuth2 token, and annotate your Pods accordingly. It’s as simple as that.

apiVersion: v1
kind: Secret
metadata:
  name: hello-token

  annotations:
    # is-token annotation makes this Secret visible to the oauth2-refresh-controller.
    oauth2-refresh-controller.cern.ch/is-token: "true"

stringData:

  # oauth2-refresh-controller expects tokens to have "oauth",
  # "clientID" and "clientSecret" fields set.

  # "oauth" must be a JSON-formatted string with "access_token"
  # and "refresh_token" fields (other fields are ignored).

  oauth: '{"access_token": "eyJhb...","expires_in":1199,"refresh_expires_in":0,"refresh_token":"eyJhbG...","token_type":"Bearer","not-before-policy":1643110683,"session_state":"5d5e8bc2-6557-4453-9ba2-8ed99be6c898","scope":"offline_access profile email"}}'
  clientID: "my-app"
  clientSecret: "my-app-client-secret"
apiVersion: v1
kind: Pod
metadata:
  name: token-printer-pod

  annotations:
    # to-inject annotation describes which token to inject into what container, and under what user.
    # It's a JSON-formatted string holding an array of objects with following schema:
    #
    # * secretName: (string) Name of the OAuth2 token Secret in Pod's namespace.
    # * container: (string) Name of the container into which inject the token.
    # * owner: (integer) The token file will have its UID and GID set to this value.
    #
    # See docs at https://kubernetes.docs.cern.ch/docs/security/credentials/#oauth2
    # for complete list of available parameters (link restricted for internal access only).

    oauth2-refresh-controller.cern.ch/to-inject: |
      [
        {
          "secretName": "hello-token",
          "container": "token-printer",
          "owner": 0
        }
      ]      

spec:
  containers:

  - name: token-printer
    image: busybox
    imagePullPolicy: IfNotPresent
    command:
    - /bin/sh
    - -c
    - |
      while true; do
        cat /tmp/oauthtk_0
        date
        sleep 20m
      done      

The controller will now automatically refresh the access token inside the secret before it expires, as well as the token files in your pods.

$ kubectl exec -it token-printer-pod -- sh
root@token-printer-pod# ls -l /tmp
total 4
-r--------    1 root     root             6 Apr  5 09:40 oauthtk_0
root@token-printer-pod# cat /tmp/oauthtk_0
eyJhb...<rest of the access JWT>

Be sure to check the full documentation (internal only) to find out more!

Example usage with EOS

You can set the oauth2-refresh-controller.cern.ch/to-inject annotation to make the token compatible with EOS-OAuth2 authentication.

kubectl create configmap oauth-token-eos-template \
    --from-literal template='oauth2:$(ACCESS_TOKEN):auth.cern.ch/auth/realms/cern/protocol/openid-connect/userinfo'
apiVersion: v1
kind: Pod
metadata:
  name: token-printer-pod

  annotations:
    oauth2-refresh-controller.cern.ch/to-inject: |
      [
        {
          "secretName": "hello-token",
          "container": "token-printer",
          "owner": 0,
          "templateConfigMapName": "oauth-token-eos-template"
        }
      ]      

spec:
  volumes:
  - name: eos
    hostPath:
      path: /var/eos

  containers:

  - name: eos-printer
    image: busybox
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: eos
      mountPath: /eos
    env:
    - name: OAUTH2_TOKEN
      value: FILE:/tmp/oauthtk_0
    command:
    - /bin/sh
    - -c
    - |
      while true; do
        cat /tmp/oauthtk_0
        ls -l /eos/home-r/rvasek
        sleep 20m
      done      

Here we have created a ConfigMap with a template of the token file and then instructed the oauth2-refresh-controller to use it by specifying the templateConfigMapName parameter in the annotation. The template contains an EOS-compatible OAuth2 access token format. $(ACCESS_TOKEN) will be expanded by the oauth2-refresh-controller to the actual token value. Lastly we add the OAUTH2_TOKEN environment variable that’s needed by the eosxd client, and we’re all set!

Plans for the future

In next iterations of the component we plan to improve and optimize the access to the Kubernetes API to lessen the number of calls needed. We would also like to hear from you, what uses you might have for this tool, and how we can improve it to suite your needs. Stay tuned!