Segurança e Configuration Server
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:
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:
- Server e Client: https://github.com/iundarigun/cloud-config2
- Repoconfig: https://github.com/iundarigun/repoconfig
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:
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