Skip to content

Commit ec01ca9

Browse files
lsiracaverikitsch
andauthored
feat: adds downscoping with Credential Access Boundaries samples (GoogleCloudPlatform#5803)
* feat: adds downscoping with Credential Access Boundaries samples * fix: switch to mvn exec:exec without args * Update auth/src/main/java/com/google/cloud/auth/samples/DownscopingExample.java Co-authored-by: Averi Kitsch <akitsch@google.com> * Update auth/src/main/java/com/google/cloud/auth/samples/DownscopingExample.java Co-authored-by: Averi Kitsch <akitsch@google.com> * fix: review * fix: add dep Co-authored-by: Averi Kitsch <akitsch@google.com>
1 parent 621575e commit ec01ca9

File tree

4 files changed

+301
-0
lines changed

4 files changed

+301
-0
lines changed

auth/README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,20 @@ You can then run a given `ClassName` via:
3434

3535
mvn exec:java -Dexec.mainClass=com.google.cloud.auth.samples.AuthExample
3636
-Dexec.args="compute"
37+
38+
## Downscoping with Credential Access Boundaries
39+
40+
The same configuration above applies.
41+
42+
To run the samples for [Downscoping with Credential Access Boundaries](https://cloud.google.com/iam/docs/downscoping-short-lived-credentials)
43+
you must provide both a bucket name and object name under the TODO(developer): in the main method of `DownscopingExample`.
44+
45+
You can then run `DownscopingExample` via:
46+
47+
mvn exec:exec
48+
49+
## Tests
50+
Run all tests:
51+
```
52+
mvn clean verify
53+
```

auth/pom.xml

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ limitations under the License.
6262
<artifactId>google-auth-library-appengine</artifactId>
6363
<version>1.1.0</version>
6464
</dependency>
65+
<dependency>
66+
<groupId>com.google.auth</groupId>
67+
<artifactId>google-auth-library-oauth2-http</artifactId>
68+
<version>1.1.0</version>
69+
</dependency>
6570
<!-- END dependencies -->
6671
<dependency>
6772
<groupId>commons-io</groupId>
@@ -77,4 +82,29 @@ limitations under the License.
7782
<!-- START dependencies -->
7883
</dependencies>
7984
<!-- END dependencies -->
85+
86+
<build>
87+
<plugins>
88+
<plugin>
89+
<groupId>org.codehaus.mojo</groupId>
90+
<artifactId>exec-maven-plugin</artifactId>
91+
<version>1.6.0</version>
92+
<executions>
93+
<execution>
94+
<goals>
95+
<goal>exec</goal>
96+
</goals>
97+
</execution>
98+
</executions>
99+
<configuration>
100+
<executable>java</executable>
101+
<arguments>
102+
<argument>-classpath</argument>
103+
<classpath/>
104+
<argument>com.google.cloud.auth.samples.DownscopingExample</argument>
105+
</arguments>
106+
</configuration>
107+
</plugin>
108+
</plugins>
109+
</build>
80110
</project>
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.auth.samples;
18+
19+
import com.google.auth.oauth2.AccessToken;
20+
import com.google.auth.oauth2.CredentialAccessBoundary;
21+
import com.google.auth.oauth2.DownscopedCredentials;
22+
import com.google.auth.oauth2.GoogleCredentials;
23+
import com.google.auth.oauth2.OAuth2CredentialsWithRefresh;
24+
import com.google.cloud.storage.Blob;
25+
import com.google.cloud.storage.Storage;
26+
import com.google.cloud.storage.StorageOptions;
27+
import java.io.IOException;
28+
29+
/** Demonstrates how to use Downscoping with Credential Access Boundaries. */
30+
public class DownscopingExample {
31+
32+
/**
33+
* Tests the downscoping functionality.
34+
*
35+
* <p>This will generate a downscoped token with readonly access to the specified GCS bucket,
36+
* inject them into a storage instance and then test print the contents of the specified object.
37+
*/
38+
public static void main(String[] args) throws IOException {
39+
// TODO(developer): Replace these variables before running the sample.
40+
// The Cloud Storage bucket name.
41+
String bucketName = "your-gcs-bucket-name";
42+
// The Cloud Storage object name that resides in the specified bucket.
43+
String objectName = "your-gcs-object-name";
44+
45+
tokenConsumer(bucketName, objectName);
46+
}
47+
48+
/** Simulates token broker generating downscoped tokens for specified bucket. */
49+
// [START auth_downscoping_token_broker]
50+
public static AccessToken getTokenFromBroker(String bucketName, String objectPrefix)
51+
throws IOException {
52+
// Retrieve the source credentials from ADC.
53+
GoogleCredentials sourceCredentials =
54+
GoogleCredentials.getApplicationDefault()
55+
.createScoped("https://www.googleapis.com/auth/cloud-platform");
56+
57+
// [START auth_downscoping_rules]
58+
// Initialize the Credential Access Boundary rules.
59+
String availableResource = "//storage.googleapis.com/projects/_/buckets/" + bucketName;
60+
61+
// Downscoped credentials will have readonly access to the resource.
62+
String availablePermission = "inRole:roles/storage.objectViewer";
63+
64+
// Only objects starting with the specified prefix string in the object name will be allowed
65+
// read access.
66+
String expression =
67+
"resource.name.startsWith('projects/_/buckets/"
68+
+ bucketName
69+
+ "/objects/"
70+
+ objectPrefix
71+
+ "')";
72+
73+
// Build the AvailabilityCondition.
74+
CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition availabilityCondition =
75+
CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
76+
.setExpression(expression)
77+
.build();
78+
79+
// Define the single access boundary rule using the above properties.
80+
CredentialAccessBoundary.AccessBoundaryRule rule =
81+
CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
82+
.setAvailableResource(availableResource)
83+
.addAvailablePermission(availablePermission)
84+
.setAvailabilityCondition(availabilityCondition)
85+
.build();
86+
87+
// Define the Credential Access Boundary with all the relevant rules.
88+
CredentialAccessBoundary credentialAccessBoundary =
89+
CredentialAccessBoundary.newBuilder().addRule(rule).build();
90+
// [END auth_downscoping_rules]
91+
92+
// [START auth_downscoping_initialize_downscoped_cred]
93+
// Create the downscoped credentials.
94+
DownscopedCredentials downscopedCredentials =
95+
DownscopedCredentials.newBuilder()
96+
.setSourceCredential(sourceCredentials)
97+
.setCredentialAccessBoundary(credentialAccessBoundary)
98+
.build();
99+
100+
// Retrieve the token.
101+
// This will need to be passed to the Token Consumer.
102+
AccessToken accessToken = downscopedCredentials.refreshAccessToken();
103+
// [END auth_downscoping_initialize_downscoped_cred]
104+
return accessToken;
105+
}
106+
// [END auth_downscoping_token_broker]
107+
108+
/** Simulates token consumer readonly access to the specified object. */
109+
// [START auth_downscoping_token_consumer]
110+
public static void tokenConsumer(final String bucketName, final String objectName)
111+
throws IOException {
112+
// You can pass an `OAuth2RefreshHandler` to `OAuth2CredentialsWithRefresh` which will allow the
113+
// library to seamlessly handle downscoped token refreshes on expiration.
114+
OAuth2CredentialsWithRefresh.OAuth2RefreshHandler handler =
115+
new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
116+
@Override
117+
public AccessToken refreshAccessToken() throws IOException {
118+
// The common pattern of usage is to have a token broker pass the downscoped short-lived
119+
// access tokens to a token consumer via some secure authenticated channel.
120+
// For illustration purposes, we are generating the downscoped token locally.
121+
// We want to test the ability to limit access to objects with a certain prefix string
122+
// in the resource bucket. objectName.substring(0, 3) is the prefix here. This field is
123+
// not required if access to all bucket resources are allowed. If access to limited
124+
// resources in the bucket is needed, this mechanism can be used.
125+
return getTokenFromBroker(bucketName, objectName.substring(0, 3));
126+
}
127+
};
128+
129+
// Downscoped token retrieved from token broker.
130+
AccessToken downscopedToken = handler.refreshAccessToken();
131+
132+
// Create the OAuth2CredentialsWithRefresh from the downscoped token and pass a refresh handler
133+
// which will handle token expiration.
134+
// This will allow the consumer to seamlessly obtain new downscoped tokens on demand every time
135+
// token expires.
136+
OAuth2CredentialsWithRefresh credentials =
137+
OAuth2CredentialsWithRefresh.newBuilder()
138+
.setAccessToken(downscopedToken)
139+
.setRefreshHandler(handler)
140+
.build();
141+
142+
// Use the credentials with the Cloud Storage SDK.
143+
StorageOptions options = StorageOptions.newBuilder().setCredentials(credentials).build();
144+
Storage storage = options.getService();
145+
146+
// Call Cloud Storage APIs.
147+
Blob blob = storage.get(bucketName, objectName);
148+
String content = new String(blob.getContent());
149+
System.out.println(
150+
"Retrieved object, "
151+
+ objectName
152+
+ ", from bucket,"
153+
+ bucketName
154+
+ ", with content: "
155+
+ content);
156+
}
157+
// [END auth_downscoping_token_consumer]
158+
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright 2021 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.google.cloud.auth.samples;
18+
19+
import static org.junit.Assert.assertNotNull;
20+
import static org.junit.Assert.assertTrue;
21+
22+
import com.google.cloud.storage.Blob;
23+
import com.google.cloud.storage.BlobId;
24+
import com.google.cloud.storage.BlobInfo;
25+
import com.google.cloud.storage.Bucket;
26+
import com.google.cloud.storage.BucketInfo;
27+
import com.google.cloud.storage.Storage;
28+
import com.google.cloud.storage.StorageOptions;
29+
import java.io.ByteArrayOutputStream;
30+
import java.io.IOException;
31+
import java.io.PrintStream;
32+
import java.nio.charset.StandardCharsets;
33+
import java.util.UUID;
34+
import org.junit.After;
35+
import org.junit.Before;
36+
import org.junit.Test;
37+
import org.junit.runner.RunWith;
38+
import org.junit.runners.JUnit4;
39+
40+
@RunWith(JUnit4.class)
41+
// CHECKSTYLE OFF: AbbreviationAsWordInName
42+
public class DownscopingExampleIT {
43+
// CHECKSTYLE ON: AbbreviationAsWordInName
44+
private static final String CONTENT = "CONTENT";
45+
private ByteArrayOutputStream bout;
46+
private PrintStream out;
47+
private String credentials;
48+
private Bucket bucket;
49+
private Blob blob;
50+
private String[] args;
51+
52+
@Before
53+
public void setUp() {
54+
bout = new ByteArrayOutputStream();
55+
out = new PrintStream(bout);
56+
System.setOut(out);
57+
58+
credentials = System.getenv("GOOGLE_APPLICATION_CREDENTIALS");
59+
assertNotNull(credentials);
60+
61+
// Create a bucket and object that are deleted once the test completes.
62+
Storage storage = StorageOptions.newBuilder().build().getService();
63+
64+
String bucketName = String.format("bucket-downscoping-test-%s", UUID.randomUUID());
65+
Bucket bucket = storage.create(BucketInfo.newBuilder(bucketName).build());
66+
67+
String objectName = String.format("blob-downscoping-test-%s", UUID.randomUUID());
68+
BlobId blobId = BlobId.of(bucketName, objectName);
69+
BlobInfo blobInfo = Blob.newBuilder(blobId).build();
70+
Blob blob = storage.create(blobInfo, CONTENT.getBytes(StandardCharsets.UTF_8));
71+
72+
this.bucket = bucket;
73+
this.blob = blob;
74+
this.args = new String[] {bucketName, objectName};
75+
}
76+
77+
@After
78+
public void cleanup() {
79+
blob.delete();
80+
bucket.delete();
81+
}
82+
83+
@Test
84+
public void testDownscoping() throws IOException {
85+
DownscopingExample.tokenConsumer(bucket.getName(), blob.getName());
86+
String expectedOutput =
87+
"Retrieved object, "
88+
+ blob.getName()
89+
+ ", from bucket,"
90+
+ bucket.getName()
91+
+ ", with content: "
92+
+ CONTENT;
93+
String output = bout.toString();
94+
assertTrue(output.contains(expectedOutput));
95+
}
96+
}

0 commit comments

Comments
 (0)