From 0df70a7abd1094d422ac49655833ee98094e7c64 Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Fri, 19 Dec 2025 13:54:17 +0200 Subject: [PATCH 1/3] 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 3c792a7380..2e8222412e 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 8e259ae732..fb3e6b63c2 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 @@ -296,6 +300,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."; @@ -557,6 +562,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()); From 148d46cd9db9533e3a7f1535a7c5479911ca964d Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Thu, 22 Jan 2026 16:05:09 +0200 Subject: [PATCH 2/3] Fixings of comments LMCROSSITXSADEPLOY-2301 --- .../src/main/java/module-info.java | 5 +- .../multiapps/controller/core/Constants.java | 7 +- .../multiapps/controller/core/Messages.java | 4 +- .../core/helpers/MtaDescriptorMerger.java | 4 +- .../encryption/AESDecryptionException.java | 17 -- .../encryption/AESEncryptionException.java | 17 -- .../encryption/AesEncryptionUtil.java | 55 ++-- .../DynamicSecureSerialization.java | 17 +- .../SecureSerializerConfiguration.java | 20 +- .../encryption/AesEncryptionUtilTest.java | 17 +- .../src/main/java/module-info.java | 8 +- .../controller/persistence/Messages.java | 9 +- .../persistence/dto/SecretTokenDto.java | 6 +- .../persistence/model/SecretToken.java | 4 + .../SqlSecretTokenQueryProvider.java | 115 -------- .../services/SecretTokenService.java | 41 +-- .../services/SecretTokenStorageException.java | 17 -- .../main/resources/META-INF/persistence.xml | 5 +- .../test/resources/META-INF/persistence.xml | 13 +- .../src/main/java/module-info.java | 10 +- .../controller/process/Constants.java | 11 +- .../controller/process/Messages.java | 2 + .../process/jobs/SecretTokenCleaner.java | 6 +- .../security/MissingSecretTokenException.java | 17 -- .../process/security/SecretConfig.java | 18 -- .../SecretParametersCollectingVisitor.java | 176 +++++++++++++ .../security/SecretTokenSerializer.java | 246 ++++++++++-------- .../SecretTokenSerializerJsonException.java | 17 -- ...cretTransformationStrategyContextImpl.java | 33 --- .../SecretTransformationStrategyImpl.java | 27 -- ...mUserProvidedServiceEncryptionRelated.java | 17 -- ...idedServiceEncryptionRelatedException.java | 17 -- .../resolver/SecretTokenKeyResolverImpl.java | 79 ++++-- .../store/SecretTokenRetrievalException.java | 17 -- .../security/store/SecretTokenStoreImpl.java | 86 +++--- .../store/SecretTokenStoreImplWithoutKey.java | 23 +- .../store/SecretTokenStoringException.java | 17 -- .../security/util/SecretTokenUtil.java | 10 +- .../security/util/SecureLoggingUtil.java | 17 ++ .../BuildApplicationDeployModelStep.java | 5 +- .../steps/BuildCloudDeployModelStep.java | 5 +- .../steps/BuildCloudUndeployModelStep.java | 5 +- .../CalculateServiceKeyForWaitingStep.java | 6 +- .../steps/CollectSystemParametersStep.java | 6 +- .../process/steps/ComputeNextModulesStep.java | 6 +- .../CreateOrUpdateServiceBrokerStep.java | 6 +- ...eDiscontinuedConfigurationEntriesStep.java | 6 +- .../steps/DeleteSubscriptionsStep.java | 6 +- .../process/steps/DetectDeployedMtaStep.java | 6 +- .../DetermineServiceBindingsToDeleteStep.java | 6 +- ...ServiceCreateUpdateServiceActionsStep.java | 6 +- ...mineServiceDeleteActionsToExecuteStep.java | 6 +- ...icesWithResolvedDynamicParametersStep.java | 5 +- .../process/steps/MergeDescriptorsStep.java | 227 +--------------- .../steps/PollServiceOperationsExecution.java | 6 +- .../process/steps/ProcessDescriptorStep.java | 8 +- .../ProcessMtaExtensionDescriptorsStep.java | 17 +- .../PublishConfigurationEntriesStep.java | 5 +- .../process/steps/SecureProcessContext.java | 40 +-- .../steps/SecureProcessContextFactory.java | 22 +- .../process/steps/SyncFlowableStep.java | 12 +- .../process/steps/UpdateSubscribersStep.java | 6 +- .../process/steps/UploadAppStep.java | 6 +- .../util/ExistingAppsToBackupCalculator.java | 6 +- .../util/ServiceBindingParametersGetter.java | 6 +- .../process/variables/WrappedVariable.java | 4 +- .../process/backup-existing-app.bpmn | 2 + .../controller/process/delete-services.bpmn | 4 + .../controller/process/deploy-app.bpmn | 10 + .../process/process-batches-sequentially.bpmn | 1 + .../process/recreate-service-keys.bpmn | 2 + .../controller/process/undeploy-app.bpmn | 6 + .../controller/process/xs2-bg-deploy.bpmn | 2 + .../controller/process/xs2-deploy.bpmn | 2 + .../controller/process/xs2-undeploy.bpmn | 2 + .../security/SecretTokenSerializerTest.java | 120 +++++++-- .../SecretTokenKeyResolverImplTest.java | 7 +- .../security/util/SecretTokenUtilTest.java | 38 +-- .../DetectApplicationsToRenameStepTest.java | 13 +- ...iceBindingOrKeyOperationExecutionTest.java | 20 +- .../process/steps/SyncFlowableStepTest.java | 3 - 81 files changed, 839 insertions(+), 1065 deletions(-) delete mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESDecryptionException.java delete mode 100644 multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESEncryptionException.java delete mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/providers/SqlSecretTokenQueryProvider.java delete mode 100644 multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenStorageException.java delete 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/SecretParametersCollectingVisitor.java delete mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerJsonException.java delete mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java delete mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyImpl.java delete mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingCredentialsFromUserProvidedServiceEncryptionRelated.java delete mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java delete mode 100644 multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenRetrievalException.java delete 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/SecureLoggingUtil.java diff --git a/multiapps-controller-core/src/main/java/module-info.java b/multiapps-controller-core/src/main/java/module-info.java index 1686ae4acb..6469829509 100644 --- a/multiapps-controller-core/src/main/java/module-info.java +++ b/multiapps-controller-core/src/main/java/module-info.java @@ -62,6 +62,7 @@ requires org.apache.httpcomponents.client5.httpclient5; requires org.apache.httpcomponents.core5.httpcore5; requires org.apache.tika.core; + requires org.bouncycastle.fips.core; requires org.cloudfoundry.multiapps.common; requires org.cloudfoundry.multiapps.controller.api; requires org.slf4j; @@ -80,9 +81,5 @@ 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 512a6432aa..c2b114d216 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,13 +29,18 @@ 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 CIPHER_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; + //The Initialisation Vector (also called nonce) is 12-bytes (96 bits), because that is the standard and recommended length for AES-GCM primarily for performance and simplicity of the implementation - + //this is the exact length that balances a sufficiently large uniqueness space with maximum computational efficiency according to the NIST specifications for the GCM variant of AES public static final int INITIALISATION_VECTOR_LENGTH = 12; + public static final int INITIALIZATION_VECTOR_POSITION = 12; + //The authentication tag in AES-GCM is always 128 bits because every version of the AES algorithm (including AES-256) processes data in fixed 128-bit blocks, + //which dictates the size of the final authentication result required by the GCM mode's internals 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 2e8222412e..84652f2548 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,8 +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!"; + public static final String ENCRYPTION_HAS_FAILED = "Encryption has failed! Errored with \"{0}\""; + public static final String DECRYPTION_HAS_FAILED = "Decryption has failed! Errored with \"{0}\""; // 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 cba0e431b0..fc291d5916 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 @@ -30,12 +30,12 @@ public MtaDescriptorMerger(CloudHandlerFactory handlerFactory, Platform platform } public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List extensionDescriptors, - List parameterNamesToBeCensored) { + List parameterNamesToBeMasked) { DescriptorValidator validator = handlerFactory.getDescriptorValidator(); validator.validateDeploymentDescriptor(deploymentDescriptor, platform); validator.validateExtensionDescriptors(extensionDescriptors, deploymentDescriptor); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parameterNamesToBeCensored); + DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parameterNamesToBeMasked); DescriptorMerger merger = handlerFactory.getDescriptorMerger(); // Merge the passed set of descriptors into one deployment descriptor. The deployment descriptor at the root of 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 deleted file mode 100644 index 575f05d219..0000000000 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESDecryptionException.java +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 2750ede2eb..0000000000 --- a/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AESEncryptionException.java +++ /dev/null @@ -1,17 +0,0 @@ -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 index 14ddfc1e98..1c1ed55173 100644 --- 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 @@ -1,12 +1,19 @@ package org.cloudfoundry.multiapps.controller.core.security.encryption; import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; import java.security.SecureRandom; +import java.text.MessageFormat; import javax.crypto.Cipher; +import javax.crypto.NoSuchPaddingException; import javax.crypto.spec.GCMParameterSpec; import javax.crypto.spec.SecretKeySpec; import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.core.Constants; import org.cloudfoundry.multiapps.controller.core.Messages; @@ -14,27 +21,25 @@ public class AesEncryptionUtil { public static byte[] encrypt(String plainText, byte[] encryptionKey) { try { + if (plainText == null) { + plainText = ""; + } 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); + Cipher cipherObject = setUpCipherObject(encryptionKey, gcmInitialisationVector, true); byte[] cipherValue = cipherObject.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); - byte[] combinedCypherValueAndInitialisationVector = new byte[gcmInitialisationVector.length + cipherValue.length]; + byte[] combinedCipherValueAndInitialisationVector = new byte[gcmInitialisationVector.length + cipherValue.length]; - System.arraycopy(gcmInitialisationVector, 0, combinedCypherValueAndInitialisationVector, 0, gcmInitialisationVector.length); - System.arraycopy(cipherValue, 0, combinedCypherValueAndInitialisationVector, gcmInitialisationVector.length, + System.arraycopy(gcmInitialisationVector, 0, combinedCipherValueAndInitialisationVector, 0, gcmInitialisationVector.length); + System.arraycopy(cipherValue, 0, combinedCipherValueAndInitialisationVector, gcmInitialisationVector.length, cipherValue.length); - return combinedCypherValueAndInitialisationVector; + return combinedCipherValueAndInitialisationVector; } catch (Exception e) { - throw new AESEncryptionException(Messages.ENCRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED - + e.getMessage(), e); + throw new SLException(MessageFormat.format(Messages.ENCRYPTION_HAS_FAILED, e.getMessage()), e); } } @@ -44,20 +49,32 @@ public static String decrypt(byte[] encryptedValue, byte[] encryptionKey) { System.arraycopy(encryptedValue, 0, gcmInitialisationVector, 0, gcmInitialisationVector.length); - byte[] cipherValue = new byte[encryptedValue.length - 12]; - System.arraycopy(encryptedValue, 12, cipherValue, 0, cipherValue.length); + byte[] cipherValue = new byte[encryptedValue.length - Constants.INITIALISATION_VECTOR_LENGTH]; + System.arraycopy(encryptedValue, Constants.INITIALIZATION_VECTOR_POSITION, 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); + Cipher cipherObject = setUpCipherObject(encryptionKey, gcmInitialisationVector, false); 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); + throw new SLException(MessageFormat.format(Messages.DECRYPTION_HAS_FAILED, e.getMessage()), e); + } + } + + private static Cipher setUpCipherObject(byte[] encryptionKey, byte[] gcmInitialisationVector, boolean shouldEncrypt) + throws InvalidAlgorithmParameterException, InvalidKeyException, NoSuchPaddingException, NoSuchAlgorithmException, + NoSuchProviderException { + Cipher cipherObject = Cipher.getInstance(Constants.CIPHER_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); + + if (shouldEncrypt) { + cipherObject.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec); + } else { + cipherObject.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec); } + + return cipherObject; } } 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 index d9bf929d01..940eff15c6 100644 --- 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 @@ -16,18 +16,17 @@ public final class DynamicSecureSerialization { private final SecureSerializerConfiguration secureSerializerConfiguration; - DynamicSecureSerialization(SecureSerializerConfiguration secureSerializerConfiguration) { + public DynamicSecureSerialization(SecureSerializerConfiguration secureSerializerConfiguration) { this.secureSerializerConfiguration = secureSerializerConfiguration; } public String toJson(Object object) { - SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializer(object, secureSerializerConfiguration); + SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializer(object); return secureJsonSerializer.serialize(object); } - private static SecureJsonSerializer createDynamicJsonSerializer(Object object, - SecureSerializerConfiguration secureSerializerConfiguration) { - SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializerForVersionedEntity(object, secureSerializerConfiguration); + private SecureJsonSerializer createDynamicJsonSerializer(Object object) { + SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializerForVersionedEntity(object); if (secureJsonSerializer == null) { return new SecureJsonSerializer(secureSerializerConfiguration); } @@ -35,17 +34,15 @@ private static SecureJsonSerializer createDynamicJsonSerializer(Object object, return secureJsonSerializer; } - private static SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(Object object, - SecureSerializerConfiguration secureSerializerConfiguration) { + private SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(Object object) { if (object instanceof VersionedEntity) { - return createDynamicJsonSerializerForVersionedEntity((VersionedEntity) object, secureSerializerConfiguration); + return createDynamicJsonSerializerForVersionedEntity((VersionedEntity) object); } return null; } - private static SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(VersionedEntity versionedEntity, - SecureSerializerConfiguration secureSerializerConfiguration) { + private SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(VersionedEntity versionedEntity) { if (versionedEntity.getMajorSchemaVersion() < 3) { return null; } 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 ac1d197f4b..f5c74908f5 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,10 +2,12 @@ import java.util.Collection; import java.util.Collections; -import java.util.LinkedList; +import java.util.HashSet; import java.util.List; +import java.util.Set; import org.apache.commons.lang3.StringUtils; +import org.springframework.util.CollectionUtils; public class SecureSerializerConfiguration { @@ -21,18 +23,18 @@ public class SecureSerializerConfiguration { private Collection additionalSensitiveElementNames = Collections.emptyList(); public Collection getSensitiveElementNames() { - if (additionalSensitiveElementNames == null || additionalSensitiveElementNames.isEmpty()) { + if (CollectionUtils.isEmpty(additionalSensitiveElementNames)) { return sensitiveElementNames; } - List mergedSensitiveElementNames = new LinkedList<>(sensitiveElementNames); + Set mergedSensitiveElementNames = new HashSet<>(sensitiveElementNames); - for (String currentAdditionalSensitiveElement : additionalSensitiveElementNames) { - boolean isExistentAlready = mergedSensitiveElementNames.stream() - .anyMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase( - currentAdditionalSensitiveElement)); - if (!isExistentAlready) { - mergedSensitiveElementNames.add(currentAdditionalSensitiveElement); + for (String additionalSensitiveElement : additionalSensitiveElementNames) { + boolean isNotExistent = mergedSensitiveElementNames.stream() + .noneMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase( + additionalSensitiveElement)); + if (isNotExistent) { + mergedSensitiveElementNames.add(additionalSensitiveElement); } } 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 index dd6b49ea2b..8ac921aa47 100644 --- 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 @@ -70,15 +70,15 @@ void testDecryptWhenEncryptedValueCorrupted() { byte[] encrypted = AesEncryptionUtil.encrypt(plainText, KEY_FOR_256_32_BYTES); byte[] tampered = Arrays.copyOf(encrypted, encrypted.length); - tampered[tampered.length - 1] ^= 0x01; + tampered[tampered.length - 1] = (byte) (tampered[tampered.length - 1] ^ 0x01); - assertThrows(RuntimeException.class, () -> AesEncryptionUtil.decrypt(tampered, KEY_FOR_256_32_BYTES)); + assertThrows(Exception.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)); + assertThrows(Exception.class, () -> AesEncryptionUtil.encrypt("simple text", wrongEncryptionKey)); } @Test @@ -87,19 +87,14 @@ void testEncryptDecryptFlowWhenWrongEncryptionKey() { 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)); + assertThrows(Exception.class, () -> AesEncryptionUtil.decrypt(encrypted, differentValidKey)); } @Test void testDecryptWhenEncryptedValueTooShort() { byte[] tooShort = new byte[7]; - assertThrows(RuntimeException.class, () -> AesEncryptionUtil.decrypt(tooShort, KEY_FOR_256_32_BYTES)); + assertThrows(Exception.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 42a7e377a3..eed9627c09 100644 --- a/multiapps-controller-persistence/src/main/java/module-info.java +++ b/multiapps-controller-persistence/src/main/java/module-info.java @@ -43,12 +43,16 @@ requires google.cloud.core; requires google.cloud.nio; requires google.cloud.storage; + requires jakarta.xml.bind; requires jakarta.inject; + requires liquibase.core; requires org.apache.logging.log4j; requires org.apache.logging.log4j.core; requires org.apache.commons.io; requires org.apache.commons.collections4; requires org.apache.commons.lang3; + requires org.bouncycastle.fips.core; + requires org.bouncycastle.fips.pkix; requires org.cloudfoundry.multiapps.common; requires org.eclipse.persistence.core; requires org.slf4j; @@ -57,8 +61,4 @@ requires static java.compiler; 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 173f189528..876fae15b6 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 @@ -46,13 +46,10 @@ public final class Messages { 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"; + public static final String INSERT_QUERY_NOT_RETURN_ID_FOR_VARIABLE_NAME_0_AND_PROCESS_WITH_ID_1 = "INSERT into secret_token table did not return an id for a variable name \"{0}\" and process with id \"{1}\""; // ERROR log messages: public static final String UPLOAD_STREAM_FAILED_TO_CLOSE = "Cannot close file upload stream"; @@ -82,6 +79,10 @@ public final class Messages { public static final String DELETED_0_FILES_WITH_ID_1_AND_SPACE_2 = "Deleted {0} files with ID \"{1}\" and space \"{2}\"."; public static final String DELETED_0_FILES_WITHOUT_CONTENT = "Deleted {0} files without content."; public static final String FAILED_TO_SAVE_OPERATION_LOG_IN_DATABASE = "Failed to save operation log in database."; + public static final String STORED_SECRET_TOKEN_WITH_VARIABLE_NAME_0_FOR_PROCESS_WITH_ID_1_AND_ENCRYPTION_KEY_ID_2 = "Stored secret token with a variable name \"{0}\" for process with id \"{1}\" and encryption key id \"{2}\""; + public static final String RETRIEVED_SECRET_TOKEN_WITH_ID_0_FOR_PROCESS_WITH_ID_1 = "Retrieved secret token with id \"{0}\" for process with id \"{1}\""; + public static final String DELETED_SECRET_TOKENS_FOR_PROCESS_WITH_ID_0 = "Deleted secret tokens for process with id \"{0}\""; + public static final String DELETED_SECRET_TOKENS_WITH_EXPIRATION_DATE_0 = "Deleted secret tokens with an expiration date \"{0}\""; protected 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 index 8df580397f..b2683eaf77 100644 --- 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 @@ -22,11 +22,11 @@ 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 PROCESS_INSTANCE_ID = "processInstanceId"; + public static final String VARIABLE_NAME = "variableName"; public static final String CONTENT = "content"; public static final String TIMESTAMP = "timestamp"; - public static final String KEY_ID = "key_id"; + public static final String KEY_ID = "keyId"; } @Id 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 index 0782862caf..558d351345 100644 --- 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 @@ -2,9 +2,13 @@ import java.time.LocalDateTime; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonSerialize; import org.immutables.value.Value; @Value.Immutable +@JsonSerialize(as = ImmutableSecretToken.class) +@JsonDeserialize(as = ImmutableSecretToken.class) public interface SecretToken { @Value.Default 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 deleted file mode 100644 index e9a1b3e727..0000000000 --- a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/query/providers/SqlSecretTokenQueryProvider.java +++ /dev/null @@ -1,115 +0,0 @@ -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 index 58808bdaff..f279ff8bbe 100644 --- 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 @@ -1,55 +1,26 @@ 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) { + public SecretTokenService(EntityManagerFactory entityManagerFactory, SecretTokenMapper secretTokenMapper) { 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)); + this.secretTokenMapper = secretTokenMapper; } public SecretTokenQuery createQuery() { @@ -100,12 +71,4 @@ public SecretTokenDto toDto(SecretToken secretToken) { } } - 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 deleted file mode 100644 index 32525d9306..0000000000 --- a/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenStorageException.java +++ /dev/null @@ -1,17 +0,0 @@ -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/META-INF/persistence.xml b/multiapps-controller-persistence/src/main/resources/META-INF/persistence.xml index 684a213264..0885b43393 100644 --- a/multiapps-controller-persistence/src/main/resources/META-INF/persistence.xml +++ b/multiapps-controller-persistence/src/main/resources/META-INF/persistence.xml @@ -14,11 +14,12 @@ org.cloudfoundry.multiapps.controller.persistence.dto.LockOwnerDto org.cloudfoundry.multiapps.controller.persistence.dto.AsyncUploadJobDto org.cloudfoundry.multiapps.controller.persistence.dto.BackupDescriptorDto + org.cloudfoundry.multiapps.controller.persistence.dto.SecretTokenDto true - + + value="org.eclipse.persistence.logging.slf4j.SLF4JLogger"/> diff --git a/multiapps-controller-persistence/src/test/resources/META-INF/persistence.xml b/multiapps-controller-persistence/src/test/resources/META-INF/persistence.xml index c3a8d536d4..3050df9d56 100644 --- a/multiapps-controller-persistence/src/test/resources/META-INF/persistence.xml +++ b/multiapps-controller-persistence/src/test/resources/META-INF/persistence.xml @@ -12,16 +12,17 @@ org.cloudfoundry.multiapps.controller.persistence.dto.AccessTokenDto org.cloudfoundry.multiapps.controller.persistence.dto.TextAttributeConverter org.cloudfoundry.multiapps.controller.persistence.dto.BackupDescriptorDto + org.cloudfoundry.multiapps.controller.persistence.dto.SecretTokenDto true - + - - + value="jdbc:h2:mem:configuration-subscriptions:DB_CLOSE_DELAY=-1"/> + + - - + + \ No newline at end of file diff --git a/multiapps-controller-process/src/main/java/module-info.java b/multiapps-controller-process/src/main/java/module-info.java index cf3854be88..c7816edcb5 100644 --- a/multiapps-controller-process/src/main/java/module-info.java +++ b/multiapps-controller-process/src/main/java/module-info.java @@ -26,6 +26,7 @@ requires org.cloudfoundry.client; requires com.fasterxml.jackson.core; requires com.fasterxml.jackson.databind; + requires com.google.common; requires flowable.bpmn.model; requires flowable.engine.common; requires flowable.engine.common.api; @@ -33,6 +34,7 @@ requires flowable.job.service.api; requires flowable.variable.service; requires flowable.variable.service.api; + requires jakarta.annotation; requires jakarta.persistence; requires java.sql; requires jakarta.xml.bind; @@ -43,6 +45,7 @@ requires org.apache.commons.collections4; requires org.apache.commons.io; requires org.apache.commons.lang3; + requires org.bouncycastle.fips.core; requires org.cloudfoundry.multiapps.common; requires org.cloudfoundry.multiapps.controller.client; requires org.cloudfoundry.multiapps.controller.persistence; @@ -60,9 +63,8 @@ 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; + requires org.mybatis; + requires java.annotation; + requires tools.jackson.databind; } \ 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 762d25e6d6..815df33ce5 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 @@ -1,5 +1,7 @@ package org.cloudfoundry.multiapps.controller.process; +import java.util.regex.Pattern; + public class Constants { public static final String DEPLOY_SERVICE_ID = "xs2-deploy"; @@ -30,10 +32,17 @@ public class Constants { 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 String PLACEHOLDER_SUFFIX = "}"; + public static final String DOUBLE_APPENDED_STRING = "%s%s"; + public static final String TRIPLE_APPENDED_STRING = "%s%s%s"; + public static final String SECURE_EXTENSION_DESCRIPTOR_ID = "__mta.secure"; public static final Long UNSET_LAST_LOG_TIMESTAMP_MS = 0L; public static final int LOG_STALLED_TASK_MINUTE_INTERVAL = 5; + public static final long TTL_CACHE_ENTRY = 60_000L; + public static final int MAX_CACHE_ENTRIES = 256; + + public static final Pattern STANDARD_INT_PATTERN = Pattern.compile("[+-]?[0-9]+"); protected Constants() { } 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 fb3e6b63c2..e2846c32b8 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 @@ -86,6 +86,7 @@ public class Messages { 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}\")"; + public static final String INVALID_ENCRYPTION_KEY_LENGTH = "Length of the encryption key is invalid - it must be 32 characters long!"; // Audit log messages @@ -793,6 +794,7 @@ public class Messages { public static final String CALCULATED_TIMEOUT_FOR_INCREMENTAL_APP_INSTANCES_UPDATE_0_SECONDS = "Calculated timeout for incremental app instances update: {0} seconds"; public static final String GETTING_FEATURES_FOR_APPLICATION_0 = "Getting features for application \"{0}\""; + public static final String SECURE_EXTENSION_DESCRIPTOR_CONSTRUCTED_AND_APPLIED_FROM_ENVIRONMENT_VARIABLES = "\n\"SECURE EXTENSION DESCRIPTOR CONSTRUCTED AND APPLIED FROM ENVIRONMENT VARIABLES\""; public static final String TOTAL_SIZE_OF_ALL_RESOLVED_CONTENT_0 = "Total size for all resolved content {0}"; // Not log messages 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 index 69fe294b5b..d579a0baf3 100644 --- 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 @@ -26,12 +26,12 @@ public SecretTokenCleaner(SecretTokenStoreFactory secretTokenStoreFactory) { } public void execute(LocalDateTime expirationTime) { - LOGGER.debug(CleanUpJob.LOG_MARKER, Messages.REMOVING_EXPIRED_SECRET_TOKENS); + LOGGER.info(CleanUpJob.LOG_MARKER, Messages.REMOVING_EXPIRED_SECRET_TOKENS); SecretTokenStoreDeletion secretTokenStore = secretTokenStoreFactory.createSecretTokenStoreDeletionRelated(); - int deletedTokenCount = secretTokenStore.deleteOlderThan(expirationTime); + int tokens = secretTokenStore.deleteOlderThan(expirationTime); - LOGGER.debug(CleanUpJob.LOG_MARKER, MessageFormat.format(Messages.REMOVED_SECRET_TOKENS_0, deletedTokenCount)); + LOGGER.info(CleanUpJob.LOG_MARKER, MessageFormat.format(Messages.REMOVED_SECRET_TOKENS_0, tokens)); } } 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 deleted file mode 100644 index 9c4b196132..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/MissingSecretTokenException.java +++ /dev/null @@ -1,17 +0,0 @@ -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 index cda2defa2f..3c9218649c 100644 --- 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 @@ -1,15 +1,10 @@ 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; @@ -17,24 +12,11 @@ @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) { diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretParametersCollectingVisitor.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretParametersCollectingVisitor.java new file mode 100644 index 0000000000..6bd6fee555 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretParametersCollectingVisitor.java @@ -0,0 +1,176 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.util.Collection; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.apache.commons.collections4.MultiValuedMap; +import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; +import org.cloudfoundry.multiapps.controller.process.Constants; +import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; +import org.cloudfoundry.multiapps.mta.model.ElementContext; +import org.cloudfoundry.multiapps.mta.model.ExtensionDescriptor; +import org.cloudfoundry.multiapps.mta.model.ExtensionHook; +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.Hook; +import org.cloudfoundry.multiapps.mta.model.Module; +import org.cloudfoundry.multiapps.mta.model.ModuleType; +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.model.ResourceType; +import org.cloudfoundry.multiapps.mta.model.Visitor; + +public class SecretParametersCollectingVisitor extends Visitor { + + private Set secretParameters = new HashSet<>(); + + private final MultiValuedMap parametersNameValueMap = new ArrayListValuedHashMap<>(); + + public Set collectSecrets(DeploymentDescriptor deploymentDescriptor, List extensionDescriptors) { + deploymentDescriptor.accept(this); + + for (ExtensionDescriptor extensionDescriptor : extensionDescriptors) { + extensionDescriptor.accept(this); + } + + Set nestedParameters = new HashSet<>(); + for (Map.Entry> element : parametersNameValueMap.asMap() + .entrySet()) { + String currentParameterName = element.getKey(); + + for (String value : element.getValue()) { + if (value == null) { + continue; + } + + for (String secretParameter : secretParameters) { + if (!secretParameter.isEmpty() && value.contains(secretParameter)) { + nestedParameters.add(currentParameterName); + } + } + } + } + + Set result = new HashSet<>(secretParameters); + result.addAll(nestedParameters); + return result; + } + + @Override + public void visit(ElementContext context, DeploymentDescriptor deploymentDescriptor) { + collectParametersProperties(deploymentDescriptor); + } + + @Override + public void visit(ElementContext context, Module module) { + collectParametersProperties(module); + } + + @Override + public void visit(ElementContext context, ProvidedDependency providedDependency) { + collectParametersProperties(providedDependency); + } + + @Override + public void visit(ElementContext context, RequiredDependency requiredDependency) { + collectParametersProperties(requiredDependency); + } + + @Override + public void visit(ElementContext context, Resource resource) { + collectParametersProperties(resource); + } + + @Override + public void visit(ElementContext context, Hook hook) { + collectParametersProperties(hook); + } + + @Override + public void visit(ElementContext context, ExtensionDescriptor extensionDescriptor) { + if (extensionDescriptor.getId() + .equals(Constants.SECURE_EXTENSION_DESCRIPTOR_ID) && extensionDescriptor.getParameters() != null) { + secretParameters.addAll(extensionDescriptor.getParameters() + .keySet()); + } + collectParametersProperties(extensionDescriptor); + } + + @Override + public void visit(ElementContext context, ExtensionModule extensionModule) { + collectParametersProperties(extensionModule); + } + + @Override + public void visit(ElementContext context, ExtensionProvidedDependency extensionProvidedDependency) { + collectParametersProperties(extensionProvidedDependency); + } + + @Override + public void visit(ElementContext context, ExtensionRequiredDependency extensionRequiredDependency) { + collectParametersProperties(extensionRequiredDependency); + } + + @Override + public void visit(ElementContext context, ExtensionResource extensionResource) { + collectParametersProperties(extensionResource); + } + + @Override + public void visit(ElementContext context, Platform platform) { + collectParametersProperties(platform); + } + + @Override + public void visit(ElementContext context, ResourceType resourceType) { + collectParametersProperties(resourceType); + } + + @Override + public void visit(ElementContext context, ModuleType moduleType) { + collectParametersProperties(moduleType); + } + + @Override + public void visit(ElementContext context, ExtensionHook extensionHook) { + collectParametersProperties(extensionHook); + } + + private void collectParametersProperties(Object object) { + if (object instanceof ParametersContainer) { + ParametersContainer parametersContainer = (ParametersContainer) object; + Map parameters = parametersContainer.getParameters(); + + for (Map.Entry parameter : parameters.entrySet()) { + addValue(parameter.getKey(), parameter.getValue()); + } + } + + if (object instanceof PropertiesContainer) { + PropertiesContainer propertiesContainer = (PropertiesContainer) object; + Map properties = propertiesContainer.getProperties(); + + for (Map.Entry property : properties.entrySet()) { + addValue(property.getKey(), property.getValue()); + } + } + } + + private void addValue(String name, Object value) { + if (value == null) { + parametersNameValueMap.put(name, ""); + } else { + parametersNameValueMap.put(name, String.valueOf(value)); + } + } + +} 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 index b3f88d470d..779075fa59 100644 --- 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 @@ -2,18 +2,21 @@ import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collections; +import java.util.HashSet; 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.core.JacksonException; +import com.fasterxml.jackson.databind.DeserializationFeature; 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.IntNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import org.cloudfoundry.multiapps.common.SLException; import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.security.store.SecretTokenStore; @@ -23,33 +26,30 @@ public class SecretTokenSerializer implements Serializer { - private static ObjectMapper objectMapper = new ObjectMapper(); + private static final ObjectMapper objectMapper = new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); - private Serializer serializer; + private final Serializer serializer; - private SecretTokenStore secretTokenStore; + private final SecretTokenStore secretTokenStore; - private SecretTransformationStrategy secretTransformationStrategy; + private final List secretValues; private final String processInstanceId; private final String variableName; - public SecretTokenSerializer(Serializer serializer, SecretTokenStore secretTokenStore, - SecretTransformationStrategy secretTransformationStrategy, String processInstanceId, String variableName) { + public SecretTokenSerializer(Serializer serializer, SecretTokenStore secretTokenStore, List secretValues, + String processInstanceId, + String variableName) { this.serializer = serializer; this.secretTokenStore = secretTokenStore; - this.secretTransformationStrategy = secretTransformationStrategy; + this.secretValues = secretValues; 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) { @@ -73,16 +73,7 @@ public T deserialize(Object serializedValue) { 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); - } - + Object valueToDecode = deserializeHelper(serializedValue); return serializer.deserialize(valueToDecode); } @@ -92,6 +83,11 @@ public T deserialize(Object serializedValue, VariableContainer container) { return serializer.deserialize(null); } + Object valueToDecode = deserializeHelper(serializedValue); + return serializer.deserialize(valueToDecode, container); + } + + private Object deserializeHelper(Object serializedValue) { Object valueToDecode = serializedValue; if (serializedValue instanceof String) { @@ -102,7 +98,7 @@ public T deserialize(Object serializedValue, VariableContainer container) { valueToDecode = handleList((List) serializedValue, false); } - return serializer.deserialize(valueToDecode, container); + return valueToDecode; } private Object handleString(String stringObject, boolean censor) { @@ -111,7 +107,7 @@ private Object handleString(String stringObject, boolean censor) { return transformedJson; } - if (!censor && SecretTokenUtil.isToken(stringObject)) { + if (!censor && SecretTokenUtil.isSecretToken(stringObject)) { return detokenize(stringObject); } @@ -134,32 +130,36 @@ private Object handleList(List list, boolean censor) { .map(element -> { if (element instanceof String) { String transformedJson = transformJson((String) element, censor); - if (transformedJson != null) { - return transformedJson; - } else { - return element; - } + return getResultFromTransformedJson(transformedJson, element, false); } else if (element instanceof byte[]) { String transformedJson = transformJson(new String((byte[]) element), censor); - if (transformedJson != null) { - return transformedJson.getBytes(); - } else { - return element; - } + return getResultFromTransformedJson(transformedJson, element, true); } return element; }) .collect(Collectors.toList()); } + private Object getResultFromTransformedJson(String transformedJson, Object element, boolean isElementInstanceOfByte) { + if (transformedJson != null) { + if (!isElementInstanceOfByte) { + return transformedJson; + } else { + return transformedJson.getBytes(); + } + } else { + return element; + } + } + private String transformJson(String candidate, boolean censor) { - if (!isStringJson(candidate)) { + if (!isValid(candidate)) { return null; } try { JsonNode rootNode = objectMapper.readTree(candidate); - Set keys = lowerCase(secretTransformationStrategy.getJsonSecretFieldNames()); + Set keys = new HashSet<>(secretValues); boolean[] changed = new boolean[1]; JsonNode output = processJsonValue(rootNode, keys, censor, changed); @@ -169,83 +169,105 @@ private String transformJson(String candidate, boolean censor) { } return null; } catch (Exception e) { - throw new SecretTokenSerializerJsonException( - MessageFormat.format(Messages.JSON_TRANSFORMATION_FAILED_FOR_VARIABLE_0, variableName), e); + throw new SLException(MessageFormat.format(Messages.JSON_TRANSFORMATION_FAILED_FOR_VARIABLE_0, variableName), e); } } - private boolean isStringJson(String string) { - if (string == null) { + public boolean isValid(String json) { + try { + objectMapper.readTree(json); + } catch (JacksonException e) { return false; } - string = string.trim(); - return string.startsWith("{") || string.startsWith("["); + return true; } private JsonNode processJsonValue(JsonNode currentNode, Set keys, boolean censor, boolean[] changed) { if (currentNode.isObject()) { - ObjectNode objectNode = currentNode.deepCopy(); + return processObjectNode(currentNode, keys, censor, changed); + } - List fields = new ArrayList<>(); - Iterator fieldsIterator = objectNode.fieldNames(); + if (currentNode.isArray()) { + return processArrayNode(currentNode, keys, censor, changed); + } - while (fieldsIterator.hasNext()) { - fields.add(fieldsIterator.next()); - } + if (currentNode.isTextual()) { + return processTextualNode(currentNode, censor, changed); + } - 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; + return currentNode; + } + + private JsonNode processObjectNode(JsonNode currentNode, Set keys, boolean censor, boolean[] changed) { + ObjectNode objectNode = currentNode.deepCopy(); + List fields = new ArrayList<>(); + Iterator fieldsIterator = objectNode.fieldNames(); + + while (fieldsIterator.hasNext()) { + fields.add(fieldsIterator.next()); } - 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)); + for (String currentField : fields) { + JsonNode childNode = objectNode.get(currentField); + JsonNode processedNode = processJsonValue(childNode, keys, censor, changed); + + boolean doesNameMatch = keys.contains(currentField); + + if (doesNameMatch && childNode.isValueNode()) { + String currentValue = convertChildNodeToText(childNode); + censorValue(objectNode, currentValue, currentField, censor, changed, processedNode); + } else { + objectNode.set(currentField, processedNode); } - return arrayNode; } + return objectNode; + } - if (currentNode.isTextual()) { - String value = currentNode.asText(); - if (!censor && SecretTokenUtil.isToken(value)) { + private JsonNode processArrayNode(JsonNode currentNode, Set keys, boolean censor, boolean[] changed) { + ArrayNode arrayNode = currentNode.deepCopy(); + for (int i = 0; i < arrayNode.size(); i++) { + arrayNode.set(i, processJsonValue(arrayNode.get(i), keys, censor, changed)); + } + return arrayNode; + } + + private JsonNode processTextualNode(JsonNode currentNode, boolean censor, boolean[] changed) { + String value = currentNode.asText(); + if (!censor && SecretTokenUtil.isSecretToken(value)) { + changed[0] = true; + String detokenizedValue = detokenize(value); + return forceToInteger(detokenizedValue); + } + return currentNode; + } + + private void censorValue(ObjectNode objectNode, String currentValue, String currentField, boolean censor, boolean[] changed, + JsonNode processedNode) { + if (censor) { + if (SecretTokenUtil.isSecretToken(currentValue) || isPlaceholder(currentValue)) { + objectNode.put(currentField, currentValue); + } else { + objectNode.put(currentField, tokenize(currentValue)); changed[0] = true; - String detokenizedValue = detokenize(value); - return TextNode.valueOf(detokenizedValue); + } + } else { + if (SecretTokenUtil.isSecretToken(currentValue)) { + String detokenizedValue = detokenize(currentValue); + JsonNode jsonConverted = forceToInteger(detokenizedValue); + objectNode.set(currentField, jsonConverted); + changed[0] = true; + } else { + objectNode.set(currentField, processedNode); } } + } - return currentNode; + private String convertChildNodeToText(JsonNode childNode) { + if (!childNode.isNull()) { + return childNode.asText(); + } + return null; } private String tokenize(String plainText) { @@ -259,38 +281,44 @@ private String tokenize(String plainText) { } private String detokenize(String token) { - long id = SecretTokenUtil.id(token); + long id = SecretTokenUtil.extractId(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)); + throw new SLException( + 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(); + private boolean isPlaceholder(String value) { + if (value == null) { + return false; } - return keys.stream() - .filter(Objects::nonNull) - .map(String::toLowerCase) - .collect(Collectors.toSet()); + return value.contains(Constants.PLACEHOLDER_PREFIX) && value.contains(Constants.PLACEHOLDER_SUFFIX); } - private boolean isPlaceholder(String value) { - if (value == null) { + private static boolean looksLikeStandardInteger(String numberString) { + if (numberString == null) { return false; } - if (value.contains(Constants.PLACEHOLDER_PREFIX) && value.contains(Constants.PLACEHOLDER_POSTFIX)) { - return true; + return Constants.STANDARD_INT_PATTERN.matcher(numberString) + .matches(); + } + + private static JsonNode forceToInteger(String numberString) { + if (!looksLikeStandardInteger(numberString)) { + return TextNode.valueOf(numberString); + } + + try { + int parsedNumber = Integer.parseInt(numberString); + return IntNode.valueOf(parsedNumber); + } catch (NumberFormatException e) { + return TextNode.valueOf(numberString); } - 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 deleted file mode 100644 index 25c3c0554d..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerJsonException.java +++ /dev/null @@ -1,17 +0,0 @@ -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/SecretTransformationStrategyContextImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java deleted file mode 100644 index 8812421f4c..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java +++ /dev/null @@ -1,33 +0,0 @@ -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 deleted file mode 100644 index 3860f80fd2..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTransformationStrategyImpl.java +++ /dev/null @@ -1,27 +0,0 @@ -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 deleted file mode 100644 index fe0a7dc6e2..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingCredentialsFromUserProvidedServiceEncryptionRelated.java +++ /dev/null @@ -1,17 +0,0 @@ -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 deleted file mode 100644 index 8908e95ec8..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java +++ /dev/null @@ -1,17 +0,0 @@ -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/SecretTokenKeyResolverImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java index 2b4be6a296..5793edd646 100644 --- 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 @@ -1,13 +1,16 @@ package org.cloudfoundry.multiapps.controller.process.security.resolver; +import java.time.Duration; import java.util.Map; import java.util.UUID; import jakarta.inject.Inject; import jakarta.inject.Named; +import org.cloudfoundry.multiapps.common.SLException; 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.core.model.CachedMap; import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.steps.StepsUtil; @@ -18,9 +21,11 @@ @Named public class SecretTokenKeyResolverImpl implements SecretTokenKeyResolver { - @Inject CloudControllerClientProvider cloudControllerClientProvider; + private UUID serviceInstanceID; + + @Inject public SecretTokenKeyResolverImpl(CloudControllerClientProvider cloudControllerClientProvider) { this.cloudControllerClientProvider = cloudControllerClientProvider; } @@ -28,45 +33,81 @@ public SecretTokenKeyResolverImpl(CloudControllerClientProvider cloudControllerC public SecretTokenKeyResolverImpl() { } + private final Duration containerExpirationTime = Duration.ofMinutes(10); + private CachedMap cachedMap = new CachedMap<>(containerExpirationTime); + @Override public SecretTokenKeyContainer resolve(DelegateExecution execution) { + SecretTokenKeyContainer secretTokenKeyContainer = null; + if (serviceInstanceID != null) { + secretTokenKeyContainer = cachedMap.get(serviceInstanceID.toString()); + } + if (secretTokenKeyContainer != null) { + return secretTokenKeyContainer; + } + + CloudControllerClient cloudControllerClient = createCloudControllerClient(execution); + String userProvidedServiceName = getUserProvidedServiceName(execution); + + CloudServiceInstance cloudServiceInstance = cloudControllerClient.getServiceInstance(userProvidedServiceName); + if (cloudServiceInstance == null) { + throw new SLException(Messages.COULD_NOT_RETRIEVE_USER_PROVIDED_SERVICE_INSTANCE_ENCRYPTION_RELATED); + } + this.serviceInstanceID = cloudServiceInstance.getGuid(); + + Map serviceInstanceCredentials = getUserProvidedServiceInstanceParameters(cloudServiceInstance, + cloudControllerClient); + if (serviceInstanceCredentials == null || serviceInstanceCredentials.isEmpty()) { + throw new SLException(Messages.COULD_NOT_RETRIEVE_CREDENTIALS_FROM_USER_PROVIDED_SERVICE_INSTANCE_ENCRYPTION_RELATED); + } + + SecretTokenKeyContainer resultContainer = createSecretTokenKeyContainer(serviceInstanceCredentials); + cachedMap.put(serviceInstanceID.toString(), resultContainer); + return resultContainer; + } + + private CloudControllerClient createCloudControllerClient(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); + return cloudControllerClientProvider.getControllerClient(userGuid, spaceGuid, correlationId); + } + private String getUserProvidedServiceName(DelegateExecution execution) { String mtaId = VariableHandling.get(execution, Variables.MTA_ID); String namespace = VariableHandling.get(execution, Variables.MTA_NAMESPACE); - String userProvidedServiceName; + if (mtaId == null) { + throw new SLException("Missing mtaId in encryption key resolver! Cannot continue from here!"); + } if (namespace != null) { - userProvidedServiceName = Constants.USER_PROVIDED_SERVICE_PREFIX_NAME_ENCRYPTION_DECRYPTION + mtaId + namespace; + return String.format(Constants.TRIPLE_APPENDED_STRING, 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); + return String.format(Constants.DOUBLE_APPENDED_STRING, Constants.USER_PROVIDED_SERVICE_PREFIX_NAME_ENCRYPTION_DECRYPTION, + mtaId); } + } + private SecretTokenKeyContainer createSecretTokenKeyContainer(Map serviceInstanceCredentials) { String encryptionKey = serviceInstanceCredentials.get(Constants.ENCRYPTION_KEY) .toString(); + if (encryptionKey.length() != 32) { + throw new SLException(Messages.INVALID_ENCRYPTION_KEY_LENGTH); + } + String encryptionKeyId = serviceInstanceCredentials.get(Constants.KEY_ID) .toString(); return new SecretTokenKeyContainer(encryptionKey, encryptionKeyId); } + private Map getUserProvidedServiceInstanceParameters(CloudServiceInstance cloudServiceInstance, + CloudControllerClient cloudControllerClient) { + UUID serviceInstanceGuid = cloudServiceInstance.getGuid(); + return cloudControllerClient.getUserProvidedServiceInstanceParameters(serviceInstanceGuid); + } + } 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 deleted file mode 100644 index fcd2aac7ce..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenRetrievalException.java +++ /dev/null @@ -1,17 +0,0 @@ -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/SecretTokenStoreImpl.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImpl.java index d679fc6a22..7a805ab251 100644 --- 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 @@ -1,25 +1,25 @@ 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.model.ImmutableSecretToken; 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 static final Logger LOGGER = LoggerFactory.getLogger(SecretTokenStoreImpl.class); - private SecretTokenService secretTokenService; + private final SecretTokenService secretTokenService; - private String encryptionKey; + private final String encryptionKey; - private String keyId; + private final String keyId; public SecretTokenStoreImpl(SecretTokenService secretTokenService, String encryptionKey, String keyId) { this.secretTokenService = secretTokenService; @@ -33,62 +33,54 @@ private byte[] keyBytes() { @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); - } + byte[] encryptedValue; + byte[] keyBytes = keyBytes(); + 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); - } + long result = secretTokenService.add(ImmutableSecretToken.builder() + .processInstanceId(processInstanceId) + .variableName(variableName) + .content(encryptedValue) + .keyId(keyId) + .timestamp(LocalDateTime.now()) + .build()) + .getId(); + LOGGER.debug(MessageFormat.format( + Messages.STORED_SECRET_TOKEN_WITH_VARIABLE_NAME_0_FOR_PROCESS_WITH_ID_1_AND_ENCRYPTION_KEY_ID_2, + variableName, processInstanceId, keyId)); + return result; } @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); + byte[] encryptedValueFromDatabase = secretTokenService.createQuery() + .id(id) + .singleResult() + .getContent(); + if (encryptedValueFromDatabase == null) { + return null; } + byte[] keyBytes = keyBytes(); + String result = AesEncryptionUtil.decrypt(encryptedValueFromDatabase, keyBytes); + LOGGER.debug(MessageFormat.format(Messages.RETRIEVED_SECRET_TOKEN_WITH_ID_0_FOR_PROCESS_WITH_ID_1, id, processInstanceId)); + return result; } @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)); - } + secretTokenService.createQuery() + .processInstanceId(processInstanceId) + .delete(); + LOGGER.debug(MessageFormat.format(Messages.DELETED_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; - } + int result = secretTokenService.createQuery() + .olderThan(expirationTime) + .delete(); + LOGGER.debug(MessageFormat.format(Messages.DELETED_SECRET_TOKENS_WITH_EXPIRATION_DATE_0, expirationTime)); + return result; } } 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 index 3f5426c30f..85ec240853 100644 --- 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 @@ -1,6 +1,5 @@ package org.cloudfoundry.multiapps.controller.process.security.store; -import java.sql.SQLException; import java.text.MessageFormat; import java.time.LocalDateTime; @@ -11,7 +10,7 @@ public class SecretTokenStoreImplWithoutKey implements SecretTokenStoreDeletion { - private final Logger logger = LoggerFactory.getLogger(getClass()); + private static final Logger LOGGER = LoggerFactory.getLogger(SecretTokenStoreImplWithoutKey.class); private SecretTokenService secretTokenService; @@ -21,21 +20,19 @@ public SecretTokenStoreImplWithoutKey(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)); - } + secretTokenService.createQuery() + .processInstanceId(processInstanceId) + .delete(); + LOGGER.debug(MessageFormat.format(Messages.DELETED_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; - } + int result = secretTokenService.createQuery() + .olderThan(expirationTime) + .delete(); + LOGGER.debug(MessageFormat.format(Messages.DELETED_SECRET_TOKENS_WITH_EXPIRATION_DATE_0, expirationTime)); + return result; } } 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 deleted file mode 100644 index 045ce4f7a0..0000000000 --- a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoringException.java +++ /dev/null @@ -1,17 +0,0 @@ -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 index cad08c2960..0f02b69c8b 100644 --- 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 @@ -8,7 +8,7 @@ private SecretTokenUtil() { } - public static boolean isToken(String token) { + public static boolean isSecretToken(String token) { if (token == null) { return false; } @@ -24,14 +24,14 @@ public static boolean isToken(String token) { } for (int i = 0; i < tail.length(); i++) { - if (!Character.isDigit(tail.charAt(i))) { + if (!isOnlyAsciiDigit(tail.charAt(i))) { return false; } } return true; } - public static long id(String token) { + public static long extractId(String token) { return Long.parseLong(token.substring(ENCRYPTED_VALUES_PREFIX.length())); } @@ -39,4 +39,8 @@ public static String of(long id) { return ENCRYPTED_VALUES_PREFIX + id; } + private static boolean isOnlyAsciiDigit(char digit) { + return digit >= '0' && digit <= '9'; + } + } diff --git a/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecureLoggingUtil.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecureLoggingUtil.java new file mode 100644 index 0000000000..84e6d5072f --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecureLoggingUtil.java @@ -0,0 +1,17 @@ +package org.cloudfoundry.multiapps.controller.process.security.util; + +import java.util.Collection; + +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; +import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; +import org.cloudfoundry.multiapps.controller.process.variables.Variables; + +public class SecureLoggingUtil { + + public static DynamicSecureSerialization getDynamicSecureSerialization(ProcessContext context) { + Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); + return SecureSerializationFactory.ofAdditionalValues(parametersToHide); + } + +} 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 e4da0db780..cd452139f6 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 @@ -18,10 +18,10 @@ import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; 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; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.AdditionalModuleParametersReporter; import org.cloudfoundry.multiapps.controller.process.util.ApplicationEnvironmentCalculator; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -42,8 +42,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); Module module = context.getVariable(Variables.MODULE_TO_DEPLOY); getStepLogger().debug(Messages.BUILDING_CLOUD_APP_MODEL, module.getName()); Module applicationModule = findModuleInDeploymentDescriptor(context, module.getName()); 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 491f6905c1..af27e62aa2 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 @@ -30,10 +30,10 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaApplication; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; 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; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.DeprecatedBuildpackChecker; import org.cloudfoundry.multiapps.controller.process.util.ProcessTypeParser; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -80,8 +80,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); // Build a map of service keys and save them in the context: Map> serviceKeys = getServiceKeysCloudModelBuilder(context).build(); 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 b87564bbdf..7237f42d72 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 @@ -28,12 +28,12 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; 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; import org.cloudfoundry.multiapps.controller.persistence.services.DescriptorBackupService; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ExistingAppsToBackupCalculator; import org.cloudfoundry.multiapps.controller.process.util.ModulesToUndeployCalculator; import org.cloudfoundry.multiapps.controller.process.util.ProcessTypeParser; @@ -64,8 +64,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); DeployedMta deployedMta = context.getVariable(Variables.DEPLOYED_MTA); if (deployedMta == null) { 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 3a56dceb48..91c2423f67 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,7 +2,6 @@ import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import jakarta.inject.Named; @@ -11,8 +10,8 @@ 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.DynamicSecureSerialization; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ServiceUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; @@ -28,8 +27,7 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { CloudControllerClient controllerClient = context.getControllerClient(); List existingServiceKeys = ServiceUtil.getExistingServiceKeys(controllerClient, serviceToProcess, getStepLogger()); List serviceKeysInProgress = getServiceKeysInProgress(existingServiceKeys); - Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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 034d7d33ca..c30e9a7297 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,7 +2,6 @@ import java.net.URL; import java.text.MessageFormat; -import java.util.Collection; import java.util.function.Supplier; import jakarta.inject.Inject; @@ -17,10 +16,10 @@ import org.cloudfoundry.multiapps.controller.core.helpers.SystemParameters; import org.cloudfoundry.multiapps.controller.core.model.DeployedMta; 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; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.NamespaceGlobalParameters; import org.cloudfoundry.multiapps.controller.process.util.ReadOnlyParametersChecker; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -60,8 +59,7 @@ protected StepPhase executeStepInternal(ProcessContext context, boolean reserveT checkForOverwrittenReadOnlyParameters(descriptor); SystemParameters systemParameters = createSystemParameters(context, defaultDomainName, reserveTemporaryRoutes, descriptor); systemParameters.injectInto(descriptor); - Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); getStepLogger().debug(Messages.DESCRIPTOR_WITH_SYSTEM_PARAMETERS, dynamicSecureSerialization.toJson(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 c63b8374b1..76bdfa3b8f 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,7 +1,6 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import jakarta.inject.Inject; @@ -9,8 +8,8 @@ import org.apache.commons.collections4.ListUtils; import org.cloudfoundry.multiapps.controller.core.helpers.ModuleToDeployHelper; 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.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ModuleDependencyChecker; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -53,8 +52,7 @@ protected StepPhase executeStep(ProcessContext context) { // Mark next iteration data as computed context.setVariable(Variables.ITERATED_MODULES_IN_PARALLEL, ListUtils.union(completedModules, modulesForNextIteration)); - Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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 4d63c93d4d..8947252de8 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,7 +4,6 @@ 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; @@ -20,8 +19,8 @@ import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationAttributes; import org.cloudfoundry.multiapps.controller.core.model.SupportedParameters; 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.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper; import org.cloudfoundry.multiapps.controller.process.util.ExceptionMessageTailMapper.CloudComponents; import org.cloudfoundry.multiapps.controller.process.variables.Variables; @@ -37,8 +36,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); CloudServiceBroker serviceBroker = getServiceBrokerToCreate(context); if (serviceBroker == null) { return StepPhase.DONE; 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 5daec1f9c1..6564cec503 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,18 +2,17 @@ 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.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; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.flowable.FlowableFacade; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -33,8 +32,7 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DELETING_PUBLISHED_DEPENDENCIES); List entriesToDelete = getEntriesToDelete(context); - Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); deleteConfigurationEntries(entriesToDelete, context, dynamicSecureSerialization); getStepLogger().debug(Messages.PUBLISHED_DEPENDENCIES_DELETED); 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 185a9ecfed..3bf9d6d8ca 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,17 +1,16 @@ 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.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; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -28,8 +27,7 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DELETING_SUBSCRIPTIONS); List subscriptionsToDelete = context.getVariable(Variables.SUBSCRIPTIONS_TO_DELETE); - Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); getStepLogger().debug(Messages.SUBSCRIPTIONS_TO_DELETE, dynamicSecureSerialization.toJson(subscriptionsToDelete)); for (ConfigurationSubscription subscription : subscriptionsToDelete) { infoSubscriptionDeletion(subscription); 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 e219152775..259ced2a46 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,7 +1,6 @@ package org.cloudfoundry.multiapps.controller.process.steps; import java.text.MessageFormat; -import java.util.Collection; import java.util.List; import java.util.Optional; @@ -18,11 +17,11 @@ import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaService; import org.cloudfoundry.multiapps.controller.core.model.DeployedMtaServiceKey; 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; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.config.BeanDefinition; @@ -43,8 +42,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); String mtaId = context.getVariable(Variables.MTA_ID); String mtaNamespace = context.getVariable(Variables.MTA_NAMESPACE); 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 561d939a2b..59f56cb78f 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,6 +1,5 @@ package org.cloudfoundry.multiapps.controller.process.steps; -import java.util.Collection; import java.util.List; import java.util.UUID; @@ -9,8 +8,8 @@ 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.DynamicSecureSerialization; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.annotation.Scope; @@ -26,8 +25,7 @@ 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); - Collection parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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 4543cfcd0d..da5a65dc5e 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,7 +10,6 @@ import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Set; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -30,9 +29,9 @@ 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.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.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ArchiveEntryExtractor; import org.cloudfoundry.multiapps.controller.process.util.ArchiveEntryExtractorUtil; import org.cloudfoundry.multiapps.controller.process.util.ArchiveEntryWithStreamPositions; @@ -58,8 +57,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); CloudServiceInstanceExtended serviceToProcess = context.getVariable(Variables.SERVICE_TO_PROCESS); getStepLogger().info(Messages.PROCESSING_SERVICE, serviceToProcess.getName()); 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 de42555169..db13a20b3c 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,7 +3,6 @@ import java.text.MessageFormat; import java.util.Collections; import java.util.List; -import java.util.Set; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -13,9 +12,9 @@ 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.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.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ProcessTypeParser; import org.cloudfoundry.multiapps.controller.process.util.ServiceAction; import org.cloudfoundry.multiapps.controller.process.util.ServiceDeletionActions; @@ -44,8 +43,7 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { } context.getStepLogger() .debug(Messages.DETERMINING_DELETE_ACTIONS_FOR_SERVICE_INSTANCE_0, serviceInstanceToDelete); - Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); return calculateDeleteActions(context, serviceInstanceToDelete, dynamicSecureSerialization); } 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 2cdcb04dd4..9ce3f34f19 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 @@ -19,8 +19,8 @@ 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.DynamicSecureSerialization; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.helpers.VisitableObject; import org.springframework.beans.factory.config.BeanDefinition; @@ -33,8 +33,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); Set dynamicResolvableParameters = context.getVariable(Variables.DYNAMIC_RESOLVABLE_PARAMETERS); List servicesCalculatedForDeployment = context.getVariableBackwardsCompatible( 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 753b74c26b..d940af99fe 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,40 +1,26 @@ 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; import org.cloudfoundry.multiapps.controller.persistence.dto.ImmutableBackupDescriptor; import org.cloudfoundry.multiapps.controller.persistence.services.DescriptorBackupService; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.SecretParametersCollectingVisitor; import org.cloudfoundry.multiapps.controller.process.util.NamespaceGlobalParameters; import org.cloudfoundry.multiapps.controller.process.util.UnsupportedParameterFinder; 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; @@ -44,14 +30,14 @@ @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class MergeDescriptorsStep extends SyncFlowableStep { - private static final String SECURE_ID = "__mta.secure"; - @Inject private DescriptorBackupService descriptorBackupService; @Inject private UnsupportedParameterFinder unsupportedParameterFinder; + private SecretParametersCollectingVisitor secretParametersCollectingVisitor = new SecretParametersCollectingVisitor(); + protected MtaDescriptorMerger getMtaDescriptorMerger(CloudHandlerFactory factory, Platform platform) { return new MtaDescriptorMerger(factory, platform, getStepLogger()); } @@ -63,15 +49,8 @@ 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); + Set parameterNamesToBeCensored = secretParametersCollectingVisitor.collectSecrets(deploymentDescriptor, + extensionDescriptors); context.setVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES, parameterNamesToBeCensored); @@ -88,97 +67,6 @@ 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, @@ -231,109 +119,4 @@ 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 f53ddcec45..c9d6cc1aeb 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 @@ -14,8 +14,8 @@ 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.DynamicSecureSerialization; -import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ServiceOperationGetter; import org.cloudfoundry.multiapps.controller.process.util.ServiceProgressReporter; import org.cloudfoundry.multiapps.controller.process.util.StepLogger; @@ -55,9 +55,7 @@ public AsyncExecutionState execute(ProcessContext context) { .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); - + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); reportDetailedServicesStates(context, servicesWithLastOperation); reportOverallProgress(context, servicesWithLastOperation.values(), triggeredServiceOperations); List remainingServicesToPoll = getRemainingServicesToPoll(servicesWithLastOperation); 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 7ebedf85f6..b82438bf32 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 @@ -17,12 +17,11 @@ 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; import org.cloudfoundry.multiapps.controller.process.Messages; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.NamespaceGlobalParameters; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -52,12 +51,12 @@ protected StepPhase executeStep(ProcessContext context) { descriptor = resolver.resolve(descriptor); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); List subscriptions = resolver.getSubscriptions(); - getStepLogger().debug(Messages.SUBSCRIPTIONS, SecureSerialization.toJson(subscriptions)); + getStepLogger().debug(Messages.SUBSCRIPTIONS, dynamicSecureSerialization.toJson(subscriptions)); 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); @@ -70,7 +69,6 @@ protected StepPhase executeStep(ProcessContext context) { Set mtaModules = getModuleNamesForDeployment(descriptor, modulesForDeployment); getStepLogger().debug(Messages.MTA_MODULES, mtaModules); context.setVariable(Variables.MTA_MODULES, mtaModules); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); getStepLogger().debug(Messages.RESOLVED_DEPLOYMENT_DESCRIPTOR, dynamicSecureSerialization.toJson(descriptor)); getStepLogger().debug(Messages.DESCRIPTOR_PROPERTIES_RESOLVED); 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 2274a9145b..c533ac4e8f 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 @@ -14,6 +14,7 @@ import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization; import org.cloudfoundry.multiapps.controller.persistence.services.FileContentConsumer; import org.cloudfoundry.multiapps.controller.persistence.services.FileStorageException; +import org.cloudfoundry.multiapps.controller.process.Constants; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.builders.ExtensionDescriptorChainBuilder; @@ -80,16 +81,16 @@ private List parseExtensionDescriptors(String spaceId, List boolean isSecureExtensionDescriptorPresent = extensionDescriptors.stream() .anyMatch(extensionDescriptor -> extensionDescriptor.getId() .equals( - "__mta.secure")); - List extensionDescriptorsWithoutSecure = null; + Constants.SECURE_EXTENSION_DESCRIPTOR_ID)); if (isSecureExtensionDescriptorPresent) { - extensionDescriptorsWithoutSecure = extensionDescriptors.stream() - .filter(extensionDescriptor -> !extensionDescriptor.getId() - .equals( - "__mta.secure")) - .toList(); + List extensionDescriptorsWithoutSecure = extensionDescriptors.stream() + .filter( + extensionDescriptor -> !extensionDescriptor.getId() + .equals( + Constants.SECURE_EXTENSION_DESCRIPTOR_ID)) + .toList(); getStepLogger().debug(Messages.PROVIDED_EXTENSION_DESCRIPTORS, SecureSerialization.toJson(extensionDescriptorsWithoutSecure) - + "\n\"SECURE EXTENSION DESCRIPTOR CONSTRUCTED AND APPLIED FROM ENVIRONMENT VARIABLES\""); + + Messages.SECURE_EXTENSION_DESCRIPTOR_CONSTRUCTED_AND_APPLIED_FROM_ENVIRONMENT_VARIABLES); } else { getStepLogger().debug(Messages.PROVIDED_EXTENSION_DESCRIPTORS, SecureSerialization.toJson(extensionDescriptors)); } 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 cf4df07fad..0d39f0a0a7 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 @@ -14,10 +14,10 @@ import org.cloudfoundry.multiapps.controller.core.auditlogging.ConfigurationEntryServiceAuditLog; import org.cloudfoundry.multiapps.controller.core.model.DynamicResolvableParameter; 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; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ConfigurationEntryDynamicParameterResolver; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.springframework.beans.factory.config.BeanDefinition; @@ -55,8 +55,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); getStepLogger().debug(Messages.PUBLISHED_ENTRIES, dynamicSecureSerialization.toJson(publishedEntries)); context.setVariable(Variables.PUBLISHED_ENTRIES, publishedEntries); 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 index a2df8022c0..bd0d91bd77 100644 --- 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 @@ -1,11 +1,11 @@ package org.cloudfoundry.multiapps.controller.process.steps; +import java.util.ArrayList; +import java.util.List; 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; @@ -18,16 +18,16 @@ public class SecureProcessContext extends ProcessContext { private SecretTokenStore secretTokenStore; - private SecretTransformationStrategy secretTransformationStrategy; + // private SecretTransformationStrategy secretTransformationStrategy; public SecureProcessContext(DelegateExecution execution, StepLogger stepLogger, CloudControllerClientProvider clientProvider, - SecretTokenStore secretTokenStore, SecretTransformationStrategy secretTransformationStrategy) { + SecretTokenStore secretTokenStore) { super(execution, stepLogger, clientProvider); this.secretTokenStore = secretTokenStore; - this.secretTransformationStrategy = secretTransformationStrategy; + // this.secretTransformationStrategy = secretTransformationStrategy; } - private String pid() { + private String getPidOfCurrentExecution() { return getExecution().getRootProcessInstanceId(); } @@ -38,29 +38,35 @@ private Variable wrap(Variable variable) { } DelegateExecution currentExecution = getExecution(); - String processInstanceId = pid(); - Set secureParameterNames; + String processInstanceId = getPidOfCurrentExecution(); 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); - } + Set secureParameterNames = extractSecureParameterNames(secureParameterNamesRaw); - SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl( - secretTransformationStrategy, secureParameterNames); + // SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl( + // secureParameterNames); + // SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration(); + // secureSerializerConfiguration.setAdditionalSensitiveElementNames(secureParameterNames); + List secureParametersNamesList = new ArrayList<>(secureParameterNames); Serializer wrappedSerializer = new SecretTokenSerializer<>(variable.getSerializer(), secretTokenStore, - secretTransformationStrategyContext, + secureParametersNamesList, processInstanceId, variable.getName()); return new WrappedVariable<>(variable, wrappedSerializer); } + private Set extractSecureParameterNames(byte[] secureParameterNamesRaw) { + if (secureParameterNamesRaw == null) { + return Set.of(); + } else { + return Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES.getSerializer() + .deserialize(secureParameterNamesRaw); + } + } + @Override public void setVariable(Variable variable, T value) { VariableHandling.set(getExecution(), wrap(variable), value); 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 index 9d2237c437..9654cbf197 100644 --- 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 @@ -1,22 +1,28 @@ 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; +import org.immutables.value.Value; -public final class SecureProcessContextFactory { +@Value.Immutable +public abstract class SecureProcessContextFactory { - private SecureProcessContextFactory() { + abstract DelegateExecution getDelegateExecution(); + + abstract StepLogger getStepLogger(); + + abstract CloudControllerClientProvider getClientProvider(); + + abstract SecretTokenStore getSecretTokenStore(); + + public SecureProcessContextFactory() { } - public static SecureProcessContext ofSecureProcessContext(DelegateExecution execution, StepLogger stepLogger, - CloudControllerClientProvider clientProvider, - SecretTokenStore secretTokenStore, - SecretTransformationStrategy secretTransformationStrategy) { - return new SecureProcessContext(execution, stepLogger, clientProvider, secretTokenStore, secretTransformationStrategy); + public SecureProcessContext ofSecureProcessContext() { + return new SecureProcessContext(getDelegateExecution(), getStepLogger(), getClientProvider(), getSecretTokenStore()); } } 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 a1f29bb335..6b6e872c13 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,7 +20,6 @@ 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; @@ -56,8 +55,6 @@ public abstract class SyncFlowableStep implements JavaDelegate { @Inject protected SecretTokenKeyResolver secretTokenKeyResolver; @Inject - protected SecretTransformationStrategy secretTransformationStrategy; - @Inject private StepLogger.Factory stepLoggerFactory; @Inject private ProcessEngineConfiguration processEngineConfiguration; @@ -106,8 +103,13 @@ protected ProcessContext createProcessContext(DelegateExecution execution) { SecretTokenKeyContainer secretTokenKeyContainer = secretTokenKeyResolver.resolve(execution); SecretTokenStore secretTokenStore = secretTokenStoreFactory.createSecretTokenStore(secretTokenKeyContainer.key(), secretTokenKeyContainer.keyId()); - return SecureProcessContextFactory.ofSecureProcessContext(execution, stepLogger, clientProvider, secretTokenStore, - secretTransformationStrategy); + return ImmutableSecureProcessContextFactory.builder() + .delegateExecution(execution) + .stepLogger(stepLogger) + .clientProvider(clientProvider) + .secretTokenStore(secretTokenStore) + .build() + .ofSecureProcessContext(); } 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 3b461e739c..ff555530bf 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,7 +6,6 @@ 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; @@ -34,7 +33,6 @@ 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.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; @@ -45,6 +43,7 @@ import org.cloudfoundry.multiapps.controller.persistence.services.ConfigurationSubscriptionService; import org.cloudfoundry.multiapps.controller.process.Messages; import org.cloudfoundry.multiapps.controller.process.flowable.FlowableFacade; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.helpers.VisitableObject; import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor; @@ -99,8 +98,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); List publishedEntries = StepsUtil.getPublishedEntriesFromSubProcesses(context, flowableFacade); List deletedEntries = StepsUtil.getDeletedEntriesFromAllProcesses(context, flowableFacade); List updatedEntries = ListUtils.union(publishedEntries, deletedEntries); 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 198fd9911f..0f19e1fa0e 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,7 +5,6 @@ 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; @@ -22,9 +21,9 @@ import org.cloudfoundry.multiapps.controller.core.helpers.ApplicationFileDigestDetector; import org.cloudfoundry.multiapps.controller.core.helpers.MtaArchiveElements; 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.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.util.ApplicationArchiveContext; import org.cloudfoundry.multiapps.controller.process.util.ApplicationDigestCalculator; import org.cloudfoundry.multiapps.controller.process.util.ApplicationStager; @@ -89,8 +88,7 @@ public StepPhase executeAsyncStep(ProcessContext context) throws FileStorageExce } } - Set secretParameters = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); Optional mostRecentPackage = cloudPackagesGetter.getMostRecentAppPackage(client, cloudApp.getGuid(), dynamicSecureSerialization); 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 4ecf853ecb..b9d4000452 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,7 +2,6 @@ import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -15,10 +14,10 @@ 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.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; +import org.cloudfoundry.multiapps.controller.process.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.model.Version; @@ -143,8 +142,7 @@ private List getAppsWithLiveProductizationState(List parametersToHide = context.getVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); - DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parametersToHide); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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/ServiceBindingParametersGetter.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/util/ServiceBindingParametersGetter.java index 54343e485b..36d6f7a5a5 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,7 +2,6 @@ import java.io.ByteArrayInputStream; import java.io.IOException; -import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; @@ -20,9 +19,9 @@ 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.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.security.util.SecureLoggingUtil; import org.cloudfoundry.multiapps.controller.process.steps.ProcessContext; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.cloudfoundry.multiapps.mta.util.NameUtil; @@ -46,8 +45,7 @@ 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); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); Optional service = getService(context.getVariable(Variables.SERVICES_TO_BIND), serviceName); if (service.isEmpty()) { 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 index 59e18f6a33..5aff053914 100644 --- 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 @@ -2,9 +2,9 @@ public class WrappedVariable implements Variable { - private Variable variableToDelegate; + private final Variable variableToDelegate; - private Serializer wrappedSerializer; + private final Serializer wrappedSerializer; public WrappedVariable(Variable delegate, Serializer serializer) { this.variableToDelegate = delegate; diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn index 6ff559a157..04ade646d9 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/backup-existing-app.bpmn @@ -19,6 +19,8 @@ + + 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 2fb3142fa9..c6c3ced89d 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 @@ -33,6 +33,8 @@ + + @@ -60,6 +62,8 @@ + + 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 27a0b8840a..00aeb0a1fa 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 @@ -92,6 +92,8 @@ + + @@ -110,6 +112,8 @@ + + @@ -137,6 +141,8 @@ + + @@ -167,6 +173,8 @@ + + @@ -200,6 +208,8 @@ + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn index 240c359f21..0501e11dd5 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/process-batches-sequentially.bpmn @@ -36,6 +36,7 @@ + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/recreate-service-keys.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/recreate-service-keys.bpmn index d32aa6c73d..3b8f4f7ca3 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/recreate-service-keys.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/recreate-service-keys.bpmn @@ -16,6 +16,8 @@ + + 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 94ff13c2ad..3134dd7064 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 @@ -20,6 +20,8 @@ + + @@ -45,6 +47,8 @@ + + @@ -91,6 +95,8 @@ + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn index b50086161f..15dc206183 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-bg-deploy.bpmn @@ -179,6 +179,8 @@ + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn index 0a7b944122..19232c2a01 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-deploy.bpmn @@ -161,6 +161,8 @@ + + diff --git a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn index 48cad48b71..953072edd1 100644 --- a/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn +++ b/multiapps-controller-process/src/main/resources/org/cloudfoundry/multiapps/controller/process/xs2-undeploy.bpmn @@ -121,6 +121,8 @@ + + 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 index 2427b4b7d6..b1623df422 100644 --- 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 @@ -1,10 +1,11 @@ package org.cloudfoundry.multiapps.controller.process.security; +import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; import java.util.List; -import java.util.Set; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; 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; @@ -25,17 +26,12 @@ 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 { @@ -75,15 +71,14 @@ public List deserialize(Object serializedValue, VariableContainer contai @Test void testSerializeAndDeserializeSuccessWhenEntireVariableIsSecretString() { - Set names = new HashSet<>(); + List names = new ArrayList<>(); 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, + new StringSerializerHelper(), secretTokenStore, names, PROCESS_INSTANCE_ID, VARIABLE_NAME); String inputJson = "{\"value\":\"secret_text\"}"; @@ -97,15 +92,14 @@ void testSerializeAndDeserializeSuccessWhenEntireVariableIsSecretString() { @Test void testSerializeAndDeserializeSuccessWhenJsonWithStringField() { - Set secretNames = new HashSet<>(); + List secretNames = new ArrayList<>(); 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, + new StringSerializerHelper(), secretTokenStore, secretNames, PROCESS_INSTANCE_ID, VARIABLE_NAME); String testInput = "{\"user\":\"u\",\"password\":\"internal_one\",\"other\":123}"; @@ -121,15 +115,14 @@ void testSerializeAndDeserializeSuccessWhenJsonWithStringField() { @Test void testSerializeAndDeserializeSuccessWhenJsonWithStringFieldLargerOne() { - Set secretNames = new HashSet<>(); + List secretNames = new ArrayList<>(); 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, + new StringSerializerHelper(), secretTokenStore, secretNames, PROCESS_INSTANCE_ID, VARIABLE_NAME); String testInput = @@ -144,14 +137,37 @@ void testSerializeAndDeserializeSuccessWhenJsonWithStringFieldLargerOne() { assertEquals(testInput, valueAtFirst); } + @Test + void testSerializeAndDeserializeSuccessWhenJsonWithStringHasTrailingTextDoesNotMakeChanges() { + List secretNames = new ArrayList<>(); + secretNames.add("config"); + + 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, secretNames, + 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}}trailingTextThatCorruptsTheJson"; + Object serialized = serializer.serialize(testInput); + assertInstanceOf(String.class, serialized); + String tokenizedJson = (String) serialized; + assertTrue(tokenizedJson.contains("config")); + assertTrue(tokenizedJson.contains("must_not_be_shown")); + + String valueAtFirst = serializer.deserialize(tokenizedJson); + assertEquals(testInput, valueAtFirst); + } + @Test void testSerializeSuccessWhenSecretParameterIsAReference() { - Set secretNames = new HashSet<>(); + List secretNames = new ArrayList<>(); secretNames.add("password"); - when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(secretNames); SecretTokenSerializer serializer = new SecretTokenSerializer<>( - new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + new StringSerializerHelper(), secretTokenStore, secretNames, PROCESS_INSTANCE_ID, VARIABLE_NAME); String testInput = "{\"password\":\"${referenceToSomething}\"}"; @@ -168,7 +184,7 @@ void testDeserializeWhenPlainTokenString() { when(secretTokenStore.get(PROCESS_INSTANCE_ID, 42L)).thenReturn("plain_value"); SecretTokenSerializer serializer = new SecretTokenSerializer<>( - new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + new StringSerializerHelper(), secretTokenStore, Collections.emptyList(), PROCESS_INSTANCE_ID, VARIABLE_NAME); String result = serializer.deserialize(token); @@ -177,10 +193,8 @@ void testDeserializeWhenPlainTokenString() { @Test void testSerializeLeavesPlainNonJsonUntouchedWhenCsvDisabled() { - when(secretTransformationStrategy.getJsonSecretFieldNames()).thenReturn(Collections.emptySet()); - SecretTokenSerializer serializer = new SecretTokenSerializer<>( - new StringSerializerHelper(), secretTokenStore, secretTransformationStrategy, + new StringSerializerHelper(), secretTokenStore, Collections.emptyList(), PROCESS_INSTANCE_ID, VARIABLE_NAME); String testInput = "just_a_plain_string"; @@ -191,15 +205,14 @@ void testSerializeLeavesPlainNonJsonUntouchedWhenCsvDisabled() { @Test void testSerializeAndDeserializeWhenListOfJsonElements() { - Set secretNames = new HashSet<>(); + List secretNames = new ArrayList<>(); 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, + new ListSerializerHelper(), secretTokenStore, secretNames, PROCESS_INSTANCE_ID, VARIABLE_NAME); List testInput = List.of("{\"password\":\"p1\"}", "{\"other\":1}"); @@ -215,4 +228,59 @@ void testSerializeAndDeserializeWhenListOfJsonElements() { assertEquals(testInput, valueAtFirst); } + @Test + void testSerializeAndDeserialize_JsonNumberFieldCoercedToInt() throws Exception { + List secretNames = Collections.singletonList("instancesNumber"); + + when(secretTokenStore.put(PROCESS_INSTANCE_ID, VARIABLE_NAME, "7")).thenReturn(77L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 77L)).thenReturn("7"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretNames, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String inputJson = "{\"name\":\"app\",\"instancesNumber\":7}"; + + Object result = serializer.serialize(inputJson); + assertInstanceOf(String.class, result); + String tokenizedJsonResult = (String) result; + + assertFalse(tokenizedJsonResult.contains("\"instancesNumber\":7")); + + String valueAtFirst = serializer.deserialize(tokenizedJsonResult); + JsonNode root = new ObjectMapper().readTree(valueAtFirst); + assertTrue(root.get("instancesNumber") + .isInt()); + assertEquals(7, root.get("instancesNumber") + .asInt()); + } + + @Test + void testSerializeAndDeserialize_ArrayOfObjects_NumberFieldsCoercedToInt() throws Exception { + List secretNames = Collections.singletonList("instancesNumber"); + + when(secretTokenStore.put(PROCESS_INSTANCE_ID, VARIABLE_NAME, "3")) + .thenReturn(100L); + when(secretTokenStore.get(PROCESS_INSTANCE_ID, 100L)).thenReturn("3"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretNames, + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String inputJson = "{\"array\":[{\"instancesNumber\":3},{\"someParameter\":358},{\"other\":\"x\"}]}"; + + Object result = serializer.serialize(inputJson); + assertInstanceOf(String.class, result); + String tokenizedJsonResult = (String) result; + + assertFalse(tokenizedJsonResult.contains("\"instancesNumber\":\"3\"")); + + String valueAtFirst = serializer.deserialize(tokenizedJsonResult); + JsonNode root = new ObjectMapper().readTree(valueAtFirst); + JsonNode arrayFromJson = root.get("array"); + assertEquals(3, arrayFromJson.get(0) + .get("instancesNumber") + .asInt()); + } + } 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 index 4bc5b17c07..b5ba5fdea9 100644 --- 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 @@ -4,6 +4,7 @@ import java.util.Map; import java.util.UUID; +import org.cloudfoundry.multiapps.common.SLException; 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; @@ -102,8 +103,7 @@ void testResolveSuccessWithoutNamespace() { void testResolveWhenServiceInstanceMissing() { when(cloudControllerClient.getServiceInstance("__mta-secure-my-mtans")).thenReturn(null); - MissingUserProvidedServiceEncryptionRelatedException exception = assertThrows( - MissingUserProvidedServiceEncryptionRelatedException.class, () -> secretTokenKeyResolver.resolve(execution)); + Exception exception = assertThrows(SLException.class, () -> secretTokenKeyResolver.resolve(execution)); assertTrue(exception.getMessage() .contains("Could not retrieve service instance")); verify(cloudControllerClient, times(1)).getServiceInstance("__mta-secure-my-mtans"); @@ -121,8 +121,7 @@ void testResolveWhenCredentialsMissing() { when(cloudControllerClient.getUserProvidedServiceInstanceParameters(upsGuid)) .thenReturn(new HashMap<>()); - MissingCredentialsFromUserProvidedServiceEncryptionRelated exception = assertThrows( - MissingCredentialsFromUserProvidedServiceEncryptionRelated.class, () -> secretTokenKeyResolver.resolve(execution)); + Exception exception = assertThrows(SLException.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 index dd688feafe..87c0b4f646 100644 --- 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 @@ -11,51 +11,51 @@ public class SecretTokenUtilTest { @Test void testIsTokenWhenNull() { - assertFalse(SecretTokenUtil.isToken(null)); + assertFalse(SecretTokenUtil.isSecretToken(null)); } @Test void testIsTokenWhenEmptyString() { - assertFalse(SecretTokenUtil.isToken("")); + assertFalse(SecretTokenUtil.isSecretToken("")); } @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")); + assertFalse(SecretTokenUtil.isSecretToken("fake:v1:123")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:V1:123")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v2:123")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v1-123")); } @Test void testIsTokenWhenWrongPostfixedString() { - assertFalse(SecretTokenUtil.isToken("dsc:v1:12a3")); - assertFalse(SecretTokenUtil.isToken("dsc:v1:12 3")); - assertFalse(SecretTokenUtil.isToken("dsc:v1:")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v1:12a3")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v1:12 3")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v1:")); } @Test void testIsTokenSuccess() { - assertTrue(SecretTokenUtil.isToken("dsc:v1:0")); - assertTrue(SecretTokenUtil.isToken("dsc:v1:42")); - assertTrue(SecretTokenUtil.isToken("dsc:v1:000123")); + assertTrue(SecretTokenUtil.isSecretToken("dsc:v1:0")); + assertTrue(SecretTokenUtil.isSecretToken("dsc:v1:42")); + assertTrue(SecretTokenUtil.isSecretToken("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")); + assertEquals(0L, SecretTokenUtil.extractId("dsc:v1:0")); + assertEquals(42L, SecretTokenUtil.extractId("dsc:v1:42")); + assertEquals(123L, SecretTokenUtil.extractId("dsc:v1:000123")); } @Test void testIdWhenNoDigitsThrows() { - assertThrows(NumberFormatException.class, () -> SecretTokenUtil.id("dsc:v1:")); + assertThrows(NumberFormatException.class, () -> SecretTokenUtil.extractId("dsc:v1:")); } @Test void testIdWhenNonDigitsThrows() { - assertThrows(NumberFormatException.class, () -> SecretTokenUtil.id("dsc:v1:abc")); + assertThrows(NumberFormatException.class, () -> SecretTokenUtil.extractId("dsc:v1:abc")); } @Test @@ -68,8 +68,8 @@ void testOfSuccess() { void testFlowOfSecretTokenBuild() { long id = 981L; String token = SecretTokenUtil.of(id); - assertTrue(SecretTokenUtil.isToken(token)); - assertEquals(id, SecretTokenUtil.id(token)); + assertTrue(SecretTokenUtil.isSecretToken(token)); + assertEquals(id, SecretTokenUtil.extractId(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 9fa582c366..e4907dcd04 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 @@ -20,6 +20,7 @@ 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.security.store.SecretTokenStore; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -30,6 +31,7 @@ import org.mockito.Mockito; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; class DetectApplicationsToRenameStepTest extends SyncFlowableStepTest { @@ -100,14 +102,21 @@ void testExecuteRenamesApps() { void testExecuteWithTwoVersionsOfAppDeletesOldAndRenamesNew() { DeployedMta deployedMta = createDeployedMta("a-live", "a"); context.setVariable(Variables.DEPLOYED_MTA, deployedMta); - + SecretTokenKeyContainer secretTokenKeyContainer = Mockito.mock(SecretTokenKeyContainer.class); + SecretTokenStore secretTokenStore = Mockito.mock(SecretTokenStore.class); + Mockito.when(secretTokenKeyContainer.key()) + .thenReturn("test-key"); + Mockito.when(secretTokenKeyContainer.keyId()) + .thenReturn("v1"); CloudControllerClient client = Mockito.mock(CloudControllerClient.class); Mockito.when(client.getApplication("a-live", false)) .thenReturn(createApplication("a-live")); Mockito.when(clientProvider.getControllerClient(anyString(), anyString(), anyString())) .thenReturn(client); Mockito.when(secretTokenKeyResolver.resolve(execution)) - .thenReturn(new SecretTokenKeyContainer(anyString(), anyString())); + .thenReturn(secretTokenKeyContainer); + Mockito.when(secretTokenStoreFactory.createSecretTokenStore(eq("test-key"), eq("v1"))) + .thenReturn(secretTokenStore); context.setVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES, Collections.emptySet()); context.setVariable(Variables.IS_SECURITY_ENABLED, Boolean.TRUE); 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 514bbfc6b7..2eda9845da 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 @@ -15,6 +15,7 @@ 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.security.store.SecretTokenStore; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -22,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; class PollServiceBindingOrKeyOperationExecutionTest extends AsyncStepOperationTest { @@ -39,6 +41,16 @@ void initialize() { context.setVariable(Variables.SERVICE_WITH_BIND_IN_PROGRESS, SERVICE_NAME); controllerClient = Mockito.mock(CloudControllerClient.class); + SecretTokenKeyContainer secretTokenKeyContainer = Mockito.mock(SecretTokenKeyContainer.class); + SecretTokenStore secretTokenStore = Mockito.mock(SecretTokenStore.class); + Mockito.when(secretTokenKeyContainer.key()) + .thenReturn("test-key"); + Mockito.when(secretTokenKeyContainer.keyId()) + .thenReturn("v1"); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .thenReturn(secretTokenKeyContainer); + Mockito.when(secretTokenStoreFactory.createSecretTokenStore(eq("test-key"), eq("v1"))) + .thenReturn(secretTokenStore); Mockito.when(clientProvider.getControllerClient(anyString(), anyString(), anyString())) .thenReturn(controllerClient); Mockito.when(controllerClient.getServiceInstance(SERVICE_NAME)) @@ -52,8 +64,6 @@ 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(); @@ -64,8 +74,6 @@ 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(); @@ -76,8 +84,6 @@ 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(); @@ -88,8 +94,6 @@ 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 db9a015268..a083c69ff0 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,7 +26,6 @@ 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; @@ -98,8 +97,6 @@ public abstract class SyncFlowableStepTest { @Mock protected SecretTokenStoreFactory secretTokenStoreFactory; @Mock - protected SecretTransformationStrategy secretTransformationStrategy; - @Mock protected SecretTokenKeyResolver secretTokenKeyResolver; @Mock protected FlowableFacade flowableFacadeFacade; From cb60e46ef4f8f9936e0f5ea4893fced17fc13cae Mon Sep 17 00:00:00 2001 From: Krasimir Kargov Date: Fri, 23 Jan 2026 11:08:42 +0200 Subject: [PATCH 3/3] Adding the Ali cloud exclusion of bouncy castle LMCROSSITXSADEPLOY-2301 --- multiapps-controller-persistence/pom.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/multiapps-controller-persistence/pom.xml b/multiapps-controller-persistence/pom.xml index 20b44ce4de..67c5ec82ab 100644 --- a/multiapps-controller-persistence/pom.xml +++ b/multiapps-controller-persistence/pom.xml @@ -115,6 +115,12 @@ com.aliyun.oss aliyun-sdk-oss + + + org.bouncycastle + bcprov-jdk18on + + com.google.auto.service