Easy GSuites Domain-Wide Delegation (DwD) in Java

2020-02-17

Simple wrapper in java to Perform G Suite Domain-Wide Delegation of Authority.

The link in the subtitle shows you how to setup dwd in a couple of languages but only how to do this in java with an actual service account credential file. It does not show how to do this in any other environment that could provide a service-account’s Credential (for example, on Compute Engine). The example above also uses the older google api client library and not the recommended google cloud client library which is awkward when combining code with other Google Cloud Services (more on this later).

I rarely use dwd (in java) but a week ago or so I helped a a colleague set this up in an environment where he wanted to use Domain-Wide delegation on a system that didn’t have a service account json but was running on ComputeEngine. In investigating, i couldn’t not clear library or example for this so i set out to generate my own and wrap the various implementations in a way you could use the cloud client libraries on any environment.

The following code is a wrapper for DomainWideDelegation in java that can handle several source credential transparently, manage its refresh lifecycle all without having to bootstrap and use a JSON based cert with the older client library. To use this, you must ofcourse setup a service account capable of domain-wide delegation and then enable its client_id permissions on the APIs you are interested in. For more information, see Delegating domain-wide authority to the service account.

First a bit of background first:

GoogleCredential vs GoogleCredentials?

Yes, these are both Credential classes but from two different google client libraries (note the extra s!!)

GoogleCredential is an older client library used normally for non-cloud APIs like AdMob, YouTube, etc. Pretty much most of what is listed here.

GoogleCredentials is the newer client library used for most Google Cloud APIs. Its designed to handle a larger variety of credential sources and plugs into a most of Google Cloud Service clients. We will use this credential type in this example.

For more information, see:

.setServiceAccountUser() vs .createDelegated()

JWT tokens derived from either of the credential types must set the subject (sub) claim to the identity to be impersonated when making the initial token request to google. That is, if a service account wants to assume the identity of alice@domain.com, the sub: field.

So how do you do that? With Google API Client Libraries you would set .setServiceAccountUser() but when using Google Cloud Client Libraries you need to set it via .createDelegated(). The username is then taken and set inside a JWT Claim as the sub field which is then signed by a serviceAccounts private key. Once signed, its exchanged with a google endpoint and a new token that represents alice is returned.

But theres one problem: only a credential object that is derived ServiceAccounts certificate file (.p12 or json) has these special methods available (and of course the certificate right there).

So…how would we generate a token on ComputeEngine since no signing certificate is available? Well, its convoluted but you basically need to make the underlying credential object somehow sign() remotely

Interface ServiceAccountSigner

So we need a signed JWT for these credential types that doesn’t directly have key on hand. What do we do? Well, it turns out that many credentials in Google Cloud Client Libraries (not the api clients!) the implements an interface that allows for remote signing: [Interface ServiceAccountSigner](https://googleapis.dev/java/google-auth-library/latest/com/google/auth/ServiceAccountSigner.html!

The credential types that implements that interface are:

ok, great…how can you sign without a local key? Generally, with most of the credentials above, you’d just use an API that does this for you:

That pretty versatile API endpoint allows you to sign an arbitrary blob using your own remote service account which is just what we need here. As mentioned earlier, we require a signed JWT where the sub: field is the identity we wish to assume.

a couple of notes abut this section:

*in AppEngine’s case, ths api isn’t used but a similar one that exists only in GAE

DomainScopedDelegation.java

The code described in this article is a wrapper around the sign() method in interface described above…which in some cases is actually implemented by the IAM api.

NOTE: This code is just a temporary workaround: ideally each credential type that can perform domain wide-delegation should implement the ccreateDelegated() method (eg, ComputeEngineCredential.createDelegated() or ImpersonatedCredentials.createDelegated()). For now, its all enclosed within this Credential implementation.

Anyway, DomainDelegatedCredentials itself extends GoogleCredentials just like any other credential type which means it will automatically work with google api or cloud client libraries (though for the later, there’s no api you can use this with..this only applies to Gsuties APIs surfaced through the older google api library set).

ok, so how would you use this? well, if you are NOT using ServiceAccountCredentials (the only one with a key locally) or AppEngineCredentials (the only one with a native signing capability), you need grant the service account IAM permissions “on itself” to sign: You do this by assigning an IAM permission:

gcloud iam service-accounts add-iam-policy-binding \
  --role roles/iam.serviceAccountTokenCreator  \
  --member "serviceAccount:svcA@project.iam.gserviceaccount.com" \
   svcA@project.iam.gserviceaccount.com

(for more information on this role, see Creating short-lived service account credentials)

The iam.serviceAccountTokenCreator role includes concentric set of permission (see Concentric IAMCredentials Permissions: The secret life of signBlob. If you just need to generate an access_token or an id_token, consider generating a custom role with that specific capability. In this article, you do need signJWT(). Also be aware of long-term consequences if the access_token is leaked for “self-impersonated” service account (again described in the cited article here)

From there, we will bootstrap a credential type and use that for a gsuties API call:

In the example below, i’m still using a serviceAccount JSON file but left the other credential types commented out:

Note the line where the request initializer uses the delegated credentials into the Gsuites API:

HttpRequestInitializer requestInitializer = new HttpCredentialsAdapter(sourceCredentials);

Directory service = new Directory.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(),
				requestInitializer).setApplicationName("foo").build();

We are doing this instead of directly applying the credential object because the sourceCredentialis using a theGoogle Cloud Client Libraries and the Directory API is the older Google API Client Libraries`…we need to adapt the credential and underlying library together. For more information on this, see Using Credentials with google-http-client

The directory API we’re using in the example here will just list the users (Note: the scopes you defined while using the api must match the scopes assigned to the service account!!)

Here’s the actual DomainDelegatedCredentials.java that you can just use in your code

Conclusion

Domain wide delegation is a powerful capability…this article showed one (temp) way to use any arbitrary credential type with this capability…Just please be careful since this service account (either via the key or IAM api), can now impersonate users in your gsuties domain. If you use impersonation on a ComputeEngine or GKE instance, be aware anyone on the system can request an access_token from the metadata server that inturn can sign and generate impersonated credentials.

Appendix

Sample Domain-Wide Delegation Setup:

  1. Create Service Account Enable domain-wide delegation and note the clientID:

images/dwdsa.png

  1. Assign GSuite scopes to client_id On the gsuite admin console under “Security > Advanced Settings > Manage API client access”, enter the client_id and the scopes this service account should be allowed to use when requesting a users credentials:

  2. Specify the scopes using comma seprated values https://www.googleapis.com/auth/admin.directory.group,https://www.googleapis.com/auth/admin.directory.user

images/dwdscopes.png

This site supports webmentions. Send me a mention via this form.