Workload Identity Federation support: 'type' field is "external_account" (expected "service_account")

Hey,

I am trying to get Rclone working with Google's Workload Identity Federation (WIF from now on).

WIF allows one to use external Identity Provider (IdP from now on) and use it to authenticate requests to GCP API. It supports AWS, ADFS and so on, but at most basic level it can be OIDC provider JWKs and this means that any other Kubernetes cluster with JWK issuer can work as IdP. That's what I am testing first.

Essentially, trying to establish trust with GCP that says: "trusts this openid connect (OIDC) token issued to a k8s service account by a given K8s API server

What I am trying to do follows quite closely the example presented here but I am using an actual K8s cluster so do not need expose the issue via ngrok.

I will try to keep the description brief yet complete.

1. Configuring workflow identity federation

I needed to configure WIF on GCP side. For that I obtain the issuer url with

kubectl get --raw /.well-known/openid-configuration | jq .issuer

for completness the response from .well-known/openid-configuration looks like this

{
  "issuer": "https://container.googleapis.com/v1/projects/<project id>/locations/<region>/clusters/<cluster name>",
  "jwks_uri": "https://<some ip>:443/openid/v1/jwks",
  "response_types_supported": [
    "id_token"
  ],
  "subject_types_supported": [
    "public"
  ],
  "id_token_signing_alg_values_supported": [
    "RS256"
  ]
}

I use the issuer to configure WIF on the GCP side and give it rclone-test-audience as allowed audience. I also set few attributes mapping but this is for now not important.

Note that this is K8s cluster running on GCP as well so normally one should just use Workload Identity (not WIF) but I want to workout more general case.

2. Configuring K8s pod to have K8s SA's creds

Now I configure the K8s service account and make sure that pod (with rclone) has right tokens available

---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: rclone-sa

---

apiVersion: v1
kind: Pod
metadata:
  name: rclone-wif
spec:
  serviceAccountName: rclone-sa
  containers:
  - name: rclone
    image: rclone/rclone:1.63.0
    command: ["/bin/sh", "-c", "--", "sleep 3600"]
    volumeMounts:
    - name: rclone-config-volume
      mountPath: /config/rclone

    - name: sa-token
      mountPath: /var/run/secrets/tokens

  volumes:
  - name: rclone-config-volume
    secret:
      secretName: rclone-config

  - name: sa-token
    projected:
      sources:
      - serviceAccountToken:
          path: sa-token
          expirationSeconds: 7200
          audience: rclone-test-audience

with this, effectively under the /run/secrets/tokens/sa-token inside the pod one will find a JWT token that after decoding with https://jwt.io/ looks like

{
  "aud": [
    "rclone-test-audience"
  ],
  "exp": <some timestamp: integer>,
  "iat": <some timestamp: integer>,
  "iss": "https://container.googleapis.com/v1/projects/<project id>/locations/<region>/clusters/<cluster name>",
  "kubernetes.io": {
    "namespace": "default",
    "pod": {
      "name": "rclone-wif",
      "uid": "<some uid>"
    },
    "serviceaccount": {
      "name": "rclone-sa",
      "uid": "<some uid>"
    }
  },
  "nbf": <some timestamp: integer>,
  "sub": "system:serviceaccount:default:rclone-sa"
}

3. Configuring Rclone

Ideally I would want to just point Rclone to JWT token under /run/secrets/tokens/sa-token but that does not seem to be possible. I tried then to create the credentials config

gcloud iam workload-identity-pools create-cred-config  \ 
         projects/<project number>/locations/global/workloadIdentityPools/<pool id>/providers/<provider id>   \
         --service-account=oidc-federated@<project id>.iam.gserviceaccount.com   \
         --output-file=creds.json  \
         --credential-source-file=/var/run/secrets/tokens

which creds.json as follow

{
  "type": "external_account",
  "audience": "//iam.googleapis.com/projects/<project number>/locations/global/workloadIdentityPools/<pool id>/providers/<provider id>",
  "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
  "token_url": "https://sts.googleapis.com/v1/token",
  "credential_source": {
    "file": "/var/run/secrets/tokens"
  },
  "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/oidc-federated@<project id>.iam.gserviceaccount.com:generateAccessToken"
}

I then hope to use that creds.json with rclone so I complete my config with

apiVersion: v1
kind: Secret
metadata:
  name: rclone-config
type: Opaque
stringData:
  rclone.conf: |
    [gs]
    type = google cloud storage
    service_account_file = /config/rclone/creds.json

  creds.json: |
    {
      "type": "external_account",
      "audience": "//iam.googleapis.com/projects/<project number>/locations/global/workloadIdentityPools/<pool id>/providers/<provider id>",
      "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
      "token_url": "https://sts.googleapis.com/v1/token",
      "credential_source": {
        "file": "/var/run/secrets/tokens/sa-token"
      },
      "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/oidc-federated@<project id>.iam.gserviceaccount.com:generateAccessToken"
    }

4. Tests and where I hit issues

Hopeful that this is all I need I exec into the pod and try to access my test bucket

$ rclone ls gs:<test bucket> -vvv
2023/07/09 13:57:54 DEBUG : rclone: Version "v1.63.0" starting with parameters ["rclone" "ls" "gs:<test bucket>" "-vvv"]
2023/07/09 13:57:54 DEBUG : Creating backend with remote "gs:<test bucket>"
2023/07/09 13:57:54 DEBUG : Using config file from "/config/rclone/rclone.conf"
2023/07/09 13:57:54 Failed to create file system for "gs:<test bucket>": failed configuring Google Cloud Storage Service Account: error processing credentials: google: read JWT from JSON credentials: 'type' field is "external_account" (expected "service_account")

The

Failed to create file system for "gs:": failed configuring Google Cloud Storage Service Account: error processing credentials: google: read JWT from JSON credentials: 'type' field is "external_account" (expected "service_account")

error seems to originate from https://github.com/rclone/rclone/blob/473d443874ef2facf16c66342cb70e806c05a1d5/backend/googlecloudstorage/googlecloudstorage.go#L487 which in results call https://github.com/golang/oauth2/blob/62b4eedd7210c3ff2fc694318a8a312b96f01a74/google/google.go#L84-L94


So... either I need to try and use different configuration or rclone needs a new code path to take into account external_account type of credentials. Any help and suggestions will be greatly appreciated!

Okay, I actually think I got it working in this simple case.
I set

    env:
    - name: GOOGLE_APPLICATION_CREDENTIALS
      value: /config/rclone/creds.json

to inform underlying oauth library of the location file and adjusted rclone config to be

stringData:
  rclone.conf: |
    [gs]
    type = google cloud storage
    env_auth = true

and now I can access my test bucket successfully!

I still wonder though if there would be option to point Rclone to /run/secrets/tokens/sa-token JWT token directly without the need to prepare the /config/rclone/creds.json file... As that would make the whole thing easier to configure.

Nice one - well done :slight_smile:

We put env_auth in recently so glad it is useful.

We rely on the google library golang.org/x/oauth2/google so if you can find the relevant API there we can wedge it into rclone.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.