Visão Geral
O HashiCorp Vault é uma ferramenta projetada para armazenar e gerenciar informações sensíveis de forma segura, como chaves de API, senhas, certificados e muito mais. Ele fornece uma solução centralizada para gerenciamento de segredos e controle de acesso.
Caso de Uso
Nesse post o objetivo é integrar o Node.js com o Vault, iremos utilizar a imagem Docker do Vault. Dessa forma subiremos uma API para interagir com o Vault. Armazenar segredos no Vault é melhor do que armazenar segredos em arquivos do tipo “.env” porque o Vault fornece uma maneira mais segura de armazenar segredos. O Vault usa criptografia para proteger os segredos, o que significa que eles não podem ser acessados por pessoas não autorizadas. Além disso, o Vault permite controlar quem pode acessar quais segredos, o que ajuda a proteger os segredos de serem usados indevidamente.
Recursos do Vault
- Gerenciamento de Segredos: O Vault oferece uma maneira segura de armazenar e gerenciar segredos, garantindo que os dados sensíveis sejam criptografados em repouso e em trânsito.
- Segredos Dinâmicos: Ele pode gerar segredos dinâmicos sob demanda para vários serviços, reduzindo o risco de credenciais de longa duração.
- Controle de Acesso: O Vault fornece políticas de controle de acesso detalhadas para restringir e gerenciar o acesso de usuários aos segredos.
- Criptografia como Serviço: Ele oferece criptografia como serviço, permitindo que aplicativos criptografem e descriptografem dados sem lidar diretamente com chaves de criptografia.
- Auditoria e Registro: O Vault mantém um registro detalhado de todas as interações, fornecendo visibilidade e rastreabilidade para fins de conformidade.
- Integração e Extensibilidade: Ele se integra a vários sistemas de autenticação, provedores de nuvem, bancos de dados e muito mais.
Instalando e configurando o Vault utilizando o Docker
O Vault pode ser executado em vários ambientes, só que nesse post, iremos utilizar containers Docker.
— https://github.com/LuksJobs/secrets-with-vault (Repositório do Vault utilizado nesse post)
1. Dockerfile
A imagem do Vault que iremos utilizar é baseado na imagem base do Alpine na versão 3.18 e com a versão do Vault 1.13.3
FROM alpine:3.18 | |
# This is the release of Vault to pull in. | |
ARG VAULT_VERSION=1.13.3 | |
# Create a vault user and group first so the IDs get set the same way, | |
# even as the rest of this may change over time. | |
RUN addgroup vault && \ | |
adduser -S -G vault vault | |
# Set up certificates, our base tools, and Vault. | |
RUN set -eux; \ | |
apk add --no-cache ca-certificates gnupg openssl libcap su-exec dumb-init tzdata && \ | |
apkArch="$(apk --print-arch)"; \ | |
case "$apkArch" in \ | |
armhf) ARCH='arm' ;; \ | |
aarch64) ARCH='arm64' ;; \ | |
x86_64) ARCH='amd64' ;; \ | |
x86) ARCH='386' ;; \ | |
*) echo >&2 "error: unsupported architecture: $apkArch"; exit 1 ;; \ | |
esac && \ | |
VAULT_GPGKEY=C874011F0AB405110D02105534365D9472D7468F; \ | |
found=''; \ | |
for server in \ | |
hkps://keys.openpgp.org \ | |
hkps://keyserver.ubuntu.com \ | |
hkps://pgp.mit.edu \ | |
; do \ | |
echo "Fetching GPG key $VAULT_GPGKEY from $server"; \ | |
gpg --batch --keyserver "$server" --recv-keys "$VAULT_GPGKEY" && found=yes && break; \ | |
done; \ | |
test -z "$found" && echo >&2 "error: failed to fetch GPG key $VAULT_GPGKEY" && exit 1; \ | |
mkdir -p /tmp/build && \ | |
cd /tmp/build && \ | |
wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_linux_${ARCH}.zip && \ | |
wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_SHA256SUMS && \ | |
wget https://releases.hashicorp.com/vault/${VAULT_VERSION}/vault_${VAULT_VERSION}_SHA256SUMS.sig && \ | |
gpg --batch --verify vault_${VAULT_VERSION}_SHA256SUMS.sig vault_${VAULT_VERSION}_SHA256SUMS && \ | |
grep vault_${VAULT_VERSION}_linux_${ARCH}.zip vault_${VAULT_VERSION}_SHA256SUMS | sha256sum -c && \ | |
unzip -d /tmp/build vault_${VAULT_VERSION}_linux_${ARCH}.zip && \ | |
cp /tmp/build/vault /bin/vault && \ | |
if [ -f /tmp/build/EULA.txt ]; then mkdir -p /usr/share/doc/vault; mv /tmp/build/EULA.txt /usr/share/doc/vault/EULA.txt; fi && \ | |
if [ -f /tmp/build/TermsOfEvaluation.txt ]; then mkdir -p /usr/share/doc/vault; mv /tmp/build/TermsOfEvaluation.txt /usr/share/doc/vault/TermsOfEvaluation.txt; fi && \ | |
cd /tmp && \ | |
rm -rf /tmp/build && \ | |
gpgconf --kill dirmngr && \ | |
gpgconf --kill gpg-agent && \ | |
apk del gnupg openssl && \ | |
rm -rf /root/.gnupg | |
# /vault/logs is made available to use as a location to store audit logs, if | |
# desired; /vault/file is made available to use as a location with the file | |
# storage backend, if desired; the server will be started with /vault/config as | |
# the configuration directory so you can add additional config files in that | |
# location. | |
RUN mkdir -p /vault/logs && \ | |
mkdir -p /vault/file && \ | |
mkdir -p /vault/config && \ | |
chown -R vault:vault /vault | |
# Expose the logs directory as a volume since there's potentially long-running | |
# state in there | |
VOLUME /vault/logs | |
# Expose the file directory as a volume since there's potentially long-running | |
# state in there | |
VOLUME /vault/file | |
# 8200/tcp is the primary interface that applications use to interact with | |
# Vault. | |
EXPOSE 8200 | |
# The entry point script uses dumb-init as the top-level process to reap any | |
# zombie processes created by Vault sub-processes. | |
# | |
# For production derivatives of this container, you shoud add the IPC_LOCK | |
# capability so that Vault can mlock memory. | |
COPY docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh | |
ENTRYPOINT ["docker-entrypoint.sh"] | |
# By default you'll get a single-node development server that stores everything | |
# in RAM and bootstraps itself. Don't use this configuration for production. | |
CMD ["server", "-dev"] |
1.1 Buildando a nossa imagem do Vault
Agora que temos nossa imagem definida em nosso Dockerfile, iremos preparar ela buildando nossa imagem:
|
|
2. Docker Compose
Agora que temos nossa imagem preparada, precisaremos instruir como iremos levantar nosso container Docker e iremos fazer isso utilizando o Docker Compose:
version: '3' | |
services: | |
vault_production: | |
image: vault:prd | |
container_name: vault | |
environment: | |
VAULT_ADDR: http://127.0.0.1:8200 | |
ports: | |
- "8200:8200" | |
restart: always | |
volumes: | |
- ./private-volume:/vault/file:rw | |
- ./vault:/vault/config:rw | |
cap_add: | |
- IPC_LOCK | |
entrypoint: vault server -config=/vault/config/vault.json |
3. Executando o Container
Para levantar o container, basta rodar o comando abaixo:
|
|
Logo após para gerar as chaves de acesso ao cofre:
|
|
Esse comando irá gerar duas chaves para acesso ao banco de dados, 🚩 é de extrema importância guardar as chaves e o Token que foram gerados em um local seguro!
Conectando ao Vault via linha de comando (CLI)
Para conectar-se ao Vault, via linha de comando é necessário primeiro instalar a linha executar o comando abaixo:
- Existe várias maneiras de realizar essa instalação, caso tenha dúvidas acesse o link da documentação: https://developer.hashicorp.com/vault/docs/install
Em nosso caso, iremos instalar via pacote:
|
|
Baixando o pacote de Instalação da CLI do Vault:
|
|
Em seguida, descompacte o pacote usando o seguinte comando:
|
|
Depois, mova o pacote para o diretório /usr/bin:
|
|
Verifique a instalação usando o seguinte comando:
|
|
Agora com a linha de comando instalada, poderemos nos conectar ao nosso Vault que está rodando em container Docker bastando apontar o ip e porta ou então DNS de onde ele está rodando:
|
|
Metódo de login utilizando “username” e “password”:
|
|
Metódo de login por linha de comando utilizando o “Token”:
|
|
Criando política de acesso
Política de acesso: list-secrets-policy.hcl
(arquivo de texto com extensão “.hcl”)`
|
|
Aplicar a política ao Vault:
|
|
Aplicar a política aos usuários:
|
|
Criando o SecretID e o APP Role
Habilitar o APP Role
O AppRole no Vault é um mecanismo de autenticação que permite que aplicativos e serviços se autentiquem e obtenham tokens de acesso para acessar recursos no Vault. Ele fornece uma maneira segura para que aplicativos se autentiquem e obtenham tokens de autenticação sem a necessidade de credenciais de usuário.
|
|
Execute o seguinte comando para criar o AppRole “node-app-role
” e definir as políticas associadas a ele:
|
|
Após criar o AppRole, você pode obter as informações do “role-id
” usando o seguinte comando:
|
|
SecretID
Agora iremos gerar um “secret-id
” para nosso “node-app-role” recentemente criado:
|
|
Relizando Conexão do NodeJS com o Vault
Certifique-se de que as variáveis de ambiente (ROLE_ID e SECRET_ID) estejam configuradas corretamente no seu ambiente de execução para garantir uma autenticação bem-sucedida. Este é um exemplo básico, e na prática, você pode precisar ajustar e expandir o código de acordo com as necessidades específicas do seu projeto e da sua configuração do Vault.
Nesse caso, declarei a variável de ambiente “Secret_ID” nas variáveis de ambiente do CI/CD do Gitlab; Pois o objeto é que em um estágio da minha pipeline, esse secret id seja adicionado no arquivo “.env” e que seja buildado em nossa aplicação, no caso, imagem docker;
Seguindo essas etapas, sua aplicação Node.js será capaz de consumir segredos armazenados na engine do seu servidor Vault. Lembre-se de tratar erros adequadamente e implementar mecanismos de tratamento de erros e autenticação apropriados, de acordo com os requisitos da sua aplicação.
Exemplo de conexão para consumir o Secret
import NodeVault from "node-vault" | |
const vault = NodeVault({ | |
apiVersion: "v1", | |
endpoint: 'https://seuvault.com.br', | |
}) | |
const vaultConnections = async () => { | |
const result = await vault.approleLogin({ | |
role_id: process.env.ROLE_ID, | |
secret_id: process.env.SECRET_ID | |
}) | |
// Adicionando o token ao objeto vault para solicitações subsequentes. | |
vault.token = result.auth.client_token | |
// Caminho onde estará seu secret criado no Vault. | |
const response = await vault.read("credentials/data/db_credentials_producao") | |
if (response && response.data) { | |
return response.data | |
} | |
else { | |
throw new Error(`Credenciais não encontradas`) | |
} | |
} | |
export default vaultConnections |
Este código é um exemplo de como usar o NodeVault para se autenticar no HashiCorp Vault usando AppRole, o objetivo é obter um token de acesso, e então ler informações sensíveis (como credenciais de banco de dados) armazenadas no Vault.
Conclusão
Certifique-se de que as variáveis de ambiente (ROLE_ID e SECRET_ID) estejam configuradas corretamente no seu ambiente de execução para garantir uma autenticação bem-sucedida. Este é um exemplo básico, e na prática, você pode precisar ajustar e expandir o código de acordo com as necessidades específicas do seu projeto e da sua configuração do Vault.