diff --git a/multiapps-controller-core/src/main/java/module-info.java b/multiapps-controller-core/src/main/java/module-info.java index d23e127880..6469829509 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; @@ -61,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; @@ -79,6 +81,5 @@ requires static java.compiler; requires static org.immutables.value; - requires spring.security.oauth2.client; } \ 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..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,10 +29,19 @@ 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 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 3c792a7380..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,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_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 b24d569c36..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 @@ -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 parameterNamesToBeMasked) { DescriptorValidator validator = handlerFactory.getDescriptorValidator(); validator.validateDeploymentDescriptor(deploymentDescriptor, platform); validator.validateExtensionDescriptors(extensionDescriptors, deploymentDescriptor); + 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 @@ -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/AesEncryptionUtil.java b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java new file mode 100644 index 0000000000..1c1ed55173 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java @@ -0,0 +1,80 @@ +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; + +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 = setUpCipherObject(encryptionKey, gcmInitialisationVector, true); + + byte[] cipherValue = cipherObject.doFinal(plainText.getBytes(StandardCharsets.UTF_8)); + + byte[] combinedCipherValueAndInitialisationVector = new byte[gcmInitialisationVector.length + cipherValue.length]; + + System.arraycopy(gcmInitialisationVector, 0, combinedCipherValueAndInitialisationVector, 0, gcmInitialisationVector.length); + System.arraycopy(cipherValue, 0, combinedCipherValueAndInitialisationVector, gcmInitialisationVector.length, + cipherValue.length); + + return combinedCipherValueAndInitialisationVector; + } catch (Exception e) { + throw new SLException(MessageFormat.format(Messages.ENCRYPTION_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 - Constants.INITIALISATION_VECTOR_LENGTH]; + System.arraycopy(encryptedValue, Constants.INITIALIZATION_VECTOR_POSITION, cipherValue, 0, cipherValue.length); + + Cipher cipherObject = setUpCipherObject(encryptionKey, gcmInitialisationVector, false); + + byte[] resultInBytes = cipherObject.doFinal(cipherValue); + return new String(resultInBytes, StandardCharsets.UTF_8); + } catch (Exception 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 new file mode 100644 index 0000000000..940eff15c6 --- /dev/null +++ b/multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/DynamicSecureSerialization.java @@ -0,0 +1,73 @@ +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; + + public DynamicSecureSerialization(SecureSerializerConfiguration secureSerializerConfiguration) { + this.secureSerializerConfiguration = secureSerializerConfiguration; + } + + public String toJson(Object object) { + SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializer(object); + return secureJsonSerializer.serialize(object); + } + + private SecureJsonSerializer createDynamicJsonSerializer(Object object) { + SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializerForVersionedEntity(object); + if (secureJsonSerializer == null) { + return new SecureJsonSerializer(secureSerializerConfiguration); + } + + return secureJsonSerializer; + } + + private SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(Object object) { + if (object instanceof VersionedEntity) { + return createDynamicJsonSerializerForVersionedEntity((VersionedEntity) object); + } + + return null; + } + + private SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(VersionedEntity versionedEntity) { + 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..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,9 +2,12 @@ import java.util.Collection; import java.util.Collections; +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 { @@ -17,8 +20,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 (CollectionUtils.isEmpty(additionalSensitiveElementNames)) { + return sensitiveElementNames; + } + + Set mergedSensitiveElementNames = new HashSet<>(sensitiveElementNames); + + for (String additionalSensitiveElement : additionalSensitiveElementNames) { + boolean isNotExistent = mergedSensitiveElementNames.stream() + .noneMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase( + additionalSensitiveElement)); + if (isNotExistent) { + mergedSensitiveElementNames.add(additionalSensitiveElement); + } + } + + return mergedSensitiveElementNames; } public Collection getSensitiveElementPaths() { @@ -33,6 +53,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..8ac921aa47 --- /dev/null +++ b/multiapps-controller-core/src/test/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtilTest.java @@ -0,0 +1,100 @@ +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] = (byte) (tampered[tampered.length - 1] ^ 0x01); + + assertThrows(Exception.class, () -> AesEncryptionUtil.decrypt(tampered, KEY_FOR_256_32_BYTES)); + } + + @Test + void testEncryptWhenWrongEncryptionKetLength() { + byte[] wrongEncryptionKey = new byte[31]; + assertThrows(Exception.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(Exception.class, () -> AesEncryptionUtil.decrypt(encrypted, differentValidKey)); + } + + @Test + void testDecryptWhenEncryptedValueTooShort() { + byte[] tooShort = new byte[7]; + assertThrows(Exception.class, () -> AesEncryptionUtil.decrypt(tooShort, KEY_FOR_256_32_BYTES)); + } + +} + 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 diff --git a/multiapps-controller-persistence/src/main/java/module-info.java b/multiapps-controller-persistence/src/main/java/module-info.java index 875fb82e2d..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,6 +61,4 @@ requires static java.compiler; requires static org.immutables.value; - requires jakarta.xml.bind; - requires org.bouncycastle.fips.pkix; } 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..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 @@ -45,8 +45,11 @@ 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 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"; @@ -76,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 new file mode 100644 index 0000000000..b2683eaf77 --- /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 = "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 = "keyId"; + } + + @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..558d351345 --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/model/SecretToken.java @@ -0,0 +1,32 @@ +package org.cloudfoundry.multiapps.controller.persistence.model; + +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 + 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/services/SecretTokenService.java b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenService.java new file mode 100644 index 0000000000..f279ff8bbe --- /dev/null +++ b/multiapps-controller-persistence/src/main/java/org/cloudfoundry/multiapps/controller/persistence/services/SecretTokenService.java @@ -0,0 +1,74 @@ +package org.cloudfoundry.multiapps.controller.persistence.services; + +import java.time.LocalDateTime; + +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.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; + +@Named +public class SecretTokenService extends PersistenceService { + + private SecretTokenMapper secretTokenMapper; + + public SecretTokenService(EntityManagerFactory entityManagerFactory, SecretTokenMapper secretTokenMapper) { + super(entityManagerFactory); + this.secretTokenMapper = secretTokenMapper; + } + + 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); + } + } + +} 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/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-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 231bedb9e5..c7816edcb5 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; @@ -22,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; @@ -29,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; @@ -39,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; @@ -56,5 +63,8 @@ requires static java.compiler; requires static org.immutables.value; + 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 7834e487be..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"; @@ -26,8 +28,21 @@ 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_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 8e259ae732..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 @@ -82,6 +82,11 @@ 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}\")"; + public static final String INVALID_ENCRYPTION_KEY_LENGTH = "Length of the encryption key is invalid - it must be 32 characters long!"; // Audit log messages @@ -296,6 +301,7 @@ public class Messages { public static final String DELETED_PROGRESS_MESSAGES_0 = "Deleted progress messages: {0}"; public static final String DELETED_HISTORIC_OPERATION_EVENTS_0 = "Deleted historic operation events: {0}"; public static final String REMOVED_TOKENS_0 = "Removed tokens: {0}"; + public static final String REMOVED_SECRET_TOKENS_0 = "Removed secret tokens: {0}"; public static final String DELETED_DATA_FOR_NON_EXISTING_USERS = "Deleted data for no-longer existing users."; public static final String CREATING_APP_FROM_DOCKER_IMAGE = "Creating app \"{0}\" from Docker image \"{1}\"..."; public static final String CREATE_SUPPORT_TICKET_GENERIC_MESSAGE = "If the problem persists, please create a support ticket."; @@ -557,6 +563,7 @@ public class Messages { public static final String DELETING_PROCESS_LOGS_MODIFIED_BEFORE_0 = "Deleting process logs modified before \"{0}\"..."; public static final String DELETING_PROGRESS_MESSAGES_STORED_BEFORE_0 = "Deleting progress messages stored before \"{0}\"..."; public static final String REMOVING_EXPIRED_TOKENS_FROM_TOKEN_STORE = "Removing expired tokens from the token store..."; + public static final String REMOVING_EXPIRED_SECRET_TOKENS = "Removing expired secret tokens..."; public static final String DELETING_HISTORIC_OPERATION_EVENTS_STORED_BEFORE_0 = "Deleting historic operation events stored before \"{0}\"..."; public static final String DELETING_DATA_FOR_NON_EXISTING_USERS = "Deleting data for no-longer existing users..."; public static final String REGISTERED_CLEANERS_IN_CLEAN_UP_JOB_0 = "Registered cleaners in clean-up job: {0}"; @@ -787,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/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..d579a0baf3 --- /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.info(CleanUpJob.LOG_MARKER, Messages.REMOVING_EXPIRED_SECRET_TOKENS); + + SecretTokenStoreDeletion secretTokenStore = secretTokenStoreFactory.createSecretTokenStoreDeletionRelated(); + int tokens = secretTokenStore.deleteOlderThan(expirationTime); + + 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/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/SecretConfig.java b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretConfig.java new file mode 100644 index 0000000000..3c9218649c --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretConfig.java @@ -0,0 +1,27 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.security.Security; + +import jakarta.annotation.PostConstruct; +import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider; +import org.cloudfoundry.multiapps.controller.persistence.services.SecretTokenService; +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 SecretTokenStoreFactory secretTokenStoreFactory(SecretTokenService secretTokenService) { + return new SecretTokenStoreFactory(secretTokenService); + } + + @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/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 new file mode 100644 index 0000000000..779075fa59 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializer.java @@ -0,0 +1,324 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +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; +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 final ObjectMapper objectMapper = new ObjectMapper().enable(DeserializationFeature.FAIL_ON_TRAILING_TOKENS); + + private final Serializer serializer; + + private final SecretTokenStore secretTokenStore; + + private final List secretValues; + + private final String processInstanceId; + + private final String variableName; + + public SecretTokenSerializer(Serializer serializer, SecretTokenStore secretTokenStore, List secretValues, + String processInstanceId, + String variableName) { + this.serializer = serializer; + this.secretTokenStore = secretTokenStore; + this.secretValues = secretValues; + this.processInstanceId = processInstanceId; + this.variableName = variableName; + } + + @Override + public Object serialize(T value) { + 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 = deserializeHelper(serializedValue); + return serializer.deserialize(valueToDecode); + } + + @Override + public T deserialize(Object serializedValue, VariableContainer container) { + if (serializedValue == null) { + 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) { + 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 valueToDecode; + } + + private Object handleString(String stringObject, boolean censor) { + String transformedJson = transformJson(stringObject, censor); + if (transformedJson != null) { + return transformedJson; + } + + if (!censor && SecretTokenUtil.isSecretToken(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); + return getResultFromTransformedJson(transformedJson, element, false); + } else if (element instanceof byte[]) { + String transformedJson = transformJson(new String((byte[]) element), censor); + 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 (!isValid(candidate)) { + return null; + } + + try { + JsonNode rootNode = objectMapper.readTree(candidate); + Set keys = new HashSet<>(secretValues); + 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 SLException(MessageFormat.format(Messages.JSON_TRANSFORMATION_FAILED_FOR_VARIABLE_0, variableName), e); + } + } + + public boolean isValid(String json) { + try { + objectMapper.readTree(json); + } catch (JacksonException e) { + return false; + } + return true; + } + + private JsonNode processJsonValue(JsonNode currentNode, Set keys, boolean censor, boolean[] changed) { + if (currentNode.isObject()) { + return processObjectNode(currentNode, keys, censor, changed); + } + + if (currentNode.isArray()) { + return processArrayNode(currentNode, keys, censor, changed); + } + + if (currentNode.isTextual()) { + return processTextualNode(currentNode, censor, changed); + } + + 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()); + } + + 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 objectNode; + } + + 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; + } + } 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); + } + } + } + + private String convertChildNodeToText(JsonNode childNode) { + if (!childNode.isNull()) { + return childNode.asText(); + } + return null; + } + + 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.extractId(token); + String result = secretTokenStore.get(processInstanceId, id); + if (result == null) { + throw new SLException( + MessageFormat.format(Messages.SECRET_VALUE_NOT_FOUND_FOR_TOKEN_0_PID_1_VARIABLE_2, token, processInstanceId, variableName)); + } + return result; + } + + private boolean isPlaceholder(String value) { + if (value == null) { + return false; + } + + return value.contains(Constants.PLACEHOLDER_PREFIX) && value.contains(Constants.PLACEHOLDER_SUFFIX); + } + + private static boolean looksLikeStandardInteger(String numberString) { + if (numberString == null) { + return false; + } + + 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); + } + + } + +} 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/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..5793edd646 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java @@ -0,0 +1,113 @@ +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; +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 { + + CloudControllerClientProvider cloudControllerClientProvider; + + private UUID serviceInstanceID; + + @Inject + public SecretTokenKeyResolverImpl(CloudControllerClientProvider cloudControllerClientProvider) { + this.cloudControllerClientProvider = cloudControllerClientProvider; + } + + 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); + + 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); + + if (mtaId == null) { + throw new SLException("Missing mtaId in encryption key resolver! Cannot continue from here!"); + } + + if (namespace != null) { + return String.format(Constants.TRIPLE_APPENDED_STRING, Constants.USER_PROVIDED_SERVICE_PREFIX_NAME_ENCRYPTION_DECRYPTION, mtaId, + namespace); + } else { + 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/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..7a805ab251 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImpl.java @@ -0,0 +1,86 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +import java.nio.charset.StandardCharsets; +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 static final Logger LOGGER = LoggerFactory.getLogger(SecretTokenStoreImpl.class); + + private final SecretTokenService secretTokenService; + + private final String encryptionKey; + + private final 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) { + byte[] encryptedValue; + byte[] keyBytes = keyBytes(); + encryptedValue = AesEncryptionUtil.encrypt(plainText, keyBytes); + + 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) { + 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) { + 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) { + 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 new file mode 100644 index 0000000000..85ec240853 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/store/SecretTokenStoreImplWithoutKey.java @@ -0,0 +1,38 @@ +package org.cloudfoundry.multiapps.controller.process.security.store; + +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 static final Logger LOGGER = LoggerFactory.getLogger(SecretTokenStoreImplWithoutKey.class); + + private SecretTokenService secretTokenService; + + public SecretTokenStoreImplWithoutKey(SecretTokenService secretTokenService) { + this.secretTokenService = secretTokenService; + } + + @Override + public void delete(String 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) { + 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/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..0f02b69c8b --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/security/util/SecretTokenUtil.java @@ -0,0 +1,46 @@ +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 isSecretToken(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 (!isOnlyAsciiDigit(tail.charAt(i))) { + return false; + } + } + return true; + } + + public static long extractId(String token) { + return Long.parseLong(token.substring(ENCRYPTED_VALUES_PREFIX.length())); + } + + 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/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..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 @@ -17,10 +17,11 @@ 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.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; @@ -41,6 +42,7 @@ public class BuildApplicationDeployModelStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { + 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()); @@ -58,7 +60,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 +111,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 +125,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..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 @@ -29,10 +29,11 @@ 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.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; @@ -79,10 +80,11 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DEPLOYED_MODULES, deployedModuleNames); Set mtaModulesForDeployment = context.getVariable(Variables.MTA_MODULES); getStepLogger().debug(Messages.MTA_MODULES, mtaModulesForDeployment); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); // 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 +97,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..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 @@ -27,12 +27,13 @@ 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.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; @@ -63,6 +64,7 @@ public class BuildCloudUndeployModelStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.BUILDING_CLOUD_UNDEPLOY_MODEL); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); DeployedMta deployedMta = context.getVariable(Variables.DEPLOYED_MTA); if (deployedMta == null) { @@ -84,14 +86,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 +115,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..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 @@ -9,8 +9,9 @@ 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.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; @@ -26,7 +27,8 @@ 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)); + 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 5f982c9bc9..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 @@ -15,10 +15,11 @@ 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.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; @@ -58,7 +59,9 @@ 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)); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); + 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..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 @@ -5,11 +5,11 @@ 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.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; @@ -52,7 +52,9 @@ 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)); + 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 0c5a4e5082..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 @@ -18,8 +18,9 @@ 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.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; @@ -35,12 +36,12 @@ public class CreateOrUpdateServiceBrokerStep extends TimeoutAsyncFlowableStep { @Override protected StepPhase executeAsyncStep(ProcessContext context) { getStepLogger().debug(Messages.CREATING_SERVICE_BROKERS); - + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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..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 @@ -6,13 +6,13 @@ 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.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; @@ -32,7 +32,8 @@ protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DELETING_PUBLISHED_DEPENDENCIES); List entriesToDelete = getEntriesToDelete(context); - deleteConfigurationEntries(entriesToDelete, context); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); + deleteConfigurationEntries(entriesToDelete, context, dynamicSecureSerialization); getStepLogger().debug(Messages.PUBLISHED_DEPENDENCIES_DELETED); return StepPhase.DONE; @@ -55,7 +56,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 +67,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..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 @@ -5,12 +5,12 @@ 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.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; @@ -27,7 +27,8 @@ 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)); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); + 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..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 @@ -16,11 +16,12 @@ 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.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; @@ -41,23 +42,25 @@ public class DetectDeployedMtaStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.DETECTING_DEPLOYED_MTA); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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 +72,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 +93,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..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 @@ -7,8 +7,9 @@ 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.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; @@ -24,7 +25,8 @@ 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)); + 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 4d0d1275b9..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 @@ -28,9 +28,10 @@ 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.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; @@ -56,6 +57,7 @@ public DetermineServiceCreateUpdateServiceActionsStep(ArchiveEntryExtractor arch @Override protected StepPhase executeStep(ProcessContext context) throws Exception { CloudControllerClient client = context.getControllerClient(); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); CloudServiceInstanceExtended serviceToProcess = context.getVariable(Variables.SERVICE_TO_PROCESS); getStepLogger().info(Messages.PROCESSING_SERVICE, serviceToProcess.getName()); @@ -63,7 +65,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 +81,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 +105,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 +119,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 +161,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 +183,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..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 @@ -11,9 +11,10 @@ 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.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; @@ -42,10 +43,12 @@ protected StepPhase executeStep(ProcessContext context) throws Exception { } context.getStepLogger() .debug(Messages.DETERMINING_DELETE_ACTIONS_FOR_SERVICE_INSTANCE_0, serviceInstanceToDelete); - return calculateDeleteActions(context, serviceInstanceToDelete); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); + 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 +67,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 +101,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..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 @@ -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.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; import org.springframework.context.annotation.Scope; - @Named("extractBatchedServicesWithResolvedDynamicParametersStep") @Scope(BeanDefinition.SCOPE_PROTOTYPE) public class ExtractBatchedServicesWithResolvedDynamicParametersStep extends SyncFlowableStep { @@ -34,6 +33,7 @@ public class ExtractBatchedServicesWithResolvedDynamicParametersStep extends Syn @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.EXTRACT_SERVICES_AND_RESOLVE_DYNAMIC_PARAMETERS_FROM_BATCH); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); Set dynamicResolvableParameters = context.getVariable(Variables.DYNAMIC_RESOLVABLE_PARAMETERS); List servicesCalculatedForDeployment = context.getVariableBackwardsCompatible( @@ -51,7 +51,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 +116,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..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 @@ -4,6 +4,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; @@ -13,6 +14,7 @@ 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; @@ -34,6 +36,8 @@ public class MergeDescriptorsStep extends SyncFlowableStep { @Inject private UnsupportedParameterFinder unsupportedParameterFinder; + private SecretParametersCollectingVisitor secretParametersCollectingVisitor = new SecretParametersCollectingVisitor(); + protected MtaDescriptorMerger getMtaDescriptorMerger(CloudHandlerFactory factory, Platform platform) { return new MtaDescriptorMerger(factory, platform, getStepLogger()); } @@ -45,8 +49,15 @@ 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 = secretParametersCollectingVisitor.collectSecrets(deploymentDescriptor, + extensionDescriptors); + 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); @@ -75,12 +86,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 +100,7 @@ private void backupDeploymentDescriptor(ProcessContext context, DeploymentDescri .mtaId(mtaId) .mtaVersion(mtaVersion) .spaceId(spaceGuid) - .namespace(mtaNamesapce) + .namespace(mtaNamespace) .build()); } } 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..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 @@ -13,8 +13,9 @@ 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.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; @@ -53,11 +54,13 @@ public AsyncExecutionState execute(ProcessContext context) { context.getStepLogger() .debug(Messages.LAST_OPERATION_FOR_SERVICE, service.getName(), JsonUtil.toJson(lastServiceOperation, true)); } + + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); 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..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 @@ -16,11 +16,12 @@ 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.SecureSerialization; +import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization; 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; @@ -50,8 +51,9 @@ 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); @@ -67,8 +69,8 @@ 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)); + 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..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 @@ -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,13 +8,13 @@ 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; 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; @@ -26,6 +24,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 +77,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( + Constants.SECURE_EXTENSION_DESCRIPTOR_ID)); + if (isSecureExtensionDescriptorPresent) { + List extensionDescriptorsWithoutSecure = extensionDescriptors.stream() + .filter( + extensionDescriptor -> !extensionDescriptor.getId() + .equals( + Constants.SECURE_EXTENSION_DESCRIPTOR_ID)) + .toList(); + getStepLogger().debug(Messages.PROVIDED_EXTENSION_DESCRIPTORS, SecureSerialization.toJson(extensionDescriptorsWithoutSecure) + + Messages.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..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 @@ -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,21 +13,16 @@ 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.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; 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,9 @@ protected StepPhase executeStep(ProcessContext context) { dynamicResolvableParameters); List publishedEntries = publish(context, resolvedEntriesToPublish); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); - 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..bd0d91bd77 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContext.java @@ -0,0 +1,90 @@ +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.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) { + super(execution, stepLogger, clientProvider); + this.secretTokenStore = secretTokenStore; + // this.secretTransformationStrategy = secretTransformationStrategy; + } + + private String getPidOfCurrentExecution() { + 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 = getPidOfCurrentExecution(); + + byte[] secureParameterNamesRaw = (byte[]) currentExecution.getVariable( + Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES.getName()); + + Set secureParameterNames = extractSecureParameterNames(secureParameterNamesRaw); + + // SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl( + // secureParameterNames); + // SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration(); + // secureSerializerConfiguration.setAdditionalSensitiveElementNames(secureParameterNames); + List secureParametersNamesList = new ArrayList<>(secureParameterNames); + + Serializer wrappedSerializer = new SecretTokenSerializer<>(variable.getSerializer(), secretTokenStore, + 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); + } + + @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..9654cbf197 --- /dev/null +++ b/multiapps-controller-process/src/main/java/org/cloudfoundry/multiapps/controller/process/steps/SecureProcessContextFactory.java @@ -0,0 +1,28 @@ +package org.cloudfoundry.multiapps.controller.process.steps; + +import org.cloudfoundry.multiapps.controller.core.cf.CloudControllerClientProvider; +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; + +@Value.Immutable +public abstract class SecureProcessContextFactory { + + abstract DelegateExecution getDelegateExecution(); + + abstract StepLogger getStepLogger(); + + abstract CloudControllerClientProvider getClientProvider(); + + abstract SecretTokenStore getSecretTokenStore(); + + public SecureProcessContextFactory() { + + } + + 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 59cc8d626c..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,6 +20,10 @@ 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.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 +51,10 @@ public abstract class SyncFlowableStep implements JavaDelegate { @Inject protected ApplicationConfiguration configuration; @Inject + protected SecretTokenStoreFactory secretTokenStoreFactory; + @Inject + protected SecretTokenKeyResolver secretTokenKeyResolver; + @Inject private StepLogger.Factory stepLoggerFactory; @Inject private ProcessEngineConfiguration processEngineConfiguration; @@ -89,6 +97,21 @@ 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 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 1bdd8536d8..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 @@ -32,7 +32,7 @@ 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.token.TokenService; import org.cloudfoundry.multiapps.controller.persistence.model.CloudTarget; import org.cloudfoundry.multiapps.controller.persistence.model.ConfigurationEntry; @@ -43,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; @@ -97,6 +98,7 @@ public class UpdateSubscribersStep extends SyncFlowableStep { @Override protected StepPhase executeStep(ProcessContext context) { getStepLogger().debug(Messages.UPDATING_SUBSCRIBERS); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); List publishedEntries = StepsUtil.getPublishedEntriesFromSubProcesses(context, flowableFacade); List deletedEntries = StepsUtil.getDeletedEntriesFromAllProcesses(context, flowableFacade); List updatedEntries = ListUtils.union(publishedEntries, deletedEntries); @@ -116,7 +118,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 +162,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 +177,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 +193,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..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 @@ -20,9 +20,10 @@ 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.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; @@ -87,26 +88,29 @@ public StepPhase executeAsyncStep(ProcessContext context) throws FileStorageExce } } - Optional mostRecentPackage = cloudPackagesGetter.getMostRecentAppPackage(client, cloudApp.getGuid()); + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); + + 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 +146,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 +159,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..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 @@ -13,10 +13,11 @@ 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.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; @@ -51,7 +52,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 +134,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 +142,8 @@ private List getAppsWithLiveProductizationState(List 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..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 @@ -18,9 +18,10 @@ 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.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; @@ -44,6 +45,8 @@ public ServiceBindingParametersGetter(ProcessContext context, ArchiveEntryExtrac } public Map getServiceBindingParametersFromMta(CloudApplicationExtended app, String serviceName) { + DynamicSecureSerialization dynamicSecureSerialization = SecureLoggingUtil.getDynamicSecureSerialization(context); + Optional service = getService(context.getVariable(Variables.SERVICES_TO_BIND), serviceName); if (service.isEmpty()) { return Collections.emptyMap(); @@ -57,7 +60,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..5aff053914 --- /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 final Variable variableToDelegate; + + private final 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/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 3bbcd81c59..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 @@ -32,6 +32,9 @@ + + + @@ -59,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 efc2b686bc..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 @@ + + @@ -166,6 +172,9 @@ + + + @@ -198,6 +207,9 @@ + + + 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 4beb858f9d..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 @@ + + @@ -90,6 +94,9 @@ + + + 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/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..b1623df422 --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/SecretTokenSerializerTest.java @@ -0,0 +1,286 @@ +package org.cloudfoundry.multiapps.controller.process.security; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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; +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 static final String VARIABLE_NAME = "fake_variable"; + private static final String PROCESS_INSTANCE_ID = "pid_test"; + + @BeforeEach + void setUp() { + secretTokenStore = Mockito.mock(SecretTokenStore.class); + } + + 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() { + List names = new ArrayList<>(); + names.add("value"); + + 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, names, + 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() { + List secretNames = new ArrayList<>(); + secretNames.add("password"); + + 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, secretNames, + 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() { + 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}}"; + 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 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() { + List secretNames = new ArrayList<>(); + secretNames.add("password"); + + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, secretNames, + 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, Collections.emptyList(), + PROCESS_INSTANCE_ID, VARIABLE_NAME); + + String result = serializer.deserialize(token); + assertEquals("plain_value", result); + } + + @Test + void testSerializeLeavesPlainNonJsonUntouchedWhenCsvDisabled() { + SecretTokenSerializer serializer = new SecretTokenSerializer<>( + new StringSerializerHelper(), secretTokenStore, Collections.emptyList(), + 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() { + List secretNames = new ArrayList<>(); + secretNames.add("password"); + + 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, secretNames, + 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); + } + + @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 new file mode 100644 index 0000000000..b5ba5fdea9 --- /dev/null +++ b/multiapps-controller-process/src/test/java/org/cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImplTest.java @@ -0,0 +1,129 @@ +package org.cloudfoundry.multiapps.controller.process.security.resolver; + +import java.util.HashMap; +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; +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); + + 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"); + 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<>()); + + 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 new file mode 100644 index 0000000000..87c0b4f646 --- /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.isSecretToken(null)); + } + + @Test + void testIsTokenWhenEmptyString() { + assertFalse(SecretTokenUtil.isSecretToken("")); + } + + @Test + void testIsTokenWhenWrongPrefixedString() { + 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.isSecretToken("dsc:v1:12a3")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v1:12 3")); + assertFalse(SecretTokenUtil.isSecretToken("dsc:v1:")); + } + + @Test + void testIsTokenSuccess() { + assertTrue(SecretTokenUtil.isSecretToken("dsc:v1:0")); + assertTrue(SecretTokenUtil.isSecretToken("dsc:v1:42")); + assertTrue(SecretTokenUtil.isSecretToken("dsc:v1:000123")); + } + + @Test + void testIdWhenParsingDigits() { + 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.extractId("dsc:v1:")); + } + + @Test + void testIdWhenNonDigitsThrows() { + assertThrows(NumberFormatException.class, () -> SecretTokenUtil.extractId("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.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 9de262849e..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 @@ -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,8 @@ 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.security.store.SecretTokenStore; import org.cloudfoundry.multiapps.controller.process.variables.Variables; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -27,6 +30,9 @@ import org.junit.jupiter.params.provider.MethodSource; import org.mockito.Mockito; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; + class DetectApplicationsToRenameStepTest extends SyncFlowableStepTest { @BeforeEach @@ -69,7 +75,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"); } @@ -96,12 +102,23 @@ 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(context.getControllerClient()) + Mockito.when(clientProvider.getControllerClient(anyString(), anyString(), anyString())) .thenReturn(client); + Mockito.when(secretTokenKeyResolver.resolve(execution)) + .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); 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..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 @@ -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,16 @@ 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.security.store.SecretTokenStore; 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; +import static org.mockito.ArgumentMatchers.eq; class PollServiceBindingOrKeyOperationExecutionTest extends AsyncStepOperationTest { @@ -36,11 +41,22 @@ void initialize() { context.setVariable(Variables.SERVICE_WITH_BIND_IN_PROGRESS, SERVICE_NAME); controllerClient = Mockito.mock(CloudControllerClient.class); - - Mockito.when(context.getControllerClient()) + 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)) .thenReturn(serviceInstance); + context.setVariable(Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES, Collections.emptySet()); + context.setVariable(Variables.IS_SECURITY_ENABLED, Boolean.TRUE); } @Test 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..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,6 +26,8 @@ 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.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 +95,10 @@ public abstract class SyncFlowableStepTest { @Mock protected CloudControllerClientProvider clientProvider; @Mock + protected SecretTokenStoreFactory secretTokenStoreFactory; + @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());