diff --git a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/worker/sidecar/GcpMetadataHttpHandlerInternal.java b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/worker/sidecar/GcpMetadataHttpHandlerInternal.java index 6a6cd2028c25..e3425904c93b 100644 --- a/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/worker/sidecar/GcpMetadataHttpHandlerInternal.java +++ b/cdap-app-fabric/src/main/java/io/cdap/cdap/internal/app/worker/sidecar/GcpMetadataHttpHandlerInternal.java @@ -76,7 +76,8 @@ public class GcpMetadataHttpHandlerInternal extends AbstractAppFabricHttpHandler private final GcpWorkloadIdentityInternalAuthenticator gcpWorkloadIdentityInternalAuthenticator; private GcpMetadataTaskContext gcpMetadataTaskContext; private final LoadingCache credentialLoadingCache; + ProvisionedCredential> provisionedCredentialLoadingCache; + private final LoadingCache credentialLoadingCache; /** * Constructs the {@link GcpMetadataHttpHandlerInternal}. @@ -91,7 +92,7 @@ public GcpMetadataHttpHandlerInternal(CConfiguration cConf, new GcpWorkloadIdentityInternalAuthenticator(gcpMetadataTaskContext); this.credentialProvider = new RemoteNamespaceCredentialProvider(remoteClientFactory, this.gcpWorkloadIdentityInternalAuthenticator); - this.credentialLoadingCache = CacheBuilder.newBuilder() + this.provisionedCredentialLoadingCache = CacheBuilder.newBuilder() // Provisioned credential expire after 60mins, assuming 20% buffer in cache exp (0.8*60). .expireAfterWrite(48, TimeUnit.MINUTES) .build(new CacheLoader() { @@ -103,6 +104,15 @@ public ProvisionedCredential load(ProvisionedCredentialCacheKey provisionedCredentialCacheKey.getScopes()); } }); + this.credentialLoadingCache = CacheBuilder.newBuilder() + // Credential expire after 60mins, assuming 20% buffer in cache exp (0.8*60). + .expireAfterWrite(48, TimeUnit.MINUTES) + .build(new CacheLoader() { + @Override + public Credential load(String scopes) throws Exception { + return remoteAuthenticator.getCredentials(scopes); + } + }); } /** @@ -162,7 +172,7 @@ public void token(HttpRequest request, HttpResponder responder, try { // fetch token from credential provider ProvisionedCredential provisionedCredential = - credentialLoadingCache.get( + provisionedCredentialLoadingCache.get( new ProvisionedCredentialCacheKey(this.gcpMetadataTaskContext, scopes)); GcpTokenResponse gcpTokenResponse = new GcpTokenResponse("Bearer", provisionedCredential.get(), @@ -179,7 +189,12 @@ public void token(HttpRequest request, HttpResponder responder, } try { - Credential credential = remoteAuthenticator.getCredentials(); + Credential credential; + if (Strings.isNullOrEmpty(scopes)) { + credential = remoteAuthenticator.getCredentials(); + } else { + credential = credentialLoadingCache.get(scopes); + } if (credential == null || Strings.isNullOrEmpty(credential.getValue())) { responder.sendJson(HttpResponseStatus.INTERNAL_SERVER_ERROR, "Failed to fetch token from metadata server"); @@ -230,6 +245,7 @@ public void setContext(FullHttpRequest request, HttpResponder responder) public void clearContext(HttpRequest request, HttpResponder responder) { this.gcpMetadataTaskContext = null; this.gcpWorkloadIdentityInternalAuthenticator.setGcpMetadataTaskContext(gcpMetadataTaskContext); + this.provisionedCredentialLoadingCache.invalidateAll(); this.credentialLoadingCache.invalidateAll(); LOG.trace("Context cleared."); responder.sendStatus(HttpResponseStatus.OK); diff --git a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/runtime/monitor/RuntimeServiceRoutingTest.java b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/runtime/monitor/RuntimeServiceRoutingTest.java index 9e74b012df1a..3f75978b4295 100644 --- a/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/runtime/monitor/RuntimeServiceRoutingTest.java +++ b/cdap-app-fabric/src/test/java/io/cdap/cdap/internal/app/runtime/monitor/RuntimeServiceRoutingTest.java @@ -62,6 +62,7 @@ import java.util.Arrays; import java.util.Base64; import java.util.EnumSet; +import javax.annotation.Nullable; import org.apache.twill.common.Cancellable; import org.apache.twill.discovery.DiscoveryService; import org.junit.After; @@ -268,6 +269,15 @@ public Credential getCredentials() { .asBytes()); return new Credential(credentialValue, Credential.CredentialType.EXTERNAL_BEARER); } + + /** + * Returns the credentials for the authentication with scopes. + */ + @Nullable + @Override + public Credential getCredentials(String scopes) throws IOException { + return getCredentials(); + } } /** diff --git a/cdap-authenticator-ext-gcp/src/main/java/io/cdap/cdap/authenticator/gcp/GCPRemoteAuthenticator.java b/cdap-authenticator-ext-gcp/src/main/java/io/cdap/cdap/authenticator/gcp/GCPRemoteAuthenticator.java index d9c45aab14c7..824b14109449 100644 --- a/cdap-authenticator-ext-gcp/src/main/java/io/cdap/cdap/authenticator/gcp/GCPRemoteAuthenticator.java +++ b/cdap-authenticator-ext-gcp/src/main/java/io/cdap/cdap/authenticator/gcp/GCPRemoteAuthenticator.java @@ -19,6 +19,7 @@ import com.google.auth.oauth2.AccessToken; import com.google.auth.oauth2.GoogleCredentials; import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Strings; import io.cdap.cdap.proto.security.Credential; import io.cdap.cdap.security.spi.authenticator.RemoteAuthenticator; import java.io.IOException; @@ -67,4 +68,19 @@ public Credential getCredentials() throws IOException { return new Credential(accessToken.getTokenValue(), Credential.CredentialType.EXTERNAL_BEARER, accessToken.getExpirationTime().getTime() / 1000L); } + + /** + * Returns the credentials for the authentication with scopes. + */ + @Nullable + @Override + public Credential getCredentials(@Nullable String scopes) throws IOException { + if (Strings.isNullOrEmpty(scopes)) { + return getCredentials(); + } + AccessToken accessToken = + GoogleCredentials.getApplicationDefault().createScoped(scopes).refreshAccessToken(); + return new Credential(accessToken.getTokenValue(), Credential.CredentialType.EXTERNAL_BEARER, + accessToken.getExpirationTime().getTime() / 1000L); + } } diff --git a/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/GceRemoteAuthenticator.java b/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/GceRemoteAuthenticator.java index 75687c7f68d7..3ac959ae188e 100644 --- a/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/GceRemoteAuthenticator.java +++ b/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/GceRemoteAuthenticator.java @@ -16,16 +16,21 @@ package io.cdap.cdap.common.internal.remote; +import com.google.common.base.Strings; import com.google.gson.Gson; import com.google.gson.JsonObject; +import io.cdap.cdap.common.discovery.URIScheme; import io.cdap.cdap.proto.security.Credential; import io.cdap.cdap.security.spi.authenticator.RemoteAuthenticator; import io.cdap.common.http.HttpRequest; import io.cdap.common.http.HttpRequests; import io.cdap.common.http.HttpResponse; import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; import java.net.URL; import java.util.concurrent.TimeUnit; +import javax.annotation.Nullable; /** * A {@link RemoteAuthenticator} that authenticate remote calls using Google Cloud token acquired @@ -46,22 +51,45 @@ public String getName() { @Override public Credential getCredentials() throws IOException { - return new Credential(getAccessToken().getToken(), Credential.CredentialType.EXTERNAL_BEARER); + return new Credential(getAccessToken(null).getToken(), + Credential.CredentialType.EXTERNAL_BEARER); + } + + /** + * Returns the credentials for the authentication with scopes. + */ + @Nullable + @Override + public Credential getCredentials(String scopes) throws IOException { + return new Credential(getAccessToken(scopes).getToken(), + Credential.CredentialType.EXTERNAL_BEARER); } /** * Returns an unexpired access token for authentication. */ - private AccessToken getAccessToken() throws IOException { + private AccessToken getAccessToken(@Nullable String scopes) throws IOException { AccessToken accessToken = this.accessToken; if (accessToken != null && !accessToken.isExpired()) { return accessToken; } - URL url = new URL( - "http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token"); + URI uri; + try { + uri = new URI(URIScheme.HTTP.getScheme(), "metadata.google.internal", + "/computeMetadata/v1/instance/service-accounts/default/token", + null, null); + if (!Strings.isNullOrEmpty(scopes)) { + uri = new URI(URIScheme.HTTP.getScheme(), "metadata.google.internal", + "/computeMetadata/v1/instance/service-accounts/default/token", + String.format("scopes=%s", scopes), null); + } + } catch (URISyntaxException e) { + throw new IOException(e); + } + HttpResponse response = HttpRequests.execute( - HttpRequest.get(url).addHeader("Metadata-Flavor", "Google").build()); + HttpRequest.get(uri.toURL()).addHeader("Metadata-Flavor", "Google").build()); if (response.getResponseCode() != 200) { throw new IOException("Failed to default service account token"); } diff --git a/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/NoOpRemoteAuthenticator.java b/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/NoOpRemoteAuthenticator.java index b416ceabcfc4..784c0023b27a 100644 --- a/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/NoOpRemoteAuthenticator.java +++ b/cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/NoOpRemoteAuthenticator.java @@ -38,4 +38,13 @@ public String getName() { public Credential getCredentials() throws IOException { return null; } + + /** + * Returns the credentials for the authentication with scopes. + */ + @Nullable + @Override + public Credential getCredentials(String scopes) throws IOException { + return null; + } } diff --git a/cdap-security-spi/src/main/java/io/cdap/cdap/security/spi/authenticator/RemoteAuthenticator.java b/cdap-security-spi/src/main/java/io/cdap/cdap/security/spi/authenticator/RemoteAuthenticator.java index edc37467c646..fa1e359ae1ac 100644 --- a/cdap-security-spi/src/main/java/io/cdap/cdap/security/spi/authenticator/RemoteAuthenticator.java +++ b/cdap-security-spi/src/main/java/io/cdap/cdap/security/spi/authenticator/RemoteAuthenticator.java @@ -39,4 +39,10 @@ public interface RemoteAuthenticator { */ @Nullable Credential getCredentials() throws IOException; + + /** + * Returns the credentials for the authentication with scopes. + */ + @Nullable + Credential getCredentials(String scopes) throws IOException; }