[PT-BR] Biblioteca Python usando Hashicorp Vault para acesso a credenciais/configuração
Tags: braziliandevs, vault, python
Published: 2022-12-06 16:17:53

Se você não conhece o [Hashicorp Vault](https://www.vaultproject.io/), cuja premissa é o gerenciamento de segredos e a proteção de dados sensíveis, vale a pena conhecer. O Vault como o nome diz, guarda como um cofre os dados e por meio de diferentes tipos de credenciais de acesso, você disponibiliza esses dados de maneira segura e auditável. 

Sendo assim, para demonstrar as vantagens do Vault, considere o seguinte cenário: 

> **Sua empresa vai prestar serviço para uma rede de lanchonetes, vamos chamar ela de Wac Ronalds. Para fazer o serviço, foi disponibilizado a credencial de acesso ao banco de dados da WR e você foi apontado como líder de projeto, devendo repassar essas credenciais para três equipes distintas de desenvolvimento.**

Considerando o cenário mais simples, o que costume ser feito é simplesmente repassar as credenciais para cada equipe, para que verifiquem o acesso e comecem a trabalhar no projeto. Desconsiderando segurança, desconsiderando boas práticas, um e-mail e instruções de como fazer o acesso ao banco de dados com as credenciais.

Agora, vamos supor que o diretor de sua empresa recebe uma ligação no meio da noite do responsável de TI da WR. E ele ligue para você desesperado informando que houve um engano e as credenciais repassadas era do banco de dados de PRODUÇÃO e não do banco de DESENVOLVIMENTO. E você, sim...VOCÊ, como líder de projeto precisa informar as três equipes para não fazer nada com as credenciais de PRODUÇÃO e troquem pelas de DEV. 

Eu acho que não preciso nem falar o tamanho da desgraça, mas basta uma pessoa não ler o e-mail ou não conseguir conversar por telefone, ou alguém esquecer de trocar a credencial para a merda ir pro ventilador e você ter que conversar com o departamento de RH, caso não seja filho ou sobrinho do dono da empresa. 

**Mas sempre tem um jeito melhor...**

É aqui que entra o Vault. No lugar de diretamente repassar acesso as credenciais do banco de dados da rede de lanchonetes WR, você fornece a credencial de acesso ao seu Vault e as equipes pegam as credenciais dele. Dessa maneira você centraliza o processo, mas de nada adianta se as equipes acessam o Vault, baixam ou copiam as credenciais e usam localmente. Sem um processo mais inteligente ou automático, você ainda corre o risco de ir conversar com o RH...

**Mas sempre tem um jeito melhor, sério!**

Uma das vantagens do Vault é que ele trabalha com API Restful e tem bibliotecas para as mais diferentes linguagens, incluindo Python (HVAC). Se você implementar uma biblioteca interna que acessa seu Vault por API usando suas credenciais de acesso ao mesmo, você garante que só serão usadas as credenciais corretas, ou pelo menos a última credencial que você cadastrou no Vault.

## Mãos na massa

> O presente artigo vai focar no lado cliente python para consumir os dados do Vault. Vou explicar por cima o que foi feito no Vault para ser acessado pelo cliente, depois preparo um artigo explicando melhor como administrar isso no Vault.

Dito isso, para este exemplo vamos trabalhar com segredo de Chave/Valor (KV) e o acesso configurado para um usuário e senha. A ACL vai restringir acesso apenas de leitura aos dados. Todas essas informações serão usadas no arquivo de configuração ou direto como variável de ambiente:

**.env**
```
VAULT_URL = http://<url_vault>:8200
VAULT_USR = <username_vault>
VAULT_PWD = <password_vault>
VAULT_MOUNT_POINT = <vault_mount_point>
VAULT_KEY_PATH = <vault_key_path>
```

Abaixo as dependências do projeto usando Python Poetry.

**pyproject.toml**
```
[tool.poetry]
name = "credentials"
version = "0.0.1"
description = "Your Project Configuration"
authors = ["John Doe <johndoe@corporation.org"]
readme = "README.md"

[tool.poetry.dependencies]
python = "^3.10"
requests = "^2.28.1"
hvac = {extras = ["parser"], version = "^1.0.2"}
python-dotenv = "^0.21.0"


[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
```

A biblioteca HVAC simplifica o acesso a API RESTful do Vault, mas para obter o token inicial de acesso achei mais fácil fazer direto usando chamada HTTP com a biblioteca Request. 

Por fim load_dotenv carrega como variáveis de ambiente os dados de um arquivo .env. É importante ter pelo menos um arquivo .env, mesmo que vazio, para não ter problema no projeto. Foi implementado assim para permitir trabalhar com arquivo para fins de teste.

Abaixo temos o arquivo completo que será usado.

**/credentials/main.py**
```
import os
import requests
import hvac
from dotenv import load_dotenv


def connect(prefix: str = "", data: dict = None):

    try:

        load_dotenv()
        if prefix:
            prefix = f"{prefix}_"

        url = os.environ[f"{prefix}VAULT_URL"] + "/v1/auth/userpass/login/" + os.environ[f"{prefix}VAULT_USR"]
        conf = {
            "url": url,
            "srv": os.environ[f"{prefix}VAULT_URL"],
            "pwd": os.environ[f"{prefix}VAULT_PWD"],
            "mount": os.environ[f"{prefix}VAULT_MOUNT_POINT"],
            "path": os.environ[f"{prefix}VAULT_KEY_PATH"],
            "payload": {
                "username": os.environ[f"{prefix}VAULT_USR"],
                "password": os.environ[f"{prefix}VAULT_PWD"]
            }
        }

        if data is not None:
            conf = data

        r = requests.post(url=conf["url"], data=conf["payload"])
        if "auth" in r.json():
            client = hvac.Client(url=conf["srv"], token=r.json()["auth"]["client_token"])
            return client.secrets.kv.v2.read_secret(mount_point=conf["mount"],
                                                    path=conf["path"])["data"]["data"]

    except Exception as error:

        return {"error": str(error)}
```

Você importa essa biblioteca em um projeto e chama ela usando:

```
from credentials.main import connect

DATA = connect(prefix="MYSQL")
```

Se você usar um prefixo, o arquivo de configuração ou as variáveis de ambiente devem adicionar antes o prefixo informado, assim:

**.env**
```
MYSQL_VAULT_URL = http://<url_vault>:8200
MYSQL_VAULT_USR = <username_vault>
MYSQL_VAULT_PWD = <password_vault>
MYSQL_VAULT_MOUNT_POINT = <vault_mount_point>
MYSQL_VAULT_KEY_PATH = <vault_key_path>
```

Eu fiz a implementação assim para permitir que se use mais credenciais no mesmo projeto, por exemplo, se você precisa acessar um MYSQL e um [RABBITMQ](https://www.rabbitmq.com/).