Segurança e Configuration Server

iundarigun
Dev Cave
Published in
5 min readDec 30, 2020

Anos atrás descobri o Spring Cloud Config, um daqueles projetos vindos da Netflix, que permite distribuir as configurações da sua aplicação. Inclusive, escrevi um artigo aqui na devcave. O passar dos anos não afetou muito o conteúdo, pois essencialmente o funcionamento mudou pouco.

Como funciona

Resumindo de forma bem tosca, o Config Server é um microservice que usa o Spring Cloud Config para disponibilizar configurações via http. As aplicações client podem consultar as configurações necessárias para funcionar no startup ou durante o ciclo de vida. Desta forma, a alteração de uma configuração, tipicamente nos application.yml, não precisa de rebuildar a aplicação. Essas configurações podem estar em varios sources, mas é comum colocar em algum repositório git:

Isso é, resumindo muito, como funciona o Spring Cloud Config. Se ficou com dúvidas, pode, novamente, consultar o artigo que escrevi em 2018.

Problemas de segurança

Desde que entrei na Wavy em setembro de 2019, implantamos e estamos usando esse modelo. E apareceu o primeiro problema: Alguns dados não podem ficar expostos no repositório, mesmo sendo privado. E menos depois da LGPD. Imagina deixar disponível a senha do banco com dados pessoais sensíveis, para qualquer um que tiver acesso ao repositório de configurações:

Começamos a procurar um solução e, oh mágica, o Spring Cloud Config já previu esse tipo de problemas.

Encriptando nossos dados mais sensíveis

Podemos habilitar o Config Server para encriptar e decriptar dados de forma relativamente simples.

Primeiro, vamos gerar uma JKS (Java Key Store) que usaremos para encriptar e decriptar os dados:

$ keytool -genkeypair -alias config-server-key -keyalg RSA -keysize 4096 -sigalg SHA512withRSA -dname 'CN=Config Server,OU=Spring Cloud,O=DevCave' -keypass '<SECRET>' -keystore config-server.jks -storepass '<PASSWORD>'

Colocamos o arquivo no classpath de nossa aplicação e referenciamos ele:

Properties do Config Server

Uma vez iniciado o projeto, podemos solicitar, para quem tem o dado que queremos criptografar, fazer a seguinte chamada:

$ curl -X POST --data-urlencode 'Text to be encrypt' <URL messaging config server>/encrypt

Por último, pegamos o conteúdo da chamada e commitamos ele no repositório de configurações com o seguinte formato: ‘{cipher}…’

Como vemos, podemos combinar propriedades encriptadas, com outras não encriptadas.

Deixei uma demo no github:

Nota: Precisa rodar o client com o profile de prod:

Ainda não suficiente

As nossas propriedades estão encriptadas, o que poderia dar errado? é o seguinte: Como comentei acima, as aplicações client, vão consultar as propriedades que precisam via http:

http://localhost:8888/app-service1/prod

A resposta da consulta traz o valor decriptado. Então, embora você precisa conhecer um pouco sobre a aplicação, não é difícil pegar a senha. Claro, nosso repositório de properties ficou seguro, mas ainda temos exposição não desejada.

Possíveis soluções

Não tem só uma solução, temos várias. Vou numera-las aqui e explica-las a continuação:

  • Limitação por rede
  • Decriptar no client
  • Token authentication
  • Basic authentication

Limitação por rede

Uma possibilidade é limitar o acesso à o Config Server. Limitando pela origem da requisição, conseguimos que unicamente as aplicações tenham acesso as properties decriptadas. Não é uma má solução, principalmente pensando em ambiente tipo Kubernetes.

Decriptar no client

A segunda opção é deixar a responsabilidade de decriptar os valores no client. Para isso, precisamos configurar o config-server para não fazer esse trabalho. Só precisamos adicionar a seguinte property no application.yml:

spring:
cloud:
config:
server:
encrypt:
enabled: false

Sim, parece contraditória, pois o que realmente não faz é decriptar, continua encriptando de mesma forma.

No client, precisamos duas coisas. A primeira é adicionar no resources o arquivo jks usado para criptografar os dados. A segunda, adicionar no bootstrap.yml com as entradas de criptografia:

encrypt:
key-store:
location: classpath:/config-server.jks
secret: my-secret-keypass
alias: config-server-key
password: my-secret-storepass

Porque no bootstrap e não no application.yml? Excelente pergunta: As propriedades que são decriptadas são carregadas juntamente com as que temos no application.yml, então na hora de decriptar precisamos já ter adicionado previamente as configurações necessárias, assim como todas as configurações que o client precisa para conectar no config-server. Fazemos isso colocando-as no bootstrap.yml.

Nota: O código desta solução está no mesmo github, branch decrypt-client.

Token Authentication

Outra opção relativamente simples é usar um token authentication. No caso, para simplificar nem consideraria adicionar um Spring Security, embora seja uma opção boa. Mas pensando em simplificar, poderíamos criar um filter no config-server:

Já no client, só precisamos adicionar duas entradas no bootstrap.yml:

spring:
cloud:
config:
token: super-secret-token
fail-fast: true

token deve ter o mesmo valor que a chave de token do client. O fail-fast indica que se não consegue conectar no config-server não deve continuar com o startup.

Nota: O código desta solução está no mesmo github, branch token-auth.

Basic Authentication

Por último, podemos “complicar” um pouco mais nosso cenário e adicionar uma autenticação com usuário e password. Vamos adicionar o spring-security no build.gradle.kts do config-server:

implementation("org.springframework.boot:spring-boot-starter-security")

Não vou entrar em detalhes de configuração do spring-security, pois não é o foco. Só para ilustrar o exemplo, vamos adicionar um usuário e senha no application.yml:

spring:
security:
user:
name: app
password: my-supersecret-password

O client, então, precisa passar esses mesmos valores, adicionando-os no bootstrap.yml:

spring:
cloud:
config:
fail-fast: true
username: app
password: my-supersecret-password

Esta última opção é mais complexa, mas fornece mais flexibilidade, pois podemos criar um usuário e senha por aplicação, aumentando assim a segurança.

Nota: O código desta solução está no mesmo github, branch basic-auth.

Conclusão

É importante pensar em segurança. Honestamente, como dev as vezes acho um saco pensar em segurança, mas aos poucos precisamos nos forçar para programar mais orientado a segurança. A minha esperança é que seja como fazer testes unitários/intregrados/ponta a ponta, que cada vez seja mais um item para considerar quando pensar numa nova feature, sem que precise um esforço extra.

No caso apresentado, temos três opções, algumas complementares, e é você quem precisa decidir qual encaixa melhor no seu cenário atual.

Referencias:

https://cloud.spring.io/spring-cloud-config/reference/html/
https://medium.com/dev-cave/spring-cloud-config-48e423446ed8

--

--

iundarigun
Dev Cave

Java and Kotlin software engineer at Clearpay