Custom client-side password decryption and Spring Cloud Config Server

When using Spring Cloud Config Server it usually doesn’t take long before passwords end up in the underlying GIT repository as well. Using the {cipher} prefix and symmetric or private/public key (stored in a keystore), it is possible to store encrypted versions of these passwords, such that they don’t appear in plaintext in the GIT repository. By default, the config server decrypts them before sending them to the client application.

This blog posts follows a slightly different use case and discusses some modification to the default approach:

  • Decrypting at the client side
  • Decryption using a custom algorithm
  • Using autoconfiguration to do this transparently

The example I’ll construct has several elements: a configuration server, a client application and a library that will do the decryption transparently.

This blog post was written for Spring Cloud Config 1.2.2.RELEASE. The code can be found at https://github.com/kuipercm/custom-spring-cloud-config-decryption-example.

Situation

The requirements we’re trying to satisfy are as follows:

  1. It is not allowed to send plaintext passwords ‘over the line’. This is a security requirement, even when using HTTPS connections with mutual authentication and a cipher.
  2. The decryption should be transparent to the application: it shouldn’t know that the passwords are encrypted, nor what kind of algorithm they use
  3. There is a custom, business-approved encryption/decryption methodology in place. For the purposes of this blog post, the algorithm is simply to reverse the encrypted password, since it is an easy method to demonstrate. In reality you can think of any other method. The method used in this example is not secure at all, so don’t use it in real applications!

Configuration Server

To get started, let’s first create a “default” configuration server.

@SpringBootApplication
@EnableConfigServer
public class ConfigurationServer {
    public static void main(String[] args) {
        SpringApplication.run(ConfigurationServer.class, args);
    }
}

By setting the appropriate properties, the configuration server will know which GIT repository to clone and to expose through its endpoints. This is all default behavior and is well documented in the documentation.

It is however important to note that the property to send decrypted passwords to clients has been disabled: spring.cloud.config.server.encrypt.enabled=false. This way the clients are in charge of decrypting the encrypted data.

Configuration Client

The client is also relatively straightforward.

@SpringBootApplication
public class ConfigurationClient {
    public static void main(String[] args) {
        SpringApplication.run(ConfigurationClient.class, args);
    }
}

where this application has a dependency on Spring Cloud Config Client in its pom.xml. (For full details, see the git repository.)

The client application also contains a repository class which connects to the in-memory h2 database using HikariCP. The properties for this connection, including the encrypted password, are in the configuration repository.

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: johnsmith
    password: '{cipher}drowssapxelpmocym'
    hikari:
      idle-timeout: 10000

Since we told the server application not to decrypt encrypted properties before sending them to the client, the client application will receive the properties as described above. So, now it’s up to the client to decrypt these properties.

The decryption library

There are several options to facilitate the decryption.

The most naive implementation I can come up with is to create a Utility class that is called manually to decrypt the properties as needed. This works, but requires us to inject properties manually, decrypt them and then use them to construct the objects we want. In the case of the Hikari datasource, this approach makes autoconfiguration of the object impossible and we’d have to do it by hand. Probably we can do better.

An alternative, less naive, approach is to use an application event, decrypt all encrypted properties upon this event and place them back in the Spring environment. That way we would still be able to use the default autoconfiguration behavior and the whole decryption part is transparent to the rest of the application, which is nice. The downside is that we have to have a lot of knowledge about the internals of the configuration client. For example, we have to know how the properties are inserted into the Spring context by the client such that we can do the right things when trying to decrypt them. Should the internals of the configuration client change, there is a good chance we have to change our code as well.

The cleanest solution I’ve found is to tap into the mechanism used by the configuration client when decrypting the encrypted values and substitute an alternative decryption strategy to be used.

The TextExcryptor

At the basis of the whole encryption/decryption mechanism is the so-called TextEncryptor and, despite what its name suggest, it is used to both encrypt and decrypt. In this example, the TextEncryptor is very, very simple, so once again the warning: don’t try this at home! This implementation is not secure at all, but is for demonstration purposes only.

public class ReversableEncryptor implements TextEncryptor {
    @Override
    public String encrypt(String toEncrypt) {
        return new StringBuilder().append(toEncrypt).reverse().toString();
    }

    @Override
    public String decrypt(String toDecrypt) {
        return new StringBuilder().append(toDecrypt).reverse().toString();
    }
}

Please note that this implementation doesn’t do anything with the prefix ‘{cipher}’. This will already be stripped by the configuration client.

Tap into autoconfiguration

Now we need to make sure that the custom encryptor is picked up by the Spring configuration and that it replaces other encryptors. The easiest way to achieve this is by using Spring Boots autoconfiguration to load the encryptor.

@Configuration
public class CustomEncryptorBootstrapConfiguration {
    public static final String CUSTOM_ENCRYPT_PROPERTY_NAME = "bldn.encryption";
    public static final String REVERSABLE_ENCRYPTION = "reversable";

    @Configuration
    @ConditionalOnProperty(name = CUSTOM_ENCRYPT_PROPERTY_NAME, havingValue = REVERSABLE_ENCRYPTION)
    protected static class ReverableEncryptorConfiguration {
        @Bean
        @ConditionalOnMissingBean(ReversableEncryptor.class)
        public TextEncryptor reversableEncryptor() {
            return new ReversableEncryptor();
        }
    }
}

In this configuration, our ReversableEncryptor is loaded when there is a property “bldn.encryption” present with the value “reverable”. This property can be provided via de commandline or via other means, although it is not advisable to provide it through the configuration server (as it might come too late then).

To actually load this Configuration as part of the autoconfiguration of the application, we need to add a spring.factories file with (at least) the following content

org.springframework.cloud.bootstrap.BootstrapConfiguration=\
nl.bldn.projects.customdecryption.CustomEncryptorBootstrapConfiguration

This will make sure the CustomEncryptorBootstrapConfiguration is loaded during application startup and when the require property is present, the custom TextEncryptor will take the place of the default one.

When the properties are now received by the client, the custom decryption mechanism will kick in and the application will use the decrypted properties for other autoconfigured beans.

Final Remarks

This blog post has shown a way to setup custom decryption of encrypted properties received from a Spring Cloud Config Server. In this scenario the decryption is left to the client application and the method of decryption cannot be captured by the default methods available in Spring Cloud Config.

The code of this example can be found at https://github.com/kuipercm/custom-spring-cloud-config-decryption-example. To test out the code, follow the instructions of the readme file.

Happy coding!

Unit testing log statements

Usually logging is not considered when writing tests. This is often because logging is not the primary purpose of a class and will, in terms of line coverage, usually be covered automatically. There are however situations when logging is the primary purpose of a class and as such it requires more thorough testing. When using a logging framework such as SLF4J however, the difficulty is in how to determine that the logging actually happening and that is has the correct level and content: the logging framework is usually not very test-orientated.

There are several posts on stackoverflow that deal with unit testing logging in java, so for a broader overview, check there. This post will give single, straightforward method of testing log statements.

The code for this example can be found at https://github.com/kuipercm/test-logging

Situation

Let’s assume that we are writing a component that handles sending and receiving messages to an external source. For traceability and auditability purposes it is a business requirement to log all incoming and outgoing messages. This makes the logging an essential part of the application and it should therefore be verified in tests.

To facilitate the logging, a separate class is created such the details of what to log and how are still abstracted from the main, more functional, program flow. It has the added benefit that the logging class can be more easily tested.

public class FruitMachine {
    private final FruitLogger logger = FruitLogger.INSTANCE;

    private final String remoteUrl;

    public FruitMachine(String remoteUrl) {
        this.remoteUrl = remoteUrl;
    }

    public void sendMessage(FruityMessage message) {
        logger.logAllFruitMessages(message);
        //do some remote call
    }

}

In the code sample above, the logging of the FruitMachine class is handled by the FruitLogger instance. The logger is called for all FruitMessages.

The FruitLogger itself it not special:

public class FruitLogger {
    public static final FruitLogger INSTANCE = new FruitLogger();

    static final Logger log = LoggerFactory.getLogger(FruitLogger.class);

    private FruitLogger() {
        //don't instatiate outside this class
    }

    public void logAllFruitMessages(FruityMessage message) {
        if (message != null) {
            log.info("Outgoing message body: {}", message.getBody());
        }
    }
}

It logs the message body at INFO level. The Logger is an SLF4j component.

Since the primary purpose of the FruitLogger is to log the message (albeit a little bit simplistic in this example), it is good to have (at least) a unit test for it.

The Test

In the FruitLogger class above, there are two important things to note:

  1. The Logger “log” field has package private (or “default”) visibility. This is to facilitate easier interaction in the unit test.
  2. Of all non-null messages, the message body is logged.

The difficult part is how to test that the “log” will actually contain the message body, because even though we can access the “log” field in the unit test, there are no methods on it such that we can examine what has been sent to it.

To solve this, we can create a test in the following way

public class FruitLoggerTest {
    private static Logger fruityLogger;
    private static ListAppender<ILoggingEvent> fruityLoggerAppender;

    @BeforeClass
    public static void setupBeforeClass() {
        LoggerContext context = (LoggerContext)LoggerFactory.getILoggerFactory();
        fruityLogger = context.getLogger(log.getName());
        fruityLoggerAppender = new ListAppender<>();
        fruityLoggerAppender.start();
        fruityLogger.addAppender(fruityLoggerAppender);
    }

    @Before
    public void setup() {
        fruityLogger.setLevel(Level.ALL);
        fruityLoggerAppender.clearAllFilters();
        fruityLoggerAppender.list.clear();
    }

    @Test
    public void verify_that_the_fruity_message_content_is_logged_at_info_level() {
        assertThat(fruityLoggerAppender.list).hasSize(0);

        FruityMessage fruityMessage = new FruityMessage("apples", "oranges");
        INSTANCE.logAllFruitMessages(fruityMessage);

        assertThat(fruityLoggerAppender.list).hasSize(1);

        ILoggingEvent loggedEvent = fruityLoggerAppender.list.get(0);
        assertThat(loggedEvent.getLevel()).isEqualTo(Level.INFO);
        assertThat(loggedEvent.getFormattedMessage()).contains("body");
        assertThat(loggedEvent.getFormattedMessage()).contains("oranges");
    }
}

This test extracts the fruityLogger from the logging context based on the name of the logger. Subsequently it adds a ListAppender to this logger. The ListAppender is a type of Appender that has an internal list of the ILoggingEvents sent to it and this list can be accessed. This makes usage of this Appender in the test context very useful, as can be seen in this test class.

There are a few things to note with respect to the setup() method:

  • the log level of the fruityLogger is reset to Level.ALL: in unit tests you can change the log level of the logger, to verify that when a certain log level is set, certain messages will no longer be logged. For example, if the log level were to be set to WARN, then the FruitLogger should no longer log the message body. Although it can be considered testing of the logging framework to execute such a test, there are situations in which it can be crucial to verify the behavior.
  • the Appender is cleared of all filters: filters can interfere with which messages are accepted in the appender. It is a good idea to start each test with a clean slate.
  • the ListAppender.list field is also cleared to make sure that each test will only put its own messages in the Appender and will not be influenced by other tests.

The test itself is not difficult to understand. It should be noted that the FruityMessage consists of a header and body field. In this example, the header is “apples”, the body is “oranges”.

Final remarks

As demonstrated above, it is possible to test the logging of classes in unit tests. This is particularly useful in situations where logging becomes a primary purpose of a class due to business requirements.

The code of this example can be found on https://github.com/kuipercm/test-logging. What is not shown in the code samples above is which dependencies are required for this code to work. These are in the pom.xml of the project in Github.

Happy testing!