SeguridadMicroserviciosAWSKubernetesFintech

Seguridad en microservicios: lecciones de un plan de 7 fases

Por Ronald Castillo·

En entornos fintech regulados, la seguridad no es una característica — es un requisito de negocio. Diseñar un programa de hardening sobre una plataforma de 40+ microservicios en producción, con múltiples squads y cero tolerancia a downtime, es uno de los desafíos más complejos que he enfrentado como líder técnico.

Este artículo documenta el enfoque estructurado que aplicamos: un programa de 7 fases que tomó seis meses ejecutar y que transformó la postura de seguridad de toda la plataforma.

Por qué un plan por fases

La tentación natural es “arréglalo todo ahora”. Pero en una plataforma con 40+ microservicios, 3 squads y cero downtime tolerado, un cambio abrupto en la gestión de credenciales puede impactar a docenas de servicios simultáneamente en producción.

El enfoque por fases permite:

  1. Aislar el radio de explosión de cada cambio
  2. Validar en staging antes de producción
  3. Dar tiempo a los equipos para adaptar sus flujos de trabajo
  4. Documentar antes de ejecutar (crítico para auditorías regulatorias como SBS)

Las 7 fases

Fase 1 — Inventario y clasificación de secretos

Antes de cambiar nada, necesitas saber qué existe. El primer paso fue construir un inventario completo de todos los secretos y variables de entorno en todos los namespaces de Kubernetes, clasificados por tipo y nivel de sensibilidad:

# Clasificación básica por patrones
kubectl get secrets --all-namespaces -o json | \
  jq '.items[] | {name: .metadata.name, namespace: .metadata.namespace, keys: [.data | keys[]]}'

Este inventario es la base de todo el programa. Sin él, cualquier esfuerzo de hardening es ciego.

Fase 2 — Bastion host y restricción de acceso directo

Un principio fundamental: ningún desarrollador debería tener acceso directo a pods de producción. La solución fue un bastion host dedicado con acceso únicamente vía SSM Session Manager, eliminando SSH abierto, con MFA obligatorio y registro completo de sesiones en CloudTrail.

# Terraform — Bastion con SSM
resource "aws_instance" "bastion" {
  ami           = data.aws_ami.amazon_linux_2023.id
  instance_type = "t3.micro"
  
  iam_instance_profile = aws_iam_instance_profile.bastion_ssm.name
  
  # Sin ingress SSH — solo SSM
  vpc_security_group_ids = [aws_security_group.bastion_no_ssh.id]
  
  tags = { Name = "bastion-prod", Environment = "prod" }
}

Fase 3 — Migración a AWS Secrets Manager

La migración de secretos desde Kubernetes a AWS Secrets Manager fue la fase más técnicamente compleja. Adoptamos el patrón de External Secrets Operator, que sincroniza automáticamente los secretos de AWS SM a Kubernetes Secrets de forma encriptada:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: db-credentials
  namespace: payments
spec:
  refreshInterval: 1h
  secretStoreRef:
    name: aws-secretsmanager
    kind: ClusterSecretStore
  target:
    name: db-credentials-k8s
    creationPolicy: Owner
  data:
    - secretKey: DB_PASSWORD
      remoteRef:
        key: payments/prod/db
        property: password

Fase 4 — IAM granular por microservicio

El modelo de un rol IAM compartido por namespace amplifica el blast radius de cualquier compromiso. Con IRSA (IAM Roles for Service Accounts), cada microservicio tiene su propio rol IAM con acceso solo a los recursos que ese servicio específicamente necesita. Las políticas se revisan trimestralmente.

El resultado: el blast radius de un compromiso baja de “acceso a toda la plataforma” a “acceso solo a los recursos de ese servicio específico”.

Fase 5 — Rotación automática de credenciales

Las credenciales estáticas son un riesgo acumulativo. AWS Secrets Manager soporta rotación automática con Lambda functions, eliminando la necesidad de rotación manual:

def lambda_handler(event, context):
    secret_id = event['SecretId']
    step = event['Step']
    
    if step == 'createSecret':
        new_password = generate_secure_password(32)
        client.put_secret_value(
            SecretId=secret_id,
            ClientRequestToken=event['ClientRequestToken'],
            SecretString=json.dumps({'password': new_password}),
            VersionStages=['AWSPENDING'],
        )
    elif step == 'setSecret':
        update_database_password(secret_id, event['ClientRequestToken'])
    elif step == 'finishSecret':
        client.update_secret_version_stage(...)

Fase 6 — Network policies y segmentación

Un microservicio de notificaciones no debería poder comunicarse con el core bancario. Kubernetes NetworkPolicies restringen el tráfico este-oeste a solo los flujos explícitamente permitidos:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: payments-isolation
  namespace: payments
spec:
  podSelector:
    matchLabels:
      app: payment-core
  policyTypes:
    - Ingress
    - Egress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: api-gateway
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: database

Fase 7 — SAST/DAST y security gates en CI/CD

La última fase cierra el ciclo: prevenir que los problemas aparezcan. Integramos:

  • Semgrep en GitHub Actions para SAST en cada PR
  • OWASP ZAP en el pipeline de staging para DAST
  • Trivy para scan de imágenes Docker antes de push a ECR
  • Checkov para validación de IaC (Terraform, Helm charts)

Si alguno de estos gates falla, el PR no puede mergear a main. Sin excepciones.

La lección más importante

La seguridad no es un proyecto que termina. Es un proceso continuo. El plan de 7 fases nos dio estructura, pero el valor real vino de los hábitos que instalamos en los equipos: revisar IAM trimestralmente, hacer threat modeling antes de cada feature importante, y tratar los secretos con el mismo cuidado que los datos de los usuarios.

En fintech regulada, la pregunta no es si te van a auditar. Es cuándo. Y cuando llegue ese momento, quieres tener respuestas claras para cada decisión de arquitectura de seguridad que tomaste.