Quantum-Safe Cloud -- Parte 6

Zero Trust en AKS: mTLS con certificados Quantum-Safe

#security #post-quantum #zero-trust #kubernetes #mtls #quantumapi #aks

El problema

Has asegurado el perímetro. HTTPS en todas partes, tokens JWT con QuantumID, secrets en QuantumVault, imágenes firmadas. Un atacante que intente entrar desde fuera se encuentra con varias capas.

Pero dentro del cluster, la historia es diferente.

En una instalación por defecto de Kubernetes, la comunicación entre servicios no está cifrada ni autenticada. Un pod en el namespace users-api puede llamar a un pod en el namespace notifications sin demostrar quién es. Si un pod se ve comprometido — un container escape, una dependencia con una backdoor, una regla RBAC mal configurada — el atacante tiene un punto de entrada dentro de tu cluster y puede llegar a todo lo demás.

Zero trust significa: verificar todo, no confiar en nada, ni siquiera dentro del cluster. Cada conexión necesita autenticación. Cada servicio demuestra su identidad en cada petición. No hay “confianza dentro” y “desconfianza fuera” — todo es fuera.

El mecanismo estándar para identidad entre servicios es mTLS (mutual TLS). Ambos lados de una conexión presentan certificados. Ambos lados verifican el certificado del otro antes de intercambiar datos. Un pod comprometido que no tenga un certificado válido no puede conectarse.

El problema: gestionar certificados a escala es difícil. Cada servicio necesita un certificado. Los certificados expiran. Necesitan rotación. Necesitas una CA que los emita y una forma de distribuir la clave pública de la CA a cada servicio.

El problema más grande: el mTLS estándar usa certificados RSA o ECDSA. Volvemos a criptografía clásica, vulnerable a ordenadores cuánticos.

QuantumAPI resuelve esto: actúa como una certificate authority que emite certificados X.509 firmados con ML-DSA. Tus servicios obtienen certificados que prueban su identidad hoy y siguen seguros contra ataques cuánticos.

La solución

mTLS con QuantumAPI como CA:

  1. QuantumAPI emite un certificado root CA (ML-DSA-65)
  2. Cada servicio recibe un leaf certificate emitido por esa CA, con su identidad en el Subject Alternative Name
  3. Los servicios presentan su certificado en cada conexión saliente
  4. Los servicios verifican el certificado del peer contra la CA antes de aceptar cualquier conexión
  5. Los certificados rotan automáticamente a través del SDK de QuantumAPI antes de expirar

Para AKS, hay dos enfoques:

  • Service mesh (Linkerd, Istio): gestiona mTLS de forma transparente a nivel de sidecar. Configuras el mesh para usar QuantumAPI como CA. Sin cambios en el código de la aplicación.
  • Manual: cada servicio obtiene su propio certificado de QuantumAPI y configura Kestrel para requerir client certificates.

Usaremos el enfoque manual — es más portable y hace que el modelo de seguridad sea explícito en tu código. Si prefieres un service mesh, el paso de emisión de certificados es el mismo.

Ejecución

ATLAS para esta tarea

[A] ARCHITECT
  Enforce mTLS between users-api and any downstream services it calls.
  QuantumAPI acts as the CA. Certificates are ML-DSA-65.
  Certificate rotation: every 24 hours (QuantumAPI handles re-issuance).
  Out of scope: service mesh sidecar injection, ingress mTLS (handled at gateway level).

[T] TRACE
  Service starts → requests certificate from QuantumAPI CA
  → stores cert + key in memory (not on disk)
  → Kestrel configured with ClientCertificateMode.RequireCertificate
  → Outgoing calls: HttpClient configured with the service certificate
  → Peer verifies certificate against CA public key
  → Background: CertificateRotationService checks expiry every hour
  → If < 4 hours left → request new certificate → hot-reload Kestrel

[L] LINK
  Service → QuantumAPI CA: HTTPS, X-Api-Key, POST /api/v1/certificates
  Service A → Service B: mTLS, both sides present ML-DSA cert
  Service → QuantumAPI (JWKS): for token validation (article 4)

[A] ASSEMBLE
  1. IServiceCertificateProvider: fetches + caches cert from QuantumAPI
  2. Kestrel configuration: require client cert on all endpoints
  3. HttpClient factory: attach service cert to all outgoing calls
  4. CertificateRotationService: background rotation before expiry
  5. K8s NetworkPolicy: restrict ingress to known service identities only

[S] STRESS-TEST
  QuantumAPI CA unreachable at startup?
    → Retry 5 times with backoff → if still failing, fail fast
  Certificate expired mid-deployment?
    → CertificateRotationService catches it before expiry
    → If not, new pod fails readiness check → old pods stay up
  New service added without a certificate?
    → Connection rejected by existing services → explicit failure (not silent)

Paso 1: Emitir un certificado de servicio

El SDK de QuantumAPI expone un endpoint de certificados:

// ServiceCertificateProvider.cs
public class ServiceCertificateProvider : IServiceCertificateProvider
{
    private readonly QuantumApiClient _client;
    private X509Certificate2? _certificate;
    private readonly SemaphoreSlim _lock = new(1, 1);

    public ServiceCertificateProvider(QuantumApiClient client)
    {
        _client = client;
    }

    public async Task<X509Certificate2> GetCertificateAsync()
    {
        if (_certificate is not null && !IsExpiringSoon(_certificate))
            return _certificate;

        await _lock.WaitAsync();
        try
        {
            // Double-check after acquiring lock
            if (_certificate is not null && !IsExpiringSoon(_certificate))
                return _certificate;

            _certificate = await IssueCertificateAsync();
            return _certificate;
        }
        finally
        {
            _lock.Release();
        }
    }

    private async Task<X509Certificate2> IssueCertificateAsync()
    {
        var serviceName = Environment.GetEnvironmentVariable("SERVICE_NAME")
            ?? "unknown-service";

        var result = await _client.Certificates.IssueAsync(new IssueCertificateRequest
        {
            Subject = $"CN={serviceName},O=users-api,C=EU",
            SubjectAlternativeNames = new[]
            {
                $"DNS:{serviceName}.users-api.svc.cluster.local",
                $"DNS:{serviceName}"
            },
            Algorithm = "ML-DSA-65",
            ValidityHours = 24,
            KeyUsage = new[] { "DigitalSignature", "KeyEncipherment" },
            ExtendedKeyUsage = new[] { "ClientAuthentication", "ServerAuthentication" }
        });

        // Load the PEM certificate + private key into an X509Certificate2
        return X509Certificate2.CreateFromPem(result.Certificate, result.PrivateKey);
    }

    private static bool IsExpiringSoon(X509Certificate2 cert)
        => cert.NotAfter < DateTime.UtcNow.AddHours(4);
}

Paso 2: Configurar Kestrel para requerir client certificates

// Program.cs
builder.Services.AddSingleton<IServiceCertificateProvider, ServiceCertificateProvider>();
builder.Services.AddQuantumApiClient(options =>
{
    options.ApiKey = builder.Configuration["QuantumApi:ApiKey"]!;
});

// Configure Kestrel with the service certificate + client cert requirement
builder.WebHost.ConfigureKestrel(async (context, options) =>
{
    var certProvider = context.ApplicationServices
        .GetRequiredService<IServiceCertificateProvider>();

    var serviceCert = await certProvider.GetCertificateAsync();

    options.ConfigureHttpsDefaults(httpsOptions =>
    {
        httpsOptions.ServerCertificate = serviceCert;
        httpsOptions.ClientCertificateMode = ClientCertificateMode.RequireCertificate;
        httpsOptions.ClientCertificateValidation = (cert, chain, errors) =>
        {
            // Verify the client cert is issued by QuantumAPI CA
            return ValidateAgainstQuantumApiCa(cert);
        };
    });
});
// CA validation helper
private static bool ValidateAgainstQuantumApiCa(X509Certificate2 clientCert)
{
    // The CA cert is fetched once at startup and cached
    // In production: fetch from QuantumAPI /api/v1/ca/certificate endpoint
    var caCert = CaStore.GetCaCertificate();

    var chain = new X509Chain();
    chain.ChainPolicy.TrustMode = X509ChainTrustMode.CustomRootTrust;
    chain.ChainPolicy.CustomTrustStore.Add(caCert);
    chain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;

    return chain.Build(clientCert);
}

Paso 3: Adjuntar el certificado de servicio a las llamadas salientes

// Program.cs (continued)
builder.Services.AddHttpClient("internal")
    .ConfigurePrimaryHttpMessageHandler(serviceProvider =>
    {
        var certProvider = serviceProvider
            .GetRequiredService<IServiceCertificateProvider>();

        return new SocketsHttpHandler
        {
            SslOptions = new SslClientAuthenticationOptions
            {
                ClientCertificates = new X509CertificateCollection
                {
                    certProvider.GetCertificateAsync().GetAwaiter().GetResult()
                },
                RemoteCertificateValidationCallback = (_, cert, chain, errors) =>
                    ValidateAgainstQuantumApiCa(new X509Certificate2(cert!))
            }
        };
    });

Usa el HttpClient "internal" para todas las llamadas entre servicios:

// NotificationServiceClient.cs
public class NotificationServiceClient
{
    private readonly HttpClient _http;

    public NotificationServiceClient(IHttpClientFactory factory)
    {
        _http = factory.CreateClient("internal");
    }

    public async Task SendAsync(NotificationRequest request)
    {
        var response = await _http.PostAsJsonAsync(
            "https://notifications.users-api.svc.cluster.local/api/v1/send",
            request);

        response.EnsureSuccessStatusCode();
    }
}

Paso 4: Rotación de certificados en background

// CertificateRotationService.cs
public class CertificateRotationService : BackgroundService
{
    private readonly IServiceCertificateProvider _certProvider;
    private readonly ILogger<CertificateRotationService> _logger;

    public CertificateRotationService(
        IServiceCertificateProvider certProvider,
        ILogger<CertificateRotationService> logger)
    {
        _certProvider = certProvider;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                // GetCertificateAsync handles renewal internally (IsExpiringSoon check)
                var cert = await _certProvider.GetCertificateAsync();
                var hoursLeft = (cert.NotAfter - DateTime.UtcNow).TotalHours;
                _logger.LogInformation(
                    "Certificate valid. Expires in {Hours:F1} hours", hoursLeft);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Certificate rotation check failed");
            }

            await Task.Delay(TimeSpan.FromHours(1), stoppingToken);
        }
    }
}

Regístralo:

builder.Services.AddHostedService<CertificateRotationService>();

Paso 5: Kubernetes NetworkPolicy

mTLS gestiona la autenticación. NetworkPolicy controla qué pods pueden siquiera intentar una conexión:

# networkpolicy-users-api.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: users-api-ingress
  namespace: users-api
spec:
  podSelector:
    matchLabels:
      app: users-api
  policyTypes:
    - Ingress
  ingress:
    # Allow ingress from API gateway only
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: api-gateway
          podSelector:
            matchLabels:
              app: gateway
      ports:
        - protocol: TCP
          port: 8080
    # Allow ingress from notification service
    - from:
        - podSelector:
            matchLabels:
              app: notifications
      ports:
        - protocol: TCP
          port: 8080

La NetworkPolicy es la capa exterior — bloquea conexiones TCP de pods que no están en la lista permitida. mTLS es la capa interior — incluso si un pod pasa la NetworkPolicy, todavía necesita un certificado ML-DSA válido.

Dos capas, fallos independientes. Una mala configuración de NetworkPolicy no te ayuda si mTLS es obligatorio. El robo de un certificado mTLS no te ayuda si NetworkPolicy bloquea la conexión TCP.

Lo que ajustamos

1. Kestrel + ConfigureKestrel es async. La IA usó ConfigureKestrel con un lambda síncrono. Obtener el certificado necesita una llamada async a QuantumAPI. Cambiamos a ConfigureKestrel con un delegate async y GetAwaiter().GetResult() para la carga inicial del certificado (solo al arranque — aceptable).

2. Certificado en el HttpClient factory. La IA creó un solo X509Certificate2 al arranque y lo usó para siempre. Si el certificado rota, el HttpClient antiguo todavía tiene el certificado antiguo. Cambiamos a resolver el certificado desde IServiceCertificateProvider en cada petición, que devuelve el certificado cacheado válido (o uno renovado si está cerca de expirar).

3. Bootstrap del certificado CA. La IA asumió que el certificado CA ya estaba en un archivo. En la práctica, lo obtienes de QuantumAPI al arranque: GET /api/v1/ca/certificate devuelve el certificado público actual de la CA en formato PEM. Lo cacheas en memoria. Si la CA rota (raro), el siguiente TLS handshake fallará y lo verás en los logs inmediatamente.

Plantilla

=== ZERO TRUST mTLS CHECKLIST ===

CERTIFICATE AUTHORITY
[ ] QuantumAPI CA: fetch root certificate at startup, cache in memory
[ ] Algorithm: ML-DSA-65 (default) — verify this in the issued cert

SERVICE CERTIFICATES
[ ] Each service: unique SAN (DNS:{service}.{namespace}.svc.cluster.local)
[ ] Validity: 24 hours (short-lived → rotation is automatic, not manual)
[ ] Storage: in-memory only, never on disk, never in K8s Secrets

KESTREL (server)
[ ] ClientCertificateMode.RequireCertificate on all internal endpoints
[ ] ClientCertificateValidation: verify against QuantumAPI CA
[ ] External-facing endpoints (ingress): separate — no client cert required from users

HTTPCLIENT (client)
[ ] Named client "internal" for all service-to-service calls
[ ] SslClientAuthenticationOptions with service cert + CA validation
[ ] Do NOT use "internal" for external API calls (wrong cert context)

ROTATION
[ ] BackgroundService checks cert expiry every hour
[ ] Renew when < 4 hours left (24-hour cert → renew at 20 hours)
[ ] Log expiry time on every check (visibility into rotation health)

KUBERNETES
[ ] NetworkPolicy restricts ingress to known pods/namespaces
[ ] mTLS + NetworkPolicy = two independent layers
[ ] Test: try connecting from a pod not in the allowed list → expect TCP rejection
[ ] Test: try connecting with an expired cert → expect TLS handshake failure

Reto

Ahora tienes la foto completa de una aplicación quantum-safe:

  • Secrets en QuantumVault ✓
  • Datos cifrados con ML-KEM + AES-256-GCM ✓
  • Autenticación con QuantumID (tokens ML-DSA) ✓
  • Secrets del pipeline desde QuantumVault, imágenes firmadas con ML-DSA ✓
  • mTLS entre servicios con certificados ML-DSA ✓

El artículo 7 lo une todo: la arquitectura de referencia completa. Un diagrama, un documento ATLAS, un prompt GOTCHA. Lo que llevas a tu equipo y dices “así es como construimos sistemas seguros.”

Si esta serie te ayuda, considera invitarme a un café.

Comments

Loading comments...