Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config

Escrito por picodotdev el , actualizado el .
java planeta-codigo programacion spring
Enlace permanente Comentarios

Spring

Java

Para no tener que hacer cambios en el código que implica recompilar la aplicación y generar de nuevos los artefactos al cambiar algunos valores de la aplicación se utilizan los archivos de configuración. Los archivos de configuración son archivos de texto plano que pueden seguir algún formato como properties, xml o yaml, externos a la aplicación pero que lee sus propiedades al iniciarse. Algunas propiedades de configuración de ejemplo pueden ser la cadena de conexión a una base de datos, el usuario y contraseña.

Dado que algunos valores de configuración son sensibles como en el ejemplo anterior la cadena de conexión, usuario y contraseña es recomendable por mayor seguridad almacenar estos valores cifrados y que la aplicación los descifre con la clave correspondiente al necesitar su valor original. Estos valores no deben estar en el código fuente para evitar un problema de seguridad aún con el código fuente compilado.

Spring Cloud Config permite guardar los archivos de configuración con algunos valores cifrados. Hay varias posibilidades de configuración para guardar los datos cifrados: mediante clave simétrica, clave privada-pública, guardarlos en el servicio externo Vault de Hashicorp, mantenerlos cifrados solo en el almacenamiento persistente o transmitirlos cifrados y que sea el cliente el que los descifre.

Utilizando la forma más simple para mantener los datos cifrados con una clave simétrica en el servicio de configuración hay que mantener en una propiedad de configuración la clave simétrica para cifrar y descifrar los datos, encrypt.key. En este ejemplo la clave simétrica y las propiedades cifradas están en archivos de configuración diferentes pero incluidos en el mismo servidor de configuración. Esto no parece que aporte mucha seguridad ya que si se tiene acceso al archivo de configuración de un servicio con una propiedad cifrada probablemente se tenga acceso al archivo con la clave cifrada y la medida de seguridad no es útil. Sin embargo, esto permite al estar separados los archivos de configuración añadir el archivo con la propiedad cifrada a un repositorio público sin peligro siempre y cuando la clave de cifrado se mantenga en secreto. Los archivos de configuración de los servicios en el servidor de configuración se podrían añadir a un repositorio de Git.

1
2
3
4
5
6
7
8
9
spring:
  cloud:
    config:
      server:
        native:
          searchLocations: file:./misc/config

encrypt:
  key: ma8FvTm1t8uWRlYE3ghPsQxxESaZwpOGdlsFwIyPNIWE25yNg1dsvvnd7orlZL9FH0qJyRkG8kcf5CBVdjmi8b2yxKzpXyfxpMXj
bootstrap.yml

Definida la clave simétrica e iniciado el servidor de configuración este ofrece dos endpoints para cifrar y descifrar datos. Utilizando el de cifrado se obtiene el valor cifrado del dato sensible que se quiere proteger. Con el endpoint de descifrado se puede descifrar. Se observa que utilizando varias veces el endpoint de cifrado se devuelve en cada una un valor distinto, sin embargo, descifrando cada uno de estos valores con el endpoint de descifrado siempre se obtiene el valor original. Esto es debido seguramente a que en la operación de cifrado se utiliza la técnica del salt para que a los valores cifrados se les pueda aplicar un ataque de diccionario, el salt es incluido en el valor devuelto para que la operación de descifrado devuelva el valor original.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
$ curl localhost:8090/encrypt -d secret
fb416133716acf5b6e3ffb64a396d521cd68fb0c2cb58727d5e938badefa942b
$ curl localhost:8090/encrypt -d secret
2b5ab02e4653bebb1b325e88eb9711df0e97e8f7efbbffc0dd0c5ae532dceedc
$ curl localhost:8090/encrypt -d secret
df06bcf1e36d910851a6e3f866d0f014388d3ddcb479b9f0e43194d7cfb5d72c

$ curl localhost:8090/decrypt -d fb416133716acf5b6e3ffb64a396d521cd68fb0c2cb58727d5e938badefa942b
secret
$ curl localhost:8090/decrypt -d 2b5ab02e4653bebb1b325e88eb9711df0e97e8f7efbbffc0dd0c5ae532dceedc
secret
$ curl localhost:8090/decrypt -d df06bcf1e36d910851a6e3f866d0f014388d3ddcb479b9f0e43194d7cfb5d72c
secret
curl-1.sh

El valor cifrado obtenido por este endpoint se puede guardar en los archivos de configuración entrecomillándolo y precediéndolo con la cadena {cipher}.

1
2
3
config:
  key: dev
  password: '{cipher}fb416133716acf5b6e3ffb64a396d521cd68fb0c2cb58727d5e938badefa942b'
client.yml

En este caso el servicio al iniciarse obtiene su configuración del servicio de configuración, los datos se transmiten en forma plana sin cifrar y el cifrado utilizando en el servidor de configuración es transparente para el cliente. Accediendo al endpoint del servidor de configuración que devuelve la configuración de un servicio con una propiedad cifrada se observa que al obtener el valor se devuelve en texto plano al cliente, esta petición es la misma que hace el servicio para obtener su configuración, de modo que aunque la información está cifrada en el servidor de configuración se transmite al servicio sin cifrar en texto plano. En este ejemplo se utiliza el protocolo inseguro HTTP, lo recomendable es utilizar el protocolo HTTPS para cifrar el tráfico entre el servidor de configuración y el cliente de modo que los valores sensibles queden protegidos también en la transmisión.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ curl http://localhost:8090/client/default.yml
{
   "name":"client",
   "profiles":[
      "default.yml"
   ],
   "label":null,
   "version":null,
   "state":null,
   "propertySources":[
      {
         "name":"file:misc/config/client.yml",
         "source":{
            "server.port":"${port:8085}",
            "management.endpoints.web.exposure.include":"*",
            "circuitBreaker.requestVolumeThreshold":4,
            "circuitBreaker.errorThresholdPercentage":50,
            "metrics.rollingStats.timeInMilliseconds":10000,
            "config.key":"dev",
            "config.password":"secret"
         }
      }
   ]
}
curl-2.sh

Iniciado el servicio de descubrimiento, el de configuración y un servicio que tiene un dato cifrado de configuración el valor que obtiene está ya descifrado. En este caso el servicio client obtiene el valor de la propiedad config.password descifrado con el valor secret.

1
2
3
4
$ ./gradlew discoveryserver:run --args="--port=8761"
$ ./gradlew configserver:run --args="--port=8090"
$ ./gradlew service:run --args="--port=8080"
$ ./gradlew client:run
gradlew-run.sh
1
2
3
4
5
6
7
8
9
$ ./gradlew client:run
...
2018-09-30 00:56:08,193  INFO                    org.apache.coyote.http11.Http11NioProtocol Starting ProtocolHandler ["http-nio-8085"]
2018-09-30 00:56:08,283  INFO                    org.apache.tomcat.util.net.NioSelectorPool Using a shared selector for servlet write/read
2018-09-30 00:56:08,689  INFO  org.springframework.boot.web.embedded.tomcat.TomcatWebServer Tomcat started on port(s): 8085 (http) with context path ''
2018-09-30 00:56:08,691  INFO  netflix.eureka.serviceregistry.EurekaAutoServiceRegistration Updating port to 8085
2018-09-30 00:56:08,695  INFO        io.github.picodotdev.blogbitix.springcloud.client.Main Started Main in 19.901 seconds (JVM running for 22.013)
Valor de propiedad de configuración (config.key): dev
Valor de propiedad de configuración (config.password): secret
System.out
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.github.picodotdev.blogbitix.springcloud.client;

...

@SpringBootApplication
@EnableDiscoveryClient
@EnableCircuitBreaker
@EnableHystrixDashboard
public class Main implements CommandLineRunner {

	@Autowired
	private DefaultConfiguration configuration;

	@Autowired
	private ClientService service;
    
	@Override
	public void run(String... args) throws Exception {
		System.out.printf("Valor de propiedad de configuración (%s): %s%n", "config.key", configuration.getKey());
		System.out.printf("Valor de propiedad de configuración (%s): %s%n", "config.password", configuration.getPassword());

    ...
	}

	public static void main(String[] args) throws Exception {
		SpringApplication application = new SpringApplication(Main.class);
		application.setApplicationContextClass(AnnotationConfigApplicationContext.class);
		SpringApplication.run(Main.class, args);
	}
}
Main.java
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package io.github.picodotdev.blogbitix.springcloud.client;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DefaultConfiguration {

    @Value("${config.key}")
    String key;

    @Value("${config.password}")
    String password;

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
DefaultConfiguration.java
Terminal

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando siguiente comando:
./gradle-run.sh, ./curl-1.sh

Este artículo forma parte de la serie spring-cloud:

  1. Datos de sesión externalizados con Spring Session
  2. Aplicación Java autocontenida con Spring Boot
  3. Configuración de una aplicación en diferentes entornos con Spring Cloud Config
  4. Información y métricas de la aplicación con Spring Boot Actuator
  5. Registro y descubrimiento de servicios con Spring Cloud y Consul
  6. Aplicaciones basadas en microservicios
  7. Registro y descubrimiento de servicios con Spring Cloud Netflix
  8. Servicio de configuración para microservicios con Spring Cloud Config
  9. Recargar sin reiniciar la configuración de una aplicación Spring Boot con Spring Cloud Config
  10. Almacenar cifrados los valores de configuración sensibles en Spring Cloud Config
  11. Tolerancia a fallos en un cliente de microservicio con Spring Cloud Netflix y Hystrix
  12. Balanceo de carga y resilencia en un microservicio con Spring Cloud Netflix y Ribbon
  13. Proxy para microservicios con Spring Cloud Netflix y Zuul
  14. Monitorizar una aplicación Java de Spring Boot con Micrometer, Prometheus y Grafana
  15. Exponer las métricas de Hystrix en Grafana con Prometheus de una aplicación Spring Boot
  16. Servidor OAuth, gateway y servicio REST utilizando tokens JWT con Spring
  17. Trazabilidad en microservicios con Spring Cloud Sleuth
  18. Implementar tolerancia a fallos con Resilience4j
  19. Iniciar una aplicación de Spring Boot en un puerto aleatorio
  20. Utilizar credenciales de conexión a la base de datos generadas por Vault en una aplicación de Spring
  21. Microservicios con Spring Cloud, Consul, Nomad y Traefik
Comparte el artículo: