Skip to content

Commit

Permalink
Add scopes support to Remote Authenticator
Browse files Browse the repository at this point in the history
  • Loading branch information
itsankit-google committed Feb 12, 2024
1 parent 4f43da1 commit 7b0d4a9
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,8 @@ public class GcpMetadataHttpHandlerInternal extends AbstractAppFabricHttpHandler
private final GcpWorkloadIdentityInternalAuthenticator gcpWorkloadIdentityInternalAuthenticator;
private GcpMetadataTaskContext gcpMetadataTaskContext;
private final LoadingCache<ProvisionedCredentialCacheKey,
ProvisionedCredential> credentialLoadingCache;
ProvisionedCredential> provisionedCredentialLoadingCache;
private final LoadingCache<String, Credential> credentialLoadingCache;

/**
* Constructs the {@link GcpMetadataHttpHandlerInternal}.
Expand All @@ -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<ProvisionedCredentialCacheKey, ProvisionedCredential>() {
Expand All @@ -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<String, Credential>() {
@Override
public Credential load(String scopes) throws Exception {
return remoteAuthenticator.getCredentials(scopes);
}
});
}

/**
Expand Down Expand Up @@ -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(),
Expand All @@ -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");
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Check warning on line 31 in cdap-common/src/main/java/io/cdap/cdap/common/internal/remote/GceRemoteAuthenticator.java

View workflow job for this annotation

GitHub Actions / Checkstyle

com.puppycrawl.tools.checkstyle.checks.imports.UnusedImportsCheck

Unused 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
Expand All @@ -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");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

0 comments on commit 7b0d4a9

Please sign in to comment.