Projectos Help

Gestor de Certificados

Introducción

Este documento describe la arquitectura y el procedimiento para la obtención segura y automatizada de certificados TLS/SSL desde HashiCorp Vault por servidores Ubuntu en Azure, utilizando la autenticación basada en Azure Instance Metadata Service (IMDS) y un agente propio programado en Go (Go SDK). La solución está diseñada para minimizar intervención manual, aumentar la seguridad y facilitar la rotación de certificados en entornos cloud.

Objetivo

Proveer un mecanismo confiable y seguro para que servidores Linux sobre Azure puedan autenticarse frente a Vault usando su identidad de Azure (Managed Identity, IMDS), obtener certificados y almacenarlos localmente, todo gestionado automáticamente por un agente/servicio en Go.

Arquitectura General

Servicio Consumidor (ej: nginx)HashiCorp VaultIMDS (Azure Managed Identity)Agente Go en VM de AzureServicio Consumidor (ej: nginx)HashiCorp VaultIMDS (Azure Managed Identity)Agente Go en VM de Azure1. Solicita token MSI2. Envía JWT (token de identidad de Azure)3. Autenticación Azure (auth/azure/login) con JWT4. Devuelve token de Vault5. Solicita certificado/llave privada6. Envía certificado/llave privada7. Escribe archivos en disco de forma segura8. Recarga servicio para aplicar nuevo certificado9. Monitorea expiración de certificado

Descripción de pasos:

  1. El agente en Go solicita un token de identidad (MSI) a IMDS de Azure.

  2. IMDS responde entregando el JWT (Azure Managed Identity token).

  3. El agente utiliza el JWT para autenticarse ante HashiCorp Vault usando el método auth/azure/login.

  4. Vault responde con un token de acceso propio para operaciones permitidas.

  5. El agente solicita el certificado y la llave privada a Vault utilizando el token recibido.

  6. Vault retorna el certificado y la llave privada.

  7. El agente escribe ambos archivos localmente con permisos seguros.

  8. El agente recarga el servicio consumidor (ejemplo: nginx) para aplicar el nuevo certificado.

  9. El agente espera o monitorea hasta el próximo ciclo de renovación, gestionando automáticamente la rotación.

Prerrequisitos

  • Suscripción Azure con permisos para habilitar Managed Identities en VMs.

  • HashiCorp Vault desplegado y accesible desde las VMs.

  • Acceso de administrador a Vault para configuración inicial.

  • Clúster con PKI interna (Vault) o punto seguro para los certificados.

  • Identidad administrada habilitada en cada VM.

Configuración de Azure

  1. Habilitar Managed Identity en cada VM:

    • Portal: VM → Identidad → Activar “Identity (System-assigned)” y guardar.

    • Tomar nota de Subscription ID, Resource Group y VM Name para alineación con políticas Vault.

Configuración de Vault

  1. Habilitar método de auth Azure:

    vault auth enable azure
  2. Registrar Service Principal para validación:
    (si no existe) en Azure Portal; otorgar permisos mínimos (rol Reader).

  3. Configurar secreto del Azure backend en Vault:

    vault write auth/azure/config \ tenant_id="AZURE_TENANT_ID" \ resource="https://management.azure.com/" \ client_id="SP_CLIENT_ID" \ client_secret="SP_CLIENT_SECRET"
  4. Crear política para lectura de certificados:

    path "secret/certificados/*" { capabilities = ["read", "list"] }

    Registrar la política en Vault:

    vault policy write certs-read ./certs-read.hcl
  5. Definir rol de Azure en Vault:

    vault write auth/azure/role/cert-fetcher \ policies="certs-read" \ bound_subscription_ids="SUBSCRIPTION_ID" \ bound_resource_groups="RESOURCE_GROUP" \ bound_vm_names="VM_NAME" \ ttl="1h"

Agente en Go – Funcionalidad y Ejemplo

a. Funcionalidad del Agente

  • Solicita token MSI a IMDS.

  • Se autentica contra Vault en /auth/azure/login usando el JWT recibido.

  • Recupera certificado y clave desde Vault (secretería KV o PKI).

  • Escribe archivos localmente con permisos restringidos (0600).

  • Recarga (o reinicia) el servicio dependiente (ej. nginx) tras actualización.

  • Realiza verificaciones periódicas para renovación automática antes de la expiración del certificado.

b. Ejemplo Básico de Implementación (Go SDK)

package main import ( "os" "io/ioutil" "net/http" "encoding/json" "github.com/hashicorp/vault/api" "log" ) func fetchMSIToken() (string, error) { req, _ := http.NewRequest("GET", "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/", nil) req.Header.Add("Metadata", "true") resp, err := http.DefaultClient.Do(req) if err != nil { return "", err } defer resp.Body.Close() var data struct { AccessToken string `json:"access_token"` } dec := json.NewDecoder(resp.Body) if err := dec.Decode(&data); err != nil { return "", err } return data.AccessToken, nil } func vaultLogin(vaultAddr, role, jwt string) (string, error) { client, err := api.NewClient(&api.Config{ Address: vaultAddr }) if err != nil { return "", err } params := map[string]interface{}{ "role": role, "jwt": jwt, } secret, err := client.Logical().Write("auth/azure/login", params) if err != nil { return "", err } return secret.Auth.ClientToken, nil } func fetchCerts(vaultAddr, vaultToken, path, certField, keyField, certFile, keyFile string) error { client, err := api.NewClient(&api.Config{ Address: vaultAddr }) if err != nil { return err } client.SetToken(vaultToken) secret, err := client.Logical().Read(path) if err != nil { return err } cert, ok1 := secret.Data[certField].(string) key, ok2 := secret.Data[keyField].(string) if !ok1 || !ok2 { return errors.New("estructura errónea en el secreto") } if err := ioutil.WriteFile(certFile, []byte(cert), 0600); err != nil { return err } if err := ioutil.WriteFile(keyFile, []byte(key), 0600); err != nil { return err } return nil } func main() { vaultAddr := "https://vault.ejemplo.com" role := "cert-fetcher" secretPath := "secret/data/certificados/miapp" certField := "certificate" keyField := "private_key" certFile := "/etc/ssl/certs/miapp.crt" keyFile := "/etc/ssl/private/miapp.key" jwt, err := fetchMSIToken() if err != nil { log.Fatalf("Error obteniendo token MSI: %v", err) } token, err := vaultLogin(vaultAddr, role, jwt) if err != nil { log.Fatalf("Error autenticando en Vault: %v", err) } err = fetchCerts(vaultAddr, token, secretPath, certField, keyField, certFile, keyFile) if err != nil { log.Fatalf("Error obteniendo certificados: %v", err) } // Aquí agregar lógica para recargar nginx, apache, etc. }

c. Implementación y Servicio

  • Compilar y desplegar el binario del agente en cada VM.

  • Configurar como servicio de sistema (systemd):

[Unit] Description=Agente de obtención de certificados desde Vault After=network.target [Service] ExecStart=/usr/local/bin/agente-cert-vault Restart=on-failure [Install] WantedBy=multi-user.target
  • El ciclo de renovación puede realizarse con un sleep/timer dentro del agente, o el propio servicio puede reiniciar/actualizarse a intervalos definidos.

Seguridad

  • El agente debe ejecutarse con usuario/restricciones mínimos requeridos.

  • Archivos de certificados/llaves siempre con permisos 0600.

  • Acceso restringido y auditado mediante roles/políticas tanto en Vault como en la infraestructura base.

  • Monitorizar logs de autenticación, auditoría y consumo de Vault.

Rotación y Renovación

  • El agente debe verificar periódicamente la validez de los certificados y renovar automáticamente (accediendo nuevamente al flujo de autenticación/JWT) antes de expiración.

  • Puede usarse la fecha de expiración del certificado X.509, TTL del secreto o información de lease otorgada por Vault.

  • Siempre notificar/recargar aplicaciones dependientes tras cualquier actualización.

Referencias y Enlaces Útiles

Notas Finales

  • Esta arquitectura permite alta seguridad, escalabilidad y mínima gestión manual.

  • La integración con IMDS elimina la necesidad de manejar secretos de acceso en disco.

  • Se recomienda revisar periódicamente roles y permisos, y mantener actualizado el agente para incorporar mejoras de seguridad.

16 June 2025