Skip to content

Conversation

@karrgov
Copy link
Contributor

@karrgov karrgov commented Dec 19, 2025

LMCROSSITXSADEPLOY-2301

@karrgov karrgov force-pushed the passing_secrets_during_deployment_new branch from f0f4384 to 7208ebc Compare December 19, 2025 18:10

public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List<ExtensionDescriptor> extensionDescriptors) {
public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List<ExtensionDescriptor> extensionDescriptors,
List<String> parameterNamesToBeCensored) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think that parameterNamesToBeMasked, Hidden, Redacted, flow better here for the name.

public static final String BUILDPACKS_NOT_ALLOWED_WITH_DOCKER = "Buildpacks must not be provided when lifecycle is set to 'docker'.";
public static final String EXTENSION_DESCRIPTORS_COULD_NOT_BE_PARSED_TO_VALID_YAML = "Extension descriptor(s) could not be parsed as a valid YAML file. These descriptors may fail future deployments once stricter validation is enforced. Please review and correct them now to avoid future issues. Use at your own risk";
public static final String UNSUPPORTED_FILE_FORMAT = "Unsupported file format! \"{0}\" detected";
public static final String ENCRYPTION_BOUNCY_CASTLE_AES256_HAS_FAILED = "Encryption with AES256 by Bouncy Castle has failed!";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This message is too specific, we should avoid leaking implementation details (algorithm and the provider). Consider using a more generic message.

Comment on lines +43 to +44
<include
file="/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why was this added?

Copy link
Contributor Author

@karrgov karrgov Jan 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is the file, responsible for introducing the new table in our database - secret_token. That is where we hold the encrypted secret values while a deployment is ongoing

Comment on lines +43 to +44
Set<String> secretParameters = VariableHandling.get(execution, Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES);
DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These two lines follow the same pattern and appear in several files (with minor variations). Might be worth extracting into a small utility or helper.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed on all places, thanks :)

@@ -0,0 +1,17 @@
package org.cloudfoundry.multiapps.controller.process.security.resolver;

public class MissingCredentialsFromUserProvidedServiceEncryptionRelated extends RuntimeException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Name feels too long and missing a exception suffix. Consider shortening it.

if (resultSet.next()) {
return resultSet.getLong(1);
}
throw new SQLException("INSERT secret_token did not return an id");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we include the process instance and variable name in the exception message?


private MultiValuedMap<String, String> getParametersNameValueMapFromExtensionDescriptors(
List<ExtensionDescriptor> extensionDescriptors) {
MultiValuedMap<String, String> parametersNameValueMapFromExtensionDescriptors = new ArrayListValuedHashMap<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parametersNameValueMapFromExtensionDescriptors and parametersNameValueMapFromDeploymentDescriptor seems to be very long. Consider a shorter, more descriptive name to improve clarity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, since I have changed the logic to follow the Visitor pattern :)

Comment on lines 91 to 107
private Set<String> getNestedParameterNamesToBeCensored(MultiValuedMap<String, String> parameterNameValueMap,
Set<String> parameterNamesToBeCensored) {
Set<String> nestedParameterNamesToBeCensored = new HashSet<>();
for (Map.Entry<String, Collection<String>> parameterEntryInStringType : parameterNameValueMap.asMap()
.entrySet()) {
List<String> entryValuesToString = parameterEntryInStringType.getValue()
.stream()
.map(String::toString)
.toList();
for (String complexValue : entryValuesToString) {
for (String nameToBeCensored : parameterNamesToBeCensored) {
if (complexValue.contains(nameToBeCensored)) {
nestedParameterNamesToBeCensored.add(parameterEntryInStringType.getKey());
}
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The nested loops make the logic a bit hard to follow. Could we simplify by using streams or helper methods?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, since I have changed the logic to follow the Visitor pattern :)

Comment on lines 253 to 267
private Map<String, String> getParametersStringCastedValue(ParametersContainer parametersContainer) {
return parametersContainer.getParameters()
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
currentParameter -> Objects.toString(currentParameter.getValue(), "")));
}

private Map<String, String> getPropertiesStringCastedValue(PropertiesContainer propertiesContainer) {
return propertiesContainer.getProperties()
.entrySet()
.stream()
.collect(Collectors.toMap(Map.Entry::getKey,
currentProperty -> Objects.toString(currentProperty.getValue(), "")));
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getParametersStringCastedValue and getPropertiesStringCastedValue share almost identical logic. Consider a generic helper method to reduce duplication.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, since I have changed the logic to follow the Visitor pattern :)

Comment on lines 283 to 284
private void getParametersAndPropertiesPerModule(MultiValuedMap<String, String> multiValuedMap, Module module, boolean isRequired,
boolean isWhole, boolean isProvided) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Long boolean flag lists reduce readability. Consider replacing them with an enum or separate helper methods for each case.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, since I have changed the logic to follow the Visitor pattern :)

LMCROSSITXSADEPLOY-2301
@karrgov karrgov force-pushed the passing_secrets_during_deployment_new branch from 7208ebc to 148d46c Compare January 22, 2026 14:11
Comment on lines +32 to +35
for (String additionalSensitiveElement : additionalSensitiveElementNames) {
boolean isNotExistent = mergedSensitiveElementNames.stream()
.noneMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase(
additionalSensitiveElement));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it needed to have this check? Why not adding them as they are unique in the set?

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}\"";
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not used, delete it

<include file="/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-2.35.0-persistence.xml"/>

<include
file="/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml"/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

version reminder

@Named
public class SecretTokenKeyResolverImpl implements SecretTokenKeyResolver {

CloudControllerClientProvider cloudControllerClientProvider;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why there is no modifier?

Comment on lines +33 to +34
public SecretTokenKeyResolverImpl() {
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needed?

Comment on lines +48 to +51
// SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl(
// secureParameterNames);
// SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration();
// secureSerializerConfiguration.setAdditionalSensitiveElementNames(secureParameterNames);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why commented?

safeExecutor.execute(() -> deleteCloudControllerClientForProcess(execution));
safeExecutor.execute(() -> setOperationState(correlationId, state));
safeExecutor.execute(() -> deletePreviousBackupDescriptors(execution, processType, state));
safeExecutor.execute(() -> deleteSecretTokensForProcess(state, correlationId, execution));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the service instance deleted after the process finishes?

Comment on lines +189 to +191
if (state != State.FINISHED) {
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed. We want to remove the secret tokens when user aborts his operations.

Comment on lines +42 to +43
public static final long TTL_CACHE_ENTRY = 60_000L;
public static final int MAX_CACHE_ENTRIES = 256;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

used?

Comment on lines +66 to +68
requires org.mybatis;
requires java.annotation;
requires tools.jackson.databind;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sort asc

@Inject
private UnsupportedParameterFinder unsupportedParameterFinder;

private SecretParametersCollectingVisitor secretParametersCollectingVisitor = new SecretParametersCollectingVisitor();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this be done through a field injection like the other fields?

Comment on lines +48 to +51
// SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl(
// secureParameterNames);
// SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration();
// secureSerializerConfiguration.setAdditionalSensitiveElementNames(secureParameterNames);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these comments.

Comment on lines +21 to +27
// private SecretTransformationStrategy secretTransformationStrategy;

public SecureProcessContext(DelegateExecution execution, StepLogger stepLogger, CloudControllerClientProvider clientProvider,
SecretTokenStore secretTokenStore) {
super(execution, stepLogger, clientProvider);
this.secretTokenStore = secretTokenStore;
// this.secretTransformationStrategy = secretTransformationStrategy;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remove these comments.

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));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its better to assert the specific exception type (in this case SLException), than a generic Exception.

Comment on lines +20 to +22
public SecureProcessContextFactory() {

}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably don’t need this explicit constructor, the generated builder is used for instantiation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants