Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .github/workflows/downstream.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -134,9 +134,11 @@ jobs:
- workflows
steps:
- uses: actions/checkout@v2
- uses: actions/setup-java@v1
- uses: actions/setup-java@v3
with:
distribution: zulu
java-version: ${{matrix.java}}
- run: java -version
- run: sudo apt-get update -y
- run: sudo apt-get install libxml2-utils
- run: .kokoro/downstream-client-library-check.sh google-auth-library-bom ${{matrix.repo}}
67 changes: 67 additions & 0 deletions oauth2_http/java/com/google/auth/oauth2/ExecutableHandler.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
* Copyright 2022 Google LLC
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
*
* * Neither the name of Google LLC nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package com.google.auth.oauth2;

import java.io.IOException;
import java.util.Map;
import javax.annotation.Nullable;

/** An interface for 3rd party executable handling. */
interface ExecutableHandler {

/** An interface for required fields needed to call 3rd party executables. */
interface ExecutableOptions {

/** An absolute path to the command used to retrieve 3rd party tokens. */
String getExecutableCommand();

/** A set of process-local environment variable mappings to be set for the script to execute. */
Map<String, String> getEnvironmentMap();

/** A timeout for waiting for the executable to finish, in milliseconds. */
int getExecutableTimeoutMs();

/**
* An output file path which points to the 3rd party credentials generated by the executable.
*/
@Nullable
String getOutputFilePath();
}

/**
* Handles executing the 3rd party script and parsing the token from the response.
*
* @param options A set executable options for handling the executable.
* @return A 3rd party token.
*/
String retrieveTokenFromExecutable(ExecutableOptions options) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
import com.google.auth.http.HttpTransportFactory;
import com.google.auth.oauth2.AwsCredentials.AwsCredentialSource;
import com.google.auth.oauth2.IdentityPoolCredentials.IdentityPoolCredentialSource;
import com.google.auth.oauth2.PluggableAuthCredentials.PluggableAuthCredentialSource;
import com.google.common.base.MoreObjects;
import java.io.IOException;
import java.io.InputStream;
Expand All @@ -58,7 +59,8 @@
/**
* Base external account credentials class.
*
* <p>Handles initializing external credentials, calls to STS, and service account impersonation.
* <p>Handles initializing external credentials, calls to the Security Token Service, and service
* account impersonation.
*/
public abstract class ExternalAccountCredentials extends GoogleCredentials
implements QuotaProjectIdProvider {
Expand All @@ -75,6 +77,7 @@ abstract static class CredentialSource {
"https://www.googleapis.com/auth/cloud-platform";

static final String EXTERNAL_ACCOUNT_FILE_TYPE = "external_account";
static final String EXECUTABLE_SOURCE_KEY = "executable";

private final String transportFactoryClassName;
private final String audience;
Expand All @@ -89,13 +92,14 @@ abstract static class CredentialSource {
@Nullable private final String clientId;
@Nullable private final String clientSecret;

// This is used for Workforce Pools. It is passed to STS during token exchange in the
// `options` param and will be embedded in the token by STS.
// This is used for Workforce Pools. It is passed to the Security Token Service during token
// exchange in the `options` param and will be embedded in the token by the Security Token
// Service.
@Nullable private final String workforcePoolUserProject;

protected transient HttpTransportFactory transportFactory;

@Nullable protected final ImpersonatedCredentials impersonatedCredentials;
@Nullable protected ImpersonatedCredentials impersonatedCredentials;

private EnvironmentProvider environmentProvider;

Expand All @@ -104,18 +108,17 @@ abstract static class CredentialSource {
* workforce credentials.
*
* @param transportFactory HTTP transport factory, creates the transport used to get access tokens
* @param audience the STS audience which is usually the fully specified resource name of the
* workload/workforce pool provider
* @param subjectTokenType the STS subject token type based on the OAuth 2.0 token exchange spec.
* Indicates the type of the security token in the credential file
* @param tokenUrl the STS token exchange endpoint
* @param audience the Security Token Service audience, which is usually the fully specified
* resource name of the workload/workforce pool provider
* @param subjectTokenType the Security Token Service subject token type based on the OAuth 2.0
* token exchange spec. Indicates the type of the security token in the credential file
* @param tokenUrl the Security Token Service token exchange endpoint
* @param tokenInfoUrl the endpoint used to retrieve account related information. Required for
* gCloud session account identification.
* @param credentialSource the external credential source
* @param serviceAccountImpersonationUrl the URL for the service account impersonation request.
* This is only required for workload identity pools when APIs to be accessed have not
* integrated with UberMint. If this is not available, the STS returned GCP access token is
* directly used. May be null.
* This URL is required for some APIs. If this URL is not available, the access token from the
* Security Token Service is used directly. May be null.
* @param quotaProjectId the project used for quota and billing purposes. May be null.
* @param clientId client ID of the service account from the console. May be null.
* @param clientSecret client secret of the service account from the console. May be null.
Expand Down Expand Up @@ -238,7 +241,7 @@ protected ExternalAccountCredentials(ExternalAccountCredentials.Builder builder)
this.impersonatedCredentials = initializeImpersonatedCredentials();
}

private ImpersonatedCredentials initializeImpersonatedCredentials() {
protected ImpersonatedCredentials initializeImpersonatedCredentials() {
if (serviceAccountImpersonationUrl == null) {
return null;
}
Expand All @@ -249,6 +252,11 @@ private ImpersonatedCredentials initializeImpersonatedCredentials() {
AwsCredentials.newBuilder((AwsCredentials) this)
.setServiceAccountImpersonationUrl(null)
.build();
} else if (this instanceof PluggableAuthCredentials) {
sourceCredentials =
PluggableAuthCredentials.newBuilder((PluggableAuthCredentials) this)
.setServiceAccountImpersonationUrl(null)
.build();
} else {
sourceCredentials =
IdentityPoolCredentials.newBuilder((IdentityPoolCredentials) this)
Expand Down Expand Up @@ -372,8 +380,20 @@ static ExternalAccountCredentials fromJson(
.setClientId(clientId)
.setClientSecret(clientSecret)
.build();
} else if (isPluggableAuthCredential(credentialSourceMap)) {
return PluggableAuthCredentials.newBuilder()
.setHttpTransportFactory(transportFactory)
.setAudience(audience)
.setSubjectTokenType(subjectTokenType)
.setTokenUrl(tokenUrl)
.setTokenInfoUrl(tokenInfoUrl)
.setCredentialSource(new PluggableAuthCredentialSource(credentialSourceMap))
.setServiceAccountImpersonationUrl(serviceAccountImpersonationUrl)
.setQuotaProjectId(quotaProjectId)
.setClientId(clientId)
.setClientSecret(clientSecret)
.build();
}

return IdentityPoolCredentials.newBuilder()
.setHttpTransportFactory(transportFactory)
.setAudience(audience)
Expand All @@ -389,17 +409,22 @@ static ExternalAccountCredentials fromJson(
.build();
}

private static boolean isPluggableAuthCredential(Map<String, Object> credentialSource) {
// Pluggable Auth is enabled via a nested executable field in the credential source.
return credentialSource.containsKey(EXECUTABLE_SOURCE_KEY);
}

private static boolean isAwsCredential(Map<String, Object> credentialSource) {
return credentialSource.containsKey("environment_id")
&& ((String) credentialSource.get("environment_id")).startsWith("aws");
}

/**
* Exchanges the external credential for a GCP access token.
* Exchanges the external credential for a Google Cloud access token.
*
* @param stsTokenExchangeRequest the STS token exchange request
* @return the access token returned by STS
* @throws OAuthException if the call to STS fails
* @param stsTokenExchangeRequest the Security Token Service token exchange request
* @return the access token returned by the Security Token Service
* @throws OAuthException if the call to the Security Token Service fails
*/
protected AccessToken exchangeExternalCredentialForAccessToken(
StsTokenExchangeRequest stsTokenExchangeRequest) throws IOException {
Expand All @@ -413,7 +438,8 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
tokenUrl, stsTokenExchangeRequest, transportFactory.create().createRequestFactory());

// If this credential was initialized with a Workforce configuration then the
// workforcePoolUserProject must passed to STS via the the internal options param.
// workforcePoolUserProject must be passed to the Security Token Service via the internal
// options param.
if (isWorkforcePoolConfiguration()) {
GenericJson options = new GenericJson();
options.setFactory(OAuth2Utils.JSON_FACTORY);
Expand All @@ -431,7 +457,7 @@ protected AccessToken exchangeExternalCredentialForAccessToken(
}

/**
* Retrieves the external subject token to be exchanged for a GCP access token.
* Retrieves the external subject token to be exchanged for a Google Cloud access token.
*
* <p>Must be implemented by subclasses as the retrieval method is dependent on the credential
* source.
Expand Down Expand Up @@ -465,6 +491,15 @@ public String getServiceAccountImpersonationUrl() {
return serviceAccountImpersonationUrl;
}

/** The service account email to be impersonated, if available. */
@Nullable
public String getServiceAccountEmail() {
if (serviceAccountImpersonationUrl == null || serviceAccountImpersonationUrl.isEmpty()) {
return null;
}
return ImpersonatedCredentials.extractTargetPrincipal(serviceAccountImpersonationUrl);
}

@Override
@Nullable
public String getQuotaProjectId() {
Expand Down Expand Up @@ -496,7 +531,7 @@ EnvironmentProvider getEnvironmentProvider() {
}

/**
* Returns whether or not the current configuration is for Workforce Pools (which enable 3p user
* Returns whether the current configuration is for Workforce Pools (which enable 3p user
* identities, rather than workloads).
*/
public boolean isWorkforcePoolConfiguration() {
Expand Down Expand Up @@ -603,24 +638,24 @@ public Builder setHttpTransportFactory(HttpTransportFactory transportFactory) {
}

/**
* Sets the STS audience which is usually the fully specified resource name of the
* workload/workforce pool provider.
* Sets the Security Token Service audience, which is usually the fully specified resource name
* of the workload/workforce pool provider.
*/
public Builder setAudience(String audience) {
this.audience = audience;
return this;
}

/**
* Sets the STS subject token type based on the OAuth 2.0 token exchange spec. Indicates the
* type of the security token in the credential file.
* Sets the Security Token Service subject token type based on the OAuth 2.0 token exchange
* spec. Indicates the type of the security token in the credential file.
*/
public Builder setSubjectTokenType(String subjectTokenType) {
this.subjectTokenType = subjectTokenType;
return this;
}

/** Sets the STS token exchange endpoint. */
/** Sets the Security Token Service token exchange endpoint. */
public Builder setTokenUrl(String tokenUrl) {
this.tokenUrl = tokenUrl;
return this;
Expand All @@ -633,9 +668,9 @@ public Builder setCredentialSource(CredentialSource credentialSource) {
}

/**
* Sets the optional URL used for service account impersonation. This is only required when APIs
* to be accessed have not integrated with UberMint. If this is not available, the STS returned
* GCP access token is directly used.
* Sets the optional URL used for service account impersonation, which is required for some
* APIs. If this URL is not available, the access token from the Security Token Service is used
* directly.
*/
public Builder setServiceAccountImpersonationUrl(String serviceAccountImpersonationUrl) {
this.serviceAccountImpersonationUrl = serviceAccountImpersonationUrl;
Expand Down
Loading