• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2021 Google LLC
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *    * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *    * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *
15  *    * Neither the name of Google LLC nor the names of its
16  * contributors may be used to endorse or promote products derived from
17  * this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30  */
31 
32 package com.google.auth.oauth2;
33 
34 import static org.junit.Assert.assertEquals;
35 import static org.junit.Assert.assertTrue;
36 import static org.junit.Assert.fail;
37 
38 import com.google.api.client.http.GenericUrl;
39 import com.google.api.client.http.HttpRequest;
40 import com.google.api.client.http.HttpRequestFactory;
41 import com.google.api.client.http.HttpResponse;
42 import com.google.api.client.http.HttpResponseException;
43 import com.google.api.client.http.javanet.NetHttpTransport;
44 import com.google.api.client.json.JsonObjectParser;
45 import com.google.api.client.json.gson.GsonFactory;
46 import com.google.auth.Credentials;
47 import com.google.auth.http.HttpCredentialsAdapter;
48 import java.io.IOException;
49 import org.junit.Test;
50 
51 /**
52  * Integration tests for Downscoping with Credential Access Boundaries via {@link
53  * DownscopedCredentials}.
54  *
55  * <p>The only requirements for this test suite to run is to set the environment variable
56  * GOOGLE_APPLICATION_CREDENTIALS to point to the same service account configured in the setup
57  * script (downscoping-with-cab-setup.sh).
58  */
59 public final class ITDownscopingTest {
60 
61   // Output copied from the setup script (downscoping-with-cab-setup.sh).
62   private static final String GCS_BUCKET_NAME = "cab-int-bucket-cbi3qrv5";
63   private static final String GCS_OBJECT_NAME_WITH_PERMISSION = "cab-first-cbi3qrv5.txt";
64   private static final String GCS_OBJECT_NAME_WITHOUT_PERMISSION = "cab-second-cbi3qrv5.txt";
65 
66   // This Credential Access Boundary enables the objectViewer permission to the specified object in
67   // the specified bucket.
68   private static final CredentialAccessBoundary CREDENTIAL_ACCESS_BOUNDARY =
69       CredentialAccessBoundary.newBuilder()
70           .addRule(
71               CredentialAccessBoundary.AccessBoundaryRule.newBuilder()
72                   .setAvailableResource(
73                       String.format(
74                           "//storage.googleapis.com/projects/_/buckets/%s", GCS_BUCKET_NAME))
75                   .addAvailablePermission("inRole:roles/storage.objectViewer")
76                   .setAvailabilityCondition(
77                       CredentialAccessBoundary.AccessBoundaryRule.AvailabilityCondition.newBuilder()
78                           .setExpression(
79                               String.format(
80                                   "resource.name.startsWith('projects/_/buckets/%s/objects/%s')",
81                                   GCS_BUCKET_NAME, GCS_OBJECT_NAME_WITH_PERMISSION))
82                           .build())
83                   .build())
84           .build();
85 
86   /**
87    * A downscoped credential is obtained from a service account credential with permissions to
88    * access an object in the GCS bucket configured. We should only have access to retrieve this
89    * object.
90    *
91    * <p>We confirm this by: 1. Validating that we can successfully retrieve this object with the
92    * downscoped token. 2. Validating that we do not have permission to retrieve a different object
93    * in the same bucket.
94    */
95   @Test
downscoping_serviceAccountSourceWithRefresh()96   public void downscoping_serviceAccountSourceWithRefresh() throws IOException {
97     OAuth2CredentialsWithRefresh.OAuth2RefreshHandler refreshHandler =
98         new OAuth2CredentialsWithRefresh.OAuth2RefreshHandler() {
99           @Override
100           public AccessToken refreshAccessToken() throws IOException {
101 
102             ServiceAccountCredentials credentials =
103                 (ServiceAccountCredentials)
104                     GoogleCredentials.getApplicationDefault()
105                         .createScoped("https://www.googleapis.com/auth/cloud-platform");
106 
107             DownscopedCredentials downscopedCredentials =
108                 DownscopedCredentials.newBuilder()
109                     .setSourceCredential(credentials)
110                     .setCredentialAccessBoundary(CREDENTIAL_ACCESS_BOUNDARY)
111                     .build();
112 
113             return downscopedCredentials.refreshAccessToken();
114           }
115         };
116 
117     OAuth2CredentialsWithRefresh credentials =
118         OAuth2CredentialsWithRefresh.newBuilder().setRefreshHandler(refreshHandler).build();
119 
120     // Attempt to retrieve the object that the downscoped token has access to.
121     retrieveObjectFromGcs(credentials, GCS_OBJECT_NAME_WITH_PERMISSION);
122 
123     // Attempt to retrieve the object that the downscoped token does not have access to. This should
124     // fail.
125     try {
126       retrieveObjectFromGcs(credentials, GCS_OBJECT_NAME_WITHOUT_PERMISSION);
127       fail("Call to GCS should have failed.");
128     } catch (HttpResponseException e) {
129       assertEquals(403, e.getStatusCode());
130     }
131   }
132 
retrieveObjectFromGcs(Credentials credentials, String objectName)133   private void retrieveObjectFromGcs(Credentials credentials, String objectName)
134       throws IOException {
135     String url =
136         String.format(
137             "https://storage.googleapis.com/storage/v1/b/%s/o/%s", GCS_BUCKET_NAME, objectName);
138 
139     HttpCredentialsAdapter credentialsAdapter = new HttpCredentialsAdapter(credentials);
140     HttpRequestFactory requestFactory =
141         new NetHttpTransport().createRequestFactory(credentialsAdapter);
142     HttpRequest request = requestFactory.buildGetRequest(new GenericUrl(url));
143 
144     JsonObjectParser parser = new JsonObjectParser(GsonFactory.getDefaultInstance());
145     request.setParser(parser);
146 
147     HttpResponse response = request.execute();
148     assertTrue(response.isSuccessStatusCode());
149   }
150 }
151