Skip to content

Commit f0f4384

Browse files
committed
Enabling using secret values during a deployment
LMCROSSITXSADEPLOY-2301
1 parent 4067975 commit f0f4384

File tree

105 files changed

+3102
-199
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

105 files changed

+3102
-199
lines changed

multiapps-controller-core/src/main/java/module-info.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
exports org.cloudfoundry.multiapps.controller.core.validators.parameters;
3939
exports org.cloudfoundry.multiapps.controller.core.validators.parameters.v2;
4040
exports org.cloudfoundry.multiapps.controller.core.validators.parameters.v3;
41+
exports org.cloudfoundry.multiapps.controller.core.security.encryption;
4142

4243
requires transitive jakarta.persistence;
4344
requires transitive org.cloudfoundry.multiapps.controller.client;
@@ -80,5 +81,8 @@
8081
requires static java.compiler;
8182
requires static org.immutables.value;
8283
requires spring.security.oauth2.client;
84+
requires java.desktop;
85+
requires io.netty.common;
86+
requires org.bouncycastle.fips.core;
8387

8488
}

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Constants.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,10 +29,14 @@ public class Constants {
2929

3030
public static final String B3_TRACE_ID_HEADER = "X-B3-TraceId";
3131
public static final String B3_SPAN_ID_HEADER = "X-B3-SpanId";
32+
public static final String CYPHER_TRANSFORMATION_NAME = "AES/GCM/NoPadding";
33+
public static final String ENCRYPTION_DECRYPTION_ALGORITHM_NAME = "AES";
3234

3335
public static final int TOKEN_SERVICE_DELETION_CORE_POOL_SIZE = 1;
3436
public static final int TOKEN_SERVICE_DELETION_MAXIMUM_POOL_SIZE = 3;
3537
public static final int TOKEN_SERVICE_DELETION_KEEP_ALIVE_THREAD_IN_SECONDS = 30;
38+
public static final int INITIALISATION_VECTOR_LENGTH = 12;
39+
public static final int GCM_AUTHENTICATION_TAG_LENGTH = 128;
3640

3741
public static final String APP_FEATURE_SSH = "ssh";
3842

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/Messages.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,8 @@ public final class Messages {
9393
public static final String BUILDPACKS_NOT_ALLOWED_WITH_DOCKER = "Buildpacks must not be provided when lifecycle is set to 'docker'.";
9494
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";
9595
public static final String UNSUPPORTED_FILE_FORMAT = "Unsupported file format! \"{0}\" detected";
96+
public static final String ENCRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED = "Encryption with AES256 by Bouncy Castle has failed!";
97+
public static final String DECRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED = "Decryption with AES256 by Bouncy Castle has failed!";
9698

9799
// Warning messages
98100
public static final String ENVIRONMENT_VARIABLE_IS_NOT_SET_USING_DEFAULT = "Environment variable \"{0}\" is not set. Using default \"{1}\"...";

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/helpers/MtaDescriptorMerger.java

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@
44

55
import org.cloudfoundry.multiapps.controller.core.Messages;
66
import org.cloudfoundry.multiapps.controller.core.cf.CloudHandlerFactory;
7-
import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerialization;
7+
import org.cloudfoundry.multiapps.controller.core.security.serialization.DynamicSecureSerialization;
8+
import org.cloudfoundry.multiapps.controller.core.security.serialization.SecureSerializationFactory;
89
import org.cloudfoundry.multiapps.controller.core.util.UserMessageLogger;
910
import org.cloudfoundry.multiapps.mta.handlers.v2.DescriptorMerger;
1011
import org.cloudfoundry.multiapps.mta.handlers.v2.DescriptorValidator;
@@ -28,11 +29,13 @@ public MtaDescriptorMerger(CloudHandlerFactory handlerFactory, Platform platform
2829
this.userMessageLogger = userMessageLogger;
2930
}
3031

31-
public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List<ExtensionDescriptor> extensionDescriptors) {
32+
public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List<ExtensionDescriptor> extensionDescriptors,
33+
List<String> parameterNamesToBeCensored) {
3234
DescriptorValidator validator = handlerFactory.getDescriptorValidator();
3335
validator.validateDeploymentDescriptor(deploymentDescriptor, platform);
3436
validator.validateExtensionDescriptors(extensionDescriptors, deploymentDescriptor);
3537

38+
DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(parameterNamesToBeCensored);
3639
DescriptorMerger merger = handlerFactory.getDescriptorMerger();
3740

3841
// 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
4548

4649
deploymentDescriptor = handlerFactory.getDescriptorParametersCompatibilityValidator(mergedDescriptor, userMessageLogger)
4750
.validate();
48-
logDebug(Messages.MERGED_DESCRIPTOR, SecureSerialization.toJson(deploymentDescriptor));
51+
logDebug(Messages.MERGED_DESCRIPTOR, dynamicSecureSerialization.toJson(deploymentDescriptor));
4952

5053
return deploymentDescriptor;
5154
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.cloudfoundry.multiapps.controller.core.security.encryption;
2+
3+
public class AESDecryptionException extends RuntimeException {
4+
5+
public AESDecryptionException(String message) {
6+
super(message);
7+
}
8+
9+
public AESDecryptionException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
13+
public AESDecryptionException(Throwable cause) {
14+
super(cause);
15+
}
16+
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.cloudfoundry.multiapps.controller.core.security.encryption;
2+
3+
public class AESEncryptionException extends RuntimeException {
4+
5+
public AESEncryptionException(String message) {
6+
super(message);
7+
}
8+
9+
public AESEncryptionException(String message, Throwable cause) {
10+
super(message, cause);
11+
}
12+
13+
public AESEncryptionException(Throwable cause) {
14+
super(cause);
15+
}
16+
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
package org.cloudfoundry.multiapps.controller.core.security.encryption;
2+
3+
import java.nio.charset.StandardCharsets;
4+
import java.security.SecureRandom;
5+
import javax.crypto.Cipher;
6+
import javax.crypto.spec.GCMParameterSpec;
7+
import javax.crypto.spec.SecretKeySpec;
8+
9+
import org.bouncycastle.jcajce.provider.BouncyCastleFipsProvider;
10+
import org.cloudfoundry.multiapps.controller.core.Constants;
11+
import org.cloudfoundry.multiapps.controller.core.Messages;
12+
13+
public class AesEncryptionUtil {
14+
15+
public static byte[] encrypt(String plainText, byte[] encryptionKey) {
16+
try {
17+
byte[] gcmInitialisationVector = new byte[Constants.INITIALISATION_VECTOR_LENGTH];
18+
new SecureRandom().nextBytes(gcmInitialisationVector);
19+
20+
Cipher cipherObject = Cipher.getInstance(Constants.CYPHER_TRANSFORMATION_NAME, BouncyCastleFipsProvider.PROVIDER_NAME);
21+
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, Constants.ENCRYPTION_DECRYPTION_ALGORITHM_NAME);
22+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(Constants.GCM_AUTHENTICATION_TAG_LENGTH, gcmInitialisationVector);
23+
24+
cipherObject.init(Cipher.ENCRYPT_MODE, secretKeySpec, gcmParameterSpec);
25+
26+
byte[] cipherValue = cipherObject.doFinal(plainText.getBytes(StandardCharsets.UTF_8));
27+
28+
byte[] combinedCypherValueAndInitialisationVector = new byte[gcmInitialisationVector.length + cipherValue.length];
29+
30+
System.arraycopy(gcmInitialisationVector, 0, combinedCypherValueAndInitialisationVector, 0, gcmInitialisationVector.length);
31+
System.arraycopy(cipherValue, 0, combinedCypherValueAndInitialisationVector, gcmInitialisationVector.length,
32+
cipherValue.length);
33+
34+
return combinedCypherValueAndInitialisationVector;
35+
} catch (Exception e) {
36+
throw new AESEncryptionException(Messages.ENCRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED
37+
+ e.getMessage(), e);
38+
}
39+
}
40+
41+
public static String decrypt(byte[] encryptedValue, byte[] encryptionKey) {
42+
try {
43+
byte[] gcmInitialisationVector = new byte[Constants.INITIALISATION_VECTOR_LENGTH];
44+
System.arraycopy(encryptedValue, 0, gcmInitialisationVector, 0,
45+
gcmInitialisationVector.length);
46+
47+
byte[] cipherValue = new byte[encryptedValue.length - 12];
48+
System.arraycopy(encryptedValue, 12, cipherValue, 0, cipherValue.length);
49+
50+
Cipher cipherObject = Cipher.getInstance(Constants.CYPHER_TRANSFORMATION_NAME, BouncyCastleFipsProvider.PROVIDER_NAME);
51+
SecretKeySpec secretKeySpec = new SecretKeySpec(encryptionKey, Constants.ENCRYPTION_DECRYPTION_ALGORITHM_NAME);
52+
GCMParameterSpec gcmParameterSpec = new GCMParameterSpec(Constants.GCM_AUTHENTICATION_TAG_LENGTH, gcmInitialisationVector);
53+
cipherObject.init(Cipher.DECRYPT_MODE, secretKeySpec, gcmParameterSpec);
54+
55+
byte[] resultInBytes = cipherObject.doFinal(cipherValue);
56+
return new String(resultInBytes, StandardCharsets.UTF_8);
57+
} catch (Exception e) {
58+
throw new AESDecryptionException(Messages.DECRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED
59+
+ e.getMessage(), e);
60+
}
61+
}
62+
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package org.cloudfoundry.multiapps.controller.core.security.serialization;
2+
3+
import org.cloudfoundry.multiapps.controller.core.security.serialization.model.DeploymentDescriptorSerializer;
4+
import org.cloudfoundry.multiapps.controller.core.security.serialization.model.ModuleSerializer;
5+
import org.cloudfoundry.multiapps.controller.core.security.serialization.model.ProvidedDependencySerializer;
6+
import org.cloudfoundry.multiapps.controller.core.security.serialization.model.RequiredDependencySerializer;
7+
import org.cloudfoundry.multiapps.controller.core.security.serialization.model.ResourceSerializer;
8+
import org.cloudfoundry.multiapps.mta.model.DeploymentDescriptor;
9+
import org.cloudfoundry.multiapps.mta.model.Module;
10+
import org.cloudfoundry.multiapps.mta.model.ProvidedDependency;
11+
import org.cloudfoundry.multiapps.mta.model.RequiredDependency;
12+
import org.cloudfoundry.multiapps.mta.model.Resource;
13+
import org.cloudfoundry.multiapps.mta.model.VersionedEntity;
14+
15+
public final class DynamicSecureSerialization {
16+
17+
private final SecureSerializerConfiguration secureSerializerConfiguration;
18+
19+
DynamicSecureSerialization(SecureSerializerConfiguration secureSerializerConfiguration) {
20+
this.secureSerializerConfiguration = secureSerializerConfiguration;
21+
}
22+
23+
public String toJson(Object object) {
24+
SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializer(object, secureSerializerConfiguration);
25+
return secureJsonSerializer.serialize(object);
26+
}
27+
28+
private static SecureJsonSerializer createDynamicJsonSerializer(Object object,
29+
SecureSerializerConfiguration secureSerializerConfiguration) {
30+
SecureJsonSerializer secureJsonSerializer = createDynamicJsonSerializerForVersionedEntity(object, secureSerializerConfiguration);
31+
if (secureJsonSerializer == null) {
32+
return new SecureJsonSerializer(secureSerializerConfiguration);
33+
}
34+
35+
return secureJsonSerializer;
36+
}
37+
38+
private static SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(Object object,
39+
SecureSerializerConfiguration secureSerializerConfiguration) {
40+
if (object instanceof VersionedEntity) {
41+
return createDynamicJsonSerializerForVersionedEntity((VersionedEntity) object, secureSerializerConfiguration);
42+
}
43+
44+
return null;
45+
}
46+
47+
private static SecureJsonSerializer createDynamicJsonSerializerForVersionedEntity(VersionedEntity versionedEntity,
48+
SecureSerializerConfiguration secureSerializerConfiguration) {
49+
if (versionedEntity.getMajorSchemaVersion() < 3) {
50+
return null;
51+
}
52+
53+
if (versionedEntity instanceof DeploymentDescriptor) {
54+
return new DeploymentDescriptorSerializer(secureSerializerConfiguration);
55+
}
56+
57+
if (versionedEntity instanceof Module) {
58+
return new ModuleSerializer(secureSerializerConfiguration);
59+
}
60+
61+
if (versionedEntity instanceof ProvidedDependency) {
62+
return new ProvidedDependencySerializer(secureSerializerConfiguration);
63+
}
64+
65+
if (versionedEntity instanceof RequiredDependency) {
66+
return new RequiredDependencySerializer(secureSerializerConfiguration);
67+
}
68+
69+
if (versionedEntity instanceof Resource) {
70+
return new ResourceSerializer(secureSerializerConfiguration);
71+
}
72+
73+
return null;
74+
}
75+
76+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.cloudfoundry.multiapps.controller.core.security.serialization;
2+
3+
import java.util.Collection;
4+
5+
public final class SecureSerializationFactory {
6+
7+
private SecureSerializationFactory() {
8+
9+
}
10+
11+
public static DynamicSecureSerialization ofAdditionalValues(Collection<String> additionalSensitiveElementNames) {
12+
SecureSerializerConfiguration secureSerializerConfigurationWithAdditionalValues = new SecureSerializerConfiguration();
13+
14+
secureSerializerConfigurationWithAdditionalValues.setAdditionalSensitiveElementNames(additionalSensitiveElementNames);
15+
return new DynamicSecureSerialization(secureSerializerConfigurationWithAdditionalValues);
16+
}
17+
18+
}

multiapps-controller-core/src/main/java/org/cloudfoundry/multiapps/controller/core/security/serialization/SecureSerializerConfiguration.java

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import java.util.Collection;
44
import java.util.Collections;
5+
import java.util.LinkedList;
56
import java.util.List;
67

78
import org.apache.commons.lang3.StringUtils;
@@ -17,8 +18,25 @@ public class SecureSerializerConfiguration {
1718
private Collection<String> sensitiveElementNames = DEFAULT_SENSITIVE_NAMES;
1819
private Collection<String> sensitiveElementPaths = Collections.emptyList();
1920

21+
private Collection<String> additionalSensitiveElementNames = Collections.emptyList();
22+
2023
public Collection<String> getSensitiveElementNames() {
21-
return sensitiveElementNames;
24+
if (additionalSensitiveElementNames == null || additionalSensitiveElementNames.isEmpty()) {
25+
return sensitiveElementNames;
26+
}
27+
28+
List<String> mergedSensitiveElementNames = new LinkedList<>(sensitiveElementNames);
29+
30+
for (String currentAdditionalSensitiveElement : additionalSensitiveElementNames) {
31+
boolean isExistentAlready = mergedSensitiveElementNames.stream()
32+
.anyMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase(
33+
currentAdditionalSensitiveElement));
34+
if (!isExistentAlready) {
35+
mergedSensitiveElementNames.add(currentAdditionalSensitiveElement);
36+
}
37+
}
38+
39+
return mergedSensitiveElementNames;
2240
}
2341

2442
public Collection<String> getSensitiveElementPaths() {
@@ -33,6 +51,10 @@ public void setSensitiveElementNames(Collection<String> sensitiveElementNames) {
3351
this.sensitiveElementNames = sensitiveElementNames;
3452
}
3553

54+
public void setAdditionalSensitiveElementNames(Collection<String> additionalSensitiveElementNames) {
55+
this.additionalSensitiveElementNames = additionalSensitiveElementNames;
56+
}
57+
3658
public void setSensitiveElementPaths(Collection<String> sensitiveElementPaths) {
3759
this.sensitiveElementPaths = sensitiveElementPaths;
3860
}

0 commit comments

Comments
 (0)