Skip to content

Commit 68bceba

Browse files
authored
docs: updates README for downscoping with CAB (googleapis#716)
* docs: updates README for downscoping with CAB * fix: document adding cloud-platform scope (should be added by developers)
1 parent aa7ede1 commit 68bceba

File tree

3 files changed

+142
-9
lines changed

3 files changed

+142
-9
lines changed

README.md

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,6 +377,143 @@ ExternalAccountCredentials credentials =
377377
ExternalAccountCredentials.fromStream(new FileInputStream("/path/to/credentials.json"));
378378
```
379379

380+
### Downscoping with Credential Access Boundaries
381+
382+
[Downscoping with Credential Access Boundaries](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials)
383+
enables the ability to downscope, or restrict, the Identity and Access Management (IAM) permissions
384+
that a short-lived credential can use for Cloud Storage.
385+
386+
The `DownscopedCredentials` class can be used to produce a downscoped access token from a
387+
`CredentialAccessBoundary` and a source credential. The Credential Access Boundary specifies which
388+
resources the newly created credential can access, as well as an upper bound on the permissions that
389+
are available on each resource. Using downscoped credentials ensures tokens in flight always have
390+
the least privileges (Principle of Least Privilege).
391+
392+
The snippet below shows how to initialize a CredentialAccessBoundary with one AccessBoundaryRule
393+
which specifies that the downscoped token will have readonly access to objects starting with
394+
"customer-a" in bucket "bucket-123":
395+
```java
396+
// Create the AccessBoundaryRule.
397+
String availableResource = "//storage.googleapis.com/projects/_/buckets/bucket-123";
398+
String availablePermission = "inRole:roles/storage.objectViewer";
399+
String expression = "resource.name.startsWith('projects/_/buckets/bucket-123/objects/customer-a')";
400+
401+
CredentialAccessBoundary.AccessBoundaryRule rule =
402+
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
403+
.setAvailableResource(availableResource)
404+
.addAvailablePermission(availablePermission)
405+
.setAvailabilityCondition(
406+
CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder().setExpression(expression).build())
407+
.build();
408+
409+
// Create the CredentialAccessBoundary with the rule.
410+
CredentialAccessBoundary credentialAccessBoundary =
411+
CredentialAccessBoundary.newBuilder().addRule(rule).build();
412+
```
413+
414+
The common pattern of usage is to have a token broker with elevated access generate these downscoped
415+
credentials from higher access source credentials and pass the downscoped short-lived access tokens
416+
to a token consumer via some secure authenticated channel for limited access to Google Cloud Storage
417+
resources.
418+
419+
Using the CredentialAccessBoundary created above in the Token Broker:
420+
```java
421+
// Retrieve the source credentials from ADC.
422+
GoogleCredentials sourceCredentials = GoogleCredentials.getApplicationDefault()
423+
.createScoped("https://www.googleapis.com/auth/cloud-platform");
424+
425+
// Initialize the DownscopedCredentials class.
426+
DownscopedCredentials downscopedCredentials =
427+
DownscopedCredentials.newBuilder()
428+
.setSourceCredential(credentials)
429+
.setCredentialAccessBoundary(credentialAccessBoundary)
430+
.build();
431+
432+
// Retrieve the downscoped access token.
433+
// This will need to be passed to the Token Consumer.
434+
AccessToken downscopedAccessToken = downscopedCredentials.refreshAccessToken();
435+
```
436+
437+
A token broker can be set up on a server in a private network. Various workloads
438+
(token consumers) in the same network will send authenticated requests to that broker for downscoped
439+
tokens to access or modify specific google cloud storage buckets.
440+
441+
The broker will instantiate downscoped credentials instances that can be used to generate short
442+
lived downscoped access tokens which will be passed to the token consumer.
443+
444+
Putting it all together:
445+
```java
446+
// Retrieve the source credentials from ADC.
447+
GoogleCredentials sourceCredentials = GoogleCredentials.getApplicationDefault()
448+
.createScoped("https://www.googleapis.com/auth/cloud-platform");
449+
450+
// Create an Access Boundary Rule which will restrict the downscoped token to having readonly
451+
// access to objects starting with "customer-a" in bucket "bucket-123".
452+
String availableResource = "//storage.googleapis.com/projects/_/buckets/bucket-123";
453+
String availablePermission = "inRole:roles/storage.objectViewer";
454+
String expression = "resource.name.startsWith('projects/_/buckets/bucket-123/objects/customer-a')";
455+
456+
CredentialAccessBoundary.AccessBoundaryRule rule =
457+
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
458+
.setAvailableResource(availableResource)
459+
.addAvailablePermission(availablePermission)
460+
.setAvailabilityCondition(
461+
new AvailabilityCondition(expression, /* title= */ null, /* description= */ null))
462+
.build();
463+
464+
// Initialize the DownscopedCredentials class.
465+
DownscopedCredentials downscopedCredentials =
466+
DownscopedCredentials.newBuilder()
467+
.setSourceCredential(credentials)
468+
.setCredentialAccessBoundary(CredentialAccessBoundary.newBuilder().addRule(rule).build())
469+
.build();
470+
471+
// Retrieve the downscoped access token.
472+
// This will need to be passed to the Token Consumer.
473+
AccessToken downscopedAccessToken = downscopedCredentials.refreshAccessToken();
474+
```
475+
476+
These downscoped access tokens can be used by the Token Consumer via `OAuth2Credentials` or
477+
`OAuth2CredentialsWithRefresh`. This credential can then be used to initialize a storage client
478+
instance to access Google Cloud Storage resources with restricted access.
479+
480+
```java
481+
// You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
482+
// library to seamlessly handle downscoped token refreshes on expiration.
483+
OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
484+
new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
485+
@Override
486+
public AccessToken refreshAccessToken() {
487+
// Add the logic here that retrieves the token from your Token Broker.
488+
return accessToken;
489+
}
490+
};
491+
492+
// Downscoped token retrieved from token broker.
493+
AccessToken downscopedToken = handler.refreshAccessToken();
494+
495+
// Build the OAuth2CredentialsWithRefresh from the downscoped token and pass a refresh handler
496+
// to handle token expiration. Passing the original downscoped token or the expiry here is optional,
497+
// as the refresh_handler will generate the downscoped token on demand.
498+
OAuth2CredentialsWithRefresh credentials =
499+
OAuth2CredentialsWithRefresh.newBuilder()
500+
.setAccessToken(downscopedToken)
501+
.setRefreshHandler(handler)
502+
.build();
503+
504+
// Use the credentials with the Cloud Storage SDK.
505+
StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
506+
Storage storage = options.getService();
507+
508+
// Call GCS APIs.
509+
// Since we passed the downscoped credential, we will have have limited readonly access to objects
510+
// starting with "customer-a" in bucket "bucket-123".
511+
storage.get(...)
512+
```
513+
514+
Note: Only Cloud Storage supports Credential Access Boundaries. Other Google Cloud services do not
515+
support this feature.
516+
380517
## Configuring a Proxy
381518

382519
For HTTP clients, a basic proxy can be configured by using `http.proxyHost` and related system properties as documented

oauth2_http/java/com/google/auth/oauth2/DownscopedCredentials.java

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@
3737
import com.google.auth.http.HttpTransportFactory;
3838
import com.google.common.annotations.VisibleForTesting;
3939
import java.io.IOException;
40-
import java.util.Arrays;
4140

4241
/**
4342
* DownscopedCredentials enables the ability to downscope, or restrict, the Identity and Access
@@ -53,7 +52,8 @@
5352
* <p>Usage:
5453
*
5554
* <pre><code>
56-
* GoogleCredentials sourceCredentials = GoogleCredentials.getApplicationDefault();
55+
* GoogleCredentials sourceCredentials = GoogleCredentials.getApplicationDefault()
56+
* .createScoped("https://www.googleapis.com/auth/cloud-platform");
5757
*
5858
* CredentialAccessBoundary.AccessBoundaryRule rule =
5959
* CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
@@ -64,7 +64,7 @@
6464
*
6565
* DownscopedCredentials downscopedCredentials =
6666
* DownscopedCredentials.newBuilder()
67-
* .setSourceCredential(credentials)
67+
* .setSourceCredential(sourceCredentials)
6868
* .setCredentialAccessBoundary(
6969
* CredentialAccessBoundary.newBuilder().addRule(rule).build())
7070
* .build();
@@ -88,9 +88,6 @@ public final class DownscopedCredentials extends OAuth2Credentials {
8888

8989
private static final String TOKEN_EXCHANGE_ENDPOINT = "https://sts.googleapis.com/v1/token";
9090

91-
private static final String CLOUD_PLATFORM_SCOPE =
92-
"https://www.googleapis.com/auth/cloud-platform";
93-
9491
private final GoogleCredentials sourceCredential;
9592
private final CredentialAccessBoundary credentialAccessBoundary;
9693
private final transient HttpTransportFactory transportFactory;
@@ -103,8 +100,7 @@ private DownscopedCredentials(
103100
firstNonNull(
104101
transportFactory,
105102
getFromServiceLoader(HttpTransportFactory.class, OAuth2Utils.HTTP_TRANSPORT_FACTORY));
106-
this.sourceCredential =
107-
checkNotNull(sourceCredential.createScoped(Arrays.asList(CLOUD_PLATFORM_SCOPE)));
103+
this.sourceCredential = checkNotNull(sourceCredential);
108104
this.credentialAccessBoundary = checkNotNull(credentialAccessBoundary);
109105
}
110106

oauth2_http/javatests/com/google/auth/oauth2/DownscopedCredentialsTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -224,7 +224,7 @@ private static GoogleCredentials getServiceAccountSourceCredentials(boolean canR
224224
transportFactory.transport.setError(new IOException());
225225
}
226226

227-
return sourceCredentials;
227+
return sourceCredentials.createScoped("https://www.googleapis.com/auth/cloud-platform");
228228
}
229229

230230
private static GoogleCredentials getUserSourceCredentials() {

0 commit comments

Comments
 (0)