Skip to content

Commit

Permalink
[CDAP-20872] Create AeadCipher SPI for supporting extensible encrypti…
Browse files Browse the repository at this point in the history
…on in CDAP

[CDAP-20872] Add data encryption for CredentialIdentityStore and CredentialProfileStore

[CDAP-20872] Refactor user credential encryption to use new SPI

[CDAP-20872] Remove Tink dependency from cdap-security

[CDAP-20872] Addressed comments, renamed AeadCipher SPI to AeadCipherCryptor

[CDAP-20872] Change CipherException to extend RuntimeException and address comments

[CDAP-20872] Fix checktstyle
  • Loading branch information
dli357 committed Nov 18, 2023
1 parent 2728b09 commit 61df5b6
Show file tree
Hide file tree
Showing 63 changed files with 2,878 additions and 844 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@
import io.cdap.cdap.scheduler.CoreSchedulerService;
import io.cdap.cdap.scheduler.Scheduler;
import io.cdap.cdap.securestore.spi.SecretStore;
import io.cdap.cdap.security.encryption.guice.DataStorageAeadEncryptionModule;
import io.cdap.cdap.security.impersonation.DefaultOwnerAdmin;
import io.cdap.cdap.security.impersonation.DefaultUGIProvider;
import io.cdap.cdap.security.impersonation.OwnerAdmin;
Expand Down Expand Up @@ -200,6 +201,7 @@ public Module getInMemoryModules() {
new EntityVerifierModule(),
new MasterCredentialProviderModule(),
new OperationModule(),
new DataStorageAeadEncryptionModule(),
BootstrapModules.getInMemoryModule(),
new AbstractModule() {
@Override
Expand Down Expand Up @@ -243,6 +245,7 @@ public Module getStandaloneModules() {
new ProvisionerModule(),
new MasterCredentialProviderModule(),
new OperationModule(),
new DataStorageAeadEncryptionModule(),
BootstrapModules.getFileBasedModule(),
new AbstractModule() {
@Override
Expand Down Expand Up @@ -298,6 +301,7 @@ public Module getDistributedModules() {
new ProvisionerModule(),
new MasterCredentialProviderModule(),
new OperationModule(),
new DataStorageAeadEncryptionModule(),
BootstrapModules.getFileBasedModule(),
new AbstractModule() {
@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ public AppFabricServer(CConfiguration cConf, SConfiguration sConf,
@Named("appfabric.handler.hooks") Set<String> handlerHookNames,
CoreSchedulerService coreSchedulerService,
CredentialProviderService credentialProviderService,
NamespaceCredentialProviderService namespaceCredentialProviderService, ProvisioningService provisioningService,
NamespaceCredentialProviderService namespaceCredentialProviderService,
ProvisioningService provisioningService,
BootstrapService bootstrapService,
SystemAppManagementService systemAppManagementService,
TransactionRunner transactionRunner,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@

import io.cdap.cdap.common.AlreadyExistsException;
import io.cdap.cdap.common.NotFoundException;
import io.cdap.cdap.internal.credential.store.CredentialIdentityStore;
import io.cdap.cdap.internal.credential.store.CredentialProfileStore;
import io.cdap.cdap.proto.credential.CredentialIdentity;
import io.cdap.cdap.proto.id.CredentialIdentityId;
import io.cdap.cdap.proto.id.CredentialProfileId;
import io.cdap.cdap.security.spi.encryption.CipherException;
import io.cdap.cdap.spi.data.StructuredTableContext;
import io.cdap.cdap.spi.data.transaction.TransactionRunner;
import io.cdap.cdap.spi.data.transaction.TransactionRunners;
Expand Down Expand Up @@ -70,7 +69,11 @@ public Collection<CredentialIdentityId> list(String namespace) throws IOExceptio
*/
public Optional<CredentialIdentity> get(CredentialIdentityId id) throws IOException {
return TransactionRunners.run(transactionRunner, context -> {
return identityStore.get(context, id);
try {
return identityStore.get(context, id);
} catch (CipherException e) {
throw new IOException("Failed to decrypt identity", e);
}
}, IOException.class);
}

Expand All @@ -86,7 +89,7 @@ public Optional<CredentialIdentity> get(CredentialIdentityId id) throws IOExcept
public void create(CredentialIdentityId id, CredentialIdentity identity)
throws AlreadyExistsException, IOException, NotFoundException {
TransactionRunners.run(transactionRunner, context -> {
if (identityStore.get(context, id).isPresent()) {
if (identityStore.exists(context, id)) {
throw new AlreadyExistsException(String.format("Credential identity '%s:%s' already exists",
id.getNamespace(), id.getName()));
}
Expand All @@ -106,7 +109,7 @@ public void create(CredentialIdentityId id, CredentialIdentity identity)
public void update(CredentialIdentityId id, CredentialIdentity identity)
throws IOException, NotFoundException {
TransactionRunners.run(transactionRunner, context -> {
if (!identityStore.get(context, id).isPresent()) {
if (!identityStore.exists(context, id)) {
throw new NotFoundException(String.format("Credential identity '%s:%s' not found",
id.getNamespace(), id.getName()));
}
Expand All @@ -123,7 +126,7 @@ public void update(CredentialIdentityId id, CredentialIdentity identity)
*/
public void delete(CredentialIdentityId id) throws IOException, NotFoundException {
TransactionRunners.run(transactionRunner, context -> {
if (!identityStore.get(context, id).isPresent()) {
if (!identityStore.exists(context, id)) {
throw new NotFoundException(String.format("Credential identity '%s:%s' not found",
id.getNamespace(), id.getName()));
}
Expand All @@ -136,10 +139,14 @@ private void validateAndWriteIdentity(StructuredTableContext context, Credential
// Validate the referenced profile exists.
CredentialProfileId profileId = new CredentialProfileId(identity.getProfileNamespace(),
identity.getProfileName());
if (!profileStore.get(context, profileId).isPresent()) {
if (!profileStore.exists(context, profileId)) {
throw new NotFoundException(String.format("Credential profile '%s:%s' not found",
profileId.getNamespace(), profileId.getName()));
}
identityStore.write(context, id, identity);
try {
identityStore.write(context, id, identity);
} catch (CipherException e) {
throw new IOException("Failed to encrypt identity", e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
* the License.
*/

package io.cdap.cdap.internal.credential.store;
package io.cdap.cdap.internal.credential;

import com.google.gson.Gson;
import io.cdap.cdap.api.dataset.lib.CloseableIterator;
import io.cdap.cdap.proto.credential.CredentialIdentity;
import io.cdap.cdap.proto.id.CredentialIdentityId;
import io.cdap.cdap.proto.id.CredentialProfileId;
import io.cdap.cdap.security.encryption.AeadCipher;
import io.cdap.cdap.security.encryption.guice.DataStorageAeadEncryptionModule;
import io.cdap.cdap.security.spi.encryption.CipherException;
import io.cdap.cdap.spi.data.StructuredRow;
import io.cdap.cdap.spi.data.StructuredTable;
import io.cdap.cdap.spi.data.StructuredTableContext;
Expand All @@ -29,6 +32,7 @@
import io.cdap.cdap.spi.data.table.field.Range;
import io.cdap.cdap.store.StoreDefinition.CredentialProviderStore;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
Expand All @@ -38,14 +42,30 @@
import java.util.Spliterators;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
import javax.inject.Named;

/**
* Storage for credential identities.
*/
public class CredentialIdentityStore {

/**
* DO NOT CHANGE THIS VALUE! CHANGING THIS VALUE IS BACKWARDS-INCOMPATIBLE. Values encrypted using
* a different value will not decrypt properly!
*/
private static final byte[] CREDENTIAL_IDENTITY_STORE_AD = "CredentialIdentityStore"
.getBytes(StandardCharsets.UTF_8);
private static final Gson GSON = new Gson();

private final AeadCipher dataStorageCipher;

@Inject
CredentialIdentityStore(@Named(DataStorageAeadEncryptionModule.DATA_STORAGE_ENCRYPTION)
AeadCipher dataStorageCipher) {
this.dataStorageCipher = dataStorageCipher;
}

/**
* Lists entries in the credential identity table for a given namespace.
*
Expand Down Expand Up @@ -84,6 +104,18 @@ public Collection<CredentialIdentityId> listForProfile(StructuredTableContext co
}
}

/**
* Returns whether an entry exists in the identity table.
*
* @param context The transaction context to use.
* @param id The identity reference to fetch.
* @return Whether the credential identity exists.
*/
public boolean exists(StructuredTableContext context, CredentialIdentityId id)
throws IOException {
return readIdentity(context, id).isPresent();
}

/**
* Fetch an entry from the identity table.
*
Expand All @@ -93,15 +125,13 @@ public Collection<CredentialIdentityId> listForProfile(StructuredTableContext co
* @throws IOException If any failure reading from storage occurs.
*/
public Optional<CredentialIdentity> get(StructuredTableContext context, CredentialIdentityId id)
throws IOException {
StructuredTable table = context.getTable(CredentialProviderStore.CREDENTIAL_IDENTITIES);
Collection<Field<?>> key = Arrays.asList(
Fields.stringField(CredentialProviderStore.NAMESPACE_FIELD,
id.getNamespace()),
Fields.stringField(CredentialProviderStore.IDENTITY_NAME_FIELD,
id.getName()));
return table.read(key).map(row -> GSON.fromJson(row
.getString(CredentialProviderStore.IDENTITY_DATA_FIELD), CredentialIdentity.class));
throws CipherException, IOException {
return readIdentity(context, id)
.map(row -> row.getBytes(CredentialProviderStore.IDENTITY_DATA_FIELD))
.map(encryptedIdentity -> dataStorageCipher
.decrypt(encryptedIdentity, CREDENTIAL_IDENTITY_STORE_AD))
.map(decrypted -> new String(decrypted, StandardCharsets.UTF_8))
.map(decryptedStr -> GSON.fromJson(decryptedStr, CredentialIdentity.class));
}

/**
Expand All @@ -113,16 +143,17 @@ public Optional<CredentialIdentity> get(StructuredTableContext context, Credenti
* @throws IOException If any failure reading from storage occurs.
*/
public void write(StructuredTableContext context, CredentialIdentityId id,
CredentialIdentity identity) throws IOException {
CredentialIdentity identity) throws CipherException, IOException {
StructuredTable identityTable =
context.getTable(CredentialProviderStore.CREDENTIAL_IDENTITIES);
Collection<Field<?>> row = Arrays.asList(
Fields.stringField(CredentialProviderStore.NAMESPACE_FIELD,
id.getNamespace()),
Fields.stringField(CredentialProviderStore.IDENTITY_NAME_FIELD,
id.getName()),
Fields.stringField(CredentialProviderStore.IDENTITY_DATA_FIELD,
GSON.toJson(identity)),
Fields.bytesField(CredentialProviderStore.IDENTITY_DATA_FIELD,
dataStorageCipher.encrypt(GSON.toJson(identity).getBytes(StandardCharsets.UTF_8),
CREDENTIAL_IDENTITY_STORE_AD)),
Fields.stringField(CredentialProviderStore.IDENTITY_PROFILE_INDEX_FIELD,
toProfileIndex(identity.getProfileNamespace(), identity.getProfileName())));
identityTable.upsert(row);
Expand Down Expand Up @@ -159,4 +190,15 @@ private static Collection<CredentialIdentityId> identitiesFromRowIterator(
private static String toProfileIndex(String profileNamespace, String profileName) {
return String.format("%s:%s", profileNamespace, profileName);
}

private Optional<StructuredRow> readIdentity(StructuredTableContext context,
CredentialIdentityId id) throws IOException {
StructuredTable table = context.getTable(CredentialProviderStore.CREDENTIAL_IDENTITIES);
Collection<Field<?>> key = Arrays.asList(
Fields.stringField(CredentialProviderStore.NAMESPACE_FIELD,
id.getNamespace()),
Fields.stringField(CredentialProviderStore.IDENTITY_NAME_FIELD,
id.getName()));
return table.read(key);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,12 @@
import io.cdap.cdap.common.BadRequestException;
import io.cdap.cdap.common.ConflictException;
import io.cdap.cdap.common.NotFoundException;
import io.cdap.cdap.internal.credential.store.CredentialIdentityStore;
import io.cdap.cdap.internal.credential.store.CredentialProfileStore;
import io.cdap.cdap.proto.credential.CredentialProfile;
import io.cdap.cdap.proto.id.CredentialIdentityId;
import io.cdap.cdap.proto.id.CredentialProfileId;
import io.cdap.cdap.security.spi.credential.CredentialProvider;
import io.cdap.cdap.security.spi.credential.ProfileValidationException;
import io.cdap.cdap.security.spi.encryption.CipherException;
import io.cdap.cdap.spi.data.transaction.TransactionRunner;
import io.cdap.cdap.spi.data.transaction.TransactionRunners;
import java.io.IOException;
Expand Down Expand Up @@ -80,7 +79,11 @@ public Collection<CredentialProfileId> list(String namespace) throws IOException
*/
public Optional<CredentialProfile> get(CredentialProfileId id) throws IOException {
return TransactionRunners.run(transactionRunner, context -> {
return profileStore.get(context, id);
try {
return profileStore.get(context, id);
} catch (CipherException e) {
throw new IOException("Failed to decrypt profile", e);
}
}, IOException.class);
}

Expand All @@ -97,11 +100,15 @@ public void create(CredentialProfileId id, CredentialProfile profile)
throws AlreadyExistsException, BadRequestException, IOException {
validateProfile(profile);
TransactionRunners.run(transactionRunner, context -> {
if (profileStore.get(context, id).isPresent()) {
if (profileStore.exists(context, id)) {
throw new AlreadyExistsException(String.format("Credential profile '%s:%s' already exists",
id.getNamespace(), id.getName()));
}
profileStore.write(context, id, profile);
try {
profileStore.write(context, id, profile);
} catch (CipherException e) {
throw new IOException("Failed to encrypt profile", e);
}
}, AlreadyExistsException.class, IOException.class);
}

Expand All @@ -118,11 +125,15 @@ public void update(CredentialProfileId id, CredentialProfile profile)
throws BadRequestException, IOException, NotFoundException {
validateProfile(profile);
TransactionRunners.run(transactionRunner, context -> {
if (!profileStore.get(context, id).isPresent()) {
if (!profileStore.exists(context, id)) {
throw new NotFoundException(String.format("Credential profile '%s:%s' not found",
id.getNamespace(), id.getName()));
}
profileStore.write(context, id, profile);
try {
profileStore.write(context, id, profile);
} catch (CipherException e) {
throw new IOException("Failed to encrypt profile", e);
}
}, IOException.class, NotFoundException.class);
}

Expand All @@ -137,7 +148,7 @@ public void update(CredentialProfileId id, CredentialProfile profile)
public void delete(CredentialProfileId id)
throws ConflictException, IOException, NotFoundException {
TransactionRunners.run(transactionRunner, context -> {
if (!profileStore.get(context, id).isPresent()) {
if (!profileStore.exists(context, id)) {
throw new NotFoundException(String.format("Credential profile '%s:%s' not found",
id.getNamespace(), id.getName()));
}
Expand Down
Loading

0 comments on commit 61df5b6

Please sign in to comment.