-
Notifications
You must be signed in to change notification settings - Fork 42
Enabling using secret values during a deployment #1755
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Enabling using secret values during a deployment #1755
Conversation
f0f4384 to
7208ebc
Compare
|
|
||
| public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List<ExtensionDescriptor> extensionDescriptors) { | ||
| public DeploymentDescriptor merge(DeploymentDescriptor deploymentDescriptor, List<ExtensionDescriptor> extensionDescriptors, | ||
| List<String> parameterNamesToBeCensored) { |
There was a problem hiding this comment.
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!"; |
There was a problem hiding this comment.
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.
| <include | ||
| file="/org/cloudfoundry/multiapps/controller/persistence/db/changelog/db-changelog-TEST-SECRET-TOKEN-TABLE-ADDITION-persistence.xml"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why was this added?
There was a problem hiding this comment.
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
| Set<String> secretParameters = VariableHandling.get(execution, Variables.SECURE_EXTENSION_DESCRIPTOR_PARAMETER_NAMES); | ||
| DynamicSecureSerialization dynamicSecureSerialization = SecureSerializationFactory.ofAdditionalValues(secretParameters); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { | |||
There was a problem hiding this comment.
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"); |
There was a problem hiding this comment.
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<>(); |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
| 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()); | ||
| } | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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 :)
| 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(), ""))); | ||
| } |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
| private void getParametersAndPropertiesPerModule(MultiValuedMap<String, String> multiValuedMap, Module module, boolean isRequired, | ||
| boolean isWhole, boolean isProvided) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 :)
...n/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java
Outdated
Show resolved
Hide resolved
.../cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java
Show resolved
Hide resolved
...n/java/org/cloudfoundry/multiapps/controller/core/security/encryption/AesEncryptionUtil.java
Outdated
Show resolved
Hide resolved
...ess/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleaner.java
Outdated
Show resolved
Hide resolved
...ess/src/main/java/org/cloudfoundry/multiapps/controller/process/jobs/SecretTokenCleaner.java
Outdated
Show resolved
Hide resolved
...udfoundry/multiapps/controller/process/security/SecretTransformationStrategyContextImpl.java
Outdated
Show resolved
Hide resolved
...ntroller/process/security/resolver/MissingUserProvidedServiceEncryptionRelatedException.java
Outdated
Show resolved
Hide resolved
.../cloudfoundry/multiapps/controller/process/security/resolver/SecretTokenKeyResolverImpl.java
Outdated
Show resolved
Hide resolved
.../org/cloudfoundry/multiapps/controller/process/steps/ProcessMtaExtensionDescriptorsStep.java
Outdated
Show resolved
Hide resolved
LMCROSSITXSADEPLOY-2301
LMCROSSITXSADEPLOY-2301
7208ebc to
148d46c
Compare
LMCROSSITXSADEPLOY-2301
| for (String additionalSensitiveElement : additionalSensitiveElementNames) { | ||
| boolean isNotExistent = mergedSensitiveElementNames.stream() | ||
| .noneMatch(sensitiveElement -> sensitiveElement.equalsIgnoreCase( | ||
| additionalSensitiveElement)); |
There was a problem hiding this comment.
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}\""; |
There was a problem hiding this comment.
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"/> |
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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?
| public SecretTokenKeyResolverImpl() { | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
needed?
| // SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl( | ||
| // secureParameterNames); | ||
| // SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration(); | ||
| // secureSerializerConfiguration.setAdditionalSensitiveElementNames(secureParameterNames); |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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?
| if (state != State.FINISHED) { | ||
| return; | ||
| } |
There was a problem hiding this comment.
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.
| public static final long TTL_CACHE_ENTRY = 60_000L; | ||
| public static final int MAX_CACHE_ENTRIES = 256; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
used?
| requires org.mybatis; | ||
| requires java.annotation; | ||
| requires tools.jackson.databind; |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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?
| // SecretTransformationStrategy secretTransformationStrategyContext = new SecretTransformationStrategyContextImpl( | ||
| // secureParameterNames); | ||
| // SecureSerializerConfiguration secureSerializerConfiguration = new SecureSerializerConfiguration(); | ||
| // secureSerializerConfiguration.setAdditionalSensitiveElementNames(secureParameterNames); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove these comments.
| // private SecretTransformationStrategy secretTransformationStrategy; | ||
|
|
||
| public SecureProcessContext(DelegateExecution execution, StepLogger stepLogger, CloudControllerClientProvider clientProvider, | ||
| SecretTokenStore secretTokenStore) { | ||
| super(execution, stepLogger, clientProvider); | ||
| this.secretTokenStore = secretTokenStore; | ||
| // this.secretTransformationStrategy = secretTransformationStrategy; |
There was a problem hiding this comment.
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)); |
There was a problem hiding this comment.
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.
| public SecureProcessContextFactory() { | ||
|
|
||
| } |
There was a problem hiding this comment.
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.
LMCROSSITXSADEPLOY-2301