Hashicorp Vault
Introduction to Hashicorp Vault
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 é distribuído 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 documento 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 hashes 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 destruída, 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 }
Links
- https://github.com/scarolan/aws-vault-in-five-minutes/blob/master/docs/index.md
- https://github.com/scarolan/vault-aws-cf
- https://jessequinn.info/blog/articles/hashicorp-boundary
- https://learn.hashicorp.com/tutorials/consul/deployment-guide
- https://learn.hashicorp.com/tutorials/vault/ha-with-consul
- https://medium.com/@pcarion/a-consul-a-vault-and-a-docker-walk-into-a-bar-d5a5bf897a87
- https://www.katacoda.com/courses/docker-production/vault-secrets
- https://www.youtube.com/watch?v=a-fq30II2cM
- https://discuss.hashicorp.com/t/vault-enable-https-with-openshift4-and-helm3/21574
- https://churrops.io/2017/08/30/meetup-churrops-iniciando-a-trilha-com-o-vault-hands-on/
- https://jovandeginste.github.io/2016/07/20/use-vault-with-client-certificates.html
- http://javierblog.com/deploy-a-hashicorp-vault-cluster-with-consul-backend/
- https://werner-dijkerman.nl/2017/01/15/setting-up-a-secure-vault-with-a-consul-backend/
- https://itspyworld.blogspot.com/2020/07/installing-hashicorp-vault-password.html
- https://learn.hashicorp.com/tutorials/consul/tls-encryption-openssl-secure
- https://github.com/ned1313/Getting-Started-Vault