From 7208ebc25e66c52ae9adc9ab579a7a0b7bc2e681 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Fri, 19 Dec 2025 13:54:17 +0200 Subject: [PATCH] Enabling using secret values during a deployment LMCROSSITXSADEPLOY-2301 --- .../src/main/java/module-info.java | 4 + .../multiapps/controller/core/Constants.java | 4 + .../multiapps/controller/core/Messages.java | 2 + .../core/helpers/MtaDescriptorMerger.java | 9 +- .../encryption/AESDecryptionException.java | 17 + .../encryption/AESEncryptionException.java | 17 + .../encryption/AesEncryptionUtil.java | 63 ++++ .../DynamicSecureSerialization.java | 76 +++++ .../SecureSerializationFactory.java | 18 ++ .../SecureSerializerConfiguration.java | 24 +- .../encryption/AesEncryptionUtilTest.java | 105 +++++++ .../src/main/java/module-info.java | 2 + .../controller/persistence/Messages.java | 6 + .../persistence/dto/SecretTokenDto.java | 100 ++++++ .../model/PersistenceMetadata.java | 9 + .../persistence/model/SecretToken.java | 28 ++ .../persistence/query/SecretTokenQuery.java | 19 ++ .../query/impl/SecretTokenQueryImpl.java | 100 ++++++ .../SqlSecretTokenQueryProvider.java | 115 +++++++ .../services/SecretTokenService.java | 111 +++++++ .../services/SecretTokenStorageException.java | 17 + ...ECRET-TOKEN-TABLE-ADDITION-persistence.xml | 42 +++ .../persistence/db/changelog/db-changelog.xml | 2 + .../src/main/java/module-info.java | 8 + .../controller/process/Constants.java | 6 + .../controller/process/Messages.java | 6 + .../client/LoggingCloudControllerClient.java | 42 ++- .../process/jobs/SecretTokenCleaner.java | 37 +++ .../metadata/BlueGreenDeployMetadata.java | 29 +- .../process/metadata/CtsDeployMetadata.java | 29 +- .../process/metadata/DeployMetadata.java | 29 +- .../process/metadata/RollbackMtaMetadata.java | 17 +- .../security/MissingSecretTokenException.java | 17 + .../process/security/SecretConfig.java | 45 +++ .../security/SecretTokenSerializer.java | 296 ++++++++++++++++++ .../SecretTokenSerializerJsonException.java | 17 + .../SecretTransformationStrategy.java | 9 + ...cretTransformationStrategyContextImpl.java | 33 ++ .../SecretTransformationStrategyImpl.java | 27 ++ ...mUserProvidedServiceEncryptionRelated.java | 17 + ...idedServiceEncryptionRelatedException.java | 17 + .../resolver/SecretTokenKeyContainer.java | 5 + .../resolver/SecretTokenKeyResolver.java | 9 + .../resolver/SecretTokenKeyResolverImpl.java | 72 +++++ .../store/SecretTokenRetrievalException.java | 17 + .../security/store/SecretTokenStore.java | 9 + .../store/SecretTokenStoreDeletion.java | 12 + .../store/SecretTokenStoreFactory.java | 25 ++ .../security/store/SecretTokenStoreImpl.java | 94 ++++++ .../store/SecretTokenStoreImplWithoutKey.java | 41 +++ .../store/SecretTokenStoringException.java | 17 + .../security/util/SecretTokenUtil.java | 42 +++ .../steps/BindServiceToApplicationStep.java | 3 +- .../BuildApplicationDeployModelStep.java | 12 +- .../steps/BuildCloudDeployModelStep.java | 9 +- .../steps/BuildCloudUndeployModelStep.java | 13 +- .../CalculateServiceKeyForWaitingStep.java | 8 +- .../steps/CollectSystemParametersStep.java | 9 +- .../process/steps/ComputeNextModulesStep.java | 10 +- .../CreateOrUpdateServiceBrokerStep.java | 9 +- ...eDiscontinuedConfigurationEntriesStep.java | 16 +- .../steps/DeleteSubscriptionsStep.java | 9 +- .../process/steps/DetectDeployedMtaStep.java | 22 +- .../steps/DetectMtaSchemaVersionStep.java | 1 - .../DetermineServiceBindingsToDeleteStep.java | 8 +- ...ServiceCreateUpdateServiceActionsStep.java | 22 +- ...mineServiceDeleteActionsToExecuteStep.java | 19 +- ...icesWithResolvedDynamicParametersStep.java | 14 +- .../process/steps/MergeDescriptorsStep.java | 236 +++++++++++++- .../steps/PollServiceOperationsExecution.java | 9 +- .../process/steps/ProcessContext.java | 12 +- .../process/steps/ProcessDescriptorStep.java | 8 +- .../ProcessMtaExtensionDescriptorsStep.java | 23 +- .../PublishConfigurationEntriesStep.java | 19 +- .../process/steps/SecureProcessContext.java | 84 +++++ .../steps/SecureProcessContextFactory.java | 22 ++ .../process/steps/SyncFlowableStep.java | 21 ++ .../process/steps/UpdateSubscribersStep.java | 22 +- .../process/steps/UploadAppStep.java | 28 +- .../process/util/ApplicationStager.java | 10 +- .../process/util/CloudPackagesGetter.java | 15 +- .../util/ExistingAppsToBackupCalculator.java | 12 +- .../util/OperationInFinalStateHandler.java | 15 + .../util/ServiceBindingParametersGetter.java | 9 +- .../process/variables/Variables.java | 13 + .../process/variables/WrappedVariable.java | 29 ++ .../controller/process/delete-services.bpmn | 1 + .../controller/process/deploy-app.bpmn | 2 + .../controller/process/undeploy-app.bpmn | 1 + .../process/jobs/SecretTokenCleanerTest.java | 46 +++ .../metadata/BlueGreenDeployMetadataTest.java | 1 + .../metadata/CtsDeployMetadataTest.java | 1 + .../process/metadata/DeployMetadataTest.java | 1 + .../metadata/RollbackMtaMetadataTest.java | 2 +- .../security/SecretTokenSerializerTest.java | 218 +++++++++++++ .../SecretTokenKeyResolverImplTest.java | 130 ++++++++ .../security/util/SecretTokenUtilTest.java | 76 +++++ .../DetectApplicationsToRenameStepTest.java | 12 +- .../steps/MergeDescriptorsStepTest.java | 4 +- ...iceBindingOrKeyOperationExecutionTest.java | 16 +- .../process/steps/SyncFlowableStepTest.java | 9 + .../steps/UploadAppStepGeneralTest.java | 6 +- .../process/util/ApplicationStagerTest.java | 13 +- .../process/util/CloudPackagesGetterTest.java | 12 +- .../OperationInFinalStateHandlerTest.java | 43 ++- 105 files changed, 3080 insertions(+), 199 deletions(-) create mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESDecryptionException.java create mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESEncryptionException.java create mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java create mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/DynamicSecureSerialization.java create mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializationFactory.java create mode 100644 multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtilTest.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/dto/SecretTokenDto.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/SecretToken.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/SecretTokenQuery.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/impl/SecretTokenQueryImpl.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/providers/SqlSecretTokenQueryProvider.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenService.java create mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenStorageException.java create mode 100644 multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleaner.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/MissingSecretTokenException.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretConfig.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializer.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerJsonException.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategy.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyImpl.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingCredentialsFromUserProvidedServiceEncryptionRelated.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyContainer.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolver.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenRetrievalException.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStore.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreDeletion.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreFactory.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImpl.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImplWithoutKey.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoringException.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtil.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContext.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContextFactory.java create mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/WrappedVariable.java create mode 100644 multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleanerTest.java create mode 100644 multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerTest.java create mode 100644 multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImplTest.java create mode 100644 multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtilTest.java diff --git a/multiapps-controller-core/src/main/java/module-info.java b/multiapps-controller-core/src/main/java/module-info.java index d23e127880..1686ae4acb 100644 --- a/multiapps-controller-core/src/main/java/module-info.java +++ b/multiapps-controller-core/src/main/java/module-info.java @@ -38,6 +38,7 @@ exports org.cloudfoundry.multiapps.controller.core.validators.parameters; exports org.cloudfoundry.multiapps.controller.core.validators.parameters.v2; exports org.cloudfoundry.multiapps.controller.core.validators.parameters.v3; + exports org.cloudfoundry.multiapps.controller.core.security.encryption; requires transitive jakarta.persistence; requires transitive org.cloudfoundry.multiapps.controller.client; @@ -80,5 +81,8 @@ requires static java.compiler; requires static org.immutables.value; requires spring.security.oauth2.client; + requires java.desktop; + requires io.netty.common; + requires org.bouncycastle.fips.core; } \ No newline at end of file diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Constants.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Constants.java index 218c216626..512a6432aa 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Constants.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Constants.java @@ -29,10 +29,14 @@ public class Constants { public static final String B3_TRACE_ID_HEADER = "X-B3-TraceId"; public static final String B3_SPAN_ID_HEADER = "X-B3-SpanId"; + public static final String CYPHER_TRANSFORMATION_NAME = "AES/GCM/NoPadding"; + public static final String ENCRYPTION_DECRYPTION_ALGORITHM_NAME = "AES"; public static final int TOKEN_SERVICE_DELETION_CORE_POOL_SIZE = 1; public static final int TOKEN_SERVICE_DELETION_MAXIMUM_POOL_SIZE = 3; public static final int TOKEN_SERVICE_DELETION_KEEP_ALIVE_THREAD_IN_SECONDS = 30; + public static final int INITIALISATION_VECTOR_LENGTH = 12; + public static final int GCM_AUTHENTICATION_TAG_LENGTH = 128; public static final String APP_FEATURE_SSH = "ssh"; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java index 9c6694d191..ce50df44f6 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java @@ -93,6 +93,8 @@ public final class Messages { public static final String BUILDPACKS_NOT_ALLOWED_WITH_DOCKER = "Buildpacks must not be provided when lifecycle is set to 'docker'."; public static final String EXTENSION_DESCRIPTORS_COULD_NOT_BE_PARSED_TO_VALID_YAML = "Extension descriptor(s) could not be parsed as a valid YAML file. These descriptors may fail future deployments once stricter validation is enforced. Please review and correct them now to avoid future issues. Use at your own risk"; public static final String UNSUPPORTED_FILE_FORMAT = "Unsupported file format! \"{0}\" detected"; + public static final String ENCRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED = "Encryption with AES256 by Bouncy Castle has failed!"; + public static final String DECRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED = "Decryption with AES256 by Bouncy Castle has failed!"; // Warning messages public static final String ENVIRONMENT_VARIABLE_IS_NOT_SET_USING_DEFAULT = "Environment variable \"{0}\" is not set. Using default \"{1}\"..."; diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/helpers/MtaDescriptorMerger.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/helpers/MtaDescriptorMerger.java index b24d569c36..cba0e431b0 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/helpers/MtaDescriptorMerger.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/helpers/MtaDescriptorMerger.java @@ -4,7 +4,8 @@ import org.cloudfoundry.multiapps.controller.core.Messages; import org.cloudfoundry.multiapps.controller.core.cf.CloudHandlerFactory; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.util.UserMessageLogger; import org.cloudfoundry.multiapps.mta.handlers.v2.DescriptorMerger; import org.cloudfoundry.multiapps.mta.handlers.v2.DescriptorValidator; @@ -28,11 +29,13 @@ public MtaDescriptorMerger(CloudHandlerFactory handlerFactory, Platform platform this.userMessageLogger = userMessageLogger; } - public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List extensionDescriptors) { + public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List extensionDescriptors, + List parameterNamesToBeCensored) { DescriptorValidator validator = handlerFactory.getDescriptorValidator(); validator.validateDeploymentDescriptor(deploymentDescriptor, platform); validator.validateExtensionDescriptors(extensionDescriptors, deploymentDescriptor); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parameterNamesToBeCensored); DescriptorMerger merger = handlerFactory.getDescriptorMerger(); // Merge the passed set of descriptors into one deployment descriptor. The deployment descriptor at the root of @@ -45,7 +48,7 @@ public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, Lis deploymentDescriptor = handlerFactory.getDescriptorParametersCompatibilityValidator(mergedDescriptor, userMessageLogger) .validate(); - logDebug(Messages.MERGED_DESCRIPTOR, SecureSerialization.toJson(deploymentDescriptor)); + logDebug(Messages.MERGED_DESCRIPTOR, dynamicSecureSerialization.toJson(deploymentDescriptor)); return deploymentDescriptor; } diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESDecryptionException.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESDecryptionException.java new file mode 100644 index 0000000000..575f05d219 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESDecryptionException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.core.security.encryption; + +public class AESDecryptionException extends RuntimeException { + + public AESDecryptionException(String message) { + super(message); + } + + public AESDecryptionException(String message, Throwable cause) { + super(message, cause); + } + + public AESDecryptionException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESEncryptionException.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESEncryptionException.java new file mode 100644 index 0000000000..2750ede2eb --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESEncryptionException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.core.security.encryption; + +public class AESEncryptionException extends RuntimeException { + + public AESEncryptionException(String message) { + super(message); + } + + public AESEncryptionException(String message, Throwable cause) { + super(message, cause); + } + + public AESEncryptionException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java new file mode 100644 index 0000000000..14ddfc1e98 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java @@ -0,0 +1,63 @@ +package org.cloudfoundry.multiapps.controller.core.security.encryption; + +import java.nio.charset.StandardCharsets; +import java.security.SecureRandom; +import javax.crypto.Cipher; +import javax.crypto.spec.GCMParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.multiapps.controller.core.Constants; +import org.cloudfoundry.multiapps.controller.core.Messages; + +public class AesEncryptionUtil { + + public static byte[] encrypt(String plainText, byte[] encryptionKey) { + try { + byte[] gcmInitialisationVector = new byte[Constants.INITIALISATION_VECTOR_LENGTH]; + new SecureRandom().nextBytes(gcmInitialisationVector); + + Cipher cipherObject = Cipher.getInstance(Constants.CYPHER_TRANSFORMATION_NAME, BouncyCastleFipsProvider.PROVIDER_NAME); + SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, Constants.ENCRYPTION_DECRYPTION_ALGORITHM_NAME); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(Constants.GCM_AUTHENTICATION_TAG_LENGTH, gcmInitialisationVector); + + cipherObject.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); + + byte[] cipherValue = cipherObject.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + + byte[] combinedCypherValueAndInitialisationVector = new byte[gcmInitialisationVector.length + cipherValue.length]; + + System.arraycopy(gcmInitialisationVector, 0, combinedCypherValueAndInitialisationVector, 0, gcmInitialisationVector.length); + System.arraycopy(cipherValue, 0, combinedCypherValueAndInitialisationVector, gcmInitialisationVector.length, + cipherValue.length); + + return combinedCypherValueAndInitialisationVector; + } catch (Exception e) { + throw new AESEncryptionException(Messages.ENCRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED + + e.getMessage(), e); + } + } + + public static String decrypt(byte[] encryptedValue, byte[] encryptionKey) { + try { + byte[] gcmInitialisationVector = new byte[Constants.INITIALISATION_VECTOR_LENGTH]; + System.arraycopy(encryptedValue, 0, gcmInitialisationVector, 0, + gcmInitialisationVector.length); + + byte[] cipherValue = new byte[encryptedValue.length - 12]; + System.arraycopy(encryptedValue, 12, cipherValue, 0, cipherValue.length); + + Cipher cipherObject = Cipher.getInstance(Constants.CYPHER_TRANSFORMATION_NAME, BouncyCastleFipsProvider.PROVIDER_NAME); + SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, Constants.ENCRYPTION_DECRYPTION_ALGORITHM_NAME); + GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(Constants.GCM_AUTHENTICATION_TAG_LENGTH, gcmInitialisationVector); + cipherObject.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); + + byte[] resultInBytes = cipherObject.doFinal(cipherValue); + return new String(resultInBytes, StandardCharsets.UTF_8); + } catch (Exception e) { + throw new AESDecryptionException(Messages.DECRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED + + e.getMessage(), e); + } + } + +} diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/DynamicSecureSerialization.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/DynamicSecureSerialization.java new file mode 100644 index 0000000000..d9bf929d01 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/DynamicSecureSerialization.java @@ -0,0 +1,76 @@ +package org.cloudfoundry.multiapps.controller.core.security.serialization; + +import org.cloudfoundry.multiapps.controller.core.security.serialization.model.DeploymentDescriptorSerializer; +import org.cloudfoundry.multiapps.controller.core.security.serialization.model.ModuleSerializer; +import org.cloudfoundry.multiapps.controller.core.security.serialization.model.ProvidedDependencySerializer; +import org.cloudfoundry.multiapps.controller.core.security.serialization.model.RequiredDependencySerializer; +import org.cloudfoundry.multiapps.controller.core.security.serialization.model.ResourceSerializer; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.Module; +import org.cloudfoundry.multiapps.mta.model.ProvidedDependency; +import org.cloudfoundry.multiapps.mta.model.RequiredDependency; +import org.cloudfoundry.multiapps.mta.model.Resource; +import org.cloudfoundry.multiapps.mta.model.VersionedEntity; + +public final class DynamicSecureSerialization { + + private final SecureSerializerConfiguration secureSerializerConfiguration; + + DynamicSecureSerialization(SecureSerializerConfiguration secureSerializerConfiguration) { + this.secureSerializerConfiguration = secureSerializerConfiguration; + } + + public String toJson(Object object) { + SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializer(object, secureSerializerConfiguration); + return secureJsonSerializer.serialize(object); + } + + private static SecureJsonSerializer createDynamicJsonSerializer(Object object, + SecureSerializerConfiguration secureSerializerConfiguration) { + SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializerForVersionedEntity(object, secureSerializerConfiguration); + if (secureJsonSerializer == null) { + return new SecureJsonSerializer(secureSerializerConfiguration); + } + + return secureJsonSerializer; + } + + private static SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(Object object, + SecureSerializerConfiguration secureSerializerConfiguration) { + if (object instanceof VersionedEntity) { + return createDynamicJsonSerializerForVersionedEntity((VersionedEntity) object, secureSerializerConfiguration); + } + + return null; + } + + private static SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(VersionedEntity versionedEntity, + SecureSerializerConfiguration secureSerializerConfiguration) { + if (versionedEntity.getMajorSchemaVersion() < 3) { + return null; + } + + if (versionedEntity instanceof DeploymentDescriptor) { + return new DeploymentDescriptorSerializer(secureSerializerConfiguration); + } + + if (versionedEntity instanceof Module) { + return new ModuleSerializer(secureSerializerConfiguration); + } + + if (versionedEntity instanceof ProvidedDependency) { + return new ProvidedDependencySerializer(secureSerializerConfiguration); + } + + if (versionedEntity instanceof RequiredDependency) { + return new RequiredDependencySerializer(secureSerializerConfiguration); + } + + if (versionedEntity instanceof Resource) { + return new ResourceSerializer(secureSerializerConfiguration); + } + + return null; + } + +} diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializationFactory.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializationFactory.java new file mode 100644 index 0000000000..841f098607 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializationFactory.java @@ -0,0 +1,18 @@ +package org.cloudfoundry.multiapps.controller.core.security.serialization; + +import java.util.Collection; + +public final class SecureSerializationFactory { + + private SecureSerializationFactory() { + + } + + public static DynamicSecureSerialization ofAdditionalValues(Collection additionalSensitiveElementNames) { + SecureSerializerConfiguration secureSerializerConfigurationWithAdditionalValues = new SecureSerializerConfiguration(); + + secureSerializerConfigurationWithAdditionalValues.setAdditionalSensitiveElementNames(additionalSensitiveElementNames); + return new DynamicSecureSerialization(secureSerializerConfigurationWithAdditionalValues); + } + +} diff --git a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializerConfiguration.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializerConfiguration.java index f4c0e97efb..ac1d197f4b 100644 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializerConfiguration.java +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializerConfiguration.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import org.apache.commons.lang3.StringUtils; @@ -17,8 +18,25 @@ public class SecureSerializerConfiguration { private Collection sensitiveElementNames = DEFAULT_SENSITIVE_NAMES; private Collection sensitiveElementPaths = Collections.emptyList(); + private Collection additionalSensitiveElementNames = Collections.emptyList(); + public Collection getSensitiveElementNames() { - return sensitiveElementNames; + if (additionalSensitiveElementNames == null || additionalSensitiveElementNames.isEmpty()) { + return sensitiveElementNames; + } + + List mergedSensitiveElementNames = new LinkedList<>(sensitiveElementNames); + + for (String currentAdditionalSensitiveElement : additionalSensitiveElementNames) { + boolean isExistentAlready = mergedSensitiveElementNames.stream() + .anyMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase( + currentAdditionalSensitiveElement)); + if (!isExistentAlready) { + mergedSensitiveElementNames.add(currentAdditionalSensitiveElement); + } + } + + return mergedSensitiveElementNames; } public Collection getSensitiveElementPaths() { @@ -33,6 +51,10 @@ public void setSensitiveElementNames(Collection sensitiveElementNames) { this.sensitiveElementNames = sensitiveElementNames; } + public void setAdditionalSensitiveElementNames(Collection additionalSensitiveElementNames) { + this.additionalSensitiveElementNames = additionalSensitiveElementNames; + } + public void setSensitiveElementPaths(Collection sensitiveElementPaths) { this.sensitiveElementPaths = sensitiveElementPaths; } diff --git a/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtilTest.java b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtilTest.java new file mode 100644 index 0000000000..dd6b49ea2b --- /dev/null +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtilTest.java @@ -0,0 +1,105 @@ +package org.cloudfoundry.multiapps.controller.core.security.encryption; + +import java.nio.charset.StandardCharsets; +import java.security.Security; +import java.util.Arrays; +import javax.crypto.Cipher; + +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class AesEncryptionUtilTest { + + private static final byte[] KEY_FOR_256_32_BYTES = "abcdefghijklmnopqrstuvwxyz123456".getBytes(StandardCharsets.UTF_8); + + @BeforeAll + static void addBouncyCastleProvider() throws Exception { + if (Security.getProvider(BouncyCastleFipsProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleFipsProvider()); + } + + Cipher.getInstance("AES/GCM/NoPadding", BouncyCastleFipsProvider.PROVIDER_NAME); + } + + @Test + void testEncryptDecryptFlowWhenShortString() { + String plainText = "hello"; + byte[] encrypted = AesEncryptionUtil.encrypt(plainText, KEY_FOR_256_32_BYTES); + assertNotNull(encrypted); + assertTrue(encrypted.length > 12, "encrypted text must include 12-byte IV (initialization vector, nonce) + GCM tag + cipher"); + + String decrypted = AesEncryptionUtil.decrypt(encrypted, KEY_FOR_256_32_BYTES); + assertEquals(plainText, decrypted); + } + + @Test + void testEncryptDecryptFlowWhenEmptyString() { + String emptyString = ""; + byte[] encrypted = AesEncryptionUtil.encrypt(emptyString, KEY_FOR_256_32_BYTES); + assertNotNull(encrypted); + String decrypted = AesEncryptionUtil.decrypt(encrypted, KEY_FOR_256_32_BYTES); + assertEquals(emptyString, decrypted); + } + + @Test + void testEncryptionThatInitializationVectorsDifferent() { + String plainText = "same text"; + byte[] firstEncrypted = AesEncryptionUtil.encrypt(plainText, KEY_FOR_256_32_BYTES); + byte[] secondEncrypted = AesEncryptionUtil.encrypt(plainText, KEY_FOR_256_32_BYTES); + + assertNotNull(firstEncrypted); + assertNotNull(secondEncrypted); + assertNotEquals(Arrays.toString(firstEncrypted), Arrays.toString(secondEncrypted)); + + byte[] firstInitializationVector = Arrays.copyOfRange(firstEncrypted, 0, 12); + byte[] secondInitializationVector = Arrays.copyOfRange(secondEncrypted, 0, 12); + assertFalse(Arrays.equals(firstInitializationVector, secondInitializationVector)); + } + + @Test + void testDecryptWhenEncryptedValueCorrupted() { + String plainText = "real message"; + byte[] encrypted = AesEncryptionUtil.encrypt(plainText, KEY_FOR_256_32_BYTES); + + byte[] tampered = Arrays.copyOf(encrypted, encrypted.length); + tampered[tampered.length - 1] ^= 0x01; + + assertThrows(RuntimeException.class, () -> AesEncryptionUtil.decrypt(tampered, KEY_FOR_256_32_BYTES)); + } + + @Test + void testEncryptWhenWrongEncryptionKetLength() { + byte[] wrongEncryptionKey = new byte[31]; + assertThrows(RuntimeException.class, () -> AesEncryptionUtil.encrypt("simple text", wrongEncryptionKey)); + } + + @Test + void testEncryptDecryptFlowWhenWrongEncryptionKey() { + String plainText = "top secret"; + byte[] encrypted = AesEncryptionUtil.encrypt(plainText, KEY_FOR_256_32_BYTES); + + byte[] differentValidKey = "0123456789abcdef0123456789ABCDEF".getBytes(StandardCharsets.UTF_8); + assertThrows(RuntimeException.class, () -> AesEncryptionUtil.decrypt(encrypted, differentValidKey)); + } + + @Test + void testEncryptWhenValueNullThrows() { + assertThrows(RuntimeException.class, () -> AesEncryptionUtil.encrypt(null, KEY_FOR_256_32_BYTES)); + } + + @Test + void testDecryptWhenEncryptedValueTooShort() { + byte[] tooShort = new byte[7]; + assertThrows(RuntimeException.class, () -> AesEncryptionUtil.decrypt(tooShort, KEY_FOR_256_32_BYTES)); + } + +} + diff --git a/multiapps-controller-persistence/src/main/java/module-info.java b/multiapps-controller-persistence/src/main/java/module-info.java index 875fb82e2d..42a7e377a3 100644 --- a/multiapps-controller-persistence/src/main/java/module-info.java +++ b/multiapps-controller-persistence/src/main/java/module-info.java @@ -59,4 +59,6 @@ requires static org.immutables.value; requires jakarta.xml.bind; requires org.bouncycastle.fips.pkix; + requires org.bouncycastle.fips.core; + requires liquibase.core; } diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/Messages.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/Messages.java index b3e79b26a8..173f189528 100644 --- a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/Messages.java +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/Messages.java @@ -45,7 +45,13 @@ public final class Messages { public static final String ASYNC_UPLOAD_JOB_ALREADY_EXISTS = "Async upload job entry with ID \"{0}\" already exists"; public static final String ERROR_GETTING_FILES_CREATED_AFTER_0_AND_BEFORE_1 = "Error getting files created after {0} and before {1]"; public static final String BACKUP_DESCRIPTOR_FOR_MTA_ID_0_AND_ID_1_ALREADY_EXIST = "Backup descriptor for mta id \"{0}\" and id \"{1}\" already exist"; + public static final String SECRET_TOKEN_FOR_VARIABLE_NAME_0_AND_ID_1_ALREADY_EXIST = "Secret token for variable name \"{0}\" and id \"{1}\" already exists"; + public static final String ERROR_INSERTING_SECRET_TOKEN_WITH_VARIABLE_NAME_0_FOR_PROCESS_WITH_ID_1_AND_ENCRYPTION_KEY_ID_2 = "Error inserting secret token with a variable name \"{0}\" for process with id \"{1}\" and encryption key id \"{2}\""; + public static final String ERROR_RETRIEVING_SECRET_TOKEN_WITH_ID_0_FOR_PROCESS_WITH_ID_1 = "Error retrieving secret token with id \"{0}\" for process with id \"{1}\""; + public static final String ERROR_DELETING_SECRET_TOKENS_FOR_PROCESS_WITH_ID_0 = "Error deleting secret tokens for process with id \"{0}\""; + public static final String ERROR_DELETING_SECRET_TOKENS_WITH_EXPIRATION_DATE_0 = "Error deleting secret tokens with an expiration date \"{0}\""; public static final String BACKUP_DESCRIPTOR_WITH_ID_NOT_EXIST = "Backup descriptor with ID \"{0}\" does not exist"; + public static final String SECRET_TOKEN_WITH_ID_NOT_EXIST = "Secret token with ID \"{0}\" does not exist"; public static final String DATABASE_HEALTH_CHECK_FAILED = "Database health check failed"; // ERROR log messages: diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/dto/SecretTokenDto.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/dto/SecretTokenDto.java new file mode 100644 index 0000000000..8df580397f --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/dto/SecretTokenDto.java @@ -0,0 +1,100 @@ +package org.cloudfoundry.multiapps.controller.persistence.dto; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import jakarta.persistence.SequenceGenerator; +import jakarta.persistence.Table; +import org.cloudfoundry.multiapps.controller.persistence.model.PersistenceMetadata; + +@Entity +@Table(name = PersistenceMetadata.TableNames.SECRET_TOKEN) +@SequenceGenerator(name = PersistenceMetadata.SequenceNames.SECRET_TOKEN_SEQUENCE, sequenceName = PersistenceMetadata.SequenceNames.SECRET_TOKEN_SEQUENCE, allocationSize = 1) +public class SecretTokenDto implements DtoWithPrimaryKey { + + public static class AttributeNames { + private AttributeNames() { + } + + public static final String ID = "id"; + public static final String PROCESS_INSTANCE_ID = "process_instance_id"; + public static final String VARIABLE_NAME = "variable_name"; + public static final String CONTENT = "content"; + public static final String TIMESTAMP = "timestamp"; + public static final String KEY_ID = "key_id"; + } + + @Id + @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = PersistenceMetadata.SequenceNames.SECRET_TOKEN_SEQUENCE) + @Column(name = PersistenceMetadata.TableColumnNames.SECRET_TOKEN_ID) + private long id; + + @Column(name = PersistenceMetadata.TableColumnNames.SECRET_TOKEN_PROCESS_INSTANCE_ID, nullable = false) + private String processInstanceId; + + @Column(name = PersistenceMetadata.TableColumnNames.SECRET_TOKEN_VARIABLE_NAME, nullable = false) + private String variableName; + + @Column(name = PersistenceMetadata.TableColumnNames.SECRET_TOKEN_CONTENT, nullable = false) + @Lob + private byte[] content; + + @Column(name = PersistenceMetadata.TableColumnNames.SECRET_TOKEN_TIMESTAMP, nullable = false) + private LocalDateTime timestamp; + + @Column(name = PersistenceMetadata.TableColumnNames.SECRET_TOKEN_KEY_ID, nullable = false) + private String keyId; + + protected SecretTokenDto() { + //Required by JPA + } + + public SecretTokenDto(long id, String processInstanceId, String variableName, byte[] content, LocalDateTime timestamp, String keyId) { + this.id = id; + this.processInstanceId = processInstanceId; + this.variableName = variableName; + this.content = content; + this.timestamp = timestamp; + this.keyId = keyId; + } + + @Override + public Long getPrimaryKey() { + return this.id; + } + + @Override + public void setPrimaryKey(Long id) { + this.id = id; + } + + public long getId() { + return this.id; + } + + public String getProcessInstanceId() { + return this.processInstanceId; + } + + public String getVariableName() { + return this.variableName; + } + + public byte[] getContent() { + return this.content; + } + + public LocalDateTime getTimestamp() { + return this.timestamp; + } + + public String getKeyId() { + return this.keyId; + } + +} diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/PersistenceMetadata.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/PersistenceMetadata.java index 7862074462..541fa06282 100644 --- a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/PersistenceMetadata.java +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/PersistenceMetadata.java @@ -20,6 +20,7 @@ private TableNames() { public static final String LOCK_OWNERS_TABLE = "lock_owners"; public static final String ASYNC_UPLOAD_JOB_TABLE = "async_upload_job"; public static final String BACKUP_DESCRIPTOR_TABLE = "backup_descriptor"; + public static final String SECRET_TOKEN = "secret_token"; } @@ -36,6 +37,7 @@ private SequenceNames() { public static final String ACCESS_TOKEN_SEQUENCE = "access_token_sequence"; public static final String LOCK_OWNERS_SEQUENCE = "lock_owners_sequence"; public static final String BACKUP_DESCRIPTOR_SEQUENCE = "backup_descriptor_sequence"; + public static final String SECRET_TOKEN_SEQUENCE = "secret_token_sequence"; } @@ -113,6 +115,13 @@ private TableColumnNames() { public static final String BACKUP_DESCRIPTOR_SPACE_ID = "space_id"; public static final String BACKUP_DESCRIPTOR_NAMESPACE = "namespace"; public static final String BACKUP_DESCRIPTOR_TIMESTAMP = "timestamp"; + + public static final String SECRET_TOKEN_ID = "id"; + public static final String SECRET_TOKEN_PROCESS_INSTANCE_ID = "process_instance_id"; + public static final String SECRET_TOKEN_VARIABLE_NAME = "variable_name"; + public static final String SECRET_TOKEN_CONTENT = "content"; + public static final String SECRET_TOKEN_KEY_ID = "key_id"; + public static final String SECRET_TOKEN_TIMESTAMP = "timestamp"; } } diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/SecretToken.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/SecretToken.java new file mode 100644 index 0000000000..0782862caf --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/SecretToken.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.persistence.model; + +import java.time.LocalDateTime; + +import org.immutables.value.Value; + +@Value.Immutable +public interface SecretToken { + + @Value.Default + default long getId() { + return 0L; + } + + String getProcessInstanceId(); + + String getKeyId(); + + String getVariableName(); + + byte[] getContent(); + + @Value.Default + default LocalDateTime getTimestamp() { + return LocalDateTime.now(); + } + +} diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/SecretTokenQuery.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/SecretTokenQuery.java new file mode 100644 index 0000000000..6a91ea4d8a --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/SecretTokenQuery.java @@ -0,0 +1,19 @@ +package org.cloudfoundry.multiapps.controller.persistence.query; + +import java.time.LocalDateTime; + +import org.cloudfoundry.multiapps.controller.persistence.model.SecretToken; + +public interface SecretTokenQuery extends Query { + + SecretTokenQuery id(Long id); + + SecretTokenQuery processInstanceId(String processInstanceId); + + SecretTokenQuery variableName(String variableName); + + SecretTokenQuery olderThan(LocalDateTime time); + + SecretTokenQuery keyId(String keyId); + +} diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/impl/SecretTokenQueryImpl.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/impl/SecretTokenQueryImpl.java new file mode 100644 index 0000000000..4884db5101 --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/impl/SecretTokenQueryImpl.java @@ -0,0 +1,100 @@ +package org.cloudfoundry.multiapps.controller.persistence.query.impl; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; + +import jakarta.persistence.EntityManager; +import jakarta.persistence.NoResultException; +import jakarta.persistence.NonUniqueResultException; +import org.cloudfoundry.multiapps.controller.persistence.dto.SecretTokenDto; +import org.cloudfoundry.multiapps.controller.persistence.dto.SecretTokenDto.AttributeNames; +import org.cloudfoundry.multiapps.controller.persistence.model.SecretToken; +import org.cloudfoundry.multiapps.controller.persistence.query.SecretTokenQuery; +import org.cloudfoundry.multiapps.controller.persistence.query.criteria.ImmutableQueryAttributeRestriction; +import org.cloudfoundry.multiapps.controller.persistence.query.criteria.QueryCriteria; +import org.cloudfoundry.multiapps.controller.persistence.services.SecretTokenService.SecretTokenMapper; + +public class SecretTokenQueryImpl extends AbstractQueryImpl implements SecretTokenQuery { + + private final QueryCriteria queryCriteria = new QueryCriteria(); + private final SecretTokenMapper secretTokenMapper; + + public SecretTokenQueryImpl(EntityManager entityManager, SecretTokenMapper secretTokenMapper) { + super(entityManager); + this.secretTokenMapper = secretTokenMapper; + } + + @Override + public SecretTokenQuery id(Long id) { + queryCriteria.addRestriction(ImmutableQueryAttributeRestriction.builder() + .attribute(AttributeNames.ID) + .condition(getCriteriaBuilder()::equal) + .value(id) + .build()); + return this; + } + + @Override + public SecretTokenQuery processInstanceId(String processInstanceId) { + queryCriteria.addRestriction(ImmutableQueryAttributeRestriction.builder() + .attribute(AttributeNames.PROCESS_INSTANCE_ID) + .condition(getCriteriaBuilder()::equal) + .value(processInstanceId) + .build()); + return this; + } + + @Override + public SecretTokenQuery variableName(String variableName) { + queryCriteria.addRestriction(ImmutableQueryAttributeRestriction.builder() + .attribute(AttributeNames.VARIABLE_NAME) + .condition(getCriteriaBuilder()::equal) + .value(variableName) + .build()); + return this; + } + + @Override + public SecretTokenQuery olderThan(LocalDateTime time) { + queryCriteria.addRestriction(ImmutableQueryAttributeRestriction. builder() + .attribute(AttributeNames.TIMESTAMP) + .condition(getCriteriaBuilder()::lessThan) + .value(time) + .build()); + return this; + } + + @Override + public SecretTokenQuery keyId(String keyId) { + queryCriteria.addRestriction(ImmutableQueryAttributeRestriction.builder() + .attribute(AttributeNames.KEY_ID) + .condition(getCriteriaBuilder()::equal) + .value(keyId) + .build()); + return this; + } + + @Override + public SecretToken singleResult() throws NoResultException, NonUniqueResultException { + SecretTokenDto secretTokenDto = executeInTransaction( + entityManager -> createQuery(entityManager, queryCriteria, SecretTokenDto.class).getSingleResult()); + return secretTokenMapper.fromDto(secretTokenDto); + } + + @Override + public List list() { + List secretTokenDtos = executeInTransaction( + entityManager -> createQuery(entityManager, queryCriteria, SecretTokenDto.class).getResultList()); + + return secretTokenDtos.stream() + .map(secretTokenMapper::fromDto) + .collect(Collectors.toList()); + } + + @Override + public int delete() { + return executeInTransaction(entityManager -> createDeleteQuery(entityManager, queryCriteria, SecretTokenDto.class).executeUpdate()); + } + +} diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/providers/SqlSecretTokenQueryProvider.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/providers/SqlSecretTokenQueryProvider.java new file mode 100644 index 0000000000..e9a1b3e727 --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/providers/SqlSecretTokenQueryProvider.java @@ -0,0 +1,115 @@ +package org.cloudfoundry.multiapps.controller.persistence.query.providers; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Timestamp; +import java.time.LocalDateTime; + +import org.cloudfoundry.multiapps.controller.persistence.model.PersistenceMetadata; +import org.cloudfoundry.multiapps.controller.persistence.query.SqlQuery; +import org.cloudfoundry.multiapps.controller.persistence.util.JdbcUtil; + +public class SqlSecretTokenQueryProvider { + + private final String tableName; + + private final String secretTokenSequence = PersistenceMetadata.SequenceNames.SECRET_TOKEN_SEQUENCE; + + private static final String INSERT_SECRET_TOKEN = "INSERT INTO %s (id, process_instance_id, variable_name, content, key_id, timestamp) VALUES (nextval('%s'), ?, ?, ?, ?, NOW()) RETURNING id"; + private static final String RETRIEVE_SECRET_TOKEN = "SELECT content FROM %s WHERE id = ? AND process_instance_id = ?"; + private static final String DELETE_SECRET_TOKEN = "DELETE FROM %s WHERE process_instance_id = ?"; + private static final String DELETE_OLDER_THAN = "DELETE FROM %s t WHERE t.timestamp < ?"; + + public SqlSecretTokenQueryProvider(String tableName) { + this.tableName = tableName; + } + + public SqlQuery insertSecretToken(String processInstanceId, String variableName, byte[] encryptedBase64, String keyId) { + return (Connection connection) -> { + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(getInsertSecretTokenQuery()); + preparedStatement.setString(1, processInstanceId); + preparedStatement.setString(2, variableName); + preparedStatement.setBytes(3, encryptedBase64); + preparedStatement.setString(4, keyId); + + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + return resultSet.getLong(1); + } + throw new SQLException("INSERT secret_token did not return an id"); + } + } finally { + JdbcUtil.closeQuietly(preparedStatement); + } + + }; + } + + public SqlQuery getSecretToken(String processInstanceId, long id) { + return (Connection connection) -> { + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(getRetrieveSecretTokenQuery()); + preparedStatement.setLong(1, id); + preparedStatement.setString(2, processInstanceId); + + try (ResultSet resultSet = preparedStatement.executeQuery()) { + if (resultSet.next()) { + byte[] resultValue = resultSet.getBytes(1); + return resultValue; + } + return null; + } + } finally { + JdbcUtil.closeQuietly(preparedStatement); + } + }; + } + + public SqlQuery deleteForProcessInstance(String processInstanceId) { + return (Connection connection) -> { + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(getDeletionSecretTokenQuery()); + preparedStatement.setString(1, processInstanceId); + return preparedStatement.executeUpdate(); + } finally { + JdbcUtil.closeQuietly(preparedStatement); + } + }; + } + + public SqlQuery deleteOlderThan(LocalDateTime expirationTime) { + return (Connection connection) -> { + PreparedStatement preparedStatement = null; + try { + preparedStatement = connection.prepareStatement(getDeleteSecretTokenOlderThanQuery()); + preparedStatement.setTimestamp(1, Timestamp.valueOf(expirationTime)); + return preparedStatement.executeUpdate(); + } finally { + JdbcUtil.closeQuietly(preparedStatement); + } + }; + } + + private String getInsertSecretTokenQuery() { + return String.format(INSERT_SECRET_TOKEN, tableName, secretTokenSequence); + } + + private String getRetrieveSecretTokenQuery() { + return String.format(RETRIEVE_SECRET_TOKEN, tableName); + } + + private String getDeletionSecretTokenQuery() { + return String.format(DELETE_SECRET_TOKEN, tableName); + } + + private String getDeleteSecretTokenOlderThanQuery() { + return String.format(DELETE_OLDER_THAN, tableName); + } + +} diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenService.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenService.java new file mode 100644 index 0000000000..58808bdaff --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenService.java @@ -0,0 +1,111 @@ +package org.cloudfoundry.multiapps.controller.persistence.services; + +import java.sql.SQLException; +import java.time.LocalDateTime; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.persistence.EntityManagerFactory; +import org.cloudfoundry.multiapps.common.ConflictException; +import org.cloudfoundry.multiapps.common.NotFoundException; +import org.cloudfoundry.multiapps.controller.persistence.DataSourceWithDialect; +import org.cloudfoundry.multiapps.controller.persistence.Messages; +import org.cloudfoundry.multiapps.controller.persistence.dto.SecretTokenDto; +import org.cloudfoundry.multiapps.controller.persistence.model.ImmutableSecretToken; +import org.cloudfoundry.multiapps.controller.persistence.model.SecretToken; +import org.cloudfoundry.multiapps.controller.persistence.query.SecretTokenQuery; +import org.cloudfoundry.multiapps.controller.persistence.query.impl.SecretTokenQueryImpl; +import org.cloudfoundry.multiapps.controller.persistence.query.providers.SqlSecretTokenQueryProvider; +import org.cloudfoundry.multiapps.controller.persistence.util.SqlQueryExecutor; + +@Named +public class SecretTokenService extends PersistenceService { + + private static final String TABLE_NAME = "secret_token"; + + @Inject + private SecretTokenMapper secretTokenMapper; + + private SqlQueryExecutor sqlQueryExecutor; + + private SqlSecretTokenQueryProvider sqlSecretTokenQueryProvider; + + public SecretTokenService(EntityManagerFactory entityManagerFactory, DataSourceWithDialect dataSourceWithDialect) { + super(entityManagerFactory); + this.sqlQueryExecutor = new SqlQueryExecutor(dataSourceWithDialect.getDataSource()); + this.sqlSecretTokenQueryProvider = new SqlSecretTokenQueryProvider(TABLE_NAME); + } + + public long putSecretToken(String processInstanceId, String variableName, byte[] content, String keyId) throws SQLException { + return sqlQueryExecutor.execute(sqlSecretTokenQueryProvider.insertSecretToken(processInstanceId, variableName, content, keyId)); + } + + public byte[] getSecretToken(String processInstanceId, long id) throws SQLException { + return sqlQueryExecutor.execute(sqlSecretTokenQueryProvider.getSecretToken(processInstanceId, id)); + } + + public int deleteForProcess(String processInstanceId) throws SQLException { + return sqlQueryExecutor.execute(sqlSecretTokenQueryProvider.deleteForProcessInstance(processInstanceId)); + } + + public int deleteOlderThan(LocalDateTime expirationTime) throws SQLException { + return sqlQueryExecutor.execute(sqlSecretTokenQueryProvider.deleteOlderThan(expirationTime)); + } + + public SecretTokenQuery createQuery() { + return new SecretTokenQueryImpl(createEntityManager(), secretTokenMapper); + } + + @Override + protected PersistenceObjectMapper getPersistenceObjectMapper() { + return secretTokenMapper; + } + + @Override + protected void onEntityConflict(SecretTokenDto secretTokenDto, Throwable t) { + throw new ConflictException(t, Messages.SECRET_TOKEN_FOR_VARIABLE_NAME_0_AND_ID_1_ALREADY_EXIST, secretTokenDto.getVariableName(), + secretTokenDto.getPrimaryKey()); + } + + @Override + protected void onEntityNotFound(Long id) { + throw new NotFoundException(Messages.SECRET_TOKEN_WITH_ID_NOT_EXIST, id); + } + + @Named + public static class SecretTokenMapper implements PersistenceObjectMapper { + + @Override + public SecretToken fromDto(SecretTokenDto dto) { + return ImmutableSecretToken.builder() + .id(dto.getPrimaryKey()) + .processInstanceId(dto.getProcessInstanceId()) + .variableName(dto.getVariableName()) + .content( + dto.getContent()) + .timestamp(dto.getTimestamp()) + .keyId(dto.getKeyId()) + .build(); + } + + @Override + public SecretTokenDto toDto(SecretToken secretToken) { + long id = secretToken.getId(); + String processInstanceId = secretToken.getProcessInstanceId(); + String variableName = secretToken.getVariableName(); + byte[] content = secretToken.getContent(); + LocalDateTime timestamp = secretToken.getTimestamp(); + String keyId = secretToken.getKeyId(); + return new SecretTokenDto(id, processInstanceId, variableName, content, timestamp, keyId); + } + } + + public SqlQueryExecutor getSqlQueryExecutor() { + return sqlQueryExecutor; + } + + public SqlSecretTokenQueryProvider getSqlSecretTokenQueryProvider() { + return sqlSecretTokenQueryProvider; + } + +} diff --git a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenStorageException.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenStorageException.java new file mode 100644 index 0000000000..32525d9306 --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenStorageException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.persistence.services; + +public class SecretTokenStorageException extends Exception { + + public SecretTokenStorageException(String message) { + super(message); + } + + public SecretTokenStorageException(Throwable cause) { + super(cause); + } + + public SecretTokenStorageException(String message, Throwable cause) { + super(message, cause); + } + +} diff --git a/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml b/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml new file mode 100644 index 0000000000..60fbec6ff8 --- /dev/null +++ b/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog.xml b/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog.xml index a5da6591b7..a3811e846e 100644 --- a/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog.xml +++ b/multiapps-controller-persistence/src/main/resources/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog.xml @@ -40,4 +40,6 @@ + diff --git a/multiapps-controller-process/src/main/java/module-info.java b/multiapps-controller-process/src/main/java/module-info.java index 231bedb9e5..cf3854be88 100644 --- a/multiapps-controller-process/src/main/java/module-info.java +++ b/multiapps-controller-process/src/main/java/module-info.java @@ -14,6 +14,10 @@ exports org.cloudfoundry.multiapps.controller.process.util; exports org.cloudfoundry.multiapps.controller.process.variables; exports org.cloudfoundry.multiapps.controller.process.stream; + exports org.cloudfoundry.multiapps.controller.process.security; + exports org.cloudfoundry.multiapps.controller.process.security.util; + exports org.cloudfoundry.multiapps.controller.process.security.resolver; + exports org.cloudfoundry.multiapps.controller.process.security.store; requires transitive flowable.engine; requires transitive org.cloudfoundry.multiapps.controller.api; @@ -56,5 +60,9 @@ requires static java.compiler; requires static org.immutables.value; + requires jakarta.annotation; + requires org.bouncycastle.fips.core; + requires com.google.common; + requires org.checkerframework.checker.qual; } \ No newline at end of file diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java index 7834e487be..762d25e6d6 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Constants.java @@ -26,6 +26,12 @@ public class Constants { public static final String MTA_BACKUP_NAMESPACE = "mta-backup"; public static final String MTA_FOR_DELETION_PREFIX = "to-be-deleted"; + public static final String USER_PROVIDED_SERVICE_PREFIX_NAME_ENCRYPTION_DECRYPTION = "__mta-secure-"; + public static final String ENCRYPTION_KEY = "encryptionKey"; + public static final String KEY_ID = "keyId"; + public static final String PLACEHOLDER_PREFIX = "${"; + public static final String PLACEHOLDER_POSTFIX = "}"; + public static final Long UNSET_LAST_LOG_TIMESTAMP_MS = 0L; public static final int LOG_STALLED_TASK_MINUTE_INTERVAL = 5; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java index a345de783c..50b226b16e 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/Messages.java @@ -82,6 +82,10 @@ public class Messages { public static final String SERVICE_INSTANCE_0_PLAN_UPDATE_FAILED_ERROR_1 = "Service instance: \"{0}\" plan update failed, error: \"{1}\""; public static final String SERVICE_INSTANCE_0_PARAMETERS_UPDATE_FAILED_ERROR_1 = "Service instance: \"{0}\" parameters update failed, error: \"{1}\""; public static final String SERVICE_INSTANCE_0_TAGS_UPDATE_FAILED_ERROR_1 = "Service instance: \"{0}\" tags update failed, error: \"{1}\""; + public static final String COULD_NOT_RETRIEVE_USER_PROVIDED_SERVICE_INSTANCE_ENCRYPTION_RELATED = "Could not retrieve service instance (user provided service instance for encryption/decryption related)!"; + public static final String COULD_NOT_RETRIEVE_CREDENTIALS_FROM_USER_PROVIDED_SERVICE_INSTANCE_ENCRYPTION_RELATED = "Could not retrieve credentials from service instance (user provided service instance for encryption/decryption related)!"; + public static final String JSON_TRANSFORMATION_FAILED_FOR_VARIABLE_0 = "Secret token JSON transformation failed for variable \"{0}\":"; + public static final String SECRET_VALUE_NOT_FOUND_FOR_TOKEN_0_PID_1_VARIABLE_2 = "Secret value not found for token \"{0}\" (process instance id=\"{1}\", variable=\"{2}\")"; // Audit log messages @@ -297,6 +301,7 @@ public class Messages { public static final String DELETED_PROGRESS_MESSAGES_0 = "Deleted progress messages: {0}"; public static final String DELETED_HISTORIC_OPERATION_EVENTS_0 = "Deleted historic operation events: {0}"; public static final String REMOVED_TOKENS_0 = "Removed tokens: {0}"; + public static final String REMOVED_SECRET_TOKENS_0 = "Removed secret tokens: {0}"; public static final String DELETED_DATA_FOR_NON_EXISTING_USERS = "Deleted data for no-longer existing users."; public static final String CREATING_APP_FROM_DOCKER_IMAGE = "Creating app \"{0}\" from Docker image \"{1}\"..."; public static final String CREATE_SUPPORT_TICKET_GENERIC_MESSAGE = "If the problem persists, please create a support ticket."; @@ -558,6 +563,7 @@ public class Messages { public static final String DELETING_PROCESS_LOGS_MODIFIED_BEFORE_0 = "Deleting process logs modified before \"{0}\"..."; public static final String DELETING_PROGRESS_MESSAGES_STORED_BEFORE_0 = "Deleting progress messages stored before \"{0}\"..."; public static final String REMOVING_EXPIRED_TOKENS_FROM_TOKEN_STORE = "Removing expired tokens from the token store..."; + public static final String REMOVING_EXPIRED_SECRET_TOKENS = "Removing expired secret tokens..."; public static final String DELETING_HISTORIC_OPERATION_EVENTS_STORED_BEFORE_0 = "Deleting historic operation events stored before \"{0}\"..."; public static final String DELETING_DATA_FOR_NON_EXISTING_USERS = "Deleting data for no-longer existing users..."; public static final String REGISTERED_CLEANERS_IN_CLEAN_UP_JOB_0 = "Registered cleaners in clean-up job: {0}"; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java index fe471d9a88..a4376ac360 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/client/LoggingCloudControllerClient.java @@ -37,7 +37,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.Upload; import org.cloudfoundry.multiapps.controller.client.facade.domain.UserRole; import org.cloudfoundry.multiapps.controller.client.facade.dto.ApplicationToCreateDto; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.cloudfoundry.multiapps.controller.core.util.UriUtil; import org.cloudfoundry.multiapps.controller.core.util.UserMessageLogger; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -46,10 +46,13 @@ public class LoggingCloudControllerClient implements CloudControllerClient { private final CloudControllerClient delegate; private final UserMessageLogger logger; + private final DynamicSecureSerialization dynamicSecureSerialization; - public LoggingCloudControllerClient(CloudControllerClient delegate, UserMessageLogger logger) { + public LoggingCloudControllerClient(CloudControllerClient delegate, UserMessageLogger logger, + DynamicSecureSerialization dynamicSecureSerialization) { this.delegate = delegate; this.logger = logger; + this.dynamicSecureSerialization = dynamicSecureSerialization; } @Override @@ -80,7 +83,8 @@ public Optional bindServiceInstance(String bindingName, String applicati public Optional bindServiceInstance(String bindingName, String applicationName, String serviceInstanceName, Map parameters, ApplicationServicesUpdateCallback updateServicesCallback) { logger.debug(Messages.BINDING_SERVICE_INSTANCE_0_TO_APPLICATION_1_WITH_PARAMETERS_2, serviceInstanceName, applicationName, - SecureSerialization.toJson(parameters)); + dynamicSecureSerialization.toJson( + parameters)); return delegate.bindServiceInstance(bindingName, applicationName, serviceInstanceName, parameters, updateServicesCallback); } @@ -89,46 +93,47 @@ public void createApplication(ApplicationToCreateDto applicationToCreateDto) { logger.debug(Messages.CREATING_APPLICATION_0_WITH_DISK_1_MEMORY_2_URIS_3_AND_STAGING_4, applicationToCreateDto.getName(), applicationToCreateDto.getDiskQuotaInMb(), applicationToCreateDto.getMemoryInMb(), UriUtil.prettyPrintRoutes(applicationToCreateDto.getRoutes()), - SecureSerialization.toJson(applicationToCreateDto.getStaging())); + dynamicSecureSerialization.toJson(applicationToCreateDto.getStaging())); delegate.createApplication(applicationToCreateDto); } @Override public void createServiceInstance(CloudServiceInstance serviceInstance) { - logger.debug(Messages.CREATING_SERVICE_INSTANCE_0, SecureSerialization.toJson(serviceInstance)); + logger.debug(Messages.CREATING_SERVICE_INSTANCE_0, dynamicSecureSerialization.toJson(serviceInstance)); delegate.createServiceInstance(serviceInstance); } @Override public String createServiceBroker(CloudServiceBroker serviceBroker) { - logger.debug(Messages.CREATING_SERVICE_BROKER_0, SecureSerialization.toJson(serviceBroker)); + logger.debug(Messages.CREATING_SERVICE_BROKER_0, dynamicSecureSerialization.toJson(serviceBroker)); return delegate.createServiceBroker(serviceBroker); } @Override public CloudServiceKey createAndFetchServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { logger.debug(Messages.CREATING_SERVICE_KEY_0_FOR_SERVICE_INSTANCE_1_WITH_PARAMETERS_2, keyModel.getName(), serviceInstanceName, - SecureSerialization.toJson(keyModel.getCredentials())); + dynamicSecureSerialization.toJson(keyModel.getCredentials())); return delegate.createAndFetchServiceKey(keyModel, serviceInstanceName); } @Override public Optional createServiceKey(CloudServiceKey keyModel, String serviceInstanceName) { logger.debug(Messages.CREATING_SERVICE_KEY_0_FOR_SERVICE_INSTANCE_1_WITH_PARAMETERS_2, keyModel.getName(), serviceInstanceName, - SecureSerialization.toJson(keyModel.getCredentials())); + dynamicSecureSerialization.toJson(keyModel.getCredentials())); return delegate.createServiceKey(keyModel, serviceInstanceName); } @Override public Optional createServiceKey(String serviceInstanceName, String serviceKeyName, Map parameters) { logger.debug(Messages.CREATING_SERVICE_KEY_0_FOR_SERVICE_INSTANCE_1_WITH_PARAMETERS_2, serviceKeyName, serviceInstanceName, - SecureSerialization.toJson(parameters)); + dynamicSecureSerialization.toJson(parameters)); return delegate.createServiceKey(serviceInstanceName, serviceKeyName, parameters); } @Override public void createUserProvidedServiceInstance(CloudServiceInstance serviceInstance) { - logger.debug(Messages.CREATING_USER_PROVIDED_SERVICE_INSTANCE_0, SecureSerialization.toJson(serviceInstance)); + logger.debug(Messages.CREATING_USER_PROVIDED_SERVICE_INSTANCE_0, + dynamicSecureSerialization.toJson(serviceInstance)); delegate.createUserProvidedServiceInstance(serviceInstance); } @@ -543,7 +548,8 @@ public void updateApplicationMemory(String applicationName, int memory) { @Override public void updateApplicationStaging(String applicationName, Staging staging) { - logger.debug(Messages.UPDATING_STAGING_OF_APPLICATION_0_TO_1, applicationName, SecureSerialization.toJson(staging)); + logger.debug(Messages.UPDATING_STAGING_OF_APPLICATION_0_TO_1, applicationName, + dynamicSecureSerialization.toJson(staging)); delegate.updateApplicationStaging(applicationName, staging); } @@ -555,7 +561,7 @@ public void updateApplicationRoutes(String applicationName, Set rout @Override public String updateServiceBroker(CloudServiceBroker serviceBroker) { - logger.debug(Messages.UPDATING_SERVICE_BROKER_TO_0, SecureSerialization.toJson(serviceBroker)); + logger.debug(Messages.UPDATING_SERVICE_BROKER_TO_0, dynamicSecureSerialization.toJson(serviceBroker)); return delegate.updateServiceBroker(serviceBroker); } @@ -616,7 +622,8 @@ public List getTasks(String applicationName) { @Override public CloudTask runTask(String applicationName, CloudTask task) { - logger.debug(Messages.RUNNING_TASK_1_ON_APPLICATION_0, applicationName, SecureSerialization.toJson(task)); + logger.debug(Messages.RUNNING_TASK_1_ON_APPLICATION_0, applicationName, + dynamicSecureSerialization.toJson(task)); return delegate.runTask(applicationName, task); } @@ -682,19 +689,22 @@ public List getServiceInstancesWithoutAuxiliaryContentByMe @Override public void updateApplicationMetadata(UUID guid, Metadata metadata) { - logger.debug(Messages.UPDATING_METADATA_OF_APPLICATION_0_TO_1, guid, SecureSerialization.toJson(metadata)); + logger.debug(Messages.UPDATING_METADATA_OF_APPLICATION_0_TO_1, guid, + dynamicSecureSerialization.toJson(metadata)); delegate.updateApplicationMetadata(guid, metadata); } @Override public void updateServiceInstanceMetadata(UUID guid, Metadata metadata) { - logger.debug(Messages.UPDATING_METADATA_OF_SERVICE_INSTANCE_0_TO_1, guid, SecureSerialization.toJson(metadata)); + logger.debug(Messages.UPDATING_METADATA_OF_SERVICE_INSTANCE_0_TO_1, guid, + dynamicSecureSerialization.toJson(metadata)); delegate.updateServiceInstanceMetadata(guid, metadata); } @Override public void updateServiceBindingMetadata(UUID guid, Metadata metadata) { - logger.debug(Messages.UPDATING_METADATA_OF_SERVICE_BINDING_0_TO_1, guid, SecureSerialization.toJson(metadata)); + logger.debug(Messages.UPDATING_METADATA_OF_SERVICE_BINDING_0_TO_1, guid, + dynamicSecureSerialization.toJson(metadata)); delegate.updateServiceBindingMetadata(guid, metadata); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleaner.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleaner.java new file mode 100644 index 0000000000..69fe294b5b --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleaner.java @@ -0,0 +1,37 @@ +package org.cloudfoundry.multiapps.controller.process.jobs; + +import java.text.MessageFormat; +import java.time.LocalDateTime; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreDeletion; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; + +@Named +@Order(10) +public class SecretTokenCleaner implements Cleaner { + + private static final Logger LOGGER = LoggerFactory.getLogger(SecretTokenCleaner.class); + + protected final SecretTokenStoreFactory secretTokenStoreFactory; + + @Inject + public SecretTokenCleaner(SecretTokenStoreFactory secretTokenStoreFactory) { + this.secretTokenStoreFactory = secretTokenStoreFactory; + } + + public void execute(LocalDateTime expirationTime) { + LOGGER.debug(CleanUpJob.LOG_MARKER, Messages.REMOVING_EXPIRED_SECRET_TOKENS); + + SecretTokenStoreDeletion secretTokenStore = secretTokenStoreFactory.createSecretTokenStoreDeletionRelated(); + int deletedTokenCount = secretTokenStore.deleteOlderThan(expirationTime); + + LOGGER.debug(CleanUpJob.LOG_MARKER, MessageFormat.format(Messages.REMOVED_SECRET_TOKENS_0, deletedTokenCount)); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java index cebcfc8c9b..0392e7eb6f 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadata.java @@ -40,22 +40,26 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_APP_NAMES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_APP_NAMES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_APP_NAMES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_SERVICE_NAMES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_SERVICE_NAMES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_SERVICE_NAMES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_APP_ROUTES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_APP_ROUTES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_APP_ROUTES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_AS_SUFFIX.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_AS_SUFFIX)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_AS_SUFFIX)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.VERSION_RULE.getName()) @@ -105,22 +109,31 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.IS_SECURITY_ENABLED.getName()) + .type(ParameterType.BOOLEAN) + .defaultValue(false) .build()) // Special blue green deploy parameters: .addParameter(ImmutableParameterMetadata.builder() diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java index d9a4331796..15d380c4a2 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadata.java @@ -42,22 +42,26 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_APP_NAMES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_APP_NAMES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_APP_NAMES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_SERVICE_NAMES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_SERVICE_NAMES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_SERVICE_NAMES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_APP_ROUTES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_APP_ROUTES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_APP_ROUTES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_AS_SUFFIX.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_AS_SUFFIX)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_AS_SUFFIX)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.VERSION_RULE.getName()) @@ -116,22 +120,31 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) + .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.IS_SECURITY_ENABLED.getName()) + .type(ParameterType.BOOLEAN) + .defaultValue(false) .build()) // Special CTS+ parameters: .addParameter(ImmutableParameterMetadata.builder() diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java index d0a28692e3..b3b7396dd1 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadata.java @@ -40,22 +40,26 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_APP_NAMES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_APP_NAMES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_APP_NAMES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_SERVICE_NAMES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_SERVICE_NAMES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_SERVICE_NAMES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_APP_ROUTES.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_APP_ROUTES)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_APP_ROUTES)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPLY_NAMESPACE_AS_SUFFIX.getName()) .type(ParameterType.BOOLEAN) - .customConverter(new ApplyNamespaceParameterConverter(Variables.APPLY_NAMESPACE_AS_SUFFIX)) + .customConverter(new ApplyNamespaceParameterConverter( + Variables.APPLY_NAMESPACE_AS_SUFFIX)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.VERSION_RULE.getName()) @@ -105,28 +109,37 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.SKIP_APP_DIGEST_CALCULATION.getName()) .type(ParameterType.BOOLEAN) .defaultValue(false) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.IS_SECURITY_ENABLED.getName()) + .type(ParameterType.BOOLEAN) + .defaultValue(false) + .build()) .build(); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java index 96288c719b..12e4fb09f3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadata.java @@ -49,27 +49,36 @@ public static OperationMetadata getMetadata() { .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName()) .type(ParameterType.INTEGER) - .customConverter(new TimeoutParameterConverter(Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) + .customConverter(new TimeoutParameterConverter( + Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE)) .build()) .addParameter(ImmutableParameterMetadata.builder() .id(Variables.PROCESS_USER_PROVIDED_SERVICES.getName()) .type(ParameterType.BOOLEAN) .build()) + .addParameter(ImmutableParameterMetadata.builder() + .id(Variables.IS_SECURITY_ENABLED.getName()) + .type(ParameterType.BOOLEAN) + .defaultValue(false) + .build()) .build(); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/MissingSecretTokenException.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/MissingSecretTokenException.java new file mode 100644 index 0000000000..9c4b196132 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/MissingSecretTokenException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +public class MissingSecretTokenException extends RuntimeException { + + public MissingSecretTokenException(String message) { + super(message); + } + + public MissingSecretTokenException(String message, Throwable cause) { + super(message, cause); + } + + public MissingSecretTokenException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretConfig.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretConfig.java new file mode 100644 index 0000000000..cda2defa2f --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretConfig.java @@ -0,0 +1,45 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.security.Security; +import java.util.HashSet; +import java.util.Set; + +import jakarta.annotation.PostConstruct; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializerConfiguration; +import org.cloudfoundry.multiapps.controller.persistence.services.SecretTokenService; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyResolver; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyResolverImpl; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SecretConfig { + + @Bean + public SecretTokenKeyResolver secretTokenKeyResolver() { + return new SecretTokenKeyResolverImpl(); + } + + @Bean + public SecretTokenStoreFactory secretTokenStoreFactory(SecretTokenService secretTokenService) { + return new SecretTokenStoreFactory(secretTokenService); + } + + @Bean + public SecretTransformationStrategy secretTransformationStrategy() { + SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration(); + Set secretNames = new HashSet<>(secureSerializerConfiguration.getSensitiveElementNames()); + + return new SecretTransformationStrategyImpl(secretNames); + } + + @PostConstruct + public void addBouncyCastleSecureProvider() { + if (Security.getProvider(BouncyCastleFipsProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleFipsProvider()); + } + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializer.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializer.java new file mode 100644 index 0000000000..b3f88d470d --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializer.java @@ -0,0 +1,296 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; +import org.cloudfoundry.multiapps.controller.process.Constants; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStore; +import org.cloudfoundry.multiapps.controller.process.security.util.SecretTokenUtil; +import org.cloudfoundry.multiapps.controller.process.variables.Serializer; +import org.flowable.common.engine.api.variable.VariableContainer; + +public class SecretTokenSerializer implements Serializer { + + private static ObjectMapper objectMapper = new ObjectMapper(); + + private Serializer serializer; + + private SecretTokenStore secretTokenStore; + + private SecretTransformationStrategy secretTransformationStrategy; + + private final String processInstanceId; + + private final String variableName; + + public SecretTokenSerializer(Serializer serializer, SecretTokenStore secretTokenStore, + SecretTransformationStrategy secretTransformationStrategy, String processInstanceId, String variableName) { + this.serializer = serializer; + this.secretTokenStore = secretTokenStore; + this.secretTransformationStrategy = secretTransformationStrategy; + this.processInstanceId = processInstanceId; + this.variableName = variableName; + } + + @Override + public Object serialize(T value) { + if (value == null) { + return serializer.serialize(null); + } + + Object encodedObject = serializer.serialize(value); + + if (encodedObject instanceof String) { + return handleString((String) encodedObject, true); + } + + if (encodedObject instanceof byte[]) { + return handleBytes((byte[]) encodedObject, true); + } + + if (encodedObject instanceof List) { + return handleList((List) encodedObject, true); + } + + return encodedObject; + } + + @Override + public T deserialize(Object serializedValue) { + if (serializedValue == null) { + return serializer.deserialize(null); + } + + Object valueToDecode = serializedValue; + + if (serializedValue instanceof String) { + valueToDecode = handleString((String) serializedValue, false); + } else if (serializedValue instanceof byte[]) { + valueToDecode = handleBytes((byte[]) serializedValue, false); + } else if (serializedValue instanceof List) { + valueToDecode = handleList((List) serializedValue, false); + } + + return serializer.deserialize(valueToDecode); + } + + @Override + public T deserialize(Object serializedValue, VariableContainer container) { + if (serializedValue == null) { + return serializer.deserialize(null); + } + + Object valueToDecode = serializedValue; + + if (serializedValue instanceof String) { + valueToDecode = handleString((String) serializedValue, false); + } else if (serializedValue instanceof byte[]) { + valueToDecode = handleBytes((byte[]) serializedValue, false); + } else if (serializedValue instanceof List) { + valueToDecode = handleList((List) serializedValue, false); + } + + return serializer.deserialize(valueToDecode, container); + } + + private Object handleString(String stringObject, boolean censor) { + String transformedJson = transformJson(stringObject, censor); + if (transformedJson != null) { + return transformedJson; + } + + if (!censor && SecretTokenUtil.isToken(stringObject)) { + return detokenize(stringObject); + } + + return stringObject; + } + + private Object handleBytes(byte[] bytesArray, boolean censor) { + String string = new String(bytesArray); + String transformedJson = transformJson(string, censor); + + if (transformedJson != null) { + return transformedJson.getBytes(); + } + + return bytesArray; + } + + private Object handleList(List list, boolean censor) { + return list.stream() + .map(element -> { + if (element instanceof String) { + String transformedJson = transformJson((String) element, censor); + if (transformedJson != null) { + return transformedJson; + } else { + return element; + } + } else if (element instanceof byte[]) { + String transformedJson = transformJson(new String((byte[]) element), censor); + if (transformedJson != null) { + return transformedJson.getBytes(); + } else { + return element; + } + } + return element; + }) + .collect(Collectors.toList()); + } + + private String transformJson(String candidate, boolean censor) { + if (!isStringJson(candidate)) { + return null; + } + + try { + JsonNode rootNode = objectMapper.readTree(candidate); + Set keys = lowerCase(secretTransformationStrategy.getJsonSecretFieldNames()); + boolean[] changed = new boolean[1]; + + JsonNode output = processJsonValue(rootNode, keys, censor, changed); + + if (changed[0]) { + return objectMapper.writeValueAsString(output); + } + return null; + } catch (Exception e) { + throw new SecretTokenSerializerJsonException( + MessageFormat.format(Messages.JSON_TRANSFORMATION_FAILED_FOR_VARIABLE_0, variableName), e); + } + } + + private boolean isStringJson(String string) { + if (string == null) { + return false; + } + string = string.trim(); + return string.startsWith("{") || string.startsWith("["); + } + + private JsonNode processJsonValue(JsonNode currentNode, Set keys, boolean censor, boolean[] changed) { + if (currentNode.isObject()) { + ObjectNode objectNode = currentNode.deepCopy(); + + List fields = new ArrayList<>(); + Iterator fieldsIterator = objectNode.fieldNames(); + + while (fieldsIterator.hasNext()) { + fields.add(fieldsIterator.next()); + } + + for (String currentField : fields) { + JsonNode childNode = objectNode.get(currentField); + JsonNode processedNode = processJsonValue(childNode, keys, censor, changed); + + boolean doesNameMatch = keys.contains(currentField.toLowerCase()); + + if (doesNameMatch && childNode.isValueNode()) { + String currentValue = null; + if (!childNode.isNull()) { + currentValue = childNode.asText(); + } + + if (censor) { + if (SecretTokenUtil.isToken(currentValue) || isPlaceholder(currentValue)) { + objectNode.put(currentField, currentValue); + } else { + objectNode.put(currentField, tokenize(currentValue)); + changed[0] = true; + } + } else { + if (SecretTokenUtil.isToken(currentValue)) { + objectNode.put(currentField, detokenize(currentValue)); + changed[0] = true; + } else { + objectNode.set(currentField, processedNode); + } + } + } else { + objectNode.set(currentField, processedNode); + } + } + return objectNode; + + } + + if (currentNode.isArray()) { + ArrayNode arrayNode = currentNode.deepCopy(); + for (int i = 0; i < arrayNode.size(); i++) { + arrayNode.set(i, processJsonValue(arrayNode.get(i), keys, censor, changed)); + } + return arrayNode; + } + + if (currentNode.isTextual()) { + String value = currentNode.asText(); + if (!censor && SecretTokenUtil.isToken(value)) { + changed[0] = true; + String detokenizedValue = detokenize(value); + return TextNode.valueOf(detokenizedValue); + } + } + + return currentNode; + } + + private String tokenize(String plainText) { + long id; + if (plainText != null) { + id = secretTokenStore.put(processInstanceId, variableName, plainText); + } else { + id = secretTokenStore.put(processInstanceId, variableName, ""); + } + return SecretTokenUtil.of(id); + } + + private String detokenize(String token) { + long id = SecretTokenUtil.id(token); + String result = secretTokenStore.get(processInstanceId, id); + if (result == null) { + throw new MissingSecretTokenException( + MessageFormat.format(Messages.SECRET_VALUE_NOT_FOUND_FOR_TOKEN_0_PID_1_VARIABLE_2, token, processInstanceId, + variableName)); + } + return result; + } + + private static Set lowerCase(Set keys) { + if (keys == null) { + return Collections.emptySet(); + } + + return keys.stream() + .filter(Objects::nonNull) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + + private boolean isPlaceholder(String value) { + if (value == null) { + return false; + } + + if (value.contains(Constants.PLACEHOLDER_PREFIX) && value.contains(Constants.PLACEHOLDER_POSTFIX)) { + return true; + } + + String trimmedValue = value.trim(); + return trimmedValue.startsWith(Constants.PLACEHOLDER_PREFIX) && trimmedValue.endsWith(Constants.PLACEHOLDER_POSTFIX); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerJsonException.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerJsonException.java new file mode 100644 index 0000000000..25c3c0554d --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerJsonException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +public class SecretTokenSerializerJsonException extends RuntimeException { + + public SecretTokenSerializerJsonException(String message) { + super(message); + } + + public SecretTokenSerializerJsonException(String message, Throwable cause) { + super(message, cause); + } + + public SecretTokenSerializerJsonException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategy.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategy.java new file mode 100644 index 0000000000..67644d52df --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategy.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.util.Set; + +public interface SecretTransformationStrategy { + + Set getJsonSecretFieldNames(); + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java new file mode 100644 index 0000000000..8812421f4c --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java @@ -0,0 +1,33 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class SecretTransformationStrategyContextImpl implements SecretTransformationStrategy { + + private SecretTransformationStrategy secretTransformationStrategy; + + private Set extraFieldNames; + + public SecretTransformationStrategyContextImpl(SecretTransformationStrategy secretTransformationStrategy, Set extraFieldNames) { + this.secretTransformationStrategy = secretTransformationStrategy; + if (extraFieldNames == null) { + this.extraFieldNames = Set.of(); + } else { + this.extraFieldNames = extraFieldNames.stream() + .filter(Objects::nonNull) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + } + + @Override + public Set getJsonSecretFieldNames() { + Set out = new HashSet<>(secretTransformationStrategy.getJsonSecretFieldNames()); + out.addAll(extraFieldNames); + return out; + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyImpl.java new file mode 100644 index 0000000000..3860f80fd2 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyImpl.java @@ -0,0 +1,27 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +public class SecretTransformationStrategyImpl implements SecretTransformationStrategy { + + private Set secretFields; + + public SecretTransformationStrategyImpl(Set secretFields) { + if (secretFields == null) { + this.secretFields = Set.of(); + } else { + this.secretFields = secretFields.stream() + .filter(Objects::nonNull) + .map(String::toLowerCase) + .collect(Collectors.toSet()); + } + } + + @Override + public Set getJsonSecretFieldNames() { + return this.secretFields; + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingCredentialsFromUserProvidedServiceEncryptionRelated.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingCredentialsFromUserProvidedServiceEncryptionRelated.java new file mode 100644 index 0000000000..fe0a7dc6e2 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingCredentialsFromUserProvidedServiceEncryptionRelated.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +public class MissingCredentialsFromUserProvidedServiceEncryptionRelated extends RuntimeException { + + public MissingCredentialsFromUserProvidedServiceEncryptionRelated(String message) { + super(message); + } + + public MissingCredentialsFromUserProvidedServiceEncryptionRelated(String message, Throwable cause) { + super(message, cause); + } + + public MissingCredentialsFromUserProvidedServiceEncryptionRelated(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java new file mode 100644 index 0000000000..8908e95ec8 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +public class MissingUserProvidedServiceEncryptionRelatedException extends RuntimeException { + + public MissingUserProvidedServiceEncryptionRelatedException(String message) { + super(message); + } + + public MissingUserProvidedServiceEncryptionRelatedException(String message, Throwable cause) { + super(message, cause); + } + + public MissingUserProvidedServiceEncryptionRelatedException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyContainer.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyContainer.java new file mode 100644 index 0000000000..8b35efbfa5 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyContainer.java @@ -0,0 +1,5 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +public record SecretTokenKeyContainer(String key, String keyId) { + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolver.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolver.java new file mode 100644 index 0000000000..6066be09c5 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolver.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +import org.flowable.engine.delegate.DelegateExecution; + +public interface SecretTokenKeyResolver { + + SecretTokenKeyContainer resolve(DelegateExecution execution); + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java new file mode 100644 index 0000000000..2b4be6a296 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java @@ -0,0 +1,72 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +import java.util.Map; +import java.util.UUID; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.process.Constants; +import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.steps.StepsUtil; +import org.cloudfoundry.multiapps.controller.process.variables.VariableHandling; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.flowable.engine.delegate.DelegateExecution; + +@Named +public class SecretTokenKeyResolverImpl implements SecretTokenKeyResolver { + + @Inject + CloudControllerClientProvider cloudControllerClientProvider; + + public SecretTokenKeyResolverImpl(CloudControllerClientProvider cloudControllerClientProvider) { + this.cloudControllerClientProvider = cloudControllerClientProvider; + } + + public SecretTokenKeyResolverImpl() { + } + + @Override + public SecretTokenKeyContainer resolve(DelegateExecution execution) { + String userGuid = StepsUtil.determineCurrentUserGuid(execution); + String spaceGuid = VariableHandling.get(execution, Variables.SPACE_GUID); + String correlationId = VariableHandling.get(execution, Variables.CORRELATION_ID); + + CloudControllerClient cloudControllerClient = cloudControllerClientProvider.getControllerClient(userGuid, spaceGuid, correlationId); + + String mtaId = VariableHandling.get(execution, Variables.MTA_ID); + String namespace = VariableHandling.get(execution, Variables.MTA_NAMESPACE); + + String userProvidedServiceName; + + if (namespace != null) { + userProvidedServiceName = Constants.USER_PROVIDED_SERVICE_PREFIX_NAME_ENCRYPTION_DECRYPTION + mtaId + namespace; + } else { + userProvidedServiceName = Constants.USER_PROVIDED_SERVICE_PREFIX_NAME_ENCRYPTION_DECRYPTION + mtaId; + } + + CloudServiceInstance cloudServiceInstance = cloudControllerClient.getServiceInstance(userProvidedServiceName); + if (cloudServiceInstance == null) { + throw new MissingUserProvidedServiceEncryptionRelatedException( + Messages.COULD_NOT_RETRIEVE_USER_PROVIDED_SERVICE_INSTANCE_ENCRYPTION_RELATED); + } + + UUID serviceInstanceGuid = cloudServiceInstance.getGuid(); + Map serviceInstanceCredentials = cloudControllerClient.getUserProvidedServiceInstanceParameters( + serviceInstanceGuid); + if (serviceInstanceCredentials == null || serviceInstanceCredentials.isEmpty()) { + throw new MissingCredentialsFromUserProvidedServiceEncryptionRelated( + Messages.COULD_NOT_RETRIEVE_CREDENTIALS_FROM_USER_PROVIDED_SERVICE_INSTANCE_ENCRYPTION_RELATED); + } + + String encryptionKey = serviceInstanceCredentials.get(Constants.ENCRYPTION_KEY) + .toString(); + String encryptionKeyId = serviceInstanceCredentials.get(Constants.KEY_ID) + .toString(); + + return new SecretTokenKeyContainer(encryptionKey, encryptionKeyId); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenRetrievalException.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenRetrievalException.java new file mode 100644 index 0000000000..fcd2aac7ce --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenRetrievalException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +public class SecretTokenRetrievalException extends RuntimeException { + + public SecretTokenRetrievalException(String message) { + super(message); + } + + public SecretTokenRetrievalException(String message, Throwable cause) { + super(message, cause); + } + + public SecretTokenRetrievalException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStore.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStore.java new file mode 100644 index 0000000000..101742ed60 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStore.java @@ -0,0 +1,9 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +public interface SecretTokenStore extends SecretTokenStoreDeletion { + + long put(String processInstanceId, String variableName, String plainText); + + String get(String processInstanceId, long id); + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreDeletion.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreDeletion.java new file mode 100644 index 0000000000..4fe4120968 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreDeletion.java @@ -0,0 +1,12 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +import java.time.LocalDateTime; + +public interface SecretTokenStoreDeletion { + + void delete(String processInstanceId); + + int deleteOlderThan(LocalDateTime expirationTime); + +} + diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreFactory.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreFactory.java new file mode 100644 index 0000000000..f109222c5a --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreFactory.java @@ -0,0 +1,25 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +import jakarta.inject.Inject; +import jakarta.inject.Named; +import org.cloudfoundry.multiapps.controller.persistence.services.SecretTokenService; + +@Named +public class SecretTokenStoreFactory { + + private SecretTokenService secretTokenService; + + @Inject + public SecretTokenStoreFactory(SecretTokenService secretTokenService) { + this.secretTokenService = secretTokenService; + } + + public SecretTokenStore createSecretTokenStore(String encryptionKey, String encryptionKeyId) { + return new SecretTokenStoreImpl(secretTokenService, encryptionKey, encryptionKeyId); + } + + public SecretTokenStoreDeletion createSecretTokenStoreDeletionRelated() { + return new SecretTokenStoreImplWithoutKey(secretTokenService); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImpl.java new file mode 100644 index 0000000000..d679fc6a22 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImpl.java @@ -0,0 +1,94 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +import java.nio.charset.StandardCharsets; +import java.sql.SQLException; +import java.text.MessageFormat; +import java.time.LocalDateTime; + +import org.cloudfoundry.multiapps.controller.core.security.encryption.AesEncryptionUtil; +import org.cloudfoundry.multiapps.controller.persistence.Messages; +import org.cloudfoundry.multiapps.controller.persistence.services.SecretTokenService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SecretTokenStoreImpl implements SecretTokenStore { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private SecretTokenService secretTokenService; + + private String encryptionKey; + + private String keyId; + + public SecretTokenStoreImpl(SecretTokenService secretTokenService, String encryptionKey, String keyId) { + this.secretTokenService = secretTokenService; + this.encryptionKey = encryptionKey; + this.keyId = keyId; + } + + private byte[] keyBytes() { + return encryptionKey.getBytes(StandardCharsets.UTF_8); + } + + @Override + public long put(String processInstanceId, String variableName, String plainText) { + try { + byte[] encryptedValue; + byte[] keyBytes = keyBytes(); + if (plainText == null) { + encryptedValue = AesEncryptionUtil.encrypt("", keyBytes); + } else { + encryptedValue = AesEncryptionUtil.encrypt(plainText, keyBytes); + } + + return secretTokenService.putSecretToken(processInstanceId, variableName, encryptedValue, keyId); + } catch (SQLException e) { + logger.debug(MessageFormat.format( + Messages.ERROR_INSERTING_SECRET_TOKEN_WITH_VARIABLE_NAME_0_FOR_PROCESS_WITH_ID_1_AND_ENCRYPTION_KEY_ID_2, + variableName, processInstanceId, keyId)); + throw new SecretTokenStoringException( + MessageFormat.format( + Messages.ERROR_INSERTING_SECRET_TOKEN_WITH_VARIABLE_NAME_0_FOR_PROCESS_WITH_ID_1_AND_ENCRYPTION_KEY_ID_2, variableName, + processInstanceId, keyId) + e.getMessage(), e); + } + } + + @Override + public String get(String processInstanceId, long id) { + try { + byte[] encryptedValueFromDatabase = secretTokenService.getSecretToken(processInstanceId, id); + if (encryptedValueFromDatabase == null) { + return null; + } + byte[] keyBytes = keyBytes(); + return AesEncryptionUtil.decrypt(encryptedValueFromDatabase, keyBytes); + } catch (SQLException e) { + logger.debug(MessageFormat.format( + Messages.ERROR_RETRIEVING_SECRET_TOKEN_WITH_ID_0_FOR_PROCESS_WITH_ID_1, id, processInstanceId)); + throw new SecretTokenRetrievalException( + MessageFormat.format(Messages.ERROR_RETRIEVING_SECRET_TOKEN_WITH_ID_0_FOR_PROCESS_WITH_ID_1, id, processInstanceId) + + e.getMessage(), e); + } + } + + @Override + public void delete(String processInstanceId) { + try { + secretTokenService.deleteForProcess(processInstanceId); + } catch (SQLException e) { + logger.error(MessageFormat.format(Messages.ERROR_DELETING_SECRET_TOKENS_FOR_PROCESS_WITH_ID_0, processInstanceId)); + } + } + + @Override + public int deleteOlderThan(LocalDateTime expirationTime) { + try { + return secretTokenService.deleteOlderThan(expirationTime); + } catch (SQLException e) { + logger.error(Messages.ERROR_DELETING_SECRET_TOKENS_WITH_EXPIRATION_DATE_0, expirationTime); + return 0; + } + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImplWithoutKey.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImplWithoutKey.java new file mode 100644 index 0000000000..3f5426c30f --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImplWithoutKey.java @@ -0,0 +1,41 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +import java.sql.SQLException; +import java.text.MessageFormat; +import java.time.LocalDateTime; + +import org.cloudfoundry.multiapps.controller.persistence.Messages; +import org.cloudfoundry.multiapps.controller.persistence.services.SecretTokenService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class SecretTokenStoreImplWithoutKey implements SecretTokenStoreDeletion { + + private final Logger logger = LoggerFactory.getLogger(getClass()); + + private SecretTokenService secretTokenService; + + public SecretTokenStoreImplWithoutKey(SecretTokenService secretTokenService) { + this.secretTokenService = secretTokenService; + } + + @Override + public void delete(String processInstanceId) { + try { + secretTokenService.deleteForProcess(processInstanceId); + } catch (SQLException e) { + logger.error(MessageFormat.format(Messages.ERROR_DELETING_SECRET_TOKENS_FOR_PROCESS_WITH_ID_0, processInstanceId)); + } + } + + @Override + public int deleteOlderThan(LocalDateTime expirationTime) { + try { + return secretTokenService.deleteOlderThan(expirationTime); + } catch (SQLException e) { + logger.error(Messages.ERROR_DELETING_SECRET_TOKENS_WITH_EXPIRATION_DATE_0, expirationTime); + return 0; + } + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoringException.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoringException.java new file mode 100644 index 0000000000..045ce4f7a0 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoringException.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +public class SecretTokenStoringException extends RuntimeException { + + public SecretTokenStoringException(String message) { + super(message); + } + + public SecretTokenStoringException(String message, Throwable cause) { + super(message, cause); + } + + public SecretTokenStoringException(Throwable cause) { + super(cause); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtil.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtil.java new file mode 100644 index 0000000000..cad08c2960 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtil.java @@ -0,0 +1,42 @@ +package org.cloudfoundry.multiapps.controller.process.security.util; + +public class SecretTokenUtil { + + public static final String ENCRYPTED_VALUES_PREFIX = "dsc:v1:"; + + private SecretTokenUtil() { + + } + + public static boolean isToken(String token) { + if (token == null) { + return false; + } + + if (!token.startsWith(ENCRYPTED_VALUES_PREFIX)) { + return false; + } + + String tail = token.substring(ENCRYPTED_VALUES_PREFIX.length()); + + if (tail.isBlank()) { + return false; + } + + for (int i = 0; i < tail.length(); i++) { + if (!Character.isDigit(tail.charAt(i))) { + return false; + } + } + return true; + } + + public static long id(String token) { + return Long.parseLong(token.substring(ENCRYPTED_VALUES_PREFIX.length())); + } + + public static String of(long id) { + return ENCRYPTED_VALUES_PREFIX + id; + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java index fc797e85d8..b5ffe14295 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BindServiceToApplicationStep.java @@ -35,7 +35,8 @@ protected StepPhase executeAsyncStep(ProcessContext context) throws Exception { Map serviceBindingParameters = context.getVariable(Variables.SERVICE_BINDING_PARAMETERS); CloudControllerClient controllerClient = context.getControllerClient(); Optional jobId = controllerClient.bindServiceInstance(bingingName, app.getName(), service, serviceBindingParameters, - getApplicationServicesUpdateCallback(context, controllerClient)); + getApplicationServicesUpdateCallback(context, + controllerClient)); if (context.getVariable(Variables.USE_LAST_OPERATION_FOR_SERVICE_BINDING_CREATION)) { return StepPhase.POLL; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java index 3cf7e74442..e4da0db780 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildApplicationDeployModelStep.java @@ -17,7 +17,8 @@ import org.cloudfoundry.multiapps.controller.core.cf.v2.ConfigurationEntriesCloudModelBuilder; import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.util.NameUtil; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -41,6 +42,8 @@ public class BuildApplicationDeployModelStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); Module module = context.getVariable(Variables.MODULE_TO_DEPLOY); getStepLogger().debug(Messages.BUILDING_CLOUD_APP_MODEL, module.getName()); Module applicationModule = findModuleInDeploymentDescriptor(context, module.getName()); @@ -58,7 +61,7 @@ protected StepPhase executeStep(ProcessContext context) { .build(); context.setVariable(Variables.APP_TO_PROCESS, modifiedApp); determineBindingUnbindingServicesStrategy(context, module); - buildConfigurationEntries(context, modifiedApp); + buildConfigurationEntries(context, modifiedApp, dynamicSecureSerialization); context.setVariable(Variables.TASKS_TO_EXECUTE, modifiedApp.getTasks()); getStepLogger().debug(Messages.CLOUD_APP_MODEL_BUILT); return StepPhase.DONE; @@ -109,7 +112,8 @@ private Set getApplicationRoutes(ProcessContext context, CloudApplic return modifiedApp.getRoutes(); } - private void buildConfigurationEntries(ProcessContext context, CloudApplicationExtended app) { + private void buildConfigurationEntries(ProcessContext context, CloudApplicationExtended app, + DynamicSecureSerialization dynamicSecureSerialization) { if (context.getVariable(Variables.SKIP_UPDATE_CONFIGURATION_ENTRIES)) { context.setVariable(Variables.CONFIGURATION_ENTRIES_TO_PUBLISH, Collections.emptyList()); return; @@ -122,7 +126,7 @@ private void buildConfigurationEntries(ProcessContext context, CloudApplicationE context.setVariable(Variables.CONFIGURATION_ENTRIES_TO_PUBLISH, updatedModuleNames); context.setVariable(Variables.SKIP_UPDATE_CONFIGURATION_ENTRIES, false); - getStepLogger().debug(Messages.CONFIGURATION_ENTRIES_TO_PUBLISH, SecureSerialization.toJson(updatedModuleNames)); + getStepLogger().debug(Messages.CONFIGURATION_ENTRIES_TO_PUBLISH, dynamicSecureSerialization.toJson(updatedModuleNames)); } private ConfigurationEntriesCloudModelBuilder getConfigurationEntriesCloudModelBuilder(ProcessContext context) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudDeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudDeployModelStep.java index 8057ba4423..491f6905c1 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudDeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudDeployModelStep.java @@ -29,7 +29,8 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.util.CloudModelBuilderUtil; import org.cloudfoundry.multiapps.controller.core.util.NameUtil; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -79,10 +80,12 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DEPLOYED_MODULES, deployedModuleNames); Set mtaModulesForDeployment = context.getVariable(Variables.MTA_MODULES); getStepLogger().debug(Messages.MTA_MODULES, mtaModulesForDeployment); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); // Build a map of service keys and save them in the context: Map> serviceKeys = getServiceKeysCloudModelBuilder(context).build(); - getStepLogger().debug(Messages.SERVICE_KEYS_TO_CREATE, SecureSerialization.toJson(serviceKeys)); + getStepLogger().debug(Messages.SERVICE_KEYS_TO_CREATE, dynamicSecureSerialization.toJson(serviceKeys)); context.setVariable(Variables.SERVICE_KEYS_TO_CREATE, serviceKeys); @@ -95,7 +98,7 @@ protected StepPhase executeStep(ProcessContext context) { moduleToDeployHelper); List moduleJsons = modulesCalculatedForDeployment.stream() - .map(SecureSerialization::toJson) + .map(dynamicSecureSerialization::toJson) .collect(toList()); getStepLogger().debug(Messages.MODULES_TO_DEPLOY, moduleJsons.toString()); context.setVariable(Variables.ALL_MODULES_TO_DEPLOY, modulesCalculatedForDeployment); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java index 75c29b4619..b87564bbdf 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/BuildCloudUndeployModelStep.java @@ -27,7 +27,8 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.util.CloudModelBuilderUtil; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription; import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationSubscriptionService; @@ -63,6 +64,8 @@ public class BuildCloudUndeployModelStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.BUILDING_CLOUD_UNDEPLOY_MODEL); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); DeployedMta deployedMta = context.getVariable(Variables.DEPLOYED_MTA); if (deployedMta == null) { @@ -84,14 +87,14 @@ protected StepPhase executeStep(ProcessContext context) { moduleToDeployHelper); List deployedAppsToUndeploy = modulesToUndeployCalculator.computeModulesToUndeploy(appNames); - getStepLogger().debug(Messages.MODULES_TO_UNDEPLOY, SecureSerialization.toJson(deployedAppsToUndeploy)); + getStepLogger().debug(Messages.MODULES_TO_UNDEPLOY, dynamicSecureSerialization.toJson(deployedAppsToUndeploy)); List appsWithoutChange = modulesToUndeployCalculator.computeModulesWithoutChange(deployedAppsToUndeploy); - getStepLogger().debug(Messages.MODULES_NOT_TO_BE_CHANGED, SecureSerialization.toJson(appsWithoutChange)); + getStepLogger().debug(Messages.MODULES_NOT_TO_BE_CHANGED, dynamicSecureSerialization.toJson(appsWithoutChange)); List subscriptionsToDelete = computeSubscriptionsToDelete(subscriptionsToCreate, deployedMta, context.getVariable(Variables.SPACE_GUID)); - getStepLogger().debug(Messages.SUBSCRIPTIONS_TO_DELETE, SecureSerialization.toJson(subscriptionsToDelete)); + getStepLogger().debug(Messages.SUBSCRIPTIONS_TO_DELETE, dynamicSecureSerialization.toJson(subscriptionsToDelete)); Set servicesForApplications = getServicesForApplications(context); List servicesToDelete = computeServicesToDelete(context, appsWithoutChange, deployedMta.getServices(), @@ -113,7 +116,7 @@ protected StepPhase executeStep(ProcessContext context) { appsToUndeploy.removeAll(existingAppsToBackup); appsToUndeploy.addAll(backupAppsToUndeploy); - getStepLogger().debug(Messages.APPS_TO_UNDEPLOY, SecureSerialization.toJson(appsToUndeploy)); + getStepLogger().debug(Messages.APPS_TO_UNDEPLOY, dynamicSecureSerialization.toJson(appsToUndeploy)); setComponentsToUndeploy(context, servicesToDelete, appsToUndeploy, subscriptionsToDelete, serviceKeysToDelete, existingAppsToBackup); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CalculateServiceKeyForWaitingStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CalculateServiceKeyForWaitingStep.java index 9ddfa0d4d9..3a56dceb48 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CalculateServiceKeyForWaitingStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CalculateServiceKeyForWaitingStep.java @@ -2,6 +2,7 @@ import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import jakarta.inject.Named; @@ -9,7 +10,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ServiceUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -26,7 +28,9 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { CloudControllerClient controllerClient = context.getControllerClient(); List existingServiceKeys = ServiceUtil.getExistingServiceKeys(controllerClient, serviceToProcess, getStepLogger()); List serviceKeysInProgress = getServiceKeysInProgress(existingServiceKeys); - getStepLogger().debug(Messages.SERVICE_KEYS_SCHEDULED_FOR_WAITING_0, SecureSerialization.toJson(serviceKeysInProgress)); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + getStepLogger().debug(Messages.SERVICE_KEYS_SCHEDULED_FOR_WAITING_0, dynamicSecureSerialization.toJson(serviceKeysInProgress)); context.setVariable(Variables.CLOUD_SERVICE_KEYS_FOR_WAITING, serviceKeysInProgress); return StepPhase.DONE; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectSystemParametersStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectSystemParametersStep.java index 5f982c9bc9..034d7d33ca 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectSystemParametersStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CollectSystemParametersStep.java @@ -2,6 +2,7 @@ import java.net.URL; import java.text.MessageFormat; +import java.util.Collection; import java.util.function.Supplier; import jakarta.inject.Inject; @@ -15,7 +16,8 @@ import org.cloudfoundry.multiapps.controller.core.helpers.CredentialsGenerator; import org.cloudfoundry.multiapps.controller.core.helpers.SystemParameters; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; import org.cloudfoundry.multiapps.controller.core.validators.parameters.HostValidator; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -58,7 +60,10 @@ protected StepPhase executeStepInternal(ProcessContext context, boolean reserveT checkForOverwrittenReadOnlyParameters(descriptor); SystemParameters systemParameters = createSystemParameters(context, defaultDomainName, reserveTemporaryRoutes, descriptor); systemParameters.injectInto(descriptor); - getStepLogger().debug(Messages.DESCRIPTOR_WITH_SYSTEM_PARAMETERS, SecureSerialization.toJson(descriptor)); + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + getStepLogger().debug(Messages.DESCRIPTOR_WITH_SYSTEM_PARAMETERS, + dynamicSecureSerialization.toJson(descriptor)); determineIsVersionAccepted(context, descriptor); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ComputeNextModulesStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ComputeNextModulesStep.java index 3cb3b3d90e..c63b8374b1 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ComputeNextModulesStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ComputeNextModulesStep.java @@ -1,14 +1,15 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import jakarta.inject.Inject; import jakarta.inject.Named; - import org.apache.commons.collections4.ListUtils; import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ModuleDependencyChecker; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -52,7 +53,10 @@ protected StepPhase executeStep(ProcessContext context) { // Mark next iteration data as computed context.setVariable(Variables.ITERATED_MODULES_IN_PARALLEL, ListUtils.union(completedModules, modulesForNextIteration)); - getStepLogger().debug(Messages.COMPUTED_NEXT_MODULES_FOR_PARALLEL_ITERATION, SecureSerialization.toJson(modulesForNextIteration)); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + getStepLogger().debug(Messages.COMPUTED_NEXT_MODULES_FOR_PARALLEL_ITERATION, + dynamicSecureSerialization.toJson(modulesForNextIteration)); return StepPhase.DONE; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateServiceBrokerStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateServiceBrokerStep.java index 0c5a4e5082..4d63c93d4d 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateServiceBrokerStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/CreateOrUpdateServiceBrokerStep.java @@ -4,6 +4,7 @@ import java.time.Duration; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import jakarta.inject.Named; @@ -18,7 +19,8 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationAttributes; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper.CloudComponents; @@ -35,12 +37,13 @@ public class CreateOrUpdateServiceBrokerStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) { getStepLogger().debug(Messages.CREATING_SERVICE_BROKERS); - + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); CloudServiceBroker serviceBroker = getServiceBrokerToCreate(context); if (serviceBroker == null) { return StepPhase.DONE; } - getStepLogger().debug(MessageFormat.format(Messages.SERVICE_BROKER, SecureSerialization.toJson(serviceBroker))); + getStepLogger().debug(MessageFormat.format(Messages.SERVICE_BROKER, dynamicSecureSerialization.toJson(serviceBroker))); CloudControllerClient client = context.getControllerClient(); List existingServiceBrokers = client.getServiceBrokers(); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteDiscontinuedConfigurationEntriesStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteDiscontinuedConfigurationEntriesStep.java index 715fe44e3e..5daec1f9c1 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteDiscontinuedConfigurationEntriesStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteDiscontinuedConfigurationEntriesStep.java @@ -2,12 +2,13 @@ import java.text.MessageFormat; import java.util.List; +import java.util.Set; import java.util.stream.Collectors; import jakarta.inject.Inject; import jakarta.inject.Named; - -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.util.ConfigurationEntriesUtil; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry; import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationEntryService; @@ -32,7 +33,9 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DELETING_PUBLISHED_DEPENDENCIES); List entriesToDelete = getEntriesToDelete(context); - deleteConfigurationEntries(entriesToDelete, context); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + deleteConfigurationEntries(entriesToDelete, context, dynamicSecureSerialization); getStepLogger().debug(Messages.PUBLISHED_DEPENDENCIES_DELETED); return StepPhase.DONE; @@ -55,7 +58,8 @@ private List getEntriesToDelete(ProcessContext context) { .collect(Collectors.toList()); } - private void deleteConfigurationEntries(List entriesToDelete, ProcessContext context) { + private void deleteConfigurationEntries(List entriesToDelete, ProcessContext context, + DynamicSecureSerialization dynamicSecureSerialization) { for (ConfigurationEntry entry : entriesToDelete) { getStepLogger().info(MessageFormat.format(Messages.DELETING_DISCONTINUED_DEPENDENCY_0, entry.getProviderId())); int deletedEntries = configurationEntryService.createQuery() @@ -65,13 +69,13 @@ private void deleteConfigurationEntries(List entriesToDelete getStepLogger().warn(Messages.COULD_NOT_DELETE_PROVIDED_DEPENDENCY, entry.getProviderId()); } } - getStepLogger().debug(Messages.DELETED_ENTRIES, SecureSerialization.toJson(entriesToDelete)); + getStepLogger().debug(Messages.DELETED_ENTRIES, dynamicSecureSerialization.toJson(entriesToDelete)); context.setVariable(Variables.DELETED_ENTRIES, entriesToDelete); } private List getEntries(ProcessContext context) { String mtaId = context.getVariable(Variables.MTA_ID); - String spaceGuid = context.getVariable(Variables.SPACE_GUID); + String spaceGuid = context.getVariable(Variables.SPACE_GUID); String namespace = context.getVariable(Variables.MTA_NAMESPACE); return configurationEntryService.createQuery() diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteSubscriptionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteSubscriptionsStep.java index c7a7e9851b..185a9ecfed 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteSubscriptionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DeleteSubscriptionsStep.java @@ -1,12 +1,13 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.util.Collection; import java.util.List; import jakarta.inject.Inject; import jakarta.inject.Named; - -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription.ResourceDto; import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationSubscriptionService; @@ -27,7 +28,9 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DELETING_SUBSCRIPTIONS); List subscriptionsToDelete = context.getVariable(Variables.SUBSCRIPTIONS_TO_DELETE); - getStepLogger().debug(Messages.SUBSCRIPTIONS_TO_DELETE, SecureSerialization.toJson(subscriptionsToDelete)); + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + getStepLogger().debug(Messages.SUBSCRIPTIONS_TO_DELETE, dynamicSecureSerialization.toJson(subscriptionsToDelete)); for (ConfigurationSubscription subscription : subscriptionsToDelete) { infoSubscriptionDeletion(subscription); int removedSubscriptions = configurationSubscriptionService.createQuery() diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java index eb25617a37..e219152775 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectDeployedMtaStep.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.util.Collection; import java.util.List; import java.util.Optional; @@ -16,7 +17,8 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; import org.cloudfoundry.multiapps.controller.core.util.NameUtil; import org.cloudfoundry.multiapps.controller.process.Constants; @@ -41,23 +43,26 @@ public class DetectDeployedMtaStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DETECTING_DEPLOYED_MTA); + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); String mtaId = context.getVariable(Variables.MTA_ID); String mtaNamespace = context.getVariable(Variables.MTA_NAMESPACE); CloudControllerClient client = context.getControllerClient(); - DeployedMta deployedMta = detectDeployedMta(mtaId, mtaNamespace, client, context); + DeployedMta deployedMta = detectDeployedMta(mtaId, mtaNamespace, client, context, dynamicSecureSerialization); - detectBackupMta(mtaId, mtaNamespace, client, context); + detectBackupMta(mtaId, mtaNamespace, client, context, dynamicSecureSerialization); var deployedServiceKeys = detectDeployedServiceKeys(mtaId, mtaNamespace, deployedMta, context); context.setVariable(Variables.DEPLOYED_MTA_SERVICE_KEYS, deployedServiceKeys); - getStepLogger().debug(Messages.DEPLOYED_MTA_SERVICE_KEYS, SecureSerialization.toJson(deployedServiceKeys)); + getStepLogger().debug(Messages.DEPLOYED_MTA_SERVICE_KEYS, dynamicSecureSerialization.toJson(deployedServiceKeys)); return StepPhase.DONE; } - private DeployedMta detectDeployedMta(String mtaId, String mtaNamespace, CloudControllerClient client, ProcessContext context) { + private DeployedMta detectDeployedMta(String mtaId, String mtaNamespace, CloudControllerClient client, ProcessContext context, + DynamicSecureSerialization dynamicSecureSerialization) { getStepLogger().debug(Messages.DETECTING_MTA_BY_ID_AND_NAMESPACE, mtaId, mtaNamespace); Optional optionalDeployedMta = deployedMtaDetector.detectDeployedMtaByNameAndNamespace(mtaId, mtaNamespace, client); @@ -69,13 +74,14 @@ private DeployedMta detectDeployedMta(String mtaId, String mtaNamespace, CloudCo DeployedMta deployedMta = optionalDeployedMta.get(); context.setVariable(Variables.DEPLOYED_MTA, deployedMta); - getStepLogger().debug(Messages.DEPLOYED_MTA, SecureSerialization.toJson(deployedMta)); + getStepLogger().debug(Messages.DEPLOYED_MTA, dynamicSecureSerialization.toJson(deployedMta)); MtaMetadata metadata = deployedMta.getMetadata(); logDetectedDeployedMta(mtaNamespace, metadata); return deployedMta; } - private void detectBackupMta(String mtaId, String mtaNamespace, CloudControllerClient client, ProcessContext context) { + private void detectBackupMta(String mtaId, String mtaNamespace, CloudControllerClient client, ProcessContext context, + DynamicSecureSerialization dynamicSecureSerialization) { getStepLogger().debug(Messages.DETECTING_BACKUP_MTA_BY_ID_AND_NAMESPACE, mtaId, mtaNamespace); Optional optionalBackupMta = deployedMtaDetector.detectDeployedMtaByNameAndNamespace(mtaId, NameUtil.computeUserNamespaceWithSystemNamespace( @@ -89,7 +95,7 @@ private void detectBackupMta(String mtaId, String mtaNamespace, CloudControllerC DeployedMta backupMta = optionalBackupMta.get(); context.setVariable(Variables.BACKUP_MTA, backupMta); - getStepLogger().debug(Messages.DETECTED_BACKUP_MTA, SecureSerialization.toJson(backupMta)); + getStepLogger().debug(Messages.DETECTED_BACKUP_MTA, dynamicSecureSerialization.toJson(backupMta)); } private List detectDeployedServiceKeys(String mtaId, String mtaNamespace, DeployedMta deployedMta, diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectMtaSchemaVersionStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectMtaSchemaVersionStep.java index b845498399..7b9c835db3 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectMtaSchemaVersionStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetectMtaSchemaVersionStep.java @@ -4,7 +4,6 @@ import java.util.function.Supplier; import jakarta.inject.Named; - import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.variables.Variables; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceBindingsToDeleteStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceBindingsToDeleteStep.java index 8928a096cc..561d939a2b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceBindingsToDeleteStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceBindingsToDeleteStep.java @@ -1,5 +1,6 @@ package org.cloudfoundry.multiapps.controller.process.steps; +import java.util.Collection; import java.util.List; import java.util.UUID; @@ -7,7 +8,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; @@ -24,7 +26,9 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { UUID applicationGuid = controllerClient.getApplicationGuid(appToDelete.getName()); List bindingsToDelete = controllerClient.getAppBindings(applicationGuid); context.setVariable(Variables.CLOUD_SERVICE_BINDINGS_TO_DELETE, bindingsToDelete); - getStepLogger().debug(Messages.EXISTING_SERVICE_BINDINGS, SecureSerialization.toJson(bindingsToDelete)); + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + getStepLogger().debug(Messages.EXISTING_SERVICE_BINDINGS, dynamicSecureSerialization.toJson(bindingsToDelete)); return StepPhase.DONE; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java index 4d0d1275b9..4543cfcd0d 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceCreateUpdateServiceActionsStep.java @@ -10,6 +10,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -28,7 +29,8 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.cf.v2.ResourceType; import org.cloudfoundry.multiapps.controller.core.helpers.MtaArchiveElements; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ArchiveEntryExtractor; @@ -56,6 +58,8 @@ public DetermineServiceCreateUpdateServiceActionsStep(ArchiveEntryExtractor arch @Override protected StepPhase executeStep(ProcessContext context) throws Exception { CloudControllerClient client = context.getControllerClient(); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); CloudServiceInstanceExtended serviceToProcess = context.getVariable(Variables.SERVICE_TO_PROCESS); getStepLogger().info(Messages.PROCESSING_SERVICE, serviceToProcess.getName()); @@ -63,7 +67,7 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { setServiceParameters(context, serviceToProcess); - List actions = determineActionsAndHandleExceptions(context, existingService); + List actions = determineActionsAndHandleExceptions(context, existingService, dynamicSecureSerialization); setServiceGuidIfPresent(context, actions, existingService, serviceToProcess); context.setVariable(Variables.SERVICE_ACTIONS_TO_EXCECUTE, actions); @@ -79,10 +83,11 @@ protected String getStepErrorMessage(ProcessContext context) { .getName()); } - private List determineActionsAndHandleExceptions(ProcessContext context, CloudServiceInstance existingService) { + private List determineActionsAndHandleExceptions(ProcessContext context, CloudServiceInstance existingService, + DynamicSecureSerialization dynamicSecureSerialization) { CloudServiceInstanceExtended service = context.getVariable(Variables.SERVICE_TO_PROCESS); try { - return determineActions(context, service, existingService); + return determineActions(context, service, existingService, dynamicSecureSerialization); } catch (CloudOperationException e) { String determineServiceActionsFailedMessage = MessageFormat.format(Messages.ERROR_DETERMINING_ACTIONS_TO_EXECUTE_ON_SERVICE, service.getName(), e.getStatusText()); @@ -102,7 +107,8 @@ private boolean shouldResolveFileParameters(ProcessContext context) { } private List determineActions(ProcessContext context, CloudServiceInstanceExtended service, - CloudServiceInstance existingService) { + CloudServiceInstance existingService, + DynamicSecureSerialization dynamicSecureSerialization) { List actions = new ArrayList<>(); if (shouldUpdateKeys(service, existingService, context)) { getStepLogger().debug(Messages.SHOULD_UPDATE_SERVICE_KEY); @@ -115,7 +121,7 @@ private List determineActions(ProcessContext context, CloudServic context.setVariable(Variables.SERVICES_TO_CREATE, Collections.singletonList(service)); return actions; } - getStepLogger().debug(Messages.EXISTING_SERVICE, SecureSerialization.toJson(existingService)); + getStepLogger().debug(Messages.EXISTING_SERVICE, dynamicSecureSerialization.toJson(existingService)); boolean shouldRecreate = false; if (haveDifferentTypesOrLabels(service, existingService)) { @@ -157,7 +163,7 @@ private List determineActions(ProcessContext context, CloudServic service.getName()); } else { getStepLogger().debug(Messages.WILL_UPDATE_SERVICE_PARAMETERS); - getStepLogger().debug(Messages.NEW_SERVICE_PARAMETERS, SecureSerialization.toJson(service.getCredentials())); + getStepLogger().debug(Messages.NEW_SERVICE_PARAMETERS, dynamicSecureSerialization.toJson(service.getCredentials())); actions.add(ServiceAction.UPDATE_CREDENTIALS); } @@ -179,7 +185,7 @@ private List determineActions(ProcessContext context, CloudServic if (shouldUpdateMetadata(service, existingService)) { getStepLogger().debug(Messages.SHOULD_UPDATE_METADATA); - getStepLogger().debug(Messages.NEW_METADATA, SecureSerialization.toJson(service.getV3Metadata())); + getStepLogger().debug(Messages.NEW_METADATA, dynamicSecureSerialization.toJson(service.getV3Metadata())); actions.add(ServiceAction.UPDATE_METADATA); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java index 65a55e5b83..de42555169 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/DetermineServiceDeleteActionsToExecuteStep.java @@ -3,6 +3,7 @@ import java.text.MessageFormat; import java.util.Collections; import java.util.List; +import java.util.Set; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -11,7 +12,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceBinding; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceKey; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.util.CloudModelBuilderUtil; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ProcessTypeParser; @@ -42,10 +44,13 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { } context.getStepLogger() .debug(Messages.DETERMINING_DELETE_ACTIONS_FOR_SERVICE_INSTANCE_0, serviceInstanceToDelete); - return calculateDeleteActions(context, serviceInstanceToDelete); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + return calculateDeleteActions(context, serviceInstanceToDelete, dynamicSecureSerialization); } - private StepPhase calculateDeleteActions(ProcessContext context, String serviceInstanceToDelete) { + private StepPhase calculateDeleteActions(ProcessContext context, String serviceInstanceToDelete, + DynamicSecureSerialization dynamicSecureSerialization) { CloudControllerClient controllerClient = context.getControllerClient(); CloudServiceInstance serviceInstance = controllerClient.getServiceInstance(serviceInstanceToDelete, false); if (serviceInstance == null) { @@ -64,7 +69,7 @@ private StepPhase calculateDeleteActions(ProcessContext context, String serviceI if (isDeletePossible(context, serviceBindings, serviceKeys)) { context.getStepLogger() .debug(Messages.WILL_DELETE_SERVICE_BINDINGS_SERVICE_KEYS_AND_SERVICE_INSTANCE_0, serviceInstanceToDelete); - logServiceBindingsAndKeys(context, serviceBindings, serviceKeys); + logServiceBindingsAndKeys(context, serviceBindings, serviceKeys, dynamicSecureSerialization); context.setVariable(Variables.CLOUD_SERVICE_BINDINGS_TO_DELETE, serviceBindings); context.setVariable(Variables.CLOUD_SERVICE_KEYS_TO_DELETE, serviceKeys); context.setVariable(Variables.SERVICE_DELETION_ACTIONS, @@ -98,11 +103,11 @@ private boolean shouldDeleteServiceKeys(ProcessContext context, List serviceBindings, - List serviceKeys) { + List serviceKeys, DynamicSecureSerialization dynamicSecureSerialization) { context.getStepLogger() - .debug(Messages.EXISTING_SERVICE_BINDINGS, SecureSerialization.toJson(serviceBindings)); + .debug(Messages.EXISTING_SERVICE_BINDINGS, dynamicSecureSerialization.toJson(serviceBindings)); context.getStepLogger() - .debug(Messages.EXISTING_SERVICE_KEYS, SecureSerialization.toJson(serviceKeys)); + .debug(Messages.EXISTING_SERVICE_KEYS, dynamicSecureSerialization.toJson(serviceKeys)); } @Override diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStep.java index 482097efd5..2cdcb04dd4 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ExtractBatchedServicesWithResolvedDynamicParametersStep.java @@ -8,7 +8,6 @@ import java.util.Set; import java.util.stream.Collectors; - import jakarta.inject.Named; import org.cloudfoundry.multiapps.common.util.MiscUtil; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; @@ -19,14 +18,14 @@ import org.cloudfoundry.multiapps.controller.core.model.DynamicResolvableParameter; import org.cloudfoundry.multiapps.controller.core.model.ImmutableDynamicResolvableParameter; import org.cloudfoundry.multiapps.controller.core.resolvers.v3.DynamicParametersResolver; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.helpers.VisitableObject; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; - @Named("extractBatchedServicesWithResolvedDynamicParametersStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class ExtractBatchedServicesWithResolvedDynamicParametersStep extends SyncFlowableStep { @@ -34,6 +33,8 @@ public class ExtractBatchedServicesWithResolvedDynamicParametersStep extends Syn @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.EXTRACT_SERVICES_AND_RESOLVE_DYNAMIC_PARAMETERS_FROM_BATCH); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); Set dynamicResolvableParameters = context.getVariable(Variables.DYNAMIC_RESOLVABLE_PARAMETERS); List servicesCalculatedForDeployment = context.getVariableBackwardsCompatible( @@ -51,7 +52,7 @@ protected StepPhase executeStep(ProcessContext context) { .collect(Collectors.toList()); checkForDuplicatedServiceNameFields(resolvedServiceInstances); - setServicesToCreate(context, resolvedServiceInstances); + setServicesToCreate(context, resolvedServiceInstances, dynamicSecureSerialization); context.setVariable(Variables.DYNAMIC_RESOLVABLE_PARAMETERS, dynamicParametersWithResolvedExistingInstances); return StepPhase.DONE; } @@ -116,13 +117,14 @@ private boolean isServiceInstanceGuidRequired(Set dy } - private void setServicesToCreate(ProcessContext context, List servicesCalculatedForDeployment) { + private void setServicesToCreate(ProcessContext context, List servicesCalculatedForDeployment, + DynamicSecureSerialization dynamicSecureSerialization) { List servicesToCreate = servicesCalculatedForDeployment.stream() .filter( CloudServiceInstanceExtended::isManaged) .collect(Collectors.toList()); - getStepLogger().debug(Messages.SERVICES_TO_CREATE, SecureSerialization.toJson(servicesToCreate)); + getStepLogger().debug(Messages.SERVICES_TO_CREATE, dynamicSecureSerialization.toJson(servicesToCreate)); context.setVariable(Variables.SERVICES_TO_CREATE, servicesToCreate); context.setVariable(Variables.SERVICES_TO_CREATE_COUNT, servicesToCreate.size()); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java index 7ec5a8439b..753b74c26b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStep.java @@ -1,12 +1,18 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; +import java.util.Collection; +import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import jakarta.inject.Inject; import jakarta.inject.Named; +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; import org.cloudfoundry.multiapps.controller.core.cf.CloudHandlerFactory; import org.cloudfoundry.multiapps.controller.core.helpers.MtaDescriptorMerger; import org.cloudfoundry.multiapps.controller.persistence.dto.BackupDescriptor; @@ -18,7 +24,17 @@ import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; import org.cloudfoundry.multiapps.mta.model.ExtensionDescriptor; +import org.cloudfoundry.multiapps.mta.model.ExtensionModule; +import org.cloudfoundry.multiapps.mta.model.ExtensionProvidedDependency; +import org.cloudfoundry.multiapps.mta.model.ExtensionRequiredDependency; +import org.cloudfoundry.multiapps.mta.model.ExtensionResource; +import org.cloudfoundry.multiapps.mta.model.Module; +import org.cloudfoundry.multiapps.mta.model.ParametersContainer; import org.cloudfoundry.multiapps.mta.model.Platform; +import org.cloudfoundry.multiapps.mta.model.PropertiesContainer; +import org.cloudfoundry.multiapps.mta.model.ProvidedDependency; +import org.cloudfoundry.multiapps.mta.model.RequiredDependency; +import org.cloudfoundry.multiapps.mta.model.Resource; import org.cloudfoundry.multiapps.mta.resolvers.ReferenceContainer; import org.cloudfoundry.multiapps.mta.resolvers.ReferencesFinder; import org.springframework.beans.factory.config.BeanDefinition; @@ -28,6 +44,8 @@ @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class MergeDescriptorsStep extends SyncFlowableStep { + private static final String SECURE_ID = "__mta.secure"; + @Inject private DescriptorBackupService descriptorBackupService; @@ -45,8 +63,22 @@ protected StepPhase executeStep(ProcessContext context) { List extensionDescriptors = context.getVariable(Variables.MTA_EXTENSION_DESCRIPTOR_CHAIN); CloudHandlerFactory handlerFactory = StepsUtil.getHandlerFactory(context.getExecution()); Platform platform = configuration.getPlatform(); + Set parameterNamesToBeCensored = collectSecureParameterKeys(extensionDescriptors); + MultiValuedMap parametersNameValueMapFromDescriptorAndExtensionDescriptors = getParametersNameValueMapFromDeploymentDescriptor( + deploymentDescriptor); + parametersNameValueMapFromDescriptorAndExtensionDescriptors.putAll( + getParametersNameValueMapFromExtensionDescriptors(extensionDescriptors)); + Set nestedParameterNamesToBeCensored = getNestedParameterNamesToBeCensored( + parametersNameValueMapFromDescriptorAndExtensionDescriptors, + parameterNamesToBeCensored); + parameterNamesToBeCensored.addAll(nestedParameterNamesToBeCensored); + context.setVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES, + parameterNamesToBeCensored); + DeploymentDescriptor descriptor = getMtaDescriptorMerger(handlerFactory, platform).merge(deploymentDescriptor, - extensionDescriptors); + extensionDescriptors, + parameterNamesToBeCensored.stream() + .toList()); context.setVariable(Variables.DEPLOYMENT_DESCRIPTOR, descriptor); warnForUnsupportedParameters(descriptor); @@ -56,6 +88,97 @@ protected StepPhase executeStep(ProcessContext context) { return StepPhase.DONE; } + private Set getNestedParameterNamesToBeCensored(MultiValuedMap parameterNameValueMap, + Set parameterNamesToBeCensored) { + Set nestedParameterNamesToBeCensored = new HashSet<>(); + for (Map.Entry> parameterEntryInStringType : parameterNameValueMap.asMap() + .entrySet()) { + List entryValuesToString = parameterEntryInStringType.getValue() + .stream() + .map(String::toString) + .toList(); + for (String complexValue : entryValuesToString) { + for (String nameToBeCensored : parameterNamesToBeCensored) { + if (complexValue.contains(nameToBeCensored)) { + nestedParameterNamesToBeCensored.add(parameterEntryInStringType.getKey()); + } + } + } + } + return nestedParameterNamesToBeCensored; + } + + private MultiValuedMap getParametersNameValueMapFromExtensionDescriptors( + List extensionDescriptors) { + MultiValuedMap parametersNameValueMapFromExtensionDescriptors = new ArrayListValuedHashMap<>(); + + for (ExtensionDescriptor currentExtensionDescriptor : extensionDescriptors) { + Map currentExtensionDescriptorParameters = currentExtensionDescriptor.getParameters(); + if (currentExtensionDescriptorParameters != null) { + parametersNameValueMapFromExtensionDescriptors.putAll(getParametersStringCastedValue(currentExtensionDescriptor)); + } + + List extensionModules = currentExtensionDescriptor.getModules(); + if (extensionModules != null) { + for (ExtensionModule extensionModule : extensionModules) { + getParametersAndPropertiesPerExtensionModule(parametersNameValueMapFromExtensionDescriptors, extensionModule, false, + true, false); + getParametersAndPropertiesPerExtensionModule(parametersNameValueMapFromExtensionDescriptors, extensionModule, true, + false, false); + getParametersAndPropertiesPerExtensionModule(parametersNameValueMapFromExtensionDescriptors, extensionModule, false, + false, true); + } + } + + List extensionResources = currentExtensionDescriptor.getResources(); + if (extensionResources != null) { + for (ExtensionResource extensionResource : extensionResources) { + getParametersAndPropertiesPerExtensionResource(parametersNameValueMapFromExtensionDescriptors, extensionResource, false, + true); + + if (currentExtensionDescriptor.getMajorSchemaVersion() >= 3) { + getParametersAndPropertiesPerExtensionResource(parametersNameValueMapFromExtensionDescriptors, extensionResource, + true, false); + } + + } + } + } + + return parametersNameValueMapFromExtensionDescriptors; + } + + private MultiValuedMap getParametersNameValueMapFromDeploymentDescriptor(DeploymentDescriptor descriptor) { + MultiValuedMap parametersNameValueMapFromDeploymentDescriptor = new ArrayListValuedHashMap<>(); + Map descriptorParameters = descriptor.getParameters(); + if (descriptorParameters != null) { + parametersNameValueMapFromDeploymentDescriptor.putAll(getParametersStringCastedValue(descriptor)); + } + + List modules = descriptor.getModules(); + if (modules != null) { + for (Module module : modules) { + getParametersAndPropertiesPerModule(parametersNameValueMapFromDeploymentDescriptor, module, false, true, false); + getParametersAndPropertiesPerModule(parametersNameValueMapFromDeploymentDescriptor, module, true, false, false); + getParametersAndPropertiesPerModule(parametersNameValueMapFromDeploymentDescriptor, module, false, false, true); + } + } + + List resources = descriptor.getResources(); + if (resources != null) { + for (Resource resource : resources) { + getParametersAndPropertiesPerResource(parametersNameValueMapFromDeploymentDescriptor, resource, false, true); + + if (descriptor.getMajorSchemaVersion() >= 3) { + getParametersAndPropertiesPerResource(parametersNameValueMapFromDeploymentDescriptor, resource, true, false); + } + + } + } + + return parametersNameValueMapFromDeploymentDescriptor; + } + private void warnForUnsupportedParameters(DeploymentDescriptor descriptor) { List references = new ReferencesFinder().getAllReferences(descriptor); Map> unsupportedParameters = unsupportedParameterFinder.findUnsupportedParameters(descriptor, @@ -75,12 +198,12 @@ private void backupDeploymentDescriptor(ProcessContext context, DeploymentDescri String spaceGuid = context.getVariable(Variables.SPACE_GUID); String mtaId = descriptor.getId(); - String mtaNamesapce = context.getVariable(Variables.MTA_NAMESPACE); + String mtaNamespace = context.getVariable(Variables.MTA_NAMESPACE); String mtaVersion = descriptor.getVersion(); List backupDescriptors = descriptorBackupService.createQuery() .mtaId(mtaId) .spaceId(spaceGuid) - .namespace(mtaNamesapce) + .namespace(mtaNamespace) .mtaVersion(mtaVersion) .list(); if (backupDescriptors.isEmpty()) { @@ -89,7 +212,7 @@ private void backupDeploymentDescriptor(ProcessContext context, DeploymentDescri .mtaId(mtaId) .mtaVersion(mtaVersion) .spaceId(spaceGuid) - .namespace(mtaNamesapce) + .namespace(mtaNamespace) .build()); } } @@ -108,4 +231,109 @@ protected String getStepErrorMessage(ProcessContext context) { return Messages.ERROR_MERGING_DESCRIPTORS; } + private Set collectSecureParameterKeys(List extensionDescriptors) { + Set resultKeysNames = new HashSet<>(); + + if (extensionDescriptors == null) { + return resultKeysNames; + } + + for (ExtensionDescriptor currentExtensionDescriptor : extensionDescriptors) { + if (currentExtensionDescriptor != null && (currentExtensionDescriptor.getId()).equals(SECURE_ID)) { + Map parameters = currentExtensionDescriptor.getParameters(); + if (parameters != null) { + resultKeysNames.addAll(parameters.keySet()); + } + } + } + + return resultKeysNames; + } + + private Map getParametersStringCastedValue(ParametersContainer parametersContainer) { + return parametersContainer.getParameters() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + currentParameter -> Objects.toString(currentParameter.getValue(), ""))); + } + + private Map getPropertiesStringCastedValue(PropertiesContainer propertiesContainer) { + return propertiesContainer.getProperties() + .entrySet() + .stream() + .collect(Collectors.toMap(Map.Entry::getKey, + currentProperty -> Objects.toString(currentProperty.getValue(), ""))); + } + + private void getParametersAndPropertiesPerResource(MultiValuedMap multiValuedMap, Resource resource, boolean isRequired, + boolean isWhole) { + if (isRequired) { + for (RequiredDependency requiredDependency : resource.getRequiredDependencies()) { + multiValuedMap.putAll(getParametersStringCastedValue(requiredDependency)); + multiValuedMap.putAll(getPropertiesStringCastedValue(requiredDependency)); + } + } + if (isWhole) { + multiValuedMap.putAll(getParametersStringCastedValue(resource)); + multiValuedMap.putAll(getPropertiesStringCastedValue(resource)); + } + } + + private void getParametersAndPropertiesPerModule(MultiValuedMap multiValuedMap, Module module, boolean isRequired, + boolean isWhole, boolean isProvided) { + if (isRequired) { + for (RequiredDependency requiredDependency : module.getRequiredDependencies()) { + multiValuedMap.putAll(getParametersStringCastedValue(requiredDependency)); + multiValuedMap.putAll(getPropertiesStringCastedValue(requiredDependency)); + } + } + if (isProvided) { + for (ProvidedDependency providedDependency : module.getProvidedDependencies()) { + multiValuedMap.putAll(getParametersStringCastedValue(providedDependency)); + multiValuedMap.putAll(getPropertiesStringCastedValue(providedDependency)); + } + } + if (isWhole) { + multiValuedMap.putAll(getParametersStringCastedValue(module)); + multiValuedMap.putAll(getPropertiesStringCastedValue(module)); + } + } + + private void getParametersAndPropertiesPerExtensionResource(MultiValuedMap multiValuedMap, + ExtensionResource extensionResource, + boolean isRequired, boolean isWhole) { + if (isRequired) { + for (ExtensionRequiredDependency extensionRequiredDependency : extensionResource.getRequiredDependencies()) { + multiValuedMap.putAll(getParametersStringCastedValue(extensionRequiredDependency)); + multiValuedMap.putAll(getPropertiesStringCastedValue(extensionRequiredDependency)); + } + } + if (isWhole) { + multiValuedMap.putAll(getParametersStringCastedValue(extensionResource)); + multiValuedMap.putAll(getPropertiesStringCastedValue(extensionResource)); + } + } + + private void getParametersAndPropertiesPerExtensionModule(MultiValuedMap multiValuedMap, + ExtensionModule extensionModule, boolean isRequired, + boolean isWhole, boolean isProvided) { + if (isRequired) { + for (ExtensionRequiredDependency extensionRequiredDependency : extensionModule.getRequiredDependencies()) { + multiValuedMap.putAll(getParametersStringCastedValue(extensionRequiredDependency)); + multiValuedMap.putAll(getPropertiesStringCastedValue(extensionRequiredDependency)); + } + } + if (isProvided) { + for (ExtensionProvidedDependency extensionProvidedDependency : extensionModule.getProvidedDependencies()) { + multiValuedMap.putAll(getParametersStringCastedValue(extensionProvidedDependency)); + multiValuedMap.putAll(getPropertiesStringCastedValue(extensionProvidedDependency)); + } + } + if (isWhole) { + multiValuedMap.putAll(getParametersStringCastedValue(extensionModule)); + multiValuedMap.putAll(getPropertiesStringCastedValue(extensionModule)); + } + } + } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceOperationsExecution.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceOperationsExecution.java index c01b3c9cc8..f53ddcec45 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceOperationsExecution.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceOperationsExecution.java @@ -13,7 +13,8 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceOperation; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ServiceOperationGetter; import org.cloudfoundry.multiapps.controller.process.util.ServiceProgressReporter; @@ -53,11 +54,15 @@ public AsyncExecutionState execute(ProcessContext context) { context.getStepLogger() .debug(Messages.LAST_OPERATION_FOR_SERVICE, service.getName(), JsonUtil.toJson(lastServiceOperation, true)); } + + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + reportDetailedServicesStates(context, servicesWithLastOperation); reportOverallProgress(context, servicesWithLastOperation.values(), triggeredServiceOperations); List remainingServicesToPoll = getRemainingServicesToPoll(servicesWithLastOperation); context.getStepLogger() - .debug(Messages.REMAINING_SERVICES_TO_POLL, SecureSerialization.toJson(remainingServicesToPoll)); + .debug(Messages.REMAINING_SERVICES_TO_POLL, dynamicSecureSerialization.toJson(remainingServicesToPoll)); context.setVariable(Variables.SERVICES_TO_POLL, remainingServicesToPoll); if (remainingServicesToPoll.isEmpty()) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java index 820d69a093..8fa6cc189d 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessContext.java @@ -1,8 +1,12 @@ package org.cloudfoundry.multiapps.controller.process.steps; +import java.util.Set; + import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.client.LoggingCloudControllerClient; import org.cloudfoundry.multiapps.controller.process.util.StepLogger; @@ -36,14 +40,18 @@ public CloudControllerClient getControllerClient() { String spaceGuid = getVariable(Variables.SPACE_GUID); String correlationId = getVariable(Variables.CORRELATION_ID); CloudControllerClient delegate = clientProvider.getControllerClient(userGuid, spaceGuid, correlationId); - return new LoggingCloudControllerClient(delegate, stepLogger); + Set secretParameters = VariableHandling.get(execution, Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + return new LoggingCloudControllerClient(delegate, stepLogger, dynamicSecureSerialization); } public CloudControllerClient getControllerClient(String spaceGuid) { String userGuid = StepsUtil.determineCurrentUserGuid(execution); String correlationId = getVariable(Variables.CORRELATION_ID); CloudControllerClient delegate = clientProvider.getControllerClient(userGuid, spaceGuid, correlationId); - return new LoggingCloudControllerClient(delegate, stepLogger); + Set secretParameters = VariableHandling.get(execution, Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + return new LoggingCloudControllerClient(delegate, stepLogger, dynamicSecureSerialization); } public T getRequiredVariable(Variable variable) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessDescriptorStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessDescriptorStep.java index 910363a573..7ebedf85f6 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessDescriptorStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessDescriptorStep.java @@ -16,7 +16,9 @@ import org.cloudfoundry.multiapps.controller.core.model.DynamicResolvableParameter; import org.cloudfoundry.multiapps.controller.core.model.ImmutableMtaDescriptorPropertiesResolverContext; import org.cloudfoundry.multiapps.controller.core.model.MtaDescriptorPropertiesResolverContext; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.persistence.model.CloudTarget; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationSubscription; import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationEntryService; @@ -55,6 +57,7 @@ protected StepPhase executeStep(ProcessContext context) { context.setVariable(Variables.SUBSCRIPTIONS_TO_CREATE, subscriptions); setDynamicResolvableParametersIfAbsent(context, resolver); + Set parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); context.setVariable(Variables.COMPLETE_DEPLOYMENT_DESCRIPTOR, descriptor); // Set MTA modules in the context List modulesForDeployment = context.getVariable(Variables.MODULES_FOR_DEPLOYMENT); @@ -67,8 +70,9 @@ protected StepPhase executeStep(ProcessContext context) { Set mtaModules = getModuleNamesForDeployment(descriptor, modulesForDeployment); getStepLogger().debug(Messages.MTA_MODULES, mtaModules); context.setVariable(Variables.MTA_MODULES, mtaModules); - - getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, SecureSerialization.toJson(descriptor)); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, + dynamicSecureSerialization.toJson(descriptor)); getStepLogger().debug(Messages.DESCRIPTOR_PROPERTIES_RESOLVED); return StepPhase.DONE; diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessMtaExtensionDescriptorsStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessMtaExtensionDescriptorsStep.java index 11cdb38ba5..2274a9145b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessMtaExtensionDescriptorsStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/ProcessMtaExtensionDescriptorsStep.java @@ -1,7 +1,5 @@ package org.cloudfoundry.multiapps.controller.process.steps; -import static java.text.MessageFormat.format; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -10,7 +8,6 @@ import jakarta.inject.Inject; import jakarta.inject.Named; - import org.apache.commons.collections4.CollectionUtils; import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.core.helpers.DescriptorParserFacadeFactory; @@ -26,6 +23,8 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; +import static java.text.MessageFormat.format; + @Named("processMtaExtensionDescriptorsStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class ProcessMtaExtensionDescriptorsStep extends SyncFlowableStep { @@ -77,7 +76,23 @@ private List parseExtensionDescriptors(String spaceId, List for (String extensionDescriptorFileId : fileIds) { fileService.consumeFileContent(spaceId, extensionDescriptorFileId, extensionDescriptorConsumer); } - getStepLogger().debug(Messages.PROVIDED_EXTENSION_DESCRIPTORS, SecureSerialization.toJson(extensionDescriptors)); + + boolean isSecureExtensionDescriptorPresent = extensionDescriptors.stream() + .anyMatch(extensionDescriptor -> extensionDescriptor.getId() + .equals( + "__mta.secure")); + List extensionDescriptorsWithoutSecure = null; + if (isSecureExtensionDescriptorPresent) { + extensionDescriptorsWithoutSecure = extensionDescriptors.stream() + .filter(extensionDescriptor -> !extensionDescriptor.getId() + .equals( + "__mta.secure")) + .toList(); + getStepLogger().debug(Messages.PROVIDED_EXTENSION_DESCRIPTORS, SecureSerialization.toJson(extensionDescriptorsWithoutSecure) + + "\n\"SECURE EXTENSION DESCRIPTOR CONSTRUCTED AND APPLIED FROM ENVIRONMENT VARIABLES\""); + } else { + getStepLogger().debug(Messages.PROVIDED_EXTENSION_DESCRIPTORS, SecureSerialization.toJson(extensionDescriptors)); + } return extensionDescriptors; } catch (FileStorageException e) { throw new SLException(e, e.getMessage()); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java index 21a8756696..cf4df07fad 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/PublishConfigurationEntriesStep.java @@ -1,5 +1,11 @@ package org.cloudfoundry.multiapps.controller.process.steps; +import java.text.MessageFormat; +import java.util.Collections; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + import jakarta.inject.Inject; import jakarta.inject.Named; import org.apache.commons.collections4.CollectionUtils; @@ -7,7 +13,8 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.auditlogging.ConfigurationEntryServiceAuditLog; import org.cloudfoundry.multiapps.controller.core.model.DynamicResolvableParameter; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry; import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationEntryService; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -16,12 +23,6 @@ import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; -import java.text.MessageFormat; -import java.util.Collections; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; - @Named("publishProvidedDependenciesStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class PublishConfigurationEntriesStep extends SyncFlowableStep { @@ -54,8 +55,10 @@ protected StepPhase executeStep(ProcessContext context) { dynamicResolvableParameters); List publishedEntries = publish(context, resolvedEntriesToPublish); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); - getStepLogger().debug(Messages.PUBLISHED_ENTRIES, SecureSerialization.toJson(publishedEntries)); + getStepLogger().debug(Messages.PUBLISHED_ENTRIES, dynamicSecureSerialization.toJson(publishedEntries)); context.setVariable(Variables.PUBLISHED_ENTRIES, publishedEntries); getStepLogger().debug(Messages.PUBLIC_PROVIDED_DEPENDENCIES_PUBLISHED); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContext.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContext.java new file mode 100644 index 0000000000..a2df8022c0 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContext.java @@ -0,0 +1,84 @@ +package org.cloudfoundry.multiapps.controller.process.steps; + +import java.util.Set; + +import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.process.security.SecretTokenSerializer; +import org.cloudfoundry.multiapps.controller.process.security.SecretTransformationStrategy; +import org.cloudfoundry.multiapps.controller.process.security.SecretTransformationStrategyContextImpl; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStore; +import org.cloudfoundry.multiapps.controller.process.util.StepLogger; +import org.cloudfoundry.multiapps.controller.process.variables.Serializer; +import org.cloudfoundry.multiapps.controller.process.variables.Variable; +import org.cloudfoundry.multiapps.controller.process.variables.VariableHandling; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.cloudfoundry.multiapps.controller.process.variables.WrappedVariable; +import org.flowable.engine.delegate.DelegateExecution; + +public class SecureProcessContext extends ProcessContext { + + private SecretTokenStore secretTokenStore; + private SecretTransformationStrategy secretTransformationStrategy; + + public SecureProcessContext(DelegateExecution execution, StepLogger stepLogger, CloudControllerClientProvider clientProvider, + SecretTokenStore secretTokenStore, SecretTransformationStrategy secretTransformationStrategy) { + super(execution, stepLogger, clientProvider); + this.secretTokenStore = secretTokenStore; + this.secretTransformationStrategy = secretTransformationStrategy; + } + + private String pid() { + return getExecution().getRootProcessInstanceId(); + } + + private Variable wrap(Variable variable) { + if (variable.getName() + .equals(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES.getName())) { + return variable; + } + + DelegateExecution currentExecution = getExecution(); + String processInstanceId = pid(); + Set secureParameterNames; + + byte[] secureParameterNamesRaw = (byte[]) currentExecution.getVariable( + Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES.getName()); + + if (secureParameterNamesRaw == null) { + secureParameterNames = Set.of(); + } else { + secureParameterNames = Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES.getSerializer() + .deserialize(secureParameterNamesRaw); + } + + SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl( + secretTransformationStrategy, secureParameterNames); + + Serializer wrappedSerializer = new SecretTokenSerializer<>(variable.getSerializer(), secretTokenStore, + secretTransformationStrategyContext, + processInstanceId, variable.getName()); + + return new WrappedVariable<>(variable, wrappedSerializer); + } + + @Override + public void setVariable(Variable variable, T value) { + VariableHandling.set(getExecution(), wrap(variable), value); + } + + @Override + public T getVariable(Variable variable) { + return VariableHandling.get(getExecution(), wrap(variable)); + } + + @Override + public T getVariableIfSet(Variable variable) { + return VariableHandling.getIfSet(getExecution(), wrap(variable)); + } + + @Override + public T getVariableBackwardsCompatible(Variable variable) { + return VariableHandling.getBackwardsCompatible(getExecution(), wrap(variable)); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContextFactory.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContextFactory.java new file mode 100644 index 0000000000..9d2237c437 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContextFactory.java @@ -0,0 +1,22 @@ +package org.cloudfoundry.multiapps.controller.process.steps; + +import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.process.security.SecretTransformationStrategy; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStore; +import org.cloudfoundry.multiapps.controller.process.util.StepLogger; +import org.flowable.engine.delegate.DelegateExecution; + +public final class SecureProcessContextFactory { + + private SecureProcessContextFactory() { + + } + + public static SecureProcessContext ofSecureProcessContext(DelegateExecution execution, StepLogger stepLogger, + CloudControllerClientProvider clientProvider, + SecretTokenStore secretTokenStore, + SecretTransformationStrategy secretTransformationStrategy) { + return new SecureProcessContext(execution, stepLogger, clientProvider, secretTokenStore, secretTransformationStrategy); + } + +} diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStep.java index 59cc8d626c..a1f29bb335 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStep.java @@ -20,6 +20,11 @@ import org.cloudfoundry.multiapps.controller.persistence.services.ProcessLoggerProvider; import org.cloudfoundry.multiapps.controller.persistence.services.ProgressMessageService; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.SecretTransformationStrategy; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyContainer; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyResolver; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStore; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper.CloudComponents; import org.cloudfoundry.multiapps.controller.process.util.ProcessHelper; @@ -47,6 +52,12 @@ public abstract class SyncFlowableStep implements JavaDelegate { @Inject protected ApplicationConfiguration configuration; @Inject + protected SecretTokenStoreFactory secretTokenStoreFactory; + @Inject + protected SecretTokenKeyResolver secretTokenKeyResolver; + @Inject + protected SecretTransformationStrategy secretTransformationStrategy; + @Inject private StepLogger.Factory stepLoggerFactory; @Inject private ProcessEngineConfiguration processEngineConfiguration; @@ -89,6 +100,16 @@ protected StepPhase getInitialStepPhase(ProcessContext context) { } protected ProcessContext createProcessContext(DelegateExecution execution) { + boolean isSecurityEnabled = VariableHandling.get(execution, + Variables.IS_SECURITY_ENABLED); + if (isSecurityEnabled) { + SecretTokenKeyContainer secretTokenKeyContainer = secretTokenKeyResolver.resolve(execution); + SecretTokenStore secretTokenStore = secretTokenStoreFactory.createSecretTokenStore(secretTokenKeyContainer.key(), + secretTokenKeyContainer.keyId()); + return SecureProcessContextFactory.ofSecureProcessContext(execution, stepLogger, clientProvider, secretTokenStore, + secretTransformationStrategy); + } + return new ProcessContext(execution, stepLogger, clientProvider); } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateSubscribersStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateSubscribersStep.java index 1bdd8536d8..3b461e739c 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateSubscribersStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UpdateSubscribersStep.java @@ -6,6 +6,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; import java.util.TreeMap; import java.util.UUID; import java.util.function.BiFunction; @@ -32,7 +33,8 @@ import org.cloudfoundry.multiapps.controller.core.helpers.ReferencingPropertiesVisitor; import org.cloudfoundry.multiapps.controller.core.helpers.v2.ConfigurationReferencesResolver; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.core.security.token.TokenService; import org.cloudfoundry.multiapps.controller.persistence.model.CloudTarget; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry; @@ -97,6 +99,8 @@ public class UpdateSubscribersStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.UPDATING_SUBSCRIBERS); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); List publishedEntries = StepsUtil.getPublishedEntriesFromSubProcesses(context, flowableFacade); List deletedEntries = StepsUtil.getDeletedEntriesFromAllProcesses(context, flowableFacade); List updatedEntries = ListUtils.union(publishedEntries, deletedEntries); @@ -116,7 +120,8 @@ protected StepPhase executeStep(ProcessContext context) { CloudControllerClient client = getClient(context, target); CloudApplication subscriberApp = client.getApplication(subscription.getAppName()); Map appEnv = client.getApplicationEnvironment(subscriberApp.getGuid()); - CloudApplication updatedApplication = updateSubscriber(context, subscription, client, subscriberApp, appEnv); + CloudApplication updatedApplication = updateSubscriber(context, subscription, client, subscriberApp, appEnv, + dynamicSecureSerialization); if (updatedApplication != null) { addApplicationToProperList(updatedSubscribers, updatedServiceBrokerSubscribers, updatedApplication, appEnv); } @@ -159,9 +164,10 @@ private List removeDuplicates(List applicati } private CloudApplication updateSubscriber(ProcessContext context, ConfigurationSubscription subscription, CloudControllerClient client, - CloudApplication subscriberApp, Map appEnv) { + CloudApplication subscriberApp, Map appEnv, + DynamicSecureSerialization dynamicSecureSerialization) { try { - return attemptToUpdateSubscriber(context, client, subscription, subscriberApp, appEnv); + return attemptToUpdateSubscriber(context, client, subscription, subscriberApp, appEnv, dynamicSecureSerialization); } catch (CloudOperationException | SLException e) { String appName = subscription.getAppName(); String mtaId = subscription.getMtaId(); @@ -173,12 +179,12 @@ private CloudApplication updateSubscriber(ProcessContext context, ConfigurationS private CloudApplication attemptToUpdateSubscriber(ProcessContext context, CloudControllerClient client, ConfigurationSubscription subscription, CloudApplication subscriberApp, - Map appEnv) { + Map appEnv, DynamicSecureSerialization dynamicSecureSerialization) { CloudHandlerFactory handlerFactory = CloudHandlerFactory.forSchemaVersion(MAJOR_SCHEMA_VERSION); DeploymentDescriptor dummyDescriptor = buildDummyDescriptor(subscription, handlerFactory); getStepLogger().debug(org.cloudfoundry.multiapps.controller.core.Messages.DEPLOYMENT_DESCRIPTOR, - SecureSerialization.toJson(dummyDescriptor)); + dynamicSecureSerialization.toJson(dummyDescriptor)); ConfigurationReferencesResolver resolver = handlerFactory.getConfigurationReferencesResolver(configurationEntryService, new DummyConfigurationFilterParser( @@ -189,12 +195,12 @@ private CloudApplication attemptToUpdateSubscriber(ProcessContext context, Cloud Variables.SPACE_NAME)), configuration); resolver.resolve(dummyDescriptor); - getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, SecureSerialization.toJson(dummyDescriptor)); + getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, dynamicSecureSerialization.toJson(dummyDescriptor)); dummyDescriptor = handlerFactory.getDescriptorReferenceResolver(dummyDescriptor, new ResolverBuilder(), new ResolverBuilder(), new ResolverBuilder(), SupportedParameters.DYNAMIC_RESOLVABLE_PARAMETERS) .resolve(); - getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, SecureSerialization.toJson(dummyDescriptor)); + getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, dynamicSecureSerialization.toJson(dummyDescriptor)); ApplicationCloudModelBuilder applicationCloudModelBuilder = handlerFactory.getApplicationCloudModelBuilder(dummyDescriptor, shouldUsePrettyPrinting(), diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java index 3feebe0f46..198fd9911f 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStep.java @@ -5,6 +5,7 @@ import java.util.Map; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ExecutorService; @@ -20,7 +21,8 @@ import org.cloudfoundry.multiapps.controller.core.Constants; import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationFileDigestDetector; import org.cloudfoundry.multiapps.controller.core.helpers.MtaArchiveElements; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.util.ApplicationArchiveContext; @@ -87,26 +89,30 @@ public StepPhase executeAsyncStep(ProcessContext context) throws FileStorageExce } } - Optional mostRecentPackage = cloudPackagesGetter.getMostRecentAppPackage(client, cloudApp.getGuid()); + Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + + Optional mostRecentPackage = cloudPackagesGetter.getMostRecentAppPackage(client, cloudApp.getGuid(), + dynamicSecureSerialization); if (mostRecentPackage.isEmpty()) { return StepPhase.POLL; } CloudPackage latestPackage = mostRecentPackage.get(); - Optional currentPackage = cloudPackagesGetter.getAppPackage(client, cloudApp.getGuid()); + Optional currentPackage = cloudPackagesGetter.getAppPackage(client, cloudApp.getGuid(), dynamicSecureSerialization); if (currentPackage.isEmpty() && isPackageInValidState(latestPackage)) { context.setVariable(Variables.SHOULD_SKIP_APPLICATION_UPLOAD, true); - return useLatestPackage(context, latestPackage); + return useLatestPackage(context, latestPackage, dynamicSecureSerialization); } - if (currentPackage.isEmpty() || !isAppStagedCorrectly(context, cloudApp)) { + if (currentPackage.isEmpty() || !isAppStagedCorrectly(context, cloudApp, dynamicSecureSerialization)) { return StepPhase.POLL; } if (isPackageInValidState(latestPackage) && (context.getVariable(Variables.APP_NEEDS_RESTAGE) || !packagesMatch( currentPackage.get(), latestPackage))) { context.setVariable(Variables.SHOULD_SKIP_APPLICATION_UPLOAD, true); - return useLatestPackage(context, latestPackage); + return useLatestPackage(context, latestPackage, dynamicSecureSerialization); } getStepLogger().info(Messages.CONTENT_OF_APPLICATION_0_IS_NOT_CHANGED, applicationToProcess.getName()); @@ -142,8 +148,9 @@ private boolean detectApplicationFileDigestChanges(Map appEnv, S return !newApplicationDigest.equals(currentApplicationDigest); } - private StepPhase useLatestPackage(ProcessContext context, CloudPackage latestUnusedPackage) { - getStepLogger().debug(Messages.THE_NEWEST_PACKAGE_WILL_BE_USED_0, SecureSerialization.toJson(latestUnusedPackage)); + private StepPhase useLatestPackage(ProcessContext context, CloudPackage latestUnusedPackage, + DynamicSecureSerialization dynamicSecureSerialization) { + getStepLogger().debug(Messages.THE_NEWEST_PACKAGE_WILL_BE_USED_0, dynamicSecureSerialization.toJson(latestUnusedPackage)); context.setVariable(Variables.CLOUD_PACKAGE, latestUnusedPackage); return StepPhase.POLL; } @@ -154,9 +161,10 @@ private boolean isPackageInValidState(CloudPackage cloudPackage) { && cloudPackage.getStatus() != Status.AWAITING_UPLOAD; } - private boolean isAppStagedCorrectly(ProcessContext context, CloudApplication cloudApp) { + private boolean isAppStagedCorrectly(ProcessContext context, CloudApplication cloudApp, + DynamicSecureSerialization dynamicSecureSerialization) { ApplicationStager appStager = new ApplicationStager(context); - return appStager.isApplicationStagedCorrectly(cloudApp); + return appStager.isApplicationStagedCorrectly(cloudApp, dynamicSecureSerialization); } @Override diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStager.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStager.java index 435fde7306..d0a4c7e30b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStager.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStager.java @@ -14,7 +14,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.PackageState; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; import org.cloudfoundry.multiapps.controller.process.steps.StepPhase; @@ -86,7 +86,7 @@ private PackageState getBuildState(CloudBuild build) { } } - public boolean isApplicationStagedCorrectly(CloudApplication app) { + public boolean isApplicationStagedCorrectly(CloudApplication app, DynamicSecureSerialization dynamicSecureSerialization) { List buildsForApplication = client.getBuildsForApplication(app.getGuid()); if (buildsForApplication.isEmpty()) { logger.debug(Messages.NO_BUILD_FOUND_FOR_APPLICATION, app.getName()); @@ -101,7 +101,7 @@ public boolean isApplicationStagedCorrectly(CloudApplication app) { if (isBuildStagedCorrectly(build)) { return true; } - logMessages(app, build); + logMessages(app, build, dynamicSecureSerialization); return false; } @@ -116,9 +116,9 @@ private boolean isBuildStagedCorrectly(CloudBuild build) { return build.getState() == CloudBuild.State.STAGED && build.getDropletInfo() != null && build.getError() == null; } - private void logMessages(CloudApplication app, CloudBuild build) { + private void logMessages(CloudApplication app, CloudBuild build, DynamicSecureSerialization dynamicSecureSerialization) { logger.info(Messages.APPLICATION_NOT_STAGED_CORRECTLY, app.getName()); - logger.debug(Messages.LAST_BUILD, SecureSerialization.toJson(build)); + logger.debug(Messages.LAST_BUILD, dynamicSecureSerialization.toJson(build)); } public void bindDropletToApplication(UUID appGuid) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetter.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetter.java index 8224a94a31..c92e54d2c6 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetter.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetter.java @@ -12,7 +12,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.CloudOperationException; import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudPackage; import org.cloudfoundry.multiapps.controller.client.facade.domain.DropletInfo; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.cloudfoundry.multiapps.controller.process.Messages; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -23,18 +23,19 @@ public class CloudPackagesGetter { private static final Logger LOGGER = LoggerFactory.getLogger(CloudPackagesGetter.class); - public Optional getAppPackage(CloudControllerClient client, UUID applicationGuid) { + public Optional getAppPackage(CloudControllerClient client, UUID applicationGuid, + DynamicSecureSerialization dynamicSecureSerialization) { Optional currentDropletForApplication = findOrReturnEmpty( () -> client.getCurrentDropletForApplication(applicationGuid)); if (currentDropletForApplication.isEmpty()) { - return getMostRecentAppPackage(client, applicationGuid); + return getMostRecentAppPackage(client, applicationGuid, dynamicSecureSerialization); } Optional currentCloudPackage = findOrReturnEmpty(() -> client.getPackage(currentDropletForApplication.get() .getPackageGuid())); if (currentCloudPackage.isEmpty()) { return Optional.empty(); } - LOGGER.info(MessageFormat.format(Messages.CURRENTLY_USED_PACKAGE_0, SecureSerialization.toJson(currentCloudPackage.get()))); + LOGGER.info(MessageFormat.format(Messages.CURRENTLY_USED_PACKAGE_0, dynamicSecureSerialization.toJson(currentCloudPackage.get()))); return currentCloudPackage; } @@ -50,10 +51,12 @@ private Optional findOrReturnEmpty(Supplier supplier) { } } - public Optional getMostRecentAppPackage(CloudControllerClient client, UUID applicationGuid) { + public Optional getMostRecentAppPackage(CloudControllerClient client, UUID applicationGuid, + DynamicSecureSerialization dynamicSecureSerialization) { List cloudPackages = client.getPackagesForApplication(applicationGuid); LOGGER.info( - MessageFormat.format(Messages.PACKAGES_FOR_APPLICATION_0_ARE_1, applicationGuid, SecureSerialization.toJson(cloudPackages))); + MessageFormat.format(Messages.PACKAGES_FOR_APPLICATION_0_ARE_1, applicationGuid, + dynamicSecureSerialization.toJson(cloudPackages))); return cloudPackages.stream() .max(Comparator.comparing(cloudPackage -> cloudPackage.getMetadata() .getCreatedAt())); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculator.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculator.java index be0666ae46..4ecf853ecb 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculator.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ExistingAppsToBackupCalculator.java @@ -2,6 +2,7 @@ import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -13,7 +14,8 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication.ProductizationState; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.persistence.dto.BackupDescriptor; import org.cloudfoundry.multiapps.controller.persistence.services.DescriptorBackupService; import org.cloudfoundry.multiapps.controller.process.Messages; @@ -51,7 +53,7 @@ public List calculateExistingAppsToBackup(ProcessContext conte return Collections.emptyList(); } - return getAppsWithLiveProductizationState(appsToUndeploy); + return getAppsWithLiveProductizationState(appsToUndeploy, context); } private boolean doesDeployedMtaVersionMatchToCurrentDeployment(DeployedMta detectedMta, String mtaVersionOfCurrentDescriptor) { @@ -133,7 +135,7 @@ private ProductizationState getProductizationStateOfApplication(CloudApplication .get(); } - private List getAppsWithLiveProductizationState(List appsToUndeploy) { + private List getAppsWithLiveProductizationState(List appsToUndeploy, ProcessContext context) { List appsToBackup = new ArrayList<>(); for (CloudApplication appToUndeploy : appsToUndeploy) { ProductizationState productizationStateOfDeployedApplication = getProductizationStateOfApplication(appToUndeploy); @@ -141,7 +143,9 @@ private List getAppsWithLiveProductizationState(List parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + LOGGER.info(MessageFormat.format(Messages.EXISTING_APPS_TO_BACKUP, dynamicSecureSerialization.toJson(appsToBackup))); return appsToBackup; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java index b9edeac622..7affaa14a6 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandler.java @@ -32,6 +32,8 @@ import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatraceProcessDuration; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatracePublisher; import org.cloudfoundry.multiapps.controller.process.dynatrace.ImmutableDynatraceProcessDuration; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreDeletion; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; import org.cloudfoundry.multiapps.controller.process.steps.StepsUtil; import org.cloudfoundry.multiapps.controller.process.variables.VariableHandling; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -60,6 +62,9 @@ public class OperationInFinalStateHandler { private OperationTimeAggregator operationTimeAggregator; @Inject private DynatracePublisher dynatracePublisher; + @Inject + private SecretTokenStoreFactory secretTokenStoreFactory; + private final SafeExecutor safeExecutor = new SafeExecutor(); public void handle(DelegateExecution execution, ProcessType processType, Operation.State state) { @@ -73,6 +78,7 @@ private void handleInternal(DelegateExecution execution, ProcessType processType safeExecutor.execute(() -> deleteCloudControllerClientForProcess(execution)); safeExecutor.execute(() -> setOperationState(correlationId, state)); safeExecutor.execute(() -> deletePreviousBackupDescriptors(execution, processType, state)); + safeExecutor.execute(() -> deleteSecretTokensForProcess(state, correlationId, execution)); safeExecutor.execute(() -> trackOperationDuration(correlationId, execution, processType, state)); } @@ -179,6 +185,15 @@ private void deletePreviousBackupDescriptors(DelegateExecution execution, Proces } + private void deleteSecretTokensForProcess(Operation.State state, String correlationId, DelegateExecution execution) { + if (state != State.FINISHED) { + return; + } + + SecretTokenStoreDeletion secretTokenStore = secretTokenStoreFactory.createSecretTokenStoreDeletionRelated(); + secretTokenStore.delete(correlationId); + } + private void addMtaVersionsOfDeployedMtas(List mtaVersionsToSkipDeletion, DeployedMta deployedMta, List appsToUndeploy) { Optional mtaVersion = getMtaVersionOfDeployedApplication(deployedMta); diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ServiceBindingParametersGetter.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ServiceBindingParametersGetter.java index 028b25fa1b..54343e485b 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ServiceBindingParametersGetter.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ServiceBindingParametersGetter.java @@ -2,6 +2,7 @@ import java.io.ByteArrayInputStream; import java.io.IOException; +import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -18,7 +19,8 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudServiceInstanceExtended; import org.cloudfoundry.multiapps.controller.core.helpers.MtaArchiveElements; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.persistence.services.FileService; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; @@ -44,6 +46,9 @@ public ServiceBindingParametersGetter(ProcessContext context, ArchiveEntryExtrac } public Map getServiceBindingParametersFromMta(CloudApplicationExtended app, String serviceName) { + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + Optional service = getService(context.getVariable(Variables.SERVICES_TO_BIND), serviceName); if (service.isEmpty()) { return Collections.emptyMap(); @@ -57,7 +62,7 @@ public Map getServiceBindingParametersFromMta(CloudApplicationEx } context.getStepLogger() - .debug(Messages.BINDING_PARAMETERS_FOR_APPLICATION, app.getName(), SecureSerialization.toJson(bindingParameters)); + .debug(Messages.BINDING_PARAMETERS_FOR_APPLICATION, app.getName(), dynamicSecureSerialization.toJson(bindingParameters)); return bindingParameters; } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java index bd9e78fc20..6c30b44a61 100644 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/Variables.java @@ -920,4 +920,17 @@ public Serializer> getSerializer() { .name("processUserProvidedServices") .defaultValue(false) .build(); + + Variable> SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES = ImmutableJsonBinaryVariable.> builder() + .name( + "secureExtensionDescriptorParameterNames") + .type(new TypeReference<>() { + }) + .build(); + + Variable IS_SECURITY_ENABLED = ImmutableSimpleVariable. builder() + .name("isSecurityEnabled") + .defaultValue(false) + .build(); + } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/WrappedVariable.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/WrappedVariable.java new file mode 100644 index 0000000000..59e18f6a33 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/variables/WrappedVariable.java @@ -0,0 +1,29 @@ +package org.cloudfoundry.multiapps.controller.process.variables; + +public class WrappedVariable implements Variable { + + private Variable variableToDelegate; + + private Serializer wrappedSerializer; + + public WrappedVariable(Variable delegate, Serializer serializer) { + this.variableToDelegate = delegate; + this.wrappedSerializer = serializer; + } + + @Override + public String getName() { + return variableToDelegate.getName(); + } + + @Override + public T getDefaultValue() { + return variableToDelegate.getDefaultValue(); + } + + @Override + public Serializer getSerializer() { + return wrappedSerializer; + } + +} diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/delete-services.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/delete-services.bpmn index 3bbcd81c59..2fb3142fa9 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/delete-services.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/delete-services.bpmn @@ -32,6 +32,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn index efc2b686bc..27a0b8840a 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/deploy-app.bpmn @@ -166,6 +166,7 @@ + @@ -198,6 +199,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn index 4beb858f9d..94ff13c2ad 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/undeploy-app.bpmn @@ -90,6 +90,7 @@ + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleanerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleanerTest.java new file mode 100644 index 0000000000..e8727b1aea --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleanerTest.java @@ -0,0 +1,46 @@ +package org.cloudfoundry.multiapps.controller.process.jobs; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; + +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreDeletion; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SecretTokenCleanerTest { + + private static final LocalDateTime EXPIRATION_TIME = LocalDateTime.ofInstant(Instant.ofEpochMilli(5000), ZoneId.systemDefault()); + + @Mock + private SecretTokenStoreFactory secretTokenStoreFactory; + + @Mock + private SecretTokenStoreDeletion secretTokenStoreDeletion; + + @InjectMocks + private SecretTokenCleaner cleaner; + + @BeforeEach + void initMocks() throws Exception { + MockitoAnnotations.openMocks(this) + .close(); + when(secretTokenStoreFactory.createSecretTokenStoreDeletionRelated()).thenReturn(secretTokenStoreDeletion); + when(secretTokenStoreDeletion.deleteOlderThan(EXPIRATION_TIME)).thenReturn(5); + } + + @Test + void testExecute() { + cleaner.execute(EXPIRATION_TIME); + + verify(secretTokenStoreFactory).createSecretTokenStoreDeletionRelated(); + verify(secretTokenStoreDeletion).deleteOlderThan(EXPIRATION_TIME); + } +} diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java index 3e30657e47..43252d5092 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/BlueGreenDeployMetadataTest.java @@ -54,6 +54,7 @@ protected String[] getParametersIds() { Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.SKIP_APP_DIGEST_CALCULATION.getName(), Variables.SHOULD_BACKUP_PREVIOUS_VERSION.getName(), + Variables.IS_SECURITY_ENABLED.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java index a3394198ba..f81c02dbbf 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/CtsDeployMetadataTest.java @@ -58,6 +58,7 @@ protected String[] getParametersIds() { Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.SKIP_APP_DIGEST_CALCULATION.getName(), + Variables.IS_SECURITY_ENABLED.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java index a55268bb5d..113c1d3f54 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/DeployMetadataTest.java @@ -49,6 +49,7 @@ protected String[] getParametersIds() { Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.SKIP_APP_DIGEST_CALCULATION.getName(), + Variables.IS_SECURITY_ENABLED.getName() // @formatter:on }; } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java index 30f2d7cc04..e866c2d91e 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/metadata/RollbackMtaMetadataTest.java @@ -27,7 +27,7 @@ protected String[] getParametersIds() { Variables.DELETE_SERVICES.getName(), Variables.NO_FAIL_ON_MISSING_PERMISSIONS.getName(), Variables.ABORT_ON_ERROR.getName(), Variables.APPS_START_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_STAGE_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_UPLOAD_TIMEOUT_PROCESS_VARIABLE.getName(), Variables.APPS_TASK_EXECUTION_TIMEOUT_PROCESS_VARIABLE.getName(), - Variables.PROCESS_USER_PROVIDED_SERVICES.getName() }; + Variables.PROCESS_USER_PROVIDED_SERVICES.getName(), Variables.IS_SECURITY_ENABLED.getName() }; } } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerTest.java new file mode 100644 index 0000000000..2427b4b7d6 --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerTest.java @@ -0,0 +1,218 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStore; +import org.cloudfoundry.multiapps.controller.process.security.util.SecretTokenUtil; +import org.cloudfoundry.multiapps.controller.process.variables.Serializer; +import org.flowable.common.engine.api.variable.VariableContainer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +public class SecretTokenSerializerTest { + + private SecretTokenStore secretTokenStore; + + private SecretTransformationStrategy secretTransformationStrategy; + + private static final String VARIABLE_NAME = "fake_variable"; + private static final String PROCESS_INSTANCE_ID = "pid_test"; + + @BeforeEach + void setUp() { + secretTokenStore = Mockito.mock(SecretTokenStore.class); + secretTransformationStrategy = Mockito.mock(SecretTransformationStrategy.class); + + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(Collections.emptySet()); + } + + public static final class StringSerializerHelper implements Serializer { + @Override + public Object serialize(String value) { + return value; + } + + @Override + public String deserialize(Object serializedValue) { + if (serializedValue == null) { + return null; + } + return serializedValue.toString(); + } + + public String deserialize(Object serializedValue, VariableContainer container) { + return deserialize(serializedValue); + } + } + + public static final class ListSerializerHelper implements Serializer> { + @Override + public Object serialize(List value) { + return value; + } + + @Override + public List deserialize(Object serializedValue) { + return (List) serializedValue; + } + + public List deserialize(Object serializedValue, VariableContainer container) { + return (List) serializedValue; + } + } + + @Test + void testSerializeAndDeserializeSuccessWhenEntireVariableIsSecretString() { + Set names = new HashSet<>(); + names.add("value"); + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(names); + + when(secretTokenStore.put(PROCESS_INSTANCE_ID, VARIABLE_NAME, "secret_text")).thenReturn(7L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 7L)).thenReturn("secret_text"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String inputJson = "{\"value\":\"secret_text\"}"; + Object encryptedToken = serializer.serialize(inputJson); + assertInstanceOf(String.class, encryptedToken); + assertNotEquals(inputJson, encryptedToken); + + String plainText = serializer.deserialize(encryptedToken); + assertEquals(inputJson, plainText); + } + + @Test + void testSerializeAndDeserializeSuccessWhenJsonWithStringField() { + Set secretNames = new HashSet<>(); + secretNames.add("password"); + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(secretNames); + + when(secretTokenStore.put(eq(PROCESS_INSTANCE_ID), eq(VARIABLE_NAME), eq("internal_one"))).thenReturn(13L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 13L)).thenReturn("internal_one"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String testInput = "{\"user\":\"u\",\"password\":\"internal_one\",\"other\":123}"; + Object serialized = serializer.serialize(testInput); + assertInstanceOf(String.class, serialized); + String tokenizedJson = (String) serialized; + assertTrue(tokenizedJson.contains("password")); + assertFalse(tokenizedJson.contains("internal_one")); + + String valueAtFirst = serializer.deserialize(tokenizedJson); + assertEquals(testInput, valueAtFirst); + } + + @Test + void testSerializeAndDeserializeSuccessWhenJsonWithStringFieldLargerOne() { + Set secretNames = new HashSet<>(); + secretNames.add("config"); + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(secretNames); + + when(secretTokenStore.put(eq(PROCESS_INSTANCE_ID), eq(VARIABLE_NAME), eq("must_not_be_shown"))).thenReturn(15L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 15L)).thenReturn("must_not_be_shown"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String testInput = + "{\"id\":\"0001\",\"type\":\"donut\",\"name\":\"Cake\",\"image\":{\"url\":\"images/0001.jpg\",\"width\":200,\"height\":200},\"thumbnail\":{\"url\":\"images/thumbnails/0001.jpg\",\"config\":\"must_not_be_shown\",\"height\":32}}"; + Object serialized = serializer.serialize(testInput); + assertInstanceOf(String.class, serialized); + String tokenizedJson = (String) serialized; + assertTrue(tokenizedJson.contains("config")); + assertFalse(tokenizedJson.contains("must_not_be_shown")); + + String valueAtFirst = serializer.deserialize(tokenizedJson); + assertEquals(testInput, valueAtFirst); + } + + @Test + void testSerializeSuccessWhenSecretParameterIsAReference() { + Set secretNames = new HashSet<>(); + secretNames.add("password"); + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(secretNames); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String testInput = "{\"password\":\"${referenceToSomething}\"}"; + Object testOutput = serializer.serialize(testInput); + + assertInstanceOf(String.class, testOutput); + String tokenizedJson = (String) testOutput; + assertEquals(testInput, tokenizedJson); + } + + @Test + void testDeserializeWhenPlainTokenString() { + String token = SecretTokenUtil.of(42L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 42L)).thenReturn("plain_value"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String result = serializer.deserialize(token); + assertEquals("plain_value", result); + } + + @Test + void testSerializeLeavesPlainNonJsonUntouchedWhenCsvDisabled() { + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(Collections.emptySet()); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String testInput = "just_a_plain_string"; + Object result = serializer.serialize(testInput); + assertEquals(testInput, result); + assertEquals(testInput, serializer.deserialize(result)); + } + + @Test + void testSerializeAndDeserializeWhenListOfJsonElements() { + Set secretNames = new HashSet<>(); + secretNames.add("password"); + when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(secretNames); + + when(secretTokenStore.put(PROCESS_INSTANCE_ID, VARIABLE_NAME, "p1")).thenReturn(100L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 100L)).thenReturn("p1"); + + SecretTokenSerializer> serializer = new SecretTokenSerializer<>( + new ListSerializerHelper(), secretTokenStore, secretTransformationStrategy, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + List testInput = List.of("{\"password\":\"p1\"}", "{\"other\":1}"); + Object result = serializer.serialize(testInput); + assertInstanceOf(List.class, result); + List tokenized = (List) result; + + String firstElement = String.valueOf(tokenized.get(0)); + assertTrue(firstElement.contains("password")); + assertFalse(firstElement.contains("p1")); + + List valueAtFirst = serializer.deserialize(tokenized); + assertEquals(testInput, valueAtFirst); + } + +} diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImplTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImplTest.java new file mode 100644 index 0000000000..4bc5b17c07 --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImplTest.java @@ -0,0 +1,130 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +import org.cloudfoundry.multiapps.controller.client.facade.CloudControllerClient; +import org.cloudfoundry.multiapps.controller.client.facade.domain.CloudServiceInstance; +import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; +import org.flowable.engine.delegate.DelegateExecution; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.Mockito; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +public class SecretTokenKeyResolverImplTest { + + private CloudControllerClientProvider cloudControllerClientProvider; + + private CloudControllerClient cloudControllerClient; + + private DelegateExecution execution; + + private CloudServiceInstance cloudServiceInstance; + + private SecretTokenKeyResolver secretTokenKeyResolver; + + @BeforeEach + void setUp() { + cloudControllerClientProvider = Mockito.mock(CloudControllerClientProvider.class); + cloudControllerClient = Mockito.mock(CloudControllerClient.class); + execution = Mockito.mock(DelegateExecution.class); + cloudServiceInstance = Mockito.mock(CloudServiceInstance.class); + + when(execution.getVariable(Variables.SPACE_GUID.getName())).thenReturn("space-guid"); + when(execution.getVariable(Variables.CORRELATION_ID.getName())).thenReturn("corr-id"); + when(execution.getVariable(Variables.USER.getName())).thenReturn("user"); + when(execution.getVariable(Variables.USER_GUID.getName())).thenReturn("user-guid"); + + when(execution.getVariable(Variables.MTA_ID.getName())).thenReturn("my-mta"); + when(execution.getVariable(Variables.MTA_NAMESPACE.getName())).thenReturn("ns"); + + when(cloudControllerClientProvider.getControllerClient(anyString(), anyString(), anyString())).thenReturn( + cloudControllerClient); + + secretTokenKeyResolver = new SecretTokenKeyResolverImpl(cloudControllerClientProvider); + } + + @Test + void testResolveSuccessWithNamespace() { + String expectedUps = "__mta-secure-my-mtans"; + + UUID upsGuid = UUID.randomUUID(); + when(cloudServiceInstance.getGuid()).thenReturn(upsGuid); + when(cloudControllerClient.getServiceInstance(expectedUps)).thenReturn(cloudServiceInstance); + + Map credentials = new HashMap<>(); + credentials.put("encryptionKey", "abcdefghijklmnopqrstuvwxyz123456"); + credentials.put("keyId", "v1"); + when(cloudControllerClient.getUserProvidedServiceInstanceParameters(upsGuid)).thenReturn(credentials); + + SecretTokenKeyContainer secretTokenKeyContainer = secretTokenKeyResolver.resolve(execution); + + assertEquals("abcdefghijklmnopqrstuvwxyz123456", secretTokenKeyContainer.key()); + assertEquals("v1", secretTokenKeyContainer.keyId()); + verify(cloudControllerClient, times(1)).getServiceInstance(expectedUps); + verify(cloudControllerClient, times(1)).getUserProvidedServiceInstanceParameters(upsGuid); + } + + @Test + void testResolveSuccessWithoutNamespace() { + when(execution.getVariable(Variables.MTA_NAMESPACE.getName())).thenReturn(null); + + String expectedUps = "__mta-secure-my-mta"; + + UUID upsGuid = UUID.randomUUID(); + when(cloudServiceInstance.getGuid()).thenReturn(upsGuid); + when(cloudControllerClient.getServiceInstance(expectedUps)).thenReturn(cloudServiceInstance); + + Map creds = new HashMap(); + creds.put("encryptionKey", "abcdefghijklmnopqrstuvwxyz123456"); + creds.put("keyId", "v1"); + when(cloudControllerClient.getUserProvidedServiceInstanceParameters(upsGuid)).thenReturn(creds); + + SecretTokenKeyContainer secretTokenKeyContainer = secretTokenKeyResolver.resolve(execution); + + assertEquals("abcdefghijklmnopqrstuvwxyz123456", secretTokenKeyContainer.key()); + assertEquals("v1", secretTokenKeyContainer.keyId()); + } + + @Test + void testResolveWhenServiceInstanceMissing() { + when(cloudControllerClient.getServiceInstance("__mta-secure-my-mtans")).thenReturn(null); + + MissingUserProvidedServiceEncryptionRelatedException exception = assertThrows( + MissingUserProvidedServiceEncryptionRelatedException.class, () -> secretTokenKeyResolver.resolve(execution)); + assertTrue(exception.getMessage() + .contains("Could not retrieve service instance")); + verify(cloudControllerClient, times(1)).getServiceInstance("__mta-secure-my-mtans"); + verify(cloudControllerClient, never()).getUserProvidedServiceInstanceParameters(any(UUID.class)); + } + + @Test + void testResolveWhenCredentialsMissing() { + String expectedUps = "__mta-secure-my-mtans"; + + UUID upsGuid = UUID.randomUUID(); + when(cloudServiceInstance.getGuid()).thenReturn(upsGuid); + when(cloudControllerClient.getServiceInstance(expectedUps)).thenReturn(cloudServiceInstance); + + when(cloudControllerClient.getUserProvidedServiceInstanceParameters(upsGuid)) + .thenReturn(new HashMap<>()); + + MissingCredentialsFromUserProvidedServiceEncryptionRelated exception = assertThrows( + MissingCredentialsFromUserProvidedServiceEncryptionRelated.class, () -> secretTokenKeyResolver.resolve(execution)); + assertTrue(exception.getMessage() + .contains("Could not retrieve credentials")); + } + +} diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtilTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtilTest.java new file mode 100644 index 0000000000..dd688feafe --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtilTest.java @@ -0,0 +1,76 @@ +package org.cloudfoundry.multiapps.controller.process.security.util; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class SecretTokenUtilTest { + + @Test + void testIsTokenWhenNull() { + assertFalse(SecretTokenUtil.isToken(null)); + } + + @Test + void testIsTokenWhenEmptyString() { + assertFalse(SecretTokenUtil.isToken("")); + } + + @Test + void testIsTokenWhenWrongPrefixedString() { + assertFalse(SecretTokenUtil.isToken("fake:v1:123")); + assertFalse(SecretTokenUtil.isToken("dsc:V1:123")); + assertFalse(SecretTokenUtil.isToken("dsc:v2:123")); + assertFalse(SecretTokenUtil.isToken("dsc:v1-123")); + } + + @Test + void testIsTokenWhenWrongPostfixedString() { + assertFalse(SecretTokenUtil.isToken("dsc:v1:12a3")); + assertFalse(SecretTokenUtil.isToken("dsc:v1:12 3")); + assertFalse(SecretTokenUtil.isToken("dsc:v1:")); + } + + @Test + void testIsTokenSuccess() { + assertTrue(SecretTokenUtil.isToken("dsc:v1:0")); + assertTrue(SecretTokenUtil.isToken("dsc:v1:42")); + assertTrue(SecretTokenUtil.isToken("dsc:v1:000123")); + } + + @Test + void testIdWhenParsingDigits() { + assertEquals(0L, SecretTokenUtil.id("dsc:v1:0")); + assertEquals(42L, SecretTokenUtil.id("dsc:v1:42")); + assertEquals(123L, SecretTokenUtil.id("dsc:v1:000123")); + } + + @Test + void testIdWhenNoDigitsThrows() { + assertThrows(NumberFormatException.class, () -> SecretTokenUtil.id("dsc:v1:")); + } + + @Test + void testIdWhenNonDigitsThrows() { + assertThrows(NumberFormatException.class, () -> SecretTokenUtil.id("dsc:v1:abc")); + } + + @Test + void testOfSuccess() { + assertEquals("dsc:v1:0", SecretTokenUtil.of(0)); + assertEquals("dsc:v1:99", SecretTokenUtil.of(99)); + } + + @Test + void testFlowOfSecretTokenBuild() { + long id = 981L; + String token = SecretTokenUtil.of(id); + assertTrue(SecretTokenUtil.isToken(token)); + assertEquals(id, SecretTokenUtil.id(token)); + } + +} + diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectApplicationsToRenameStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectApplicationsToRenameStepTest.java index 9de262849e..9fa582c366 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectApplicationsToRenameStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/DetectApplicationsToRenameStepTest.java @@ -1,6 +1,7 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -18,6 +19,7 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMta; import org.cloudfoundry.multiapps.controller.core.model.ImmutableDeployedMtaApplication; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyContainer; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -27,6 +29,8 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; +import static org.mockito.ArgumentMatchers.anyString; + class DetectApplicationsToRenameStepTest extends SyncFlowableStepTest { @BeforeEach @@ -69,7 +73,7 @@ void testExecuteWithoutRenamingApps(String appName1, String appName2) { @Test void testExecuteFailsOnException() { - Mockito.when(execution.getVariable(Mockito.anyString())) + Mockito.when(execution.getVariable(anyString())) .thenThrow(new SLException("exception")); Assertions.assertThrows(SLException.class, () -> step.execute(execution), "exception"); } @@ -100,8 +104,12 @@ void testExecuteWithTwoVersionsOfAppDeletesOldAndRenamesNew() { CloudControllerClient client = Mockito.mock(CloudControllerClient.class); Mockito.when(client.getApplication("a-live", false)) .thenReturn(createApplication("a-live")); - Mockito.when(context.getControllerClient()) + Mockito.when(clientProvider.getControllerClient(anyString(), anyString(), anyString())) .thenReturn(client); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .thenReturn(new SecretTokenKeyContainer(anyString(), anyString())); + context.setVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES, Collections.emptySet()); + context.setVariable(Variables.IS_SECURITY_ENABLED, Boolean.TRUE); step.execute(execution); assertStepFinishedSuccessfully(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStepTest.java index 97a16b4274..4f87109ccb 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/MergeDescriptorsStepTest.java @@ -61,7 +61,7 @@ private void prepareContext() { @Test void testExecute1() { when(unsupportedParameterFinder.findUnsupportedParameters(Mockito.any(), Mockito.any())).thenReturn(Collections.emptyMap()); - when(merger.merge(any(), eq(Collections.emptyList()))).thenReturn(DEPLOYMENT_DESCRIPTOR); + when(merger.merge(any(), eq(Collections.emptyList()), eq(Collections.emptyList()))).thenReturn(DEPLOYMENT_DESCRIPTOR); step.execute(execution); verify(unsupportedParameterFinder, times(1)).findUnsupportedParameters(Mockito.any(), Mockito.any()); @@ -73,7 +73,7 @@ void testExecute1() { @Test void testExecute2() { - when(merger.merge(any(), eq(Collections.emptyList()))).thenThrow(new ContentException("Error!")); + when(merger.merge(any(), eq(Collections.emptyList()), eq(Collections.emptyList()))).thenThrow(new ContentException("Error!")); assertThrows(SLException.class, () -> step.execute(execution)); } diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOrKeyOperationExecutionTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOrKeyOperationExecutionTest.java index f1709049ef..514bbfc6b7 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOrKeyOperationExecutionTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/PollServiceBindingOrKeyOperationExecutionTest.java @@ -1,5 +1,6 @@ package org.cloudfoundry.multiapps.controller.process.steps; +import java.util.Collections; import java.util.List; import java.util.UUID; @@ -13,12 +14,14 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudServiceKey; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableServiceCredentialBindingOperation; import org.cloudfoundry.multiapps.controller.client.facade.domain.ServiceCredentialBindingOperation; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyContainer; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; class PollServiceBindingOrKeyOperationExecutionTest extends AsyncStepOperationTest { @@ -36,11 +39,12 @@ void initialize() { context.setVariable(Variables.SERVICE_WITH_BIND_IN_PROGRESS, SERVICE_NAME); controllerClient = Mockito.mock(CloudControllerClient.class); - - Mockito.when(context.getControllerClient()) + Mockito.when(clientProvider.getControllerClient(anyString(), anyString(), anyString())) .thenReturn(controllerClient); Mockito.when(controllerClient.getServiceInstance(SERVICE_NAME)) .thenReturn(serviceInstance); + context.setVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES, Collections.emptySet()); + context.setVariable(Variables.IS_SECURITY_ENABLED, Boolean.TRUE); } @Test @@ -48,6 +52,8 @@ void testWithSucceededBindingsAndKeys() { expectedExecutionStatus = AsyncExecutionState.FINISHED; setUpServiceBindings(ServiceCredentialBindingOperation.State.SUCCEEDED); setUpServiceKeys(ServiceCredentialBindingOperation.State.SUCCEEDED); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .thenReturn(new SecretTokenKeyContainer(anyString(), anyString())); context.setVariable(Variables.IS_SERVICE_BINDING_KEY_OPERATION_IN_PROGRESS, false); testExecuteOperations(); @@ -58,6 +64,8 @@ void testWithSucceededBindingsAndInProgressKeys() { expectedExecutionStatus = AsyncExecutionState.RUNNING; setUpServiceBindings(ServiceCredentialBindingOperation.State.SUCCEEDED); setUpServiceKeys(ServiceCredentialBindingOperation.State.IN_PROGRESS); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .thenReturn(new SecretTokenKeyContainer(anyString(), anyString())); context.setVariable(Variables.IS_SERVICE_BINDING_KEY_OPERATION_IN_PROGRESS, false); testExecuteOperations(); @@ -68,6 +76,8 @@ void testWithInProgressBindingsAndSucceededKeys() { expectedExecutionStatus = AsyncExecutionState.RUNNING; setUpServiceBindings(ServiceCredentialBindingOperation.State.IN_PROGRESS); setUpServiceKeys(ServiceCredentialBindingOperation.State.SUCCEEDED); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .thenReturn(new SecretTokenKeyContainer(anyString(), anyString())); context.setVariable(Variables.IS_SERVICE_BINDING_KEY_OPERATION_IN_PROGRESS, false); testExecuteOperations(); @@ -78,6 +88,8 @@ void testWithInProgressBindingsAndKeys() { expectedExecutionStatus = AsyncExecutionState.RUNNING; setUpServiceBindings(ServiceCredentialBindingOperation.State.IN_PROGRESS); setUpServiceKeys(ServiceCredentialBindingOperation.State.IN_PROGRESS); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .thenReturn(new SecretTokenKeyContainer(anyString(), anyString())); context.setVariable(Variables.IS_SERVICE_BINDING_KEY_OPERATION_IN_PROGRESS, false); testExecuteOperations(); diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java index 370a465745..db9a015268 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/SyncFlowableStepTest.java @@ -26,6 +26,9 @@ import org.cloudfoundry.multiapps.controller.persistence.services.ProcessLogsPersistenceService; import org.cloudfoundry.multiapps.controller.persistence.services.ProgressMessageService; import org.cloudfoundry.multiapps.controller.process.flowable.FlowableFacade; +import org.cloudfoundry.multiapps.controller.process.security.SecretTransformationStrategy; +import org.cloudfoundry.multiapps.controller.process.security.resolver.SecretTokenKeyResolver; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; import org.cloudfoundry.multiapps.controller.process.util.MockDelegateExecution; import org.cloudfoundry.multiapps.controller.process.util.ProcessHelper; import org.cloudfoundry.multiapps.controller.process.util.StepLogger; @@ -93,6 +96,12 @@ public abstract class SyncFlowableStepTest { @Mock protected CloudControllerClientProvider clientProvider; @Mock + protected SecretTokenStoreFactory secretTokenStoreFactory; + @Mock + protected SecretTransformationStrategy secretTransformationStrategy; + @Mock + protected SecretTokenKeyResolver secretTokenKeyResolver; + @Mock protected FlowableFacade flowableFacadeFacade; @Mock protected ApplicationConfiguration configuration; diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepGeneralTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepGeneralTest.java index d417058f2e..a9ca29df7a 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepGeneralTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/steps/UploadAppStepGeneralTest.java @@ -232,7 +232,7 @@ void testWithBuildStates(List builds, StepPhase stepPhase, CloudPack Map.of("DEPLOY_ATTRIBUTES", "{\"app-content-digest\":\"" + CURRENT_MODULE_DIGEST + "\"}")); var dropletPackage = createCloudPackage(Status.READY); mockCloudPackagesGetter(dropletPackage); - when(cloudPackagesGetter.getMostRecentAppPackage(any(), any())).thenReturn(Optional.of(dropletPackage)); + when(cloudPackagesGetter.getMostRecentAppPackage(any(), any(), any())).thenReturn(Optional.of(dropletPackage)); } when(client.getCurrentDropletForApplication(APP_GUID)).thenReturn(createDropletInfo(DROPLET_GUID, PACKAGE_GUID)); when(client.getBuildsForApplication(any())).thenReturn(builds); @@ -259,8 +259,8 @@ private DropletInfo createDropletInfo(UUID guid, UUID packageGuid) { } private void mockCloudPackagesGetter(CloudPackage cloudPackage) { - when(cloudPackagesGetter.getAppPackage(any(), any())).thenReturn(Optional.of(cloudPackage)); - when(cloudPackagesGetter.getMostRecentAppPackage(any(), any())).thenReturn(Optional.of(cloudPackage)); + when(cloudPackagesGetter.getAppPackage(any(), any(), any())).thenReturn(Optional.of(cloudPackage)); + when(cloudPackagesGetter.getMostRecentAppPackage(any(), any(), any())).thenReturn(Optional.of(cloudPackage)); } private void prepareClients(String applicationDigest) { diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStagerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStagerTest.java index 0a1c1e1c30..cc9cce24e2 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStagerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/ApplicationStagerTest.java @@ -21,6 +21,7 @@ import org.cloudfoundry.multiapps.controller.client.lib.domain.CloudApplicationExtended; import org.cloudfoundry.multiapps.controller.client.lib.domain.ImmutableCloudApplicationExtended; import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; import org.cloudfoundry.multiapps.controller.process.steps.StepPhase; @@ -156,7 +157,7 @@ void testIsApplicationStagedCorrectlyNoLastBuild() { CloudApplication app = createApplication(); Mockito.when(client.getBuildsForApplication(any(UUID.class))) .thenReturn(Collections.emptyList()); - Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app)); + Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app, Mockito.mock(DynamicSecureSerialization.class))); } @Test @@ -167,7 +168,7 @@ void testIsApplicationStagedCorrectlyValidBuild() { .thenReturn(List.of(build)); Mockito.when(client.getCurrentDropletForApplication(APP_GUID)) .thenReturn(Mockito.mock(DropletInfo.class)); - Assertions.assertTrue(applicationStager.isApplicationStagedCorrectly(app)); + Assertions.assertTrue(applicationStager.isApplicationStagedCorrectly(app, Mockito.mock(DynamicSecureSerialization.class))); } @Test @@ -178,7 +179,7 @@ void testIsApplicationStagedCorrectlyBuildStagedFailed() { .thenReturn(List.of(build)); Mockito.when(client.getCurrentDropletForApplication(APP_GUID)) .thenReturn(ImmutableDropletInfo.of(DROPLET_GUID, null)); - Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app)); + Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app, Mockito.mock(DynamicSecureSerialization.class))); } @Test @@ -189,7 +190,7 @@ void testIsApplicationStagedCorrectlyDropletInfoInBuildIsNull() { .thenReturn(List.of(build)); Mockito.when(client.getCurrentDropletForApplication(APP_GUID)) .thenReturn(ImmutableDropletInfo.of(DROPLET_GUID, null)); - Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app)); + Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app, Mockito.mock(DynamicSecureSerialization.class))); } @Test @@ -203,7 +204,7 @@ void testIsApplicationStagedCorrectlyApplicationCurrentDropletInfoIsNull() { .thenReturn(HttpStatus.NOT_FOUND); Mockito.when(client.getCurrentDropletForApplication(APP_GUID)) .thenThrow(cloudOperationExceptionNotFound); - Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app)); + Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app, Mockito.mock(DynamicSecureSerialization.class))); } @Test @@ -216,7 +217,7 @@ void testIsApplicationStagedCorrectlyBuildErrorNotNull() { .thenReturn(List.of(build1, build2)); Mockito.when(client.getCurrentDropletForApplication(APP_GUID)) .thenReturn(dropletInfo); - Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app)); + Assertions.assertFalse(applicationStager.isApplicationStagedCorrectly(app, Mockito.mock(DynamicSecureSerialization.class))); } @Test diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetterTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetterTest.java index c6239ac204..33cd78c3f9 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetterTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/CloudPackagesGetterTest.java @@ -15,6 +15,7 @@ import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableCloudPackage; import org.cloudfoundry.multiapps.controller.client.facade.domain.ImmutableDropletInfo; import org.cloudfoundry.multiapps.controller.client.facade.domain.Status; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; import org.junit.jupiter.api.Test; import org.mockito.Mockito; import org.springframework.http.HttpStatus; @@ -31,12 +32,14 @@ class CloudPackagesGetterTest { private static final UUID DROPLET_GUID = UUID.randomUUID(); private final CloudPackagesGetter cloudPackagesGetter = new CloudPackagesGetter(); private final CloudControllerClient client = Mockito.mock(CloudControllerClient.class); + private final DynamicSecureSerialization dynamicSecureSerialization = Mockito.mock(DynamicSecureSerialization.class); @Test void getAppPackageWithNoPackagesNoDroplet() { Mockito.when(client.getCurrentDropletForApplication(APPLICATION_GUID)) .thenThrow(getNotFoundCloudOperationException()); - Optional latestUnusedPackage = cloudPackagesGetter.getAppPackage(client, APPLICATION_GUID); + Optional latestUnusedPackage = cloudPackagesGetter.getAppPackage(client, APPLICATION_GUID, + dynamicSecureSerialization); assertFalse(latestUnusedPackage.isPresent()); } @@ -45,7 +48,7 @@ void getAppPackageExceptionIsThrown() { Mockito.when(client.getCurrentDropletForApplication(APPLICATION_GUID)) .thenThrow(getInternalServerErrorCloudOperationException()); Exception exception = assertThrows(CloudOperationException.class, - () -> cloudPackagesGetter.getAppPackage(client, APPLICATION_GUID)); + () -> cloudPackagesGetter.getAppPackage(client, APPLICATION_GUID, dynamicSecureSerialization)); assertEquals("500 Internal Server Error", exception.getMessage()); } @@ -58,8 +61,9 @@ void getLatestUnusedPackageWhenCurrentPackageIsTheSameAsNewestPackage() { .thenReturn(cloudPackage); Mockito.when(client.getPackagesForApplication(APPLICATION_GUID)) .thenReturn(List.of(cloudPackage)); - Optional currentPackage = cloudPackagesGetter.getAppPackage(client, APPLICATION_GUID); - Optional latestPackage = cloudPackagesGetter.getMostRecentAppPackage(client, APPLICATION_GUID); + Optional currentPackage = cloudPackagesGetter.getAppPackage(client, APPLICATION_GUID, dynamicSecureSerialization); + Optional latestPackage = cloudPackagesGetter.getMostRecentAppPackage(client, APPLICATION_GUID, + dynamicSecureSerialization); assertTrue(currentPackage.isPresent()); assertTrue(latestPackage.isPresent()); assertEquals(currentPackage.get() diff --git a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java index 471813c01d..efd3962d58 100644 --- a/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/util/OperationInFinalStateHandlerTest.java @@ -1,8 +1,5 @@ package org.cloudfoundry.multiapps.controller.process.util; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; - import java.time.ZonedDateTime; import java.util.stream.Stream; @@ -18,10 +15,13 @@ import org.cloudfoundry.multiapps.controller.persistence.services.OperationService; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatraceProcessDuration; import org.cloudfoundry.multiapps.controller.process.dynatrace.DynatracePublisher; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreDeletion; +import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStoreFactory; import org.cloudfoundry.multiapps.controller.process.variables.VariableHandling; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.flowable.engine.delegate.DelegateExecution; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; @@ -31,6 +31,11 @@ import org.mockito.Mockito; import org.mockito.MockitoAnnotations; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + class OperationInFinalStateHandlerTest { private static final ProcessType PROCESS_TYPE = ProcessType.DEPLOY; @@ -61,13 +66,17 @@ class OperationInFinalStateHandlerTest { private ProcessTime processTime; @Mock private OperationService operationService; + @Mock + private SecretTokenStoreFactory secretTokenStoreFactory; + @Mock + private SecretTokenStoreDeletion secretTokenStoreDeletion; @InjectMocks private final OperationInFinalStateHandler eventHandler = new OperationInFinalStateHandler(); public static Stream testHandle() { return Stream.of( -//@formatter:off + //@formatter:off Arguments.of("10", "20", PROCESS_ID, true, new String[] { }), Arguments.of("10", null, PROCESS_ID, true, new String[] { }), Arguments.of(null, "20", PROCESS_ID, true, new String[] { }), @@ -89,6 +98,8 @@ public void setUp() throws Exception { .close(); Mockito.when(stepLoggerFactory.create(Mockito.any(), Mockito.any(), Mockito.any(), Mockito.any())) .thenReturn(stepLogger); + Mockito.when(secretTokenStoreFactory.createSecretTokenStoreDeletionRelated()) + .thenReturn(secretTokenStoreDeletion); } @ParameterizedTest @@ -106,6 +117,18 @@ void testHandle(String archiveIds, String extensionDescriptorIds, String fileOwn verifyOperationSetState(); verifyDeleteDeploymentFiles(expectedFileIdsToSweep); verifyDynatracePublisher(); + verify(secretTokenStoreDeletion).delete(PROCESS_ID); + } + + @Test + void testDeleteSecretTokensForProcessWhenOperationStateNotFinished() { + prepareContext(null, null, true); + prepareOperationTimeAggregator(); + prepareOperationService(); + + eventHandler.handle(execution, PROCESS_TYPE, State.ABORTED); + + verify(secretTokenStoreFactory, never()).createSecretTokenStoreDeletionRelated(); } private void prepareContext(String archiveIds, String extensionDescriptorIds, boolean keepFiles) { @@ -156,15 +179,15 @@ private void prepareFileService(String fileIds, String fileOwnershipProcessId) t private void verifyDeleteDeploymentFiles(String[] expectedFileIdsToSweep) throws FileStorageException { for (String fileId : expectedFileIdsToSweep) { - Mockito.verify(fileService) - .deleteFile(SPACE_ID, fileId); + verify(fileService) + .deleteFile(SPACE_ID, fileId); } } private void verifyOperationSetState() { ArgumentCaptor arg = ArgumentCaptor.forClass(ImmutableOperation.class); - Mockito.verify(operationService) - .update(Mockito.any(), arg.capture()); + verify(operationService) + .update(Mockito.any(), arg.capture()); Operation updatedOperation = arg.getValue(); assertEquals(OPERATION_STATE, updatedOperation.getState()); assertFalse(updatedOperation.hasAcquiredLock()); @@ -172,8 +195,8 @@ private void verifyOperationSetState() { private void verifyDynatracePublisher() { ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(DynatraceProcessDuration.class); - Mockito.verify(dynatracePublisher) - .publishProcessDuration(argumentCaptor.capture(), Mockito.any()); + verify(dynatracePublisher) + .publishProcessDuration(argumentCaptor.capture(), Mockito.any()); DynatraceProcessDuration actualDynatraceEvent = argumentCaptor.getValue(); assertEquals(PROCESS_ID, actualDynatraceEvent.getProcessId()); assertEquals(MTA_ID, actualDynatraceEvent.getMtaId());