Featured image of post Utilizando o Vault para Gerenciar Segredos em Aplicações NodeJS

Utilizando o Vault para Gerenciar Segredos em Aplicações NodeJS

Nesse post o objetivo é integrar o Node.js com o Vault, iremos utilizar a imagem Docker do Vault.

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

Diagrama

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.

GitHubhttps://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"]
view raw Dockerfile hosted with ❤ by GitHub

1.1 Buildando a nossa imagem do Vault

Agora que temos nossa imagem definida em nosso Dockerfile, iremos preparar ela buildando nossa imagem:

1
docker build . -t vault:prd

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:

1
$ docker compose up -d

Logo após para gerar as chaves de acesso ao cofre:

1
$ docker exec -it vault vault operator init -n 2 -t 2

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:

Em nosso caso, iremos instalar via pacote:

1
 1. sudo apt update && sudo apt install gpg

Baixando o pacote de Instalação da CLI do Vault:

1
2. wget https://releases.hashicorp.com/vault/1.15.2/vault_1.15.2_linux_amd64.zip

Em seguida, descompacte o pacote usando o seguinte comando:

1
3. unzip vault_1.15.2_linux_amd64.zip

Depois, mova o pacote para o diretório /usr/bin:

1
4 mv vault /usr/bin

Verifique a instalação usando o seguinte comando:

1
5. vault -v

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:

1
$ export VAULT_ADDR='http://seu-servidor-vault:8200'

Metódo de login utilizando “username” e “password”:

1
$ vault login -method=userpass username=lucas.dantas

Metódo de login por linha de comando utilizando o “Token”:

1
$ vault login -method=token

Criando política de acesso

Política de acesso: list-secrets-policy.hcl(arquivo de texto com extensão “.hcl”)`

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
path "secret/metadata"
{
  capabilities = [ "list" ]
}

path "secret/metadata/*"
{
  capabilities = [ "list", "read" ]
}

# Allow a token to manage its own credentials
path "credentials/*" {
    capabilities = ["create", "read", "update", "delete", "list"]
}

# Allow a token to look up its own capabilities on a path
path "sys/capabilities-self" {
    capabilities = ["update"]
}

# Allow general purpose tools
path "sys/tools/hash" {
    capabilities = ["update"]
}
path "sys/tools/hash/*" {
    capabilities = ["update"]
}

Aplicar a política ao Vault:

1
vault policy write list-secrets-policy list-secrets-policy.hcl

Aplicar a política aos usuários:

1
2
3
vault write auth/userpass/users/lucas.dantas policies=list-secrets-policy
vault write auth/userpass/users/lucas.dantas policies=list-secrets-policy
vault write auth/userpass/users/lucas.dantas policies=list-secrets-policy

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.

1
vault auth enable approle

Execute o seguinte comando para criar o AppRole “node-app-role” e definir as políticas associadas a ele:

1
2
3
4
5
6
7
8
vault write auth/approle/role/node-app-role \
    token_ttl=1h \
    token_max_ttl=4h \
    token_policies=default

resultado esperado:

Success! Data written to: auth/approle/role/node-app-role

Após criar o AppRole, você pode obter as informações do “role-id” usando o seguinte comando:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
vault read auth/approle/role/node-app-role/role-id

resultado esperado:

Key                   Value
---                   -----
secret_id             000000000000000000000000000000000000
secret_id_accessor    000000000000000000000000000000000000
secret_id_num_uses    0
secret_id_ttl         0s

SecretID

Agora iremos gerar um “secret-id” para nosso “node-app-role” recentemente criado:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
vault write -f auth/approle/role/node-app-role/secret-id


resultado esperado:

Key                   Value
---                   -----
secret_id             000000000000000000000000000000000000
secret_id_accessor    000000000000000000000000000000000000
secret_id_num_uses    0
secret_id_ttl         0s

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.

Gitlab ENV

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
view raw vault.js hosted with ❤ by GitHub

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.

Framework utilizado Hugo
Desenvolvido por Lucas Oliveira