Gerencie parâmetros de forma simples e segura
Nos meus 6 anos de experiência como programador passei por diversos projetos, e a maioria possuía parâmetros configurados em texto plano no código. Essa prática compromete a segurança destas informações (muitas vezes chaves para outros serviços), além de tornar ainda mais complicado atualizar seus valores.
Uma solução simples e segura
No último mês recebi o desafio de solucionar este problema em um projeto. Estudando um pouco mais a fundo e conversando com alguns amigos, cogitei utilizar o Consul + Vault, mas essas são ferramentas que ficam em instâncias separadas, precisam ser gerenciadas, além de terem varias outras complexidades atreladas. Conversando e pesquisando um pouco mais, cheguei a uma solução simples e segura, usando o confd e o Systems Manager (SSM) Parameters Store.
Confd? SSM?
O confd é uma ferramenta open source, mantida pelo Kelsey Hightower, que roda localmente junto à aplicação, integrada a vários backends (SSM, Consul, Vault, DynamoDB, Etcd, Redis, Rencher e Zookeeper) eliminando o problema de vendor lock-in. Após configurado e executado, ele gera um arquivo a partir de um template, com os parâmetros solicitados para o backend. Além disso, ele pode ficar fazendo polling no backend para atualizar os valores ou pegar novos parâmetros.
Já o SSM Parameters Store é um key value store gratuito da AWS, para armazenar valores de até 2048 bytes. Ele possui integração com o KMS (serviço pago) para criptografar os valores quando necessário.
Como eu configuro o confd?
Vamos começar pelos 3 arquivos utilizados na configuração básica do confd. O primeiro deles é o confd.toml
:
backend = "ssm"
confdir = "/etc/confd"
log-level = "debug"
noop = false # permitir ou não atualizar arquivo já gerado
prefix = "/example" # segmentação por ambiente
scheme = "https"
O próximo arquivo é o template.toml
, onde ficam as configurações do template:
[template]
src = "template.conf.tmpl" # nome do arquivo de template
dest = "/confd-example/.env" # para onde o confd deve enviar ele
keys = [ # propriedades no backend escolhido, sem o prefixo
"/foo"
]
E o último é o template de fatotemplate.conf.tmpl
. O confd utiliza o interpretador de templates da linguagem Go para parsear o arquivo. Nessa implementação simples, não utilizei nenhuma feature do interpretador.
FOO={{ getv "/foo" }}
Entendi, mas e a implementação?
Na implementação dessa solução utilizei Docker e Node.js. Se você não tem familiaridade com Docker, recomendo ver o excelente artigo do Augusto Amaral antes de seguir a leitura deste. A utilização de Node.js serve apenas para ficar mais fácil de visualizar esta solução com uma aplicação. Você pode vê-la completa no Github.
https//github.com/moog/confd-node-example
Vamos lá! Como você pode ver no Dockerfile abaixo, estou instalando o pacote ca-certificates
, que me fornece criptografia para me conectar com serviços de fora (como o backend que eu escolhi). Além disso dou permissão de execução para o docker-entrypoint.sh
.
FROM node:8.9-alpineWORKDIR /confd-exampleCOPY package.json .
RUN npm install --progress=false
COPY . .RUN apk update && \
apk add ca-certificates wgetRUN chmod +x docker-entrypoint.sh
O docker-entrypoint.sh
é executado sempre que subimos o container, e é nele onde tudo acontece. Primeiro fazemos o download e tornamos o confd um comando executável, criamos os diretórios básicos da configuração, e movemos os arquivos de configuração que mostrei ali em cima. Por último, executamos o comando do confd, onde passamos os parâmetros --onetime
e o -node
.
O parametro --onetime
informa que não queremos fazer polling no backend em busca de parâmetros atualizados, e o -node
informa o endereço do nosso backend. Depois que o confd fizer o seu trabalho, vamos executar nossa aplicação Node.js com o comando npm start
.
#!/bin/shwget --quiet "https://github.com/kelseyhightower/confd/releases/download/v0.15.0/confd-0.15.0-linux-amd64"mkdir -p /opt/confd/bin
mv "confd-0.15.0-linux-amd64" /opt/confd/bin/confdchmod +x /opt/confd/bin/confd
export PATH="/opt/confd/bin/:$PATH"mkdir -p /etc/confd/conf.d
mkdir -p /etc/confd/templatesmv /confd-example/confd/template.toml /etc/confd/conf.d/api.toml
mv /confd-example/confd/template.conf.tmpl /etc/confd/templates/template.conf.tmpl
mv /confd-example/confd/confd.toml /etc/confd/confd.tomlconfd --onetime -node "https://ssm.$AWS_REGION.amazonaws.com"npm start
O comando start
está configurado no arquivo package.json
desta forma: node -r dotenv/config app/server.js
. Nele importamos o pacote dotenv
e executamos a função config
do pacote, que vai ler o arquivo .env
que o confd gerou a partir do template, e vai jogar todos os parâmetros escritos neste arquivo para dentro do processo da nossa aplicação.
No arquivo server.js
, oexpress
é usado para criar um web server, que expõe o valor do parâmetro FOO
.
const express = require('express')
const app = express()app.get('/', (req, res) => {
res.json({ foo: process.env.FOO })
})app.listen(3000, () => {
console.log('Running on port 3000')
})
Conclusão
O confd é uma solução muito simples para gerenciamento de parâmetros, que está integrada a diversos backends, evitando o problema de vendor lock-in que você teria usando diretamento o Credstash, por exemplo. A implementação que fiz em um projeto real ainda é recente e está sendo testada, mas tem se mostrado cada vez mais uma escolha acertada.
Se você leu o artigo até aqui, por favor, escreva abaixo sua crítica ou sugestão, isso vai me ajudar muito a escrever artigos melhores.
Espero ter ajudado, até mais! :)
UPDATE: O Adriano Soares me lembrou de copiar o package.json
e executar o comando npm install
antes de copiar o restante dos arquivos da aplicação, no Dockerfile. Desta forma o docker executa o npm install
somente quando houver alteração nopackage.json
.