Centralized configuration management

Dariusz Andrzej Stefański edited this page Apr 22, 2015 · 24 revisions

This module aims to provide the following features to microservices:

  • centralized, managed and versioned repository for configuration (addresses, tuning options)
  • systematic way of storing secrets like database passwords and external API keys
  • automatic propagation of configuration changes across environments and applications

Most of the implementation uses Spring Cloud project.

Central configuration repository

This project supports reading configuration from one, globally managed directory on the file system. This directory, in principle, should contain all configuration in .properties or .yaml files in hierarchy mirroring microservice name. Let's take a look at the example of /com/ofg/service-pl service. The /com/ofg/service-pl path is the path that is present in the microservice.json file in the this section concatenated with the realm.

{
    "pl": {
        "this": "com/ofg/service"
    }
}

The base directory takes CONFIG_FOLDER (e.g. /home/user/properties) and APP_ENV (e.g. prod) system properties/environment variables into account, for example. /home/user/properties/prod/com/ofg/...

common/global.(yaml|properties) configuration files can be shared accross all microservices.

Configuration files from common directory can be shared across all environments.

For the aforementioned setup the following configuration files are scanned:

  • /home/user/properties/common/global.properties
  • /home/user/properties/common/global.yaml
  • /home/user/properties/common/com/ofg/service.properties
  • /home/user/properties/common/com/ofg/service.yaml
  • /home/user/properties/prod/com/ofg/service.properties
  • /home/user/properties/prod/com/ofg/service.yaml
  • /home/user/properties/common/com/ofg/pl/service-pl.properties
  • /home/user/properties/common/com/ofg/pl/service-pl.yaml
  • /home/user/properties/prod/com/ofg/pl/service-pl.properties
  • /home/user/properties/prod/com/ofg/pl/service-pl.yaml

Order is important, in case of duplicated keys last configuration option overrides previous ones. Notice that this setup allows you to share configuration between different realms (e.g. countries). Core configuration is in service.yaml while country-specific overrides are placed in service-pl.yaml.

Encrypting confidential properties

If configuration option should be kept in secret, we can still put it in global, public repository. Just encrypt it (see aes.rb) using symmetric key and prefix with {cipher}. The application will automatically decrypt it with provided key. Please make sure that a value is surrounded by quotes. In yaml {} means a map.

Encrypted keys in .properties file:

db.password=f29ed1c69fa39d4d33c72305a2bf49bf30f2841aa1e9da7e052e3336ce

or in YAML:

db:
  password: "{cipher}f29ed1c69fa39d4d33c72305a2bf49bf30f2841aa1e9da7e052e3336ce"

Encrypt/decrypt script

You can use aes.rb to encrypt arbitrary text using master password (see encrypt.key):

$ ruby aes.rb -e 'S3cret' 'master password'
{cipher}68854851c84984d6e188c9615a44e5122db7ec46f34a468a60ff7ca4efde2779

decrypt it:

$ ruby aes.rb -d '68854851c84984d6e188c9615a44e5122db7ec46f34a468a60ff7ca4efde2779' 'master password'
S3cret

and decrypt whole file:

$ ruby aes.rb -f micro-app-pl.yaml 'master password'
...

It is important to use single quotation mark (') if you have a dollar ($) in your password.

Troubleshooting

Could not locate PropertySource: Unable to invoke Cipher due to bad padding

This message on startup is slightly misleading, most likely your encrypt.key is incorrect.

WARNING. It seems your JDK does not support strong encryption

See general development troubleshooting section.

Refreshing configuration

When you modify (preferably via centralized git repository) given configuration file, this module will automatically discover it and refresh chosen beans. If your bean wants to take advantage of configuration updates, simply annotate with @RefreshScope:

@Bean
@RefreshScope
public MyService myService() { /* ... */ }

This bean will be lazily reloaded (destroyed and created from scratch) when any configuration change is discovered.

Refreshing on Mac OS

Native file system polling implementation used in JDK on Mac OS in some cases needs 10+ seconds to detect a change done in observed directory.

Installing

Just add micro-infra-spring-property to your CLASSPATH. Also you need to add the following system properties on startup:

-DCONFIG_FOLDER=/home/.../properties
-DAPP_ENV=...
-Dencrypt.key=your_encryption_key
-Dspring.cloud.config.enabled=false

Where

  • CONFIG_FOLDER represents root configuration folder, under which properties for all applications are located (e.g. /home/user/config/prod/com/ofg/...)
  • APP_ENV defines application environment (e.g. 'prod')
  • encrypt.key is a symmetric key for decrypting secret properties
  • spring.cloud.config.enabled disables built-in Spring Cloud property resolution

All of the above can be either system properties (-D) or environment variables. Environment variables must be written camel case with underscores. For example encrypt.key should be changed to ENCRYPT_KEY.

Make sure encrypt.key is NOT accessible publicly.

Different configuration file name

In some specific cases (e.g. integration tests) it could be required to read configuration from different file. It can be achieved by setting microservice.config.file system property, e.g.: -Dmicroservice.config.file=classpath:microservice2.json.

Disabling

If you don't want to use this functionality, simply exclude micro-infra-spring-config from CLASSPATH:

compile ("com.ofg:micro-infra-spring-boot-starter:$microInfraSpringVersion") {
    exclude module: 'micro-infra-spring-config'
}