El Baúl

Instalando el baúl

Versión en este post es: Vault v1.18.3

761456?s=200&v=4

Los pasos son desde la freebsd fundation son buenos y funcionan, solo se debe estar pendiente de que el servicio inicie correctamente

El vault.hcl

root@Hashicorp-Vault:/ # cat /usr/local/etc/vault.hcl
listener "tcp" {
  address = "ip:8200" (1)
  tls_disable = 1
}

storage "file" {
  path = "/var/db/vault"
  node_id = "v"
}

cluster_addr = "ip:8201" (2)
api_addr = "ip:8200"     (3)
ui = true
1 Es la direccion ip del jail
2 igual pero con puerto distinto.
3 igual.

connection refused

Por alguna razón falla la conexion con el vault, en mi caso es por el fichero vault.hcl no se esta leyendo correctamente, y nos puede lanzar este error.

Get "http://192.168.1.21:8200/v1/sys/seal-status": dial tcp 192.168.1.21:8200: connect: connection refused
root@vault-test:~ # vault server -config /usr/local/etc/vault.hcl
Error initializing core: Failed to lock memory: cannot allocate memory

This usually means that the mlock syscall is not available.
Vault uses mlock to prevent memory from being swapped to
disk. This requires root privileges as well as a machine
that supports mlock. Please enable mlock on your system or
disable Vault from using it. To disable Vault from using it,
set the `disable_mlock` configuration option in your configuration
file.
2025-01-27T11:02:28.636Z [INFO]  proxy environment: http_proxy="" https_proxy="" no_proxy=""
2025-01-27T11:02:28.717Z [INFO]  incrementing seal generation: generation=1

env o export

En nuestro caso ninguno funcionará, es por nuestra shell al iniciar en el jail.

env VAULT_ADDR=http://10.10.0.33:8200 vault operator init

Si no se setea el VAULT_ADD correctamente tenemos

WARNING! VAULT_ADDR and -address unset. Defaulting to https://127.0.0.1:8200.

Seteando environment correctamente

En el jail si hacemos un echo $SHELL tenemos es /bin/csh nos funcionara es usar el setenv

root@vault-test:/etc # setenv VAULT_ADDR ip:8200
root@vault-test:/etc # echo $VAULT_ADDR
ip:8200
root@vault-test:/etc # service vault start
Starting vault.

vault server -config

Este es el comando necesario para ejecucíon bajo fondo, muy importante para que la conexión se de.

vault server -config /usr/local/etc/vault/vault.hcl
root@vault-test:/etc # service vault start
Starting vault.
root@vault-test:/etc # vault operator init
Unseal Key 1: token
Unseal Key 2: token
Unseal Key 3: token
Unseal Key 4: token
Unseal Key 5: token

Initial Root Token: token

Vault initialized with 5 key shares and a key threshold of 3. Please securely
distribute the key shares printed above. When the Vault is re-sealed,
restarted, or stopped, you must supply at least 3 of these keys to unseal it
before it can start servicing requests.

Vault does not store the generated root key. Without at least 3 keys to
reconstruct the root key, Vault will remain permanently sealed!

It is possible to generate new unseal keys, provided you have a quorum of
existing unseal keys shares. See "vault operator rekey" for more information.
root@vault-test:/etc # echo $SHELL

Guardando en el environment

Guardamos la variable en el fichero /etc/csh.cshrc para que cada vez que entremos en el jail se encuentre la variable, y no tengamos error de connection refused.

root@Hashicorp-Vault:~ # nano /etc/csh.cshrc
# System-wide .cshrc file for csh(1).
setenv VAULT_ADDR http://192.168.1.21:8200

Creando alias

Tan fácil como

alias vault-server-config="vault server -config /usr/local/etc/vault.hcl"
De momento, con cada inicio del jail debemos ejecutar esto vault-server-config.

Con Bastille usando zsh

Este template fácil se puede aplicar a un jail activo

PKG zsh

CMD chsh -s /usr/local/bin/zsh root

PKG ohmyzsh

CMD cp /usr/local/share/ohmyzsh/templates/zshrc.zsh-template ~/.zshrc

#zsh-autosuggestions

PKG powerline-fonts zsh-autosuggestions

CMD echo 'source /usr/local/share/zsh-autosuggestions/zsh-autosuggestions.zsh' >> ~/.zshrc (1)
1 Para autocompletado.

Deberia salir esto en la consola luego del proceso

➜  ~ echo $SHELL
/usr/local/bin/zsh

Luego se pueden aplicar las variables de entorno

export VAULT_ADDR=ip
export VAULT_TOKEN=token

Igual para el alias, editar el fichero .zshrc

La UI

Proceso de unseal

En la ui introducir 3 claves de esas 5 password que se generaron con el comando vault operator init

unseal

El seal desde la UI

Desde la UI, podemos sellar nuevamente el vault, jkaaja esto me recordo al baúl rojo de los recuerdos. 😂

seal ui

Importante
  1. Con el sello del baúl, ya no tendríamos acceso ni por consola, ni menos por el api. tendriamos un error 503.

root@Hashicorp-Vault:~ # vault read cubbyhole/mi.secreto

Error reading cubbyhole/mi.secreto: Error making API request.

URL: GET http://192.168.1.21:8200/v1/cubbyhole/mi.secreto
Code: 503. Errors:

* Vault is sealed (1)
1 Baúl esta sellado/presintado

Manejo de Secretos

Normalmente al buscar un secreto, tendremos error, porque necesitamos al menos Initial Root Token en el environment.

Podemos setearlo con:
setenv VAULT_TOKEN your-token (1)
nano /etc/csh.cshrc           (2)
source /etc/csh.cshrc         (3)
1 seteando en el environmnet.
2 guardandolo permanente.
3 actualizamos el .cshrc

Leyendo secreto por consola

Antes sin setear el token en el enviroment, no podriamos autenticarnos tal cual.
root@Hashicorp-Vault:/ # vault read cubbyhole/mi.secreto
Error making API request.

URL: GET http://addreess/v1/sys/internal/ui/mounts/secret/my-secret
Code: 403. Errors:

* permission denied
Despues de setearlo
root@Hashicorp-Vault:~ # vault read cubbyhole/mi.secreto
Key     Value
---     -----
test    mi.secreto

Guardando un secreto por consola

vault kv put secret/myapp my.property=mySecretValue
vault kv put path \ (1)
/myapp \            (2)
my.property         (3)
=mySecretValue      (4)
1 path
2 nombre de la aplicacion
3 nombre de la password
4 valor

Via http

El Initial Root Token

Nos servira para autenticarnos en la web vía token, también para consultar el api y obtener secretos, pro ejemplo con este curl podemos obtener un secreto

  • El header X-Vault-Token se envia al hacer la request, con el Initial Root Token

curl --location 'http://ip:8200/v1/cubbyhole/mi.secreto' \
--header 'Accept: */*' \
--header 'Accept-Language: es,en;q=0.9,fr;q=0.8,zh-TW;q=0.7,zh-CN;q=0.6,zh;q=0.5' \
--header 'Connection: keep-alive' \
--header 'DNT: 1' \
--header 'Referer: http://ip:8200/ui/vault/secrets/cubbyhole/list' \
--header 'User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' \
--header 'X-Vault-Token: YourToken' (1)
1 Esto en realidad es el Initial Root Token

Dando una respuesta asi

{
    "request_id": "8ac9f6c5-9f08-12f3-ed87-70de1ab96842",
    "lease_id": "",
    "renewable": false,
    "lease_duration": 0,
    "data": {
        "test": "mi.secreto" (1)
    },
    "wrap_info": null,
    "warnings": null,
    "auth": null,
    "mount_type": "cubbyhole" (2)
}
1 El secreto
2 punto de montaje del secreto

Plugin para PostgreSQL

Postgresql elephant

Instalando el plugin para PostgreSQL

Nos permitira gestionar dinámicamente credenciales de bases de datos (usuarios y contraseñas). Vault puede generar credenciales temporales en PostgreSQL, lo que aumenta la seguridad y elimina la necesidad de gestionar manualmente las credenciales estáticas.

Podemos configurarlo medianta la UI o por consola

Desde la UI

Debenos crear un nuevo PostgreSQL database secrets engine y usamos databases es bastante intuitivo

La cadena de conexión debe cumplir los requistios por ejemplo

postgresql://user:password@ip_address:port/database?

cadena conexion postgresql vault

Habilitando TLS

Necesitamos hacer un par de cosas:
  • Generar la CA.

  • Generar certificado de servidor.

  • Generar certificado de cliente.

Generando CA (certificate authority)

El certificate authority o autoridad certificadora lo usaremos para firmar otros certificados, crearemos una configuración llamada ca.conf (es un fichero), el openSSL nos permitira crear tanto el ca-key.pem y el ca-cert.pem, por ejemplo:

  • el fichero ca.conf

[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
distinguished_name = dn
x509_extensions    = v3_ca

[ dn ]
CN = vault.server.local

[ v3_ca ]
subjectKeyIdentifier   = hash
authorityKeyIdentifier = keyid:always,issuer
basicConstraints       = critical, CA:true
keyUsage               = critical, keyCertSign, cRLSign
openssl genpkey -algorithm RSA -out ca-key.pem (1)
openssl req -x509 -new -nodes -key ca-key.pem -out ca-cert.pem -days 3650 -config ca.conf (2)
1 Genera la clave privada de la CA
2 Genera el certificado de la CA (válido por 10 años 🔥)

Generar certificado del servidor

Creamos el vault-cert-conf para generar el vault-key.pem y vault-cert.pem

  • el fichero vault-cert-conf

[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
distinguished_name = dn
x509_extensions    = v3_req

[ dn ]
CN = la-ip (1)

[ v3_req ]
subjectAltName = @alt_names

[ alt_names ]
IP.1 = la-ip
DNS.1 = vault.server.local
1 Esta dirección ip puesta aquí, esta relacionada con el vault-cert.pem
openssl req -x509 -newkey rsa:2048 -nodes -keyout vault-key.pem -out vault-cert.pem -days 365 -config vault-cert.conf

Esto debería salir en la consola, con el comando anterior:

Generating a RSA private key
.................+++++
...........................................................................................................................+++++
writing new private key to 'vault-key.pem'
-----

Quedando:

  • vault-key.pem Clave privada del servidor

  • vault-cert.pem Certificado del servidor (válido para 'ip especificada, local en mi caso' / vault.server.local)

Tanto para el server y cliente, firmaremos los certificados con nuestro CA.
openssl genpkey -algorithm RSA -out vault-key.pem (1)
openssl req -new -key vault-key.pem -out vault-req.csr -config vault-cert.conf (2)
(3)
openssl x509 -req -in vault-req.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out vault-cert.pem -days 365 -sha256 -extfile vault-cert.conf -extensions v3_req
1 Genera la clave privada, aunque este paso es opcional, lo tenía generado al ejecutar el comando de aquí
2 Genera la CSR.
3 Firmando el CSR con el CA.

Al firmar el CSR con el CA tendriamos esto:

Signature ok
subject=CN = ip (1)
Getting CA Private Key
1 Dirección ip que usamos en el vault-cert.conf

Generar Certificado cliente

Necesitamos generar una configuración client.conf, porque somos flojos jajaja, también:

  • El client-key.pem

  • La CSR (client-req.csr)

  • Luego firmar la CSR con la CA para obtener client-cert.pem.

  • el fichero client.conf

[ req ]
default_bits       = 2048
prompt             = no
default_md         = sha256
distinguished_name = dn
req_extensions     = v3_req

[ dn ]
CN = vault-client

[ v3_req ]
subjectAltName = @alt_names
basicConstraints = CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = clientAuth

[ alt_names ]
DNS.1 = cliente.local
openssl genpkey -algorithm RSA -out client-key.pem (1)
(2)
openssl req -new -key client-key.pem -out client-req.csr -config client.conf
(3)
openssl x509 -req -in client-req.csr -CA ca-cert.pem -CAkey ca-key.pem -CAcreateserial -out client-cert.pem -days 365 -sha256 -extfile client.conf -extensions v3_req
1 Genera la clave privada del cliente.
2 Crea la Solicitud de Firma (CSR) para el cliente.
3 Firmamos la CSR con nuestro CA para obtener el certificado del cliente.

Finalizando esta parte deberiamos tener 2 ficheros, Clave privada del cliente y certificado de cliente firmado por nuestro CA.

  • client-key.pem

  • client-cert.pem

Generar Certificado cliente PKCS#12

Pasos para crear e importar el certificado de cliente en formato PKCS#12

openssl pkcs12 -export -out client-cert.p12 -inkey client-key.pem -in client-cert.pem -certfile ca-cert.pem -name "Vault Client Cert"
openssl pkcs12 -export \  (1)
-out client-cert.p12 \    (2)
-inkey client-key.pem \   (3)
-in client-cert.pem \     (4)
-certfile ca-cert.pem \   (5)
-name "Vault Client Cert" (6)
1 Exporta el certificado en formato PKCS#12.
2 Nombre del archivo de salida, este lo importaremos el browser.
3 Clave privada del cliente.
4 Certificado del cliente.
5 Incluye la CA para que el navegador pueda validar la cadena de confianza.
6 Nombre visible en el navegador al seleccionarlo.

Luego de ese comando nos pedira introducir la password para lo que sera la passphrase

Configuración vault.hcl para TLS

Debemos configurar nuevamente el listener permitiendo la compatibilidad con TLS.

listener "tcp" {
  address            = "192.168.1.21:8200"
  tls_cert_file      = "/ruta/a/vault-cert.pem" (1)
  tls_key_file       = "/ruta/a/vault-key.pem"  (2)
  tls_client_ca_file = "/ruta/a/ca-cert.pem"    (3)
}

storage "file" {
  path    = "/var/db/vault"
  node_id = "v"
}

api_addr = "https://ip:8200"
ui       = true
1 Ruta al certificado del servidor
2 Ruta a la clave privada del servidor
3 Ruta al certificado de la CA (usado para verificar certificados de clientes)

Importar el certificado al navegador

En mozilla, podemos ir al apartado de seguridad e importamos como en la siguiente imagen

Con Chrome por ejemplo chrome://settings/certificates

importamos el certificado llamado ca-cert.crt que es de tipo X.509 y luego reiniciamos chrome
Importar CA En Mozilla

import ca cert mozilla vault hashicorp

Importar CA En Chrome

ca crt chrome

https en Mozilla

ssl mozilla with ca

https en Chrome

vault ssl enable chrome

Importar certificado del OS

En este caso andamos en linux, y el vault en FreeBSD, pero por la UI podemos acceder, aunque habilitar la UI en producción no se recomienda.

sudo cp vault-cert.crt /usr/local/share/ca-certificates/vault-cert.crt (1)
sudo update-ca-certificates (2)
Updating certificates in /etc/ssl/certs...
rehash: warning: skipping ca-certificates.crt,it does not contain exactly one certificate or CRL
rehash: warning: skipping vault-cert.pem,it does not contain exactly one certificate or CRL
1 added, 0 removed; done.
Running hooks in /etc/ca-certificates/update.d...
done.
1 El certificado debe estar con extension .crt
2 Actualiza la base de datos de certificados del sistema.

Ver certificados instalados en linux

awk -v cmd='openssl x509 -noout -subject' '/BEGIN/{close(cmd)};{print | cmd}' < /etc/ssl/certs/ca-certificates.crt
awk \                                   (1)
-v cmd='openssl x509 -noout -subject' \ (2)
'/BEGIN/{close(cmd)}; \                 (3)
{print | cmd}' \                        (4)
< /etc/ssl/certs/ca-certificates.crt    (5)
1 awk: tool para procesamiento, estructuración de texto.
2 -v cmd='openssl x509 -noout -subject'
  • Define una variable cmd en awk que contiene el comando openssl

  • openssl x509: Utilidad para manejar certificados X.509

  • noout: Suprime la salida del certificado codificado

  • subject: Muestra solo la línea del sujeto del certificado

3 /BEGIN/{close(cmd)}:
  • Busca el patrón "BEGIN" en cada línea

  • Cuando lo encuentra, cierra el proceso anterior de openssl para iniciar uno nuevo

  • Esto es necesario porque cada certificado en el archivo comienza con "-----BEGIN CERTIFICATE-----"

4 {print | cmd}:
  • Envía cada línea del archivo al comando openssl definido en cmd

  • El | actúa como un pipe, conectando la salida de print con el comando openssl.

5 /etc/ssl/certs/ca-certificates.crt:
  • Lee el archivo que contiene todos los certificados CA del sistema

  • Este archivo es una concatenación de múltiples certificados de confianza

El resultado final es que el comando muestra la línea "subject" (que contiene información sobre el titular) de cada certificado CA instalado en el sistema, por ejemplo:

subject=C = US, O = Amazon, CN = Amazon Root CA 1
subject=C = BE, O = GlobalSign nv-sa, OU = Root CA, CN = GlobalSign Root CA

Endpoint /sys/health

Este endpoint está diseñado específicamente para permitir verificaciones básicas del estado del servidor sin autenticación, incluso sobre HTTPS.

curl -k https://192.168.1.21:8200/v1/sys/health

{
  "initialized": true,
  "sealed": true,
  "standby": true,
  "performance_standby": false,
  "replication_performance_mode": "unknown",
  "replication_dr_mode": "unknown",
  "server_time_utc": 1739132639,
  "version": "1.18.3",
  "enterprise": false,
  "echo_duration_ms": 0,
  "clock_skew_ms": 0,
  "replication_primary_canary_age_ms": 0
}

Haciendo GET via TLS

  • Fácilmente para obtener un curl de un secreto en especifico y rapido (como somos novatos), desde la UI podemos hacer esto. copy/paste

path secreto rest api read

Ahora cuando intentamos consultar un secreto via api rest tenemos esto

➜  ~ curl \
  --header "X-Vault-Token: ${VAULT_TOKEN}" \
  --request GET \
  https://192.168.1.21:8200/v1/${API_PATH} (1)
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the webpage mentioned above.
1 Es donde tenemos el punto de montaje y el secreto

Con nuestro certificado generado aquí podemos usarlo ahora para hacer las request.

➜  ~ curl -k --cert-type P12 --key client-cert.p12 \
  --header "X-Vault-Token: ${VAULT_TOKEN}" \
  --request GET \
  https://vault.server.local:8200/v1/${PATH_SECRET}

==== salida

{
  "request_id": "f2a77063-189c-2c29-2df8-8952962f0dc5",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "data": {
      "secret.": "value" (1)
    },
    "metadata": {
      "created_time": "2025-01-31T02:05:03.750285314Z",
      "custom_metadata": null,
      "deletion_time": "",
      "destroyed": false,
      "version": 9
    }
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null,
  "mount_type": "kv"
}
1 El secreto en este array.

Forzado de TLS sin nginx

De momento aun con todos los pasos realizados, si entramos a la url principal del vault tengamos o no certificado, podemos entrar y visualizar la UI, y deberia de rechazarnos, entonces debemos añadir en nuestro listener tcp una linea.

listener "tcp" {
  address = "ip-address:8200"
  tls_cert_file = "/root/vault-cert.pem"
  tls_key_file  = "/root/vault-key.pem"
  tls_client_ca_file = "/root/ca-cert.pem"
  tls_disable_client_certs = false  # Forzar autenticación de cliente
  tls_min_version = "tls12"
  tls_require_and_verify_client_cert = "true" (1)
}
1 Linea mágica, forzara digamos el mTLS, autenticación mutua
➜  ~ curl --cert client-cert.pem --key client-key.pem --cacert ca-cert.pem \ (1)
  --header "X-Vault-Token: ${VAULT_TOKEN}" \
  --request GET \
  https://vault.server.local:8200/v1/${PATH_SECRET}

==== salida

{
  "request_id": "f2a77063-189c-2c29-2df8-8952962f0dc5",
  "lease_id": "",
  "renewable": false,
  "lease_duration": 0,
  "data": {
    "data": {
      "secret.": "value" (2)
    },
    "metadata": {
      "created_time": "2025-01-31T02:05:03.750285314Z",
      "custom_metadata": null,
      "deletion_time": "",
      "destroyed": false,
      "version": 9
    }
  },
  "wrap_info": null,
  "warnings": null,
  "auth": null,
  "mount_type": "kv"
}
1 Los archivo en formato .pem son más compatibles con curl por lo visto, y es la única manera de que funcione el curl en esta prueba.
2 El secret

Transit Secrets Engine de Vault

"encryption as a service"

Aka cifrado como servicio, aplica criptografía simétrica a nuestras passwords, útil para casos de necesitar la password en texto plano 🤐…​

Ese método de cifrado reversible, puede venir útil, por ejemplo para una request con un usuario y password a un sistema remoto mi caso…​

Como tenemos ahora TLS tocaria ajustar un poco nuestro environment con

export VAULT_ADDR=https://direccion-ip:8200
export VAULT_CLIENT_CERT=/path/client-cert.pem
export VAULT_CLIENT_KEY=/path/client-key.pem
export VAULT_CACERT=/path/ca-cert.pem
export VAULT_TOKEN=token (1)
1 Token, pero ojo, ya no deberia ser el "initial root token"

Genera token

El initial root token generado, cuando inicia el operador es un token root, ya sabemos como es esto, entonces lo mejor sera crear una politica y a esa politica generada le asignamos un token.

Cualquier persona con este token puede borrar/leer/escribir cualquier dato en Vault.

How do we do it ? from the UI or by console?

shell

We create a file called kv-lector.hcl

path "app/data/test" {
capabilities = ["read", "list"]
}

path "app/metadata/test" {
capabilities = ["list"]
}