spring-cloud / spring-cloud/spring-cloud-commons

TextEncryptor not resolved from bootstrapContext in DecryptEnvironmentPostProcessor

Open
#897 13 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

feedback-provided
Dominant language
Java
Stars
751
Forks
744
Avg merge
1d 14h
Merged PRs (30d)
9

Description

Version Information

spring-cloud-context: 3.0.1

Details

Pre-Ilford, it was possible to inject a custom TextEncryptor into the framework to override the default implementation of decrypting a secret. (In my case, I am using GCP KMS to decrypt the secrets.)

The TextEncryptorBindHandler introduced in #872 allows for a custom TextEncryptor implementations by resolving from the bootstrap context: TextEncryptorConfigBootstrapper:L70

registry.registerIfAbsent(BindHandler.class, context -> {
    TextEncryptor textEncryptor = context.get(TextEncryptor.class);
    if (textEncryptor != null) {
        KeyProperties keyProperties = context.get(KeyProperties.class);
        return new TextEncryptorBindHandler(textEncryptor, keyProperties);
    }
    return null;
});

However, DecryptEnvironmentPostProcessor prefers to create it's own TextEncryptor: DecryptEnvironmentPostProcessor:L78

protected TextEncryptor getTextEncryptor(ConfigurableEnvironment environment) {
    Binder binder = Binder.get(environment);
    KeyProperties keyProperties = binder.bind(KeyProperties.PREFIX, KeyProperties.class)
            .orElseGet(KeyProperties::new);
    if (keysConfigured(keyProperties)) {
        setFailOnError(keyProperties.isFailOnError());
        if (ClassUtils.isPresent("org.springframework.security.rsa.crypto.RsaSecretEncryptor", null)) {
            RsaProperties rsaProperties = binder.bind(RsaProperties.PREFIX, RsaProperties.class)
                    .orElseGet(RsaProperties::new);
            return EncryptionBootstrapConfiguration.createTextEncryptor(keyProperties, rsaProperties);
        }
        return new EncryptorFactory(keyProperties.getSalt()).create(keyProperties.getKey());
    }
    // no keys configured
    return new FailsafeTextEncryptor();
}

Workaround

To enable custom decryption of properties in the spring environment:

  • Subclass DecryptEnvironmentPostProcessor (with higher priority) to:
    • decrypt properties using custom TextEncryptor by overriding getTextEncryptor to create a new instance of the custom TextEncryptor.
    • inject a custom PropertySource to enable (for example) spring.config.use-legacy-processing to prevent DecryptEnvironmentPostProcessor from processing encrypted properties (*).
  • Because the standard DecryptEnvironmentPostProcessor executes at lowest priority, use a high-priority ApplicationContextInitializer to remove the custom PropertySource before any other ApplicationContextInitializer implementations might execute and use the temporary value.

In addition to the above, to support decrypting properties with TextEncryptorBindHandler:

  • Add a custom EnvironmentPostProcessor to register a TextEncryptor supplier that creates a new instance of the custom TextEncryptor.

(*) If DecryptEnvironmentPostProcessor executes (because neither spring cloud bootstrap nor legacy processing is enabled), this results in a FailsafeTextEncryptor being created (**). An exception gets thrown when DecryptEnvironmentPostProcessor also tries to decrypt any values that have been encrypted.

(**) Because the properties to configure a normal TextEncryptor are not defined, as we are intending to use a custom implementation instead.

Questions

  • Should people be allowed to customise TextEncryptor during bootstrap to decrypt ConfigData values?
  • If not, should people simply use a different prefix for encrypted values and clone the code for DecryptEnvironmentPostProcessor and AbstractEnvironmentDecrypt?
  • Or alternatively, people could implement a ConfigData version of what is described in Spring Boot Configuration with Jasypt. However, this means moving away from using the {cipher} prefix and so away from how spring-cloud denotes encrypted values.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with DecryptEnvironmentPostProcessor#getTextEncryptor and TextEncryptorConfigBootstrapper, especially the referenced lines, and trace how each obtains a TextEncryptor during bootstrap and ConfigData processing. Reproduce the custom-encryptor scenario described in the workaround, then establish the intended bootstrap behavior and cover the agreed resolution with relevant tests.

Written by the indexing model from the issue text.

Assessment

Tech stack
java, spring, spring-boot
Domain
backend
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
25/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.