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