google-auth-library-java icon indicating copy to clipboard operation
google-auth-library-java copied to clipboard

ImpersonatedCredentials can't negotiate proper access token with delegated access to user account

Open tzhou2021 opened this issue 4 years ago • 12 comments

Hello,

For my use case, I have to impersonate a service account B (own by our customer) that has domain wide delegation enabled using another source service account A (own by us). The goal is to access all user emails that service account B has access to using service account A.

        String emailAddress = "[email protected]"
        ServiceAccountCredentials sourceCredentials = (ServiceAccountCredentials) ServiceAccountCredentials.fromStream(new FileInputStream("service_account_A.json")).createScoped(Arrays.asList("https://www.googleapis.com/auth/iam"));
        GoogleCredentials impersonatedCredentials = ImpersonatedCredentials.create(
                sourceCredentials,
                "[email protected]",
                null,
                Arrays.asList("https://mail.google.com/", "https://www.googleapis.com/auth/calendar"),
                300)
               .createDelegated(emailAddress);

        try {
            impersonatedCredentials.refreshAccessToken();
        } catch (IOException e) {
            System.out.println(e);
            return;
        }
        HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(impersonatedCredentials);
        Gmail service = new Gmail.Builder(httpTransport, jacksonFactory, requestInitializer).setApplicationName("Foundation POC").build();
	ListMessagesResponse response = service.users().messages().list(emailAddress).execute();
	for (Message message : response.getMessages()) {
		System.out.println(message.toPrettyString());
	}

However, the access token retrieved doesn't have access to fetching user email, I got the error below:

Exception in thread "main" com.google.api.client.googleapis.json.GoogleJsonResponseException: 400 Bad Request
{
  "code": 400,
  "errors": [
    {
      "domain": "global",
      "message": "Precondition check failed.",
      "reason": "failedPrecondition"
    }
  ],
  "message": "Precondition check failed.",
  "status": "FAILED_PRECONDITION"
}

By further examining the implementation of createDelegated(String user) of ImpersonatedCredentials, it actually does not respect the input user, which is not what I was expecting:

Screen Shot 2021-10-08 at 3 15 44 AM

I'm using

        <dependency>
            <groupId>com.google.auth</groupId>
            <artifactId>google-auth-library-oauth2-http</artifactId>
            <version>1.2.0</version>
        </dependency>

I don't really know what could solve this issue for my unique use case, thoughts on this? Thank you!

tzhou2021 avatar Oct 08 '21 07:10 tzhou2021

@silvolu Could you ask someone to look at this?

lesv avatar Nov 01 '21 22:11 lesv

Hi @tzhou2021,

The code that you referenced is expected. It is a default functionality, which is - not implemented. Individual credentials should have an override with a specific implementation. As an example you can see ServiceAccountCredential has this method implemented.

The README for this library has an example how to instantiate ImpersonatedCredentials, have you tried that?

TimurSadykov avatar May 06 '22 03:05 TimurSadykov

requilifying as question

TimurSadykov avatar Jun 25 '22 02:06 TimurSadykov

Hello, I have the same problem. Does Google Auth not support impersonation + domain-wide delegation? If not, it's certainly a design flaw.

@TimurSadykov the answer is not satisfying as it's saying to either look at domain-wide delegation or impersonation separately, but there is no example on how to do them together. It also fails for me with "bad request" so it is a bug or just not supported at all.

alamothe avatar Aug 24 '22 23:08 alamothe

@tzhou2021 did you find a solution?

alamothe avatar Aug 24 '22 23:08 alamothe

@alamothe Please provide details of your particular issue and we try to figure it out.

In the original issue the problem that @tzhou2021 misunderstood that user override is not respected by referencing GoogleCredentials. The GoogleCredentials is a base class, but actual impersonation is credential specific. If ServiceAccount is used, it has an override implementation:

@Override public GoogleCredentials createDelegated(String user) { return this.toBuilder().setServiceAccountUser(user).build(); }

TimurSadykov avatar Aug 27 '22 09:08 TimurSadykov

@TimurSadykov You're mixing the part where @tzhou2021 tried to debug why it doesn't work and the problem description.

Let me try to explain just the problem description.

  • We have a service account A (owned by us) and service account B (owned by a third party).
  • Service account A is allowed to impersonate service account B (by giving token creator permission on B)
  • Service account B is given domain-wide delegation for a Google Workspace W (https://developers.google.com/admin-sdk/directory/v1/guides/delegation)

Now, we want A to be able to perform an action on W as user U in that workspace. This should work, because permissions have been adequately set up. However it fails at runtime:

		val credentials = (GoogleCredentials.getApplicationDefault() as ServiceAccountCredentials)
				.createScoped("https://www.googleapis.com/auth/iam")
				.let {
					ImpersonatedCredentials.create(
							it,
							null,
							listOf("[email protected]"),
							listOf(DirectoryScopes.ADMIN_DIRECTORY_USER),
							300)
				}
				.createDelegated("[email protected]")

		val service = Directory.Builder(
				httpTransport,
				GsonFactory.getDefaultInstance(),
				HttpCredentialsAdapter(credentials),
		)
				.setApplicationName("test")
				.build()

		service.users()
				.list()
				.setCustomer("my_customer")
				.setMaxResults(10)
				.setOrderBy("email")
				.execute()

alamothe avatar Aug 27 '22 19:08 alamothe

@alamothe The use case is clear from the original description, but thanks for providing specific code you use.

I'm trying to understand the issue you are seeing. "It fails" does not provide much data to investigate.

Do you see exactly the same error like @tzhou2021? If you just do refresh token for the created credential, does it work or it throws same error?

TimurSadykov avatar Aug 29 '22 02:08 TimurSadykov

@TimurSadykov

We are also seeing the same problem in our Appengine Java app. The details are below:

  • Service Account S has domain-wide delegation access to our domain as user U.
  • The Appengine Default Service Account ([email protected]) has Service Account Token Creator permission to S. This is the credential returned by GoogleCredentials.getApplicationDefault().
  • The credential is created as follows:
        ImpersonatedCredentials.newBuilder()
            .setSourceCredentials(GoogleCredentials.getApplicationDefault())
            .setTargetPrincipal(service_account_S_email)
            .setScopes(requiredScopesList)
            .build();

The error message we get back is

{
  "code": 403,
  "errors": [
    {
      "domain": "global",
      "message": "Not Authorized to access this resource/api",
      "reason": "forbidden"
    }
  ],
  "message": "Not Authorized to access this resource/api"
}

One thing we find it odd is that since ImpersonatedCredentials does not override parent's createDelegated() method, we don't have a way to set the subject's email address ([email protected]).

By the way, the python library's service account implementation is reportedly working for domain-wide delegation. The GCP professional services even has an example here.

The python service_account code is closer to the Java JwtCredentials than to the ImpersonatedCredentials. Unfortunately, the JwTCredentials does not take another Credentials as signer. Is ImpersonatedCredentials meant to support domain-wide delegation, or should we make a feature request for JwTCredentials to accept another credential as signer?

weiminyu avatar Oct 20 '22 16:10 weiminyu

Following... Our org will disallow using SA Key soon. It would be great if our APP's SA can just impersonate domain-wide delegation SA. It might be a more common case now as using SA key is discouraged as it does introduce security risks?

It's been 2+ years since this issue opened, is there any workaround? Thanks

yonghaoy avatar May 30 '24 20:05 yonghaoy