Hashicorp Vault

April 19, 2021   

O HashiCorp Vault é uma ferramenta para acessar segredos (senhas, chaves de APIs, certificados, etc.) de forma segura, através de uma interface comum e com logs detalhados para auditoria.

Algumas das funcionalidades do Vault:

  • Criptografia
  • Geração de segredos dinâmicos com prazo de expiração
  • Renovação e revogação de segredos

Muitas dessas funcionalidades também estão disponíveis em serviços como o Azure Key Vault e o AWS Key Management Service, porém o Vault tem a vantagem de ser open source e não estar associado a uma cloud específica (cloud agnostic).

Os segredos são salvos em um storage backend que pode ser memória, disco, banco de dados, Azure Blob, AWS S3 entre outros. O Vault serve como interface.
Em outro artigo mostrei como configurar o Vault para usar a key-value store do Consul.

O Vault foi escrito em GO e é distribuido como um único binário. É possível interagir com ele pela linha de comando, por HTTP REST ou pela interface web.

Secrets Engines

As secret engines são responsáveis por armazenar, gerar e encriptar dados.

Algumas secret engines apenas armazenam dados, outras se conectam a serviços e geram credenciais dinâmicas on demand. Outras provêm encryption as a service, geração de TOTP e certificados.

O site do Vault lista quase 50 secret engines. Entre elas estão AWS, Azure, MySQL, PostgreSQL, Active Directory, PKI e SSH.

As engines são ativadas em um path. O comando vault path-help pode ser utilizado para obter uma descrição da engine e descobrir a quais paths ela responde.

Ciclo de vida de uma secret engine:

  • Enable: vault secrets enable
  • Disable: vault secrets disable
  • Move: vault secrets move
  • Tune: vault secrets tune

KV Secrets Engine

A secret engine kv serve para armazenar segredos do tipo chave-valor.

Existem duas versões:

  • KV versão 1:
    • Não tem versionamento
    • Um segredo deletado não pode ser recuperado
    • Melhor performance
  • KV versão 2:
    • Tem versionamento (10 versões por padrão)
    • Possui soft delete
    • Performance menor

Ciclo de vida dos segredos

  • Create: kv put
  • Read: kv get
  • Update: kv put
  • Delete/Undelete: kv delete / kv undelete
  • Destroy: kv destroy
  • Metadata Destroy: kv metadata delete

Métodos de autenticação

O Vault suporta múltiplos métodos de autenticação, como LDAP, Azure Active Directory e AWS Identity & Access Management.

  • Listar métodos ativos: vault auth list
  • Ativar um método: vault auth enable [método]
  • Configurar um método de autenticação: vault write auth/[método]/config
  • Adicionar um role: vault write auth/[método]/role/[nome_do_role]
  • Desativar um método: vault auth disable [método]

Política de acessos

Uma política de acessos do Vault (Vault Policy) é um ocumento em HCL (Hashicorp Configuration Language) ou JSON que diz quem pode fazer o quê de que forma.

É possível ter um controle refinado do que cada usuário pode ou não acessar, inclusive quais comandos pode/deve utilizar em cada path.
A documentação oficial tem vários exemplos úteis.

  • Listar policies: vault policy list
  • Criar uma policy: vault policy write [policy] [policy_file.hcl]
  • Atualizar uma policy: vault write sys/policy/[policy]/ policy=[policy_file.hcl]
  • Deletar uma policy: vault delete sys/policy/[policy]

Segredos dinâmicos

Dynamic Secrets são segredos criados on-demand com um prazo de validade (lease). Se não for renovado, o segredo é revogado.

Cubbyhole Response Wrapping

Em vez de fornecer um token diretamente ao usuário, podemos colocar esse token em um cubbyhole (“cubículo seguro”) e gerar um token de uso único e pequena duração que tem acesso ao cubbyhole. Isso é chamado Cubbyhole Response Wrapping.

Vantagens:

  • O segredo fica exposto por pouco tempo (lease TTL)
  • Se o token vazar, ele não poderá ser utilizado novamente
  • O usuário não tem acesso direto ao segredo, mas sim a uma referência

Auditoria

O Vault loga todas as requisições feitas ao servidor e todas as respostas enviadas, incluindo erros.

Os logs são no formato JSON e podem ser salvos em mais de um lugar (audit device: arquivo, syslog ou socket) ao mesmo tempo.

Se a opção log_raw=true não for passada durante a ativação, apenas os hashs dos segredos serão salvos no log.

Por segurança, o Vault para de aceitar requests se não conseguir acessar nenhum dos audit devices ativados.

  • Ativar um audit device: vault audit enable -path=vault_path [type]
  • Desativar um audit device: vault audit disable [path]
  • Listar audit devices: vault audit list --detailed

Passo-a-passo prático

Exemplos envolvendo tudo o que foi explicado acima.

  • Instale e inicie o Vault

    Usando o gerenciador de pacotes da sua distribuição ou como detalhado no outro artigo.

    # pacman -S vault
    
    # cat /etc/vault.hcl
    backend "file" {
        path = "/var/lib/vault"
    }
    
    listener "tcp" {
        tls_disable = 1
    }
    
    # systemctl start vault.service
    
    $ vault operator init
    Unseal Key 1: p/rCDw28akkqMBogtkpuH7OoB0IbwNK6fOPE1/K56mCw
    Unseal Key 2: 4Qy/bymi4BShb0JaoMnQm8Qry/MrrM3pP3CQDeJu93s+
    Unseal Key 3: +Qdh8mBl1yzqi5V2u1tKG1u3pWtRfXEQ1cwMjoYpuSAy
    Unseal Key 4: lG6Vl8iiJhUpvmmWF5S8EgKd89wMUHsL4PNHuT4xGZ82
    Unseal Key 5: rsufBM3HiobRE39El8ErIkzx+qkxz9unzIrMb/w5gZTB
    
    Initial Root Token: s.3iZvNY9lhNdeJVH5fIHU1RDv
    
  • Exporte as variáveis de ambiente com o endereço e o token

    $ export VAULT_ADDR='http://127.0.0.1:8200'
    $ export VAULT_TOKEN='s.3iZvNY9lhNdeJVH5fIHU1RDv'
    

    Se você estiver executando o Vault em um container Docker, vale a pena criar o seguinte alias:

    $ alias vault='docker exec -it -e VAULT_TOKEN=$VAULT_TOKEN vault.server1 vault "$@"'
    
  • Unseal

    $ vault operator unseal p/rCDw28akkqMBogtkpuH7OoB0IbwNK6fOPE1/K56mCw
    ...
    Sealed             true
    Unseal Progress    1/3
    ...
    
    $ vault operator unseal +Qdh8mBl1yzqi5V2u1tKG1u3pWtRfXEQ1cwMjoYpuSAy
    ...
    Sealed             true
    Unseal Progress    2/3
    ...
    
    $ vault operator unseal rsufBM3HiobRE39El8ErIkzx+qkxz9unzIrMb/w5gZTB
    Sealed          false
    
  • Login

    $ vault login
    
  • Ative o secrets backend

    $ vault secrets enable -path=kv -version=2 kv
    Success! Enabled the kv secrets engine at: kv/
    
  • Configure para guardar no máximo 2 versões

    $ vault write kv/config max_versions=2
    Success! Data written to: kv/config
    
  • Escreva um segredo

    $ vault kv put kv/meu_app senha=123456
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:34.948088026Z
    deletion_time    n/a
    destroyed        false
    version          1
    
  • Crie uma segunda versão do segredo

    $ vault kv put kv/meu_app senha=123456 usuario=julio
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    n/a
    destroyed        false
    version          2
    
  • Crie uma terceira versão do segredo

    $ vault kv put kv/meu_app senha=654321 usuario=julio
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:53.768980035Z
    deletion_time    n/a
    destroyed        false
    version          3
    
  • Leia o valor da última versão

    $ vault kv get kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:53.768980035Z
    deletion_time    n/a
    destroyed        false
    version          3
    
    ===== Data =====
    Key        Value
    ---        -----
    senha      654321
    usuario    julio
    
  • Leia o valor da segunda versão

    $ vault kv get -version=2 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    n/a
    destroyed        false
    version          2
    
    ===== Data =====
    Key        Value
    ---        -----
    senha      123456
    usuario    julio
    
  • Leia o valor da primeira versão

    Essa versão foi destruida, pois definimos um limite de salvar apenas as duas últimas.

    $ vault kv get -version=1 kv/meu_app
    No value found at kv/data/meu_app
    
  • Pegue o valor da segunda versão em JSON

    $ vault kv get -format=json -version=2 kv/meu_app
    {
      "request_id": "379bbb04-a21b-cebe-9e99-392b85372592",
      "lease_id": "",
      "lease_duration": 0,
      "renewable": false,
      "data": {
        "data": {
          "senha": "123456",
          "usuario": "julio"
        },
        "metadata": {
          "created_time": "2021-04-19T04:11:44.839504165Z",
          "deletion_time": "",
          "destroyed": false,
          "version": 2
        }
      },
      "warnings": null
    }
    
  • Filtre com o jq

    $ vault kv get -format=json -version=2 kv/meu_app | jq -r .data.data.senha
    123456
    
  • Pegue o valor da segunda versão usando a API

    $ curl -k --header "X-Vault-Token: $VAULT_TOKEN" $VAULT_ADDR/v1/kv/data/meu_app?version=2 | jq .data.data
    {
      "senha": "123456",
      "usuario": "julio"
    }
    
  • Delete uma as versões 2 e 3 do segredo

    Se não especificar a versão, a última é deletada.

    $ vault kv delete -versions="2,3" kv/meu_app
    Success! Data deleted (if it existed) at: kv/meu_app
    
    $ vault kv get -version=2 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    2021-04-19T04:14:27.514456987Z
    destroyed        false
    version          2
    
    $ vault kv get -version=3 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:53.768980035Z
    deletion_time    2021-04-19T04:14:27.514457229Z
    destroyed        false
    version          3
    
  • Recupere a versão 2

    $ vault kv undelete -versions=2 kv/meu_app
    Success! Data written to: kv/undelete/meu_app
    
  • Destrua a versão 2 do segredo

    $ vault kv destroy -versions=2 kv/meu_app
    Success! Data written to: kv/destroy/meu_app
    
    $ vault kv get -version=2 kv/meu_app
    ====== Metadata ======
    Key              Value
    ---              -----
    created_time     2021-04-19T04:11:44.839504165Z
    deletion_time    n/a
    destroyed        true
    version          2
    
  • Delete todos os metadados

    $ vault kv list kv/
    Keys
    ----
    meu_app
    
    $ vault kv metadata delete kv/meu_app
    Success! Data deleted (if it existed) at: kv/metadata/meu_app
    
    $ vault kv list kv/
    No value found at kv/metadata
    
  • Crie a policy admin

    $ cat policy_admin.hcl
    # Mount secrets engines
    path "sys/mounts/*" {
      capabilities = [ "create", "read", "update", "delete", "list" ]
    }
    
    # Configure the database secrets engine and create roles
    path "database/*" {
      capabilities = [ "create", "read", "update", "delete", "list" ]
    }
    
    # Manage the leases
    path "sys/leases/+/database/creds/readonly/*" {
      capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
    }
    
    path "sys/leases/+/database/creds/readonly" {
      capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
    }
    
    # Write ACL policies
    path "sys/policies/acl/*" {
      capabilities = [ "create", "read", "update", "delete", "list" ]
    }
    
    # Manage tokens for verification
    path "auth/token/create" {
      capabilities = [ "create", "read", "update", "delete", "list", "sudo" ]
    }
    
    $ vault policy write admin policy_admin.hcl
    Success! Uploaded policy: admin
    
  • Crie a policy apps

    $ cat policy_apps.hcl
    # Get credentials from the database secrets engine 'readonly' role.
    path "database/creds/readonly" {
      capabilities = [ "read" ]
    }
    
    $ vault policy write apps policy_apps.hcl
    Success! Uploaded policy: apps
    
  • Liste as policies

    $ vault policy list
    admin
    apps
    default
    root
    
  • Inicie o docker e baixe a imagem do PostgreSQL

    # systemctl start docker.service
    $ docker pull postgres:13
    
  • Crie um banco de dados

    $ docker run \
          --name postgres \
          --env POSTGRES_USER=root \
          --env POSTGRES_PASSWORD=rootpassword \
          --detach  \
          --publish 5432:5432 \
          postgres:13
    
  • Conecte-se ao banco e crie uma role chamada ro com permissão de leitura em todas as tabelas

    $ docker exec -it postgres psql
    root=# CREATE ROLE ro NOINHERIT;
    CREATE ROLE
    root=# GRANT SELECT ON ALL TABLES IN SCHEMA public TO "ro";
    GRANT
    root=# \q
    
  • Ative a secrets engine database

    $ vault secrets enable database
    Success! Enabled the database secrets engine at: database/
    
  • Configure a engine para funcionar com o Postgres

    $ vault write database/config/postgresql \
         plugin_name=postgresql-database-plugin \
         connection_url="postgresql://{{username}}:{{password}}@localhost:5432/postgres?sslmode=disable" \
         allowed_roles=readonly \
         username="root" \
         password="rootpassword"
    
  • SQL usado para criar credenciais

    $ cat readonly.sql
    CREATE ROLE "{{name}}" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}' INHERIT;
    GRANT ro TO "{{name}}";
    
  • Crie a role readonly

    $ vault write database/roles/readonly \
          db_name=postgresql \
          creation_statements=@readonly.sql \
          default_ttl=1h \
          max_ttl=24h
    Success! Data written to: database/roles/readonly
    
  • Gere uma credencial no BD

    $ vault read database/creds/readonly
    Key                Value
    ---                -----
    lease_id           database/creds/readonly/hTFHqyO2AVcCBQRFmRTv8bGV
    lease_duration     1h
    lease_renewable    true
    password           NMVS-H2YO5BNMAkzP6Nl
    username           v-root-readonly-sKjgJBT41RYrrLtB1MtS-1618807755
    
  • Liste os usuários do Postgres

    $ docker exec -it postgres psql
    root=# \du
                                                 List of roles
                Role name       |                   Attributes                | Member of 
    ----------------------------+---------------------------------------------+-----------
     ro                         | No inheritance, Cannot login                | {}
     root                       | Superuser, Create...Replication, Bypass RLS | {}
     v-root-readonly-sKj...7755 | Password valid until 2021-04-19 05:49:20+00 | {ro}
    
    root=# \q
    
  • Liste as leases

    $ vault list sys/leases/lookup/database/creds/readonly
    Keys
    ----
    hTFHqyO2AVcCBQRFmRTv8bGV
    
  • Guarde o ID em uma variável

    $ LEASE_ID=$(vault list -format=json sys/leases/lookup/database/creds/readonly | jq -r ".[0]")
    
    $ echo $LEASE_ID
    hTFHqyO2AVcCBQRFmRTv8bGV
    
  • Lease renew

    Se o valor solicitado passar o limite, ele ficará no limite.

    $ vault lease renew -increment=3600 database/creds/readonly/$LEASE_ID
    
    $ vault lease renew -increment=96400 database/creds/readonly/$LEASE_ID
    WARNING! The following warnings were returned from Vault:
    
    * TTL of "26h46m40s" exceeded the effective max_ttl of "23h33m18s"; TTL
    value is capped accordingly
    
    Key                Value
    ---                -----
    lease_id           database/creds/readonly/hTFHqyO2AVcCBQRFmRTv8bGV
    lease_duration     23h33m18s
    lease_renewable    true
    
    root=# \du
                                                 List of roles
                Role name       |                   Attributes                | Member of 
    ----------------------------+---------------------------------------------+-----------
     ro                         | No inheritance, Cannot login                | {}
     root                       | Superuser, Create...Replication, Bypass RLS | {}
     v-root-readonly-sKj...7755 | Password valid until 2021-04-20 04:49:20+00 | {ro}
    
    root=# \q
    
  • Lease revoke

    $ vault lease revoke database/creds/readonly/$LEASE_ID
    All revocation operations queued successfully!
    
    $ docker exec -it postgres psql
    root=# \du
                                       List of roles
     Role name |                         Attributes                   | Member of 
    -----------+------------------------------------------------------+-----------
     ro        | No inheritance, Cannot login                         | {}
     root      | Superuser, Create role, ..., Replication, Bypass RLS | {}
    
    root=# \q
    

    <! –>

    $ vault list sys/leases/lookup/database/creds/readonly
    No value found at sys/leases/lookup/database/creds/readonly
    
  • Liste os métodos de autenticação ativos:

    $ vault auth list
    Path      Type     Accessor               Description
    ----      ----     --------               -----------
    token/    token    auth_token_5e067aef    token based credentials
    
  • Ative o userpass:

    $ vault auth enable userpass
    Success! Enabled userpass auth method at: userpass/
    
  • Veja as opções disponíveis

    $ vault path-help auth/userpass
    
  • Adicione um usuário

    $ vault write auth/userpass/users/julio \
        password=minha_senha \
        policies=admin
    Success! Data written to: auth/userpass/users/julio
    
  • Liste usuários

    $ vault list auth/userpass/users
    Keys
    ----
    julio
    
  • Abra um novo terminal e faça login com o usuário criado:

    $ export VAULT_ADDR='http://127.0.0.1:8200'
    $ vault login -method=userpass username=julio
    Success! You are now authenticated. The token information displayed below
    is already stored in the token helper. You do NOT need to run "vault login"
    again. Future Vault requests will automatically use this token.
    
    Key                    Value
    ---                    -----
    token                  s.8vRGmBVEQ1indCPtGL796od3
    token_accessor         CmTWhmRHFq4RMLb6wccFgOFT
    token_duration         768h
    token_renewable        true
    token_policies         ["admin" "default"]
    identity_policies      []
    policies               ["admin" "default"]
    token_meta_username    julio
    
  • Delete o usuário:

    $ vault delete auth/userpass/users/julio
    Success! Data deleted (if it existed) at: auth/userpass/users/julio
    
  • Salva um segredo no Vault:

    $ vault kv put kv/app-server api-key=123456
    Key              Value
    ---              -----
    created_time     2021-04-19T06:02:13.217806976Z
    deletion_time    n/a
    destroyed        false
    version          1
    
  • Cria um wrapping token válido por 5 minutos:

    $ vault kv get -wrap-ttl=300 kv/app-server
    Key                              Value
    ---                              -----
    wrapping_token:                  s.wqyqcD8lPcaOGznSg4ZNVFPC
    wrapping_accessor:               sy6OAxfoKcmPLbUufzGhxkFY
    wrapping_token_ttl:              5m
    wrapping_token_creation_time:    2021-04-19 13:08:13.210327049 -0300 -03
    wrapping_token_creation_path:    kv/data/app-server
    
  • Acesse o segredo usando esse token

    $ curl --header "X-Vault-Token: s.wqyqcD8lPcaOGznSg4ZNVFPC" --request POST \
      $VAULT_ADDR/v1/sys/wrapping/unwrap | jq
    {
      "request_id": "69598fb3-0f90-a1c8-22d8-387f5c79564e",
      "lease_id": "",
      "renewable": false,
      "lease_duration": 0,
      "data": {
        "data": {
          "api-key": "123456"
        },
        "metadata": {
          "created_time": "2021-04-19T06:02:13.217806976Z",
          "deletion_time": "",
          "destroyed": false,
          "version": 1
        }
      },
      "wrap_info": null,
      "warnings": null,
      "auth": null
    }
    
  • Crie um kv store no path webkv

    $ vault secrets enable -path=webkv kv
    Success! Enabled the kv secrets engine at: webkv/
    
  • Adicione um segredo no webkv

    $ vault kv put webkv/app-server api-key=123456
    Success! Data written to: webkv/app-server
    
  • Crie uma web policy

    $ cat webpol.hcl
    path "webkv/*" {
      capabilities = ["read", "list"]
    }
    
    $ vault policy write web webpol.hcl
    Success! Uploaded policy: web
    
  • Crie um token válido por 5 minutos usando a web policy

    $ vault token create -policy=web -wrap-ttl=300
    Key                              Value
    ---                              -----
    wrapping_token:                  s.FPSHG7GJ3pWWliA0zSDEb77w
    wrapping_accessor:               oLR1tRBpyQACPICrMIWrtfDm
    wrapping_token_ttl:              5m
    wrapping_token_creation_time:    2021-04-19 14:58:31.807307058 -0300 -03
    wrapping_token_creation_path:    auth/token/create
    wrapped_accessor:                eP85Zj7EmwrDgE1GoagsNJ6p
    
  • Pegue o token

    $ curl --header "X-Vault-Token: s.FPSHG7GJ3pWWliA0zSDEb77w" --request POST \
        $VAULT_ADDR/v1/sys/wrapping/unwrap | jq
    {
      "request_id": "4cfe3fd2-942a-d7e3-34e5-2b7d644b3e28",
      "lease_id": "",
      "renewable": false,
      "lease_duration": 0,
      "data": null,
      "wrap_info": null,
      "warnings": null,
      "auth": {
        "client_token": "s.lDqthe94B6bFVyXxNqLMuwYO",
        "accessor": "eP85Zj7EmwrDgE1GoagsNJ6p",
        "policies": [
          "default",
          "web"
        ],
        "token_policies": [
          "default",
          "web"
        ],
        "metadata": null,
        "lease_duration": 2764800,
        "renewable": true,
        "entity_id": "",
        "token_type": "service",
        "orphan": false
      }
    }
    
  • Pegue o segredo usando o token recebido na resposta do comando anterior

    $ curl --header "X-Vault-Token: s.lDqthe94B6bFVyXxNqLMuwYO" $VAULT_ADDR/v1/webkv/app-server | jq
    {
      "request_id": "f63dcc67-b9f6-43f6-dbb8-756c709e6a2f",
      "lease_id": "",
      "renewable": false,
      "lease_duration": 2764800,
      "data": {
        "api-key": "123456"
      },
      "wrap_info": null,
      "warnings": null,
      "auth": null
    }
    



comments powered by Disqus