Skip to content

Commit

Permalink
Implement GcpWorkloadIdentityCredentialProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
itsankit-google committed Aug 14, 2023
1 parent 1cc0a61 commit ae512fe
Show file tree
Hide file tree
Showing 18 changed files with 737 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

import com.google.common.util.concurrent.AbstractIdleService;
import io.cdap.cdap.common.conf.CConfiguration;
import io.cdap.cdap.proto.NamespaceMeta;
import io.cdap.cdap.proto.credential.CredentialIdentity;
import io.cdap.cdap.proto.credential.CredentialProfile;
import io.cdap.cdap.proto.credential.CredentialProvisioningException;
Expand All @@ -33,13 +34,16 @@
import java.util.Map;
import java.util.Optional;
import javax.inject.Inject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
* Default implementation for {@link CredentialProviderService} used in AppFabric.
*/
public class DefaultCredentialProviderService extends AbstractIdleService
implements CredentialProviderService {

private static final Logger LOG = LoggerFactory.getLogger(DefaultCredentialProviderService.class);
private final CConfiguration cConf;
private final ContextAccessEnforcer contextAccessEnforcer;
private final Map<String, CredentialProvider> credentialProviders;
Expand All @@ -57,10 +61,21 @@ public class DefaultCredentialProviderService extends AbstractIdleService
this.credentialProviders = credentialProviderLoader.loadCredentialProviders();
this.credentialIdentityManager = credentialIdentityManager;
this.credentialProfileManager = credentialProfileManager;
for (Map.Entry<String, CredentialProvider> credentialProviderEntry :
this.credentialProviders.entrySet()) {
LOG.info("Credential Provider {}, Name {}", credentialProviderEntry.getKey(),
credentialProviderEntry.getValue().getName());
}
if (credentialProviders.size() <= 0) {
LOG.info("No credential providers found");
}
}

@Override
protected void startUp() throws Exception {
if (credentialProviders.size() <= 0) {
LOG.info("No credential providers found");
}
for (CredentialProvider provider : credentialProviders.values()) {
provider.initialize(new DefaultCredentialProviderContext(cConf, provider.getName()));
}
Expand All @@ -74,46 +89,49 @@ protected void shutDown() throws Exception {
/**
* Provisions a credential.
*
* @param namespace The identity namespace.
* @param namespaceMeta The identity namespace metadata.
* @param identityName The identity name.
* @return A provisioned credential.
* @throws CredentialProvisioningException If provisioning fails in the extension.
* @throws IOException If any transport errors occur.
* @throws NotFoundException If the identity or profile are not found.
*/
@Override
public ProvisionedCredential provision(String namespace, String identityName)
public ProvisionedCredential provision(NamespaceMeta namespaceMeta, String identityName)
throws CredentialProvisioningException, IOException, NotFoundException {
CredentialIdentityId identityId = new CredentialIdentityId(namespace, identityName);
CredentialIdentityId identityId =
new CredentialIdentityId(namespaceMeta.getName(), identityName);
contextAccessEnforcer.enforce(identityId, StandardPermission.USE);
Optional<CredentialIdentity> optIdentity = credentialIdentityManager.get(identityId);
if (!optIdentity.isPresent()) {
throw new NotFoundException(String.format("Credential identity '%s' was not found.",
identityId.toString()));
}
CredentialIdentity identity = optIdentity.get();
return validateAndProvisionIdentity(identity);
return validateAndProvisionIdentity(namespaceMeta, identity);
}

/**
* Validates an identity.
*
* @param namespaceMeta The identity namespace metadata.
* @param identity The identity to validate.
* @throws IdentityValidationException If identity validation fails in the extension.
* @throws IOException If any transport errors occur.
* @throws NotFoundException If the identity or profile are not found.
*/
@Override
public void validateIdentity(CredentialIdentity identity) throws IdentityValidationException,
IOException, NotFoundException {
public void validateIdentity(NamespaceMeta namespaceMeta, CredentialIdentity identity)
throws IdentityValidationException, IOException, NotFoundException {
try {
validateAndProvisionIdentity(identity);
validateAndProvisionIdentity(namespaceMeta, identity);
} catch (CredentialProvisioningException e) {
throw new IdentityValidationException(e);
}
}

private ProvisionedCredential validateAndProvisionIdentity(CredentialIdentity identity)
private ProvisionedCredential validateAndProvisionIdentity(NamespaceMeta namespaceMeta,
CredentialIdentity identity)
throws CredentialProvisioningException, IOException, NotFoundException {
CredentialProfileId profileId = new CredentialProfileId(identity.getProfileNamespace(),
identity.getProfileName());
Expand All @@ -131,6 +149,6 @@ private ProvisionedCredential validateAndProvisionIdentity(CredentialIdentity id
+ "'%'", providerType));
}
// Provision and return the credential.
return credentialProviders.get(providerType).provision(profile, identity);
return credentialProviders.get(providerType).provision(namespaceMeta, profile, identity);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import io.cdap.cdap.common.namespace.NamespaceQueryAdmin;
import io.cdap.cdap.internal.credential.CredentialIdentityManager;
import io.cdap.cdap.internal.credential.CredentialProfileManager;
import io.cdap.cdap.proto.NamespaceMeta;
import io.cdap.cdap.proto.credential.CreateCredentialIdentityRequest;
import io.cdap.cdap.proto.credential.CreateCredentialProfileRequest;
import io.cdap.cdap.proto.credential.CredentialIdentity;
Expand Down Expand Up @@ -114,12 +115,20 @@ public void listProviders(HttpRequest request, HttpResponder responder) {
* @throws IOException If transport errors occur.
*/
@POST
@Path("/credentials/identities/validate")
public void validateIdentity(FullHttpRequest request, HttpResponder responder)
@Path("/namespaces/{namespace-id}/credentials/identities/validate")
public void validateIdentity(FullHttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespace)
throws BadRequestException, NotFoundException, IOException {
CredentialIdentity identity = deserializeRequestContent(request, CredentialIdentity.class);
NamespaceMeta namespaceMeta;
try {
credentialProvider.validateIdentity(identity);
namespaceMeta = namespaceQueryAdmin.get(new NamespaceId(namespace));
} catch (Exception e) {
throw new IOException(String.format("Failed to get namespace '%s' metadata",
namespace), e) ;
}
try {
credentialProvider.validateIdentity(namespaceMeta, identity);
} catch (IdentityValidationException e) {
throw new BadRequestException(String.format("Identity failed validation with error: %s",
e.getMessage()), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,11 @@
import com.google.inject.Singleton;
import io.cdap.cdap.common.NotFoundException;
import io.cdap.cdap.common.conf.Constants;
import io.cdap.cdap.common.namespace.NamespaceQueryAdmin;
import io.cdap.cdap.proto.NamespaceMeta;
import io.cdap.cdap.proto.credential.CredentialProvider;
import io.cdap.cdap.proto.credential.CredentialProvisioningException;
import io.cdap.cdap.proto.id.NamespaceId;
import io.cdap.http.AbstractHttpHandler;
import io.cdap.http.HttpHandler;
import io.cdap.http.HttpResponder;
Expand All @@ -46,10 +49,13 @@ public class CredentialProviderHttpHandlerInternal extends AbstractHttpHandler {
new InstantEpochSecondsTypeAdapter()).create();

private final CredentialProvider credentialProvider;
private final NamespaceQueryAdmin namespaceQueryAdmin;

@Inject
CredentialProviderHttpHandlerInternal(CredentialProvider credentialProvider) {
CredentialProviderHttpHandlerInternal(CredentialProvider credentialProvider,
NamespaceQueryAdmin namespaceQueryAdmin) {
this.credentialProvider = credentialProvider;
this.namespaceQueryAdmin = namespaceQueryAdmin;
}

/**
Expand All @@ -68,9 +74,16 @@ public class CredentialProviderHttpHandlerInternal extends AbstractHttpHandler {
public void provisionCredential(HttpRequest request, HttpResponder responder,
@PathParam("namespace-id") String namespace, @PathParam("identity-name") String identityName)
throws CredentialProvisioningException, IOException, NotFoundException {
NamespaceMeta namespaceMeta;
try {
namespaceMeta = namespaceQueryAdmin.get(new NamespaceId(namespace));
} catch (Exception e) {
throw new IOException(String.format("Failed to get namespace '%s' metadata",
namespace), e) ;
}
try {
responder.sendJson(HttpResponseStatus.OK,
GSON.toJson(credentialProvider.provision(namespace, identityName)));
GSON.toJson(credentialProvider.provision(namespaceMeta, identityName)));
} catch (io.cdap.cdap.proto.credential.NotFoundException e) {
throw new NotFoundException(e.getMessage());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -101,14 +101,14 @@ protected void configure() {
.getInstance(StructuredTableAdmin.class));
// Setup mock credential providers.
CredentialProvider mockCredentialProvider = mock(CredentialProvider.class);
when(mockCredentialProvider.provision(any(), any())).thenReturn(RETURNED_TOKEN);
when(mockCredentialProvider.provision(any(), any(), any())).thenReturn(RETURNED_TOKEN);
CredentialProvider validationFailureMockCredentialProvider = mock(CredentialProvider.class);
when(validationFailureMockCredentialProvider.provision(any(), any()))
when(validationFailureMockCredentialProvider.provision(any(), any(), any()))
.thenReturn(RETURNED_TOKEN);
doThrow(new ProfileValidationException("profile validation always fails with this provider"))
.when(validationFailureMockCredentialProvider).validateProfile(any());
CredentialProvider provisionFailureMockCredentialProvider = mock(CredentialProvider.class);
when(provisionFailureMockCredentialProvider.provision(any(), any()))
when(provisionFailureMockCredentialProvider.provision(any(), any(), any()))
.thenThrow(new CredentialProvisioningException("provisioning always fails with this "
+ "provider"));
credentialProviders.put(CREDENTIAL_PROVIDER_TYPE_SUCCESS, mockCredentialProvider);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package io.cdap.cdap.internal.credential;

import io.cdap.cdap.common.conf.CConfiguration;
import io.cdap.cdap.proto.NamespaceMeta;
import io.cdap.cdap.proto.credential.CredentialIdentity;
import io.cdap.cdap.proto.credential.CredentialProvisioningException;
import io.cdap.cdap.proto.credential.IdentityValidationException;
Expand Down Expand Up @@ -45,6 +46,12 @@ contextAccessEnforcer, new MockCredentialProviderLoader(), credentialIdentityMan
public void testProvisionSuccess() throws Exception {
// Create a new profile.
String namespace = "testProvisionSuccess";
String identityName = "test";
NamespaceMeta namespaceMeta = new
NamespaceMeta.Builder()
.setName(namespace)
.setIdentity(identityName)
.buildWithoutKeytabUriVersion();
CredentialProfileId profileId = createDummyProfile(CREDENTIAL_PROVIDER_TYPE_SUCCESS, namespace,
"test-profile");

Expand All @@ -54,13 +61,20 @@ public void testProvisionSuccess() throws Exception {
profileId.getName(), "some-identity", "some-secure-value");
credentialIdentityManager.create(id, identity);

Assert.assertEquals(RETURNED_TOKEN, credentialProviderService.provision(namespace, "test"));
Assert.assertEquals(RETURNED_TOKEN,
credentialProviderService.provision(namespaceMeta, identityName));
}

@Test(expected = NotFoundException.class)
public void testProvisionWithNotFoundIdentityThrowsException() throws Exception {
String namespace = "testProvisionWithNotFoundIdentityThrowsException";
credentialProviderService.provision(namespace, "does-not-exist");
String identityName = "does-not-exist";
NamespaceMeta namespaceMeta = new
NamespaceMeta.Builder()
.setName(namespace)
.setIdentity(identityName)
.buildWithoutKeytabUriVersion();
credentialProviderService.provision(namespaceMeta, identityName);
}

@Test(expected = CredentialProvisioningException.class)
Expand All @@ -71,43 +85,68 @@ public void testProvisionFailureThrowsException() throws Exception {
namespace, "test-profile");

// Create a new identity.
CredentialIdentityId id = new CredentialIdentityId(namespace, "test");
String identityName = "test";
CredentialIdentityId id = new CredentialIdentityId(namespace, identityName);
CredentialIdentity identity = new CredentialIdentity(profileId.getNamespace(),
profileId.getName(), "some-identity", "some-secure-value");
credentialIdentityManager.create(id, identity);

Assert.assertEquals(RETURNED_TOKEN, credentialProviderService.provision(namespace, "test"));
NamespaceMeta namespaceMeta = new
NamespaceMeta.Builder()
.setName(namespace)
.setIdentity(identityName)
.buildWithoutKeytabUriVersion();
Assert.assertEquals(RETURNED_TOKEN, credentialProviderService.provision(namespaceMeta,
identityName));
}

@Test
public void testIdentityValidationSuccess() throws Exception {
// Create a new profile.
String identityName = "some-identity";
String namespace = "testIdentityValidationSuccess";
NamespaceMeta namespaceMeta = new
NamespaceMeta.Builder()
.setName(namespace)
.setIdentity(identityName)
.buildWithoutKeytabUriVersion();
CredentialProfileId profileId = createDummyProfile(CREDENTIAL_PROVIDER_TYPE_SUCCESS,
namespace, "test-profile");

CredentialIdentity identity = new CredentialIdentity(profileId.getNamespace(),
profileId.getName(), "some-identity", "some-secure-value");
credentialProviderService.validateIdentity(identity);
profileId.getName(), identityName, "some-secure-value");
credentialProviderService.validateIdentity(namespaceMeta, identity);
}

@Test(expected = IdentityValidationException.class)
public void testIdentityValidationOnProvisionFailureThrowsException() throws Exception {
// Create a new profile.
String namespace = "testIdentityValidationFailureThrowsException";
String identityName = "some-identity";
NamespaceMeta namespaceMeta = new
NamespaceMeta.Builder()
.setName(namespace)
.setIdentity(identityName)
.buildWithoutKeytabUriVersion();
CredentialProfileId profileId = createDummyProfile(CREDENTIAL_PROVIDER_TYPE_PROVISION_FAILURE,
namespace, "test-profile");

CredentialIdentity identity = new CredentialIdentity(profileId.getNamespace(),
profileId.getName(), "some-identity", "some-secure-value");
credentialProviderService.validateIdentity(identity);
profileId.getName(), identityName, "some-secure-value");
credentialProviderService.validateIdentity(namespaceMeta, identity);
}

@Test(expected = NotFoundException.class)
public void testIdentityValidationWithNotFoundProfileThrowsException() throws Exception {
String namespace = "testIdentityValidationWithNotFoundProfileThrowsException";
String identityName = "some-identity";
NamespaceMeta namespaceMeta = new
NamespaceMeta.Builder()
.setName(namespace)
.setIdentity(identityName)
.buildWithoutKeytabUriVersion();
CredentialIdentity identity = new CredentialIdentity(namespace, "does-not-exist",
"some-identity", "some-secure-value");
credentialProviderService.validateIdentity(identity);
identityName, "some-secure-value");
credentialProviderService.validateIdentity(namespaceMeta, identity);
}
}
2 changes: 1 addition & 1 deletion cdap-common/src/main/resources/cdap-default.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5888,7 +5888,7 @@

<property>
<name>feature.namespaced.service.accounts.enabled</name>
<value>false</value>
<value>true</value>
<description>
If true, namespace service accounts feature will be enabled.
</description>
Expand Down
Loading

0 comments on commit ae512fe

Please sign in to comment.