Gerencie parâmetros de forma simples e segura

Mateus Noguez Moog
Training Center
Published in
4 min readMar 21, 2018

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.

Lidar com este problema sempre é um tabu para vários programadores que conheci, inclusive era para mim, pois esbarramos em conhecimentos de infra que não possuímos, e as soluções conhecidas geralmente são bem complexas para algo relativamente simples.

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 wget
RUN 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 -nodeinforma 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/confd
chmod +x /opt/confd/bin/confd
export PATH="/opt/confd/bin/:$PATH"
mkdir -p /etc/confd/conf.d
mkdir -p /etc/confd/templates
mv /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.toml
confd --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.

--

--