# Vault en producción: configuración HA con Raft

Desde hace tiempo me han estado preguntando sobre este tema, así que vamos a crear un clúster de **Hashicorp Vault**, algo fundamental para una empresa moderna. Una bóveda que nos ayudará a gestionar mejor los secretos y también a reducir los costos de nuestro uso en la nube.

La idea es tener un **Hashicorp Vault** multinodo con alta disponibilidad, usando **raft** como almacenamiento backend. De esta forma, podremos ofrecer resiliencia, escalabilidad y seguridad. Además, mostraremos cómo conectarnos a través de la interfaz de usuario y cómo las aplicaciones pueden utilizar esos secretos.

**HashiCorp Vault** es una herramienta de código abierto diseñada para almacenar y gestionar de forma segura información sensible como secretos (claves API, contraseñas, certificados, etc.), tokens y claves de cifrado. Vault proporciona una interfaz unificada para administrar estos secretos de manera segura en distintos entornos, incluyendo infraestructuras en la nube, aplicaciones y servicios. Es capaz de manejar secretos dinámicos, ofrecer cifrado como servicio y admite diversos métodos de autenticación para controlar el acceso.

## Preparación de Entorno

Tengo un cluster con un master y un worker, en Kubernetes. Para esa prueba de concepto, estoy usando [microK8s](https://ubuntu.com/tutorials/install-a-local-kubernetes-with-microk8s#1-overview).

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748382102192/4c736c23-0dfb-46bc-90a4-8cfd50998082.png align="center")

Esta todo listo para comenzar a trabajar. Vamos a necesitar tener helm, instalado. Corremos los siguientes comandos para agregar y actualizar nuestras fuentes.

```bash
helm repo add hashicorp https://helm.releases.hashicorp.com
helm repo update
```

Para tener una idea de que vamos a estar haciendo, les dejo esta grafica.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748605234118/2b1cc779-6f3d-4c7a-860f-064d5242fa1a.png align="center")

## Instalación Vault con Raft

Antes que nada vamos a crear `custom-values.yaml` para habilitar la alta disponibilidad (HA) y configurar el backend Raft.

```yaml
server:
  affinity: ""
  ha:
    enabled: true
    raft:
      enabled: true
```

**HashiCorp Vault** utiliza **Raft** como backend de almacenamiento para garantizar alta disponibilidad, replicación de datos y una fuerte consistencia entre los nodos del clúster. Raft permite que Vault funcione sin dependencias externas, lo que facilita su gestión y escalabilidad, asegurando al mismo tiempo tolerancia a fallos y una recuperación confiable ante fallos.

Aplicamos.

```yaml
helm install vault hashicorp/vault -f custom-values.yaml --namespace vault --create-namespace
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748605660327/93b79261-27a8-4fc8-941e-108b956f8c5a.png align="center")

Deberías ver pods con el estado 0/1. Esto indica que los pods de Vault están en ejecución, pero aún no han sido inicializados ni desbloqueados (unsealed).

Vamos a inicializar el vault-0 de la siguiente manera.

```yaml
kubectl exec -n vault vault-0 -- vault operator init
```

Ahora tenemos inicializado el pod master.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748605867812/a488b2d5-dd8b-4af9-aea6-2dda11d17558.png align="center")

> Importante: Guarda estas claves de forma segura, ya vas a necesitarár al menos 3 de ellas para desbloquear (unseal) Vault en caso de un reinicio o de que se vuelva a reseal.

Desbloquea (unseal) Vault utilizando tres de las claves de desbloqueo en cada pod de Vault. Esto hay que hacerlo en todos los podes vault-0, vault-1 & vault-2.

```yaml
kubectl exec -n vault vault-0 -- vault operator unseal <Unseal Key1>
kubectl exec -n vault vault-0 -- vault operator unseal <Unseal Key2>
kubectl exec -n vault vault-0 -- vault operator unseal <Unseal Key3>
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748606194433/7fe6f488-4861-463c-9aab-e3bab743f8f2.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748606339436/418cf296-7279-4f1e-9067-f13d0c4ee4a6.png align="center")

Nos queda sumar los demas pods al cluster raft.

```yaml
kubectl exec -ti vault-1 -n vault -- vault operator raft join http://vault-0.vault-internal:8200
```

Luego hacemos el unseal.

```yaml
kubectl exec -n vault vault-1 -- vault operator unseal <Unseal Key1>
kubectl exec -n vault vault-1 -- vault operator unseal <Unseal Key2>
kubectl exec -n vault vault-1 -- vault operator unseal <Unseal Key3>
```

Repetimos esta operación con el **vault-2**.

Una vez que terminamos el trabajo vamos a ver como nos quedo el cluster.

```yaml
kubectl exec -ti vault-0 -n vault -- vault login
kubectl exec -ti vault-0 -n vault -- vault operator raft list-peers
```

Uala! Tenemos el “lider” y los seguidores.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748699958665/8d984fa5-5910-4f12-9972-3ab4aad49e19.png align="center")

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748729900141/3e412d70-f368-497b-b6ae-ff2141131631.png align="center")

Verificamos el estado de todos nuestros pods. Y vemos que estan listos para ser utilizados.

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748700020883/47fde22f-4035-4277-ad44-16f284e98c54.png align="center")

## Ingress a Vault

Para exponer la interfaz web (UI) de HashiCorp Vault mediante Ingress y asegurarla con TLS, seguí estos pasos. Asegurate de tener **Cert-Manager** instalado para gestionar la provisión de certificados a través de **Let’s Encrypt**. Si no sabes como hacerlo, podes revisar [aca](https://medium.com/@bhojeshwar.sahu/lets-encrypt-certificate-using-cert-manager-on-a-kubernetes-cluster-a851c3a67950).

Voy agregar el Token, que necesito de **Cloudflare**, en el Cluster.

```yaml
kubectl create secret generic cloudflare-api-token-secret \
  --namespace cert-manager \
  --from-literal=api-token="TOKEN"
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748700619896/bd4345f6-fc57-4999-8cf8-4ff3b8e0bb92.png align="center")

Vamos a crear nuestro `cloudflare-clusterissuer.yaml` y realizar el challange.

```yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
  name: letsencrypt-dns-cloudflare
spec:
  acme:
    email: TUMAIL
    server: https://acme-v02.api.letsencrypt.org/directory
    privateKeySecretRef:
      name: letsencrypt-dns-cloudflare-key
    solvers:
      - dns01:
          cloudflare:
            email: TUMAIL
            apiTokenSecretRef:
              name: cloudflare-api-token-secret
              key: api-token
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748703624467/443e351d-4743-470c-8130-9ce5bd8422b5.png align="center")

Ahora si! Vamos aplicar el `ingress-vault.yaml`.

```yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: vault-ingress
  namespace: vault
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-dns-cloudflare
    nginx.ingress.kubernetes.io/ssl-redirect: "true"
    nginx.ingress.kubernetes.io/backend-protocol: "HTTP"
spec:
  ingressClassName: nginx
  rules:
    - host: vault.esprueba.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: vault # Ojo aca, puede ser que sea vault-ui
                port:
                  number: 8200
  tls:
    - hosts:
        - vault.esprueba.com
      secretName: vault-tls
```

Listo! Ya esta con interfaz!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748703712793/049629b0-cdb7-4877-ab4f-fbefe992f5eb.png align="center")

Siguiendo estos pasos, has configurado con éxito un clúster de HashiCorp Vault de múltiples nodos con alta disponibilidad, utilizando Raft para la replicación y consistencia de los datos. Esta configuración garantiza que tu sistema de gestión de secretos sea resiliente, escalable y seguro sobre Kubernetes. Al exponer la interfaz web de Vault de forma segura mediante Ingress, la administración y monitoreo de secretos se vuelve aún más ágil.

## Implementación

Nos vamos a autenticar y generar unos secretos para consumir.

```yaml
kubectl exec -ti vault-0 -n vault -- vault login
vault secrets enable -path=secret kv
vault kv put secret/demo username=santiago password=supersecreto
```

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748705162136/43b4bd39-a740-4ffd-a75a-5e45dd27d694.png align="center")

Ahora vamos a generar el código para consumirlo. Voy hacer algo sencillo en Python. Pero antes tengo que crear un Rol, que tenga permisos para consumirlo.

```yaml
vault policy write read-secret - <<EOF
path "secret/demo" {
  capabilities = ["read"]
}
EOF
```

Esto otorga permiso solo para leer `secret/demo`. Ahora habilitamos el AppRole.

```yaml
vault auth enable approle
```

Creamos el AppRole con tokens validos por 1 hora y renovables cada 4 horas. Lo vamos a llamar `python-reader`.

```yaml
vault write auth/approle/role/python-reader \
  token_policies="read-secret" \
  token_ttl=1h \
  token_max_ttl=4h
```

Vamos a obtener los datos necesarios para nuestra aplicación. Solo esta vez.

```yaml
/ $ vault read -field=role_id auth/approle/role/python-reader/role-id
3faac7e2-4ba8-1de1-eea6-92c420a47015
/ $ vault write -f -field=secret_id auth/approle/role/python-reader/secret-id
107f3d2d-9b18-8fd5-4194-74562d6339b9
```

Ya tenemos nuestro `role-id` & `secret-id`. Ahora vamos correr nuestro `app.py`.

```python
import hvac
import warnings


client = hvac.Client(url="https://vault.esprueba.com")

ROLE_ID = "3faac7e2-4ba8-1de1-eea6-92c420a47015"
SECRET_ID = "1803cfd7-ce7e-8e18-1a68-e1e512e77f9d"

# Login con AppRole
login_response = client.auth.approle.login(role_id=ROLE_ID, secret_id=SECRET_ID)
client.token = login_response["auth"]["client_token"]

# Si tu secreto está en KV v1 (como vimos antes)
secret_response = client.secrets.kv.v1.read_secret(path="demo", mount_point="secret")

data = secret_response["data"]
print("✅ Secreto leído:")
print("👤 Usuario:", data.get("username"))
print("🔑 Password:", data.get("password"))
```

Listo!

![](https://cdn.hashnode.com/res/hashnode/image/upload/v1748707007535/74f136a1-e90b-4c9d-a469-064b6f4a182d.png align="center")

> La advertencia del **módulo SSL de Python (**`ssl`) que está enlazado contra **LibreSSL**. `urllib3` inspecciona esa biblioteca en tiempo de ejecución, y si ve que no es OpenSSL 1.1.1+ por eso lanza el warning.

Estas son las ventajas de implementar de esta manera.

1. **Gestión centralizada de secretos**  
    Vault se convierte en el único punto de control de secretos. Ya no hay credenciales hardcodeadas en el código, archivos `.env`, imágenes de Docker ni pipelines de CI/CD.
    
2. **Autenticación robusta con AppRole**  
    AppRole separa identidad (`role_id`) de autenticación (`secret_id`), lo que permite controlar y auditar accesos con precisión. Incluso si un atacante accede al `role_id`, no puede autenticarse sin el `secret_id`.
    
3. **Rotación de secretos sin downtime**  
    Si cambiás un secreto en Vault, la app podrá leer el nuevo valor en el próximo acceso. No es necesario reiniciar ni desplegar nuevamente.
    
4. **Aplicación del principio de menor privilegio**  
    Las políticas de Vault permiten que cada AppRole acceda únicamente a los paths que necesita. Si una app solo necesita `secret/demo`, no puede ver nada más.
    
5. **Auditoría completa y trazabilidad**  
    Vault registra cada solicitud: qué secreto se accedió, desde qué AppRole, en qué momento. Esto permite trazabilidad y cumplimiento normativo.
    

Espero que les sirva!
