tink-crypto / tink

Tink is a multi-language, cross-platform, open source library that provides cryptographic APIs that are secure, easy to use correctly, and hard(er) to misuse.
https://developers.google.com/tink
Apache License 2.0
13.47k stars 1.18k forks source link

GcpKmsClient does not work with workload-identity-federation #569

Closed tobiasendres closed 2 years ago

tobiasendres commented 2 years ago

Describe the bug

We have implemented a workload-identity-federation based workflow in Github-Actions. We are using the "auth" plugin for github-actions. This works all fine, until our unit-tests try to establish a connection to GCP KMS in "GcpKmsClient".

The credentials provided by workload-identity-federation looks as follows:

{ "type": "external_account", "audience": "***", "subject_token_type": "urn:ietf:params:oauth:token-type:jwt", "token_url": "https://sts.googleapis.com/v1/token", "service_account_impersonation_url": "***", "credential_source": { "url": "***", "headers": { "Authorization": "***" }, "format": { "type": "json", "subject_token_field_name": "value" } } }

The type is set to "external_account" which leads to the following exception during our test-execution:

Caused by: java.io.IOException: Error reading credentials from stream, 'type' value 'external_account' not recognized. Expecting 'authorized_user' or 'service_account'. at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.fromStream(GoogleCredential.java:249) at com.google.api.client.googleapis.auth.oauth2.DefaultCredentialProvider.getCredentialUsingEnvironmentVariable(DefaultCredentialProvider.java:217) ... 155 common frames omitted

The "GcpKmsClient" implementation uses "com.google.api-client:google-api-client:1.31.4" and the "GoogleCredential" class which does only support the "authorized_user" and "service_account" credential-type and not the "external_account"-type. Also, the "GoogleCredential" class is marked deprecated and the use of "google-auth-library" is encouraged. This module seems to handle "external_account"-types correctly.

To Reproduce

Expected behavior

GcpKmsClient works with credentials as provided by workload-identity-federation.

Version information

SanjayVas commented 2 years ago

Related to #584?

lucjross commented 2 years ago

@tobiasendres did you by chance find a way to make this work? i'm trying to get Java tests running in Github Actions with WIP auth.

tobiasendres commented 2 years ago

@lucjross , unfortunately not. We did a workaround where we are generating a temporary service-account key and use this one when running the tests by setting the GOOGLE_APPLICATION_CREDENTIALS env-var. Also, the key is being deleted when the tests had run.

eschultink commented 2 years ago

I'm using the GoogleCredentials class, but was getting the same error until I did the following:

1) setting up the auth action to output an access token

      - id: 'auth-gcp'
        name: 'Authenticate to Google Cloud'
        uses: google-github-actions/auth@v0.6.0
        with:
          workload_identity_provider: '---'
          service_account: '----'
          token_format: 'access_token'

2) providing that token + expiration time via env var

        env:
          GCP_ACCESS_TOKEN: '${{ steps.auth-gcp.outputs.access_token }}'
          GCP_ACCESS_TOKEN_EXPIRATION: '${{ steps.auth-gcp.outputs.access_token_expiration }}'

3) consuming it in java

    @SneakyThrows
    static GoogleCredentials getCredentialsForIntegrationTest() {
        try {

            //in local, should be the developer's personal credentials
            return GoogleCredentials.getApplicationDefault();
        } catch (IOException e) {
            //fails if default credential is `external_user`, so workaround by using access token
            String accessTokenString = System.getenv("GCP_ACCESS_TOKEN");
            if (accessTokenString == null) {
                throw e;
            } else {
                AccessToken accessToken = new AccessToken(accessTokenString,
                    Date.from(Instant.parse(System.getenv("GCP_ACCESS_TOKEN_EXPIRATION"))));
                return GoogleCredentials.create(accessToken);
            }
        }
    }

as I recall, you can use com.google.auth.http.HttpCredentialsAdapter to use GoogleCredentials in the older clients that expect GoogleCredential