• 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 com.google.api.client.json.GenericJson;
35 import com.google.auth.http.HttpTransportFactory;
36 import com.google.common.annotations.VisibleForTesting;
37 import com.google.errorprone.annotations.CanIgnoreReturnValue;
38 import java.io.IOException;
39 import java.io.UnsupportedEncodingException;
40 import java.net.URLEncoder;
41 import java.util.ArrayList;
42 import java.util.Collection;
43 import java.util.HashMap;
44 import java.util.List;
45 import java.util.Map;
46 import javax.annotation.Nullable;
47 
48 /**
49  * Credentials representing an AWS third-party identity for calling Google APIs. AWS security
50  * credentials are either sourced by calling EC2 metadata endpoints, environment variables, or a
51  * user provided supplier method.
52  *
53  * <p>By default, attempts to exchange the external credential for a GCP access token.
54  */
55 public class AwsCredentials extends ExternalAccountCredentials {
56 
57   static final String DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL =
58       "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15";
59 
60   static final String AWS_METRICS_HEADER_VALUE = "aws";
61 
62   private static final long serialVersionUID = -3670131891574618105L;
63 
64   private final AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier;
65   private final ExternalAccountSupplierContext supplierContext;
66   // Regional credential verification url override. This needs to be its own value so we can
67   // correctly pass it to a builder.
68   @Nullable private final String regionalCredentialVerificationUrlOverride;
69   @Nullable private final String regionalCredentialVerificationUrl;
70   private final String metricsHeaderValue;
71 
72   /** Internal constructor. See {@link AwsCredentials.Builder}. */
AwsCredentials(Builder builder)73   AwsCredentials(Builder builder) {
74     super(builder);
75     this.supplierContext =
76         ExternalAccountSupplierContext.newBuilder()
77             .setAudience(this.getAudience())
78             .setSubjectTokenType(this.getSubjectTokenType())
79             .build();
80 
81     // Check that one and only one of supplier or credential source are provided.
82     if (builder.awsSecurityCredentialsSupplier != null && builder.credentialSource != null) {
83       throw new IllegalArgumentException(
84           "AwsCredentials cannot have both an awsSecurityCredentialsSupplier and a credentialSource.");
85     }
86     if (builder.awsSecurityCredentialsSupplier == null && builder.credentialSource == null) {
87       throw new IllegalArgumentException(
88           "An awsSecurityCredentialsSupplier or a credentialSource must be provided.");
89     }
90 
91     AwsCredentialSource credentialSource = (AwsCredentialSource) builder.credentialSource;
92     // Set regional credential verification url override if provided.
93     this.regionalCredentialVerificationUrlOverride =
94         builder.regionalCredentialVerificationUrlOverride;
95 
96     // Set regional credential verification url depending on inputs.
97     if (this.regionalCredentialVerificationUrlOverride != null) {
98       this.regionalCredentialVerificationUrl = this.regionalCredentialVerificationUrlOverride;
99     } else if (credentialSource != null) {
100       this.regionalCredentialVerificationUrl = credentialSource.regionalCredentialVerificationUrl;
101     } else {
102       this.regionalCredentialVerificationUrl = DEFAULT_REGIONAL_CREDENTIAL_VERIFICATION_URL;
103     }
104 
105     // If user has provided a security credential supplier, use that to retrieve the AWS security
106     // credentials.
107     if (builder.awsSecurityCredentialsSupplier != null) {
108       this.awsSecurityCredentialsSupplier = builder.awsSecurityCredentialsSupplier;
109       this.metricsHeaderValue = PROGRAMMATIC_METRICS_HEADER_VALUE;
110     } else {
111       this.awsSecurityCredentialsSupplier =
112           new InternalAwsSecurityCredentialsSupplier(
113               credentialSource, this.getEnvironmentProvider(), this.transportFactory);
114       this.metricsHeaderValue = AWS_METRICS_HEADER_VALUE;
115     }
116   }
117 
118   @Override
refreshAccessToken()119   public AccessToken refreshAccessToken() throws IOException {
120     StsTokenExchangeRequest.Builder stsTokenExchangeRequest =
121         StsTokenExchangeRequest.newBuilder(retrieveSubjectToken(), getSubjectTokenType())
122             .setAudience(getAudience());
123 
124     // Add scopes, if possible.
125     Collection<String> scopes = getScopes();
126     if (scopes != null && !scopes.isEmpty()) {
127       stsTokenExchangeRequest.setScopes(new ArrayList<>(scopes));
128     }
129 
130     return exchangeExternalCredentialForAccessToken(stsTokenExchangeRequest.build());
131   }
132 
133   @Override
retrieveSubjectToken()134   public String retrieveSubjectToken() throws IOException {
135 
136     // The targeted region is required to generate the signed request. The regional
137     // endpoint must also be used.
138     String region = awsSecurityCredentialsSupplier.getRegion(supplierContext);
139 
140     AwsSecurityCredentials credentials =
141         awsSecurityCredentialsSupplier.getCredentials(supplierContext);
142 
143     // Generate the signed request to the AWS STS GetCallerIdentity API.
144     Map<String, String> headers = new HashMap<>();
145     headers.put("x-goog-cloud-target-resource", getAudience());
146 
147     AwsRequestSigner signer =
148         AwsRequestSigner.newBuilder(
149                 credentials,
150                 "POST",
151                 this.regionalCredentialVerificationUrl.replace("{region}", region),
152                 region)
153             .setAdditionalHeaders(headers)
154             .build();
155 
156     AwsRequestSignature awsRequestSignature = signer.sign();
157     return buildSubjectToken(awsRequestSignature);
158   }
159 
160   /** Clones the AwsCredentials with the specified scopes. */
161   @Override
createScoped(Collection<String> newScopes)162   public GoogleCredentials createScoped(Collection<String> newScopes) {
163     return new AwsCredentials((AwsCredentials.Builder) newBuilder(this).setScopes(newScopes));
164   }
165 
166   @Override
getCredentialSourceType()167   String getCredentialSourceType() {
168     return this.metricsHeaderValue;
169   }
170 
buildSubjectToken(AwsRequestSignature signature)171   private String buildSubjectToken(AwsRequestSignature signature)
172       throws UnsupportedEncodingException {
173     Map<String, String> canonicalHeaders = signature.getCanonicalHeaders();
174     List<GenericJson> headerList = new ArrayList<>();
175     for (String headerName : canonicalHeaders.keySet()) {
176       headerList.add(formatTokenHeaderForSts(headerName, canonicalHeaders.get(headerName)));
177     }
178 
179     headerList.add(formatTokenHeaderForSts("Authorization", signature.getAuthorizationHeader()));
180 
181     // The canonical resource name of the workload identity pool provider.
182     headerList.add(formatTokenHeaderForSts("x-goog-cloud-target-resource", getAudience()));
183 
184     GenericJson token = new GenericJson();
185     token.setFactory(OAuth2Utils.JSON_FACTORY);
186 
187     token.put("headers", headerList);
188     token.put("method", signature.getHttpMethod());
189     token.put(
190         "url", this.regionalCredentialVerificationUrl.replace("{region}", signature.getRegion()));
191     return URLEncoder.encode(token.toString(), "UTF-8");
192   }
193 
194   @VisibleForTesting
getRegionalCredentialVerificationUrl()195   String getRegionalCredentialVerificationUrl() {
196     return this.regionalCredentialVerificationUrl;
197   }
198 
199   @VisibleForTesting
getEnv(String name)200   String getEnv(String name) {
201     return System.getenv(name);
202   }
203 
204   @VisibleForTesting
getAwsSecurityCredentialsSupplier()205   AwsSecurityCredentialsSupplier getAwsSecurityCredentialsSupplier() {
206     return this.awsSecurityCredentialsSupplier;
207   }
208 
209   @Nullable
getRegionalCredentialVerificationUrlOverride()210   public String getRegionalCredentialVerificationUrlOverride() {
211     return this.regionalCredentialVerificationUrlOverride;
212   }
213 
formatTokenHeaderForSts(String key, String value)214   private static GenericJson formatTokenHeaderForSts(String key, String value) {
215     // The GCP STS endpoint expects the headers to be formatted as:
216     // [
217     //  {key: 'x-amz-date', value: '...'},
218     //  {key: 'Authorization', value: '...'},
219     //  ...
220     // ]
221     GenericJson header = new GenericJson();
222     header.setFactory(OAuth2Utils.JSON_FACTORY);
223     header.put("key", key);
224     header.put("value", value);
225     return header;
226   }
227 
newBuilder()228   public static AwsCredentials.Builder newBuilder() {
229     return new AwsCredentials.Builder();
230   }
231 
newBuilder(AwsCredentials awsCredentials)232   public static AwsCredentials.Builder newBuilder(AwsCredentials awsCredentials) {
233     return new AwsCredentials.Builder(awsCredentials);
234   }
235 
236   public static class Builder extends ExternalAccountCredentials.Builder {
237 
238     private AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier;
239 
240     private String regionalCredentialVerificationUrlOverride;
241 
Builder()242     Builder() {}
243 
Builder(AwsCredentials credentials)244     Builder(AwsCredentials credentials) {
245       super(credentials);
246       if (this.credentialSource == null) {
247         this.awsSecurityCredentialsSupplier = credentials.awsSecurityCredentialsSupplier;
248       }
249       this.regionalCredentialVerificationUrlOverride =
250           credentials.regionalCredentialVerificationUrlOverride;
251     }
252 
253     /**
254      * Sets the AWS security credentials supplier. The supplier should return a valid {@code
255      * AwsSecurityCredentials} object and a valid AWS region.
256      *
257      * @param awsSecurityCredentialsSupplier the supplier to use.
258      * @return this {@code Builder} object
259      */
260     @CanIgnoreReturnValue
setAwsSecurityCredentialsSupplier( AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier)261     public Builder setAwsSecurityCredentialsSupplier(
262         AwsSecurityCredentialsSupplier awsSecurityCredentialsSupplier) {
263       this.awsSecurityCredentialsSupplier = awsSecurityCredentialsSupplier;
264       return this;
265     }
266 
267     /**
268      * Sets the AWS regional credential verification URL. If set, will override any credential
269      * verification URL provided in the credential source. If not set, the credential verification
270      * URL will default to
271      * https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
272      *
273      * @param regionalCredentialVerificationUrlOverride the AWS credential verification url to set.
274      * @return this {@code Builder} object
275      */
276     @CanIgnoreReturnValue
setRegionalCredentialVerificationUrlOverride( String regionalCredentialVerificationUrlOverride)277     public Builder setRegionalCredentialVerificationUrlOverride(
278         String regionalCredentialVerificationUrlOverride) {
279       this.regionalCredentialVerificationUrlOverride = regionalCredentialVerificationUrlOverride;
280       return this;
281     }
282 
283     @CanIgnoreReturnValue
setHttpTransportFactory(HttpTransportFactory transportFactory)284     public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
285       super.setHttpTransportFactory(transportFactory);
286       return this;
287     }
288 
289     @CanIgnoreReturnValue
setAudience(String audience)290     public Builder setAudience(String audience) {
291       super.setAudience(audience);
292       return this;
293     }
294 
295     @CanIgnoreReturnValue
setSubjectTokenType(String subjectTokenType)296     public Builder setSubjectTokenType(String subjectTokenType) {
297       super.setSubjectTokenType(subjectTokenType);
298       return this;
299     }
300 
301     @CanIgnoreReturnValue
setSubjectTokenType(SubjectTokenTypes subjectTokenType)302     public Builder setSubjectTokenType(SubjectTokenTypes subjectTokenType) {
303       super.setSubjectTokenType(subjectTokenType);
304       return this;
305     }
306 
307     @CanIgnoreReturnValue
setTokenUrl(String tokenUrl)308     public Builder setTokenUrl(String tokenUrl) {
309       super.setTokenUrl(tokenUrl);
310       return this;
311     }
312 
313     @CanIgnoreReturnValue
setCredentialSource(AwsCredentialSource credentialSource)314     public Builder setCredentialSource(AwsCredentialSource credentialSource) {
315       super.setCredentialSource(credentialSource);
316       return this;
317     }
318 
319     @CanIgnoreReturnValue
setServiceAccountImpersonationUrl(String serviceAccountImpersonationUrl)320     public Builder setServiceAccountImpersonationUrl(String serviceAccountImpersonationUrl) {
321       super.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl);
322       return this;
323     }
324 
325     @CanIgnoreReturnValue
setTokenInfoUrl(String tokenInfoUrl)326     public Builder setTokenInfoUrl(String tokenInfoUrl) {
327       super.setTokenInfoUrl(tokenInfoUrl);
328       return this;
329     }
330 
331     @CanIgnoreReturnValue
setQuotaProjectId(String quotaProjectId)332     public Builder setQuotaProjectId(String quotaProjectId) {
333       super.setQuotaProjectId(quotaProjectId);
334       return this;
335     }
336 
337     @CanIgnoreReturnValue
setClientId(String clientId)338     public Builder setClientId(String clientId) {
339       super.setClientId(clientId);
340       return this;
341     }
342 
343     @CanIgnoreReturnValue
setClientSecret(String clientSecret)344     public Builder setClientSecret(String clientSecret) {
345       super.setClientSecret(clientSecret);
346       return this;
347     }
348 
349     @CanIgnoreReturnValue
setScopes(Collection<String> scopes)350     public Builder setScopes(Collection<String> scopes) {
351       super.setScopes(scopes);
352       return this;
353     }
354 
355     @CanIgnoreReturnValue
setWorkforcePoolUserProject(String workforcePoolUserProject)356     public Builder setWorkforcePoolUserProject(String workforcePoolUserProject) {
357       super.setWorkforcePoolUserProject(workforcePoolUserProject);
358       return this;
359     }
360 
361     @CanIgnoreReturnValue
setServiceAccountImpersonationOptions(Map<String, Object> optionsMap)362     public Builder setServiceAccountImpersonationOptions(Map<String, Object> optionsMap) {
363       super.setServiceAccountImpersonationOptions(optionsMap);
364       return this;
365     }
366 
367     @CanIgnoreReturnValue
setUniverseDomain(String universeDomain)368     public Builder setUniverseDomain(String universeDomain) {
369       super.setUniverseDomain(universeDomain);
370       return this;
371     }
372 
373     @CanIgnoreReturnValue
setEnvironmentProvider(EnvironmentProvider environmentProvider)374     Builder setEnvironmentProvider(EnvironmentProvider environmentProvider) {
375       super.setEnvironmentProvider(environmentProvider);
376       return this;
377     }
378 
379     @Override
build()380     public AwsCredentials build() {
381       return new AwsCredentials(this);
382     }
383   }
384 }
385