Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Unifica identidad y observabilidad multicloud con Microsoft Entra, Pinniped y OpenTelemetry

By Lucas CarranzaNov 13, 202515 min read

Esta página también está disponible en English, Deutsch, Français, Italiano, 日本語 y Português.

Una POC práctica y de bajo costo que puedes montar en un fin de semana, con pasos de despliegue claros centrados en Azure.

imagen generada por IA (chatgpt5.0)

TL;DR

  • Identidad: Convierte a Microsoft Entra ID en tu única fuente de verdad para las personas. Conéctalo a cualquier Kubernetes (AKS/EKS/GKE) con Pinniped (Supervisor + Concierge) para que todos los clusters acepten el inicio de sesión con Entra.
  • Observabilidad: Instrumenta apps e infraestructura con OpenTelemetry → envía a Azure Monitor / Log Analytics.
  • Detección: Usa KQL time-series ML (sin modelos personalizados) para identificar y detectar anomalías en los servicios.
  • Acción: Conecta las alertas de Azure Monitor con Logic Apps/Azure Functions para una remediación segura y auditable.

Resultado: un plano de autenticación , una capa de observabilidad , un MTTR más rápido y controles zero-trust pragmáticos, sin tener que montar infraestructura de ML.

A quién va dirigido

Arquitectos de plataforma, SRE y seguridad que operan Kubernetes multicloud (AKS/EKS/GKE) y buscan:

  • Un plano de identidad humana centralizado y auditable
  • Telemetría estandarizada entre nubes
  • Detección de anomalías con foco en costos y automatización segura

¿Qué problemas resuelve?

  • Autenticación de usuarios fragmentada entre AKS/EKS/GKE → drift y trabajo manual.
  • Telemetría en silos → detección inconsistente y respuesta lenta a incidentes.
  • Levantar stacks de ML resulta caro y excesivo para muchas señales.

Objetivo: un patrón repetible que (1) centralice la identidad, (2) estandarice la telemetría, (3) detecte con KQL ML ligero y (4) automatice la remediación con guardarrieles.

Arquitectura de un vistazo

Plano de identidad

  • Entra ID (IdP) → Pinniped Supervisor (emisor/federación OIDC) → Pinniped Concierge (por cluster).
  • Los desarrolladores inician sesión con Entra una sola vez y obtienen tokens de Kubernetes de corta duración para AKS/EKS/GKE.

Plano de observabilidad

  • Telemetría de aplicaciones e infraestructura mediante SDKs/auto-instrumentación de OpenTelemetryOpenTelemetry Collector (DaemonSet)Azure Monitor / Log Analytics (workspace central). Opcional: métricas con remote_write a Prometheus/Cortex y dashboards en Grafana.

Detección y automatización

  • KQL series_decompose_anomalies / anomaly_detection para anomalías de identidad y de servicio → alertas de Azure MonitorLogic App/Azure Function para enriquecimiento y remediación.

Política y automatización (opcional)

  • Kyverno/OPA Gatekeeper para políticas de admisión/runtime; alertas de Azure Monitor → Logic App / Azure Function para remediación orquestada (ticketing, Conditional Access, cuarentena).

Prerrequisitos centrados en Azure (alcance de la POC)

  • Suscripción y Resource Group para monitoreo y servicios compartidos.
  • Log Analytics Workspace (central).
  • (Opcional) Application Insights (si prefieres vistas de telemetría de aplicaciones basadas en AI sobre LA).
  • Tenant de Microsoft Entra con permisos para crear App Registrations.
  • Azure Key Vault para secretos (client secret de la app de Entra; cadenas de conexión opcionales).
  • kubectl + acceso a por lo menos un cluster (AKS para el recorrido en Azure; EKS/GKE más adelante).
  • Helm para Pinniped y OpenTelemetry Collector.

Tip: mantén nombres y ubicaciones consistentes (por ejemplo,rg-plat-shared ,law-plat-central ).

Despliegue rápido en Azure: paso a paso

Esta sección te lleva desde cero hasta señales y alertas, con foco en Azure. Después podrás extenderlo a EKS/GKE.

1) Crea los recursos centrales de monitoreo

Opción A — Azure CLI

# Variables
LOC=westeurope
RG=rg-plat-shared
LAW=law-plat-central
az group create -n $RG -l $LOC
az monitor log-analytics workspace create -g $RG -n $LAW -l $LOC# Obtén la información del workspace para más adelante
LAW_ID=$(az monitor log-analytics workspace show -g $RG -n $LAW --query id -o tsv)
LAW_CUST_ID=$(az monitor log-analytics workspace show -g $RG -n $LAW --query customerId -o tsv)
LAW_KEY=$(az monitor log-analytics workspace get-shared-keys -g $RG -n $LAW --query primarySharedKey -o tsv)

Opción B — Bicep (idempotente)

param location string = 'westeurope'
param rgName string = 'rg-plat-shared'
param workspaceName string = 'law-plat-central'
resource law 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
  name: workspaceName
  location: location
  properties: {
    retentionInDays: 30
    features: {
      searchVersion: 1
    }
  }
}

La retención impacta de lleno en el costo. Empieza con 30 días y ajusta según tus requisitos de cumplimiento.

2) Exporta los logs de inicio de sesión y auditoría de Entra a Log Analytics

  • En el centro de administración de EntraMonitoring & healthDiagnostics settingsAdd → envía SigninLogs y AuditLogs al Log Analytics Workspace que creaste.
  • Verifica que la configuración esté habilitada y que los logs estén llegando ejecutando SigninLogs | take 5 después de 10 a 15 minutos.

3) Registra la app de Entra para Pinniped Supervisor

  • App Registration: otorga openid, profile, email, offline_access.
  • Agrega la Redirect URI para el callback del Supervisor (por ejemplo, https://pinniped.<your-domain>/callback).
  • Crea un client secret → guárdalo en Key Vault.
  • Anota el Tenant ID y el Client ID.

4) Despliega Pinniped en AKS (Supervisor + Concierge)

Puedes correr el Supervisor en un pequeño cluster de control compartido (recomendado) y desplegar Concierge en cada cluster de workloads (AKS/EKS/GKE).

Instalar Supervisor (Helm)

helm repo add pinniped https://pinniped.dev/helm-charts
helm repo update
kubectl create ns pinniped-supervisor
helm upgrade --install pinniped-supervisor pinniped/supervisor \
  -n pinniped-supervisor \
  --set service.type=LoadBalancer \
  --set config.generateTLS=true

Crear FederationDomain (vincular a Entra)

apiVersion: authentication.supervisor.pinniped.dev/v1alpha1
kind: FederationDomain
metadata:
  name: entra-domain
  namespace: pinniped-supervisor
spec:
  identityProviders:
  - name: azuread
    type: OIDC
    oidc:
      issuer: "https://login.microsoftonline.com/<TENANT_ID>/v2.0"
      clientID: "<CLIENT_ID>"
      clientSecret: { name: "pinniped-azure-secret" }
  issuer: https://pinniped.<your-domain> # URL pública del Supervisor

Guarda el client secret de Entra como un secreto de Kubernetes llamadopinniped-azure-secret enpinniped-supervisor .

Instalar Concierge en los clusters de AKS

kubectl create ns pinniped-concierge
helm upgrade --install pinniped-concierge pinniped/concierge \
  -n pinniped-concierge \
  --set credentialIssuer.enable=true

Validar el login del desarrollador

  • Desde la laptop del desarrollador: pinniped login oidc --issuer https://pinniped.<your-domain> --ca-bundle <CA.pem>
  • Confirma que se genera un kubeconfig con tokens de corta duración y que kubectl get pods funciona contra AKS.

Repite la instalación de Concierge en EKS/GKE para unificar el inicio de sesión entre nubes.

5) Despliega OpenTelemetry Collector en AKS (con exportación a Azure Monitor)

Helm (collector genérico)

helm repo add open-telemetry https://open-telemetry.github.io/opentelemetry-helm-charts
helm repo update
kubectl create ns observability
cat <<'EOF' > values-otel.yaml
config:
  receivers:
    otlp:
      protocols:
        grpc: {}
        http: {}
  processors:
    batch: {}
    resourcedetection:
      detectors: [env, k8snode, k8scluster, k8s]
  exporters:
    azuremonitor:
      connection_string: "InstrumentationKey=<APP_INSIGHTS_KEY>;IngestionEndpoint=https://<region>.in.applicationinsights.azure.com/"
  service:
    pipelines:
      traces:
        receivers: [otlp]
        processors: [batch, resourcedetection]
        exporters: [azuremonitor]
      metrics:
        receivers: [otlp]
        processors: [batch, resourcedetection]
        exporters: [azuremonitor]
      logs:
        receivers: [otlp]
        processors: [batch, resourcedetection]
        exporters: [azuremonitor]
EOFhelm upgrade --install otel-collector open-telemetry/opentelemetry-collector -n observability -f values-otel.yaml

¿De dónde sale el connection string? En Azure, abre tu recurso de Application Insights Overview Connection string . Puedes enviar directamente a Log Analytics mediante Data Collection Endpoints/Rules, pero AI es la vía más sencilla para los traces de aplicaciones.

Etiqueta y muestrea la telemetría

  • Agrega atributos de recurso: service.name, service.namespace, deployment.environment, team, costCenter.
  • Empieza con tail-based sampling (10–20 %) para los traces; mantén una resolución de 1–5 minutos para las métricas.

A. Anomalía de identidad (picos): excluye ruido, suma "viaje imposible" y dimensiona por usuario

Lo nuevo:

  • Excluye cuentas de servicio y break-glass
  • Usa una línea base móvil de 7 días con estacionalidad de 24 h
  • Suma enriquecimiento geográfico + verificación opcional de viaje imposible
  • Genera un payload limpio listo para enrutar alertas
// ---- Parámetros
let lookback = 14d;
let baseline = 7d;
let binSize = 1h;
let sensitivity = 95;          // 90–99; mayor = menos alertas
let serviceAccounts = dynamic(["svc_", "automaton@", "spn-"]);  // prefijos/marcadores a excluir
// ---- Principal
SigninLogs
| where TimeGenerated > ago(lookback)
| where ResultType != 0                      // solo fallos
| extend UPN = tostring(UserPrincipalName)
| where not(UPN has_any (serviceAccounts))  // excluye svc/breakglass
| summarize failed=count(),
           ips = make_set(IPAddress, 4),
           apps = make_set(AppDisplayName, 4)
    by UPN, bin(TimeGenerated, binSize)
| make-series failedSeries = avg(failed)
    on TimeGenerated from ago(baseline) to now() step binSize by UPN
| extend decomp = series_decompose(failedSeries, 24)        // modela el patrón diario
| extend anomalies = series_decompose_anomalies(failedSeries, sensitivity, 24)
| mv-expand TimeGenerated to typeof(datetime),
           failedSeries to typeof(double),
           decomp to typeof(dynamic),
           anomalies to typeof(double)
| where anomalies > 0
// ---- Geo opcional + viaje imposible
| join kind=leftouter (
    SigninLogs
    | where TimeGenerated > ago(lookback)
    | summarize arg_max(TimeGenerated, *) by UserId, IPAddress
    | project UserPrincipalName, IPAddress, Country = LocationDetails.countryOrRegion, Latitude = todouble(LocationDetails.geoCoordinates.latitude), Longitude = todouble(LocationDetails.geoCoordinates.longitude)
) on $left.UPN == $right.UserPrincipalName
| project TimeGenerated, UPN, failed=failedSeries, IPs=ips, Apps=apps, Country, Latitude, Longitude, anomalyScore=anomalies
| order by anomalyScore desc

Complemento opcional de "viaje imposible" (saltos geográficos por pares > 1.500 km en < 2 h):

let minKm = 1500.0;
let maxHours = 2.0;
let toRad = (d:real) { d * pi() / 180.0 };
let haversine = (lat1:real, lon1:real, lat2:real, lon2:real) {
    let dlat = toRad(lat2 - lat1);
    let dlon = toRad(lon2 - lon1);
    let a = pow(sin(dlat/2),2) + cos(toRad(lat1)) * cos(toRad(lat2)) * pow(sin(dlon/2),2);
    6371.0 * 2 * asin(sqrt(a))  // km
};
SigninLogs
| where TimeGenerated > ago(lookback) and ResultType == 0
| project UPN = UserPrincipalName, TimeGenerated, Lat = todouble(LocationDetails.geoCoordinates.latitude), Lon = todouble(LocationDetails.geoCoordinates.longitude)
| where isnotempty(Lat) and isnotempty(Lon)
| serialize
| extend prevTime = prev(TimeGenerated), prevLat = prev(Lat), prevLon = prev(Lon), prevUPN = prev(UPN)
| where UPN == prevUPN
| extend hrs = real(datetime_diff("minute", TimeGenerated, prevTime)) / 60.0
| extend km = haversine(prevLat, prevLon, Lat, Lon)
| where hrs > 0 and km / hrs > (minKm / maxHours)
| project TimeGenerated, UPN, km, hrs, speedKmh = km/hrs

Tip: deja la anomalía de identidad y el viaje imposible como alertas separadas ; enruta esta última a una severidad mayor.

B. Anomalías de servicio: alinéalas con tus tablas de datos y suma tasa de error + SLO burn

Si envías la telemetría de aplicaciones mediante Application Insights (algo común con OTel→Azure Monitor), reemplaza Perf por estas tablas más completas:

Pico de latencia p95 (App Insights / OTel)

let window = 7d;
let step = 5m;
requests
| where timestamp > ago(window)
| summarize p95 = percentile(duration, 95) by bin(timestamp, step), cloud_RoleName
| make-series p95Series = avg(p95) on timestamp from ago(window) to now() step step by cloud_RoleName
| extend anomalies = series_decompose_anomalies(p95Series, 95, 24)
| mv-expand timestamp to typeof(datetime), p95Series to typeof(real), anomalies to typeof(double)
| where anomalies > 0
| project timestamp, service=cloud_RoleName, p95_ms = toreal(p95Series), anomalyScore = anomalies
| order by anomalyScore desc

Pico en la tasa de error (más accionable que la latencia pura)

let window = 24h;
let step = 5m;
requests
| where timestamp > ago(window)
| summarize total=count(), errors = countif(success == false) by bin(timestamp, step), cloud_RoleName
| extend errRate = todouble(errors)/todouble(total)
| make-series errSeries = avg(errRate) on timestamp from ago(window) to now() step step by cloud_RoleName
| extend anomalies = series_decompose_anomalies(errSeries, 95, 24)
| mv-expand timestamp to typeof(datetime), errSeries to typeof(real), anomalies to typeof(double)
| where anomalies > 0 and errSeries > 0.02     // guardarriel: tasa de error >2 %
| project timestamp, service=cloud_RoleName, errorRate = round(errSeries*100.0, 2), anomalyScore = anomalies
| order by anomalyScore desc

SLO burn-rate (ventanas rápida/lenta): ideal para paginar

// Ejemplo: SLO de éxito del 99,9 %
let slo = 0.999;
let fast = 5m;
let slow = 1h;
let targetBurn = 14.4; // pagina cuando se está consumiendo 14,4x el error budget
let agg = (win:timespan) {
  requests
  | where timestamp > ago(win)
  | summarize total=count(), errors=countif(success == false) by cloud_RoleName
  | project cloud_RoleName, errRate = todouble(errors)/todouble(total)
};let fastW = agg(fast);
let slowW = agg(slow);fastW
| join kind=inner slowW on cloud_RoleName
| extend burnFast = (1.0 - slo) == 0 ? 0.0 : errRate_left / (1.0 - slo),
         burnSlow = (1.0 - slo) == 0 ? 0.0 : errRate_right / (1.0 - slo)
| extend burnRate = burnFast / burnSlow
| where burnRate > targetBurn and errRate_left > (1.0 - slo)
| project service=cloud_RoleName, burnRate=round(burnRate,2), errRateFast=round(errRate_left*100,3), errRateSlow=round(errRate_right*100,3)

Si dependes de la tablaPerf (Legacy/VM/Container Insights), replica la misma lógica sobreInsightsMetrics o sobre tu nombre de métrica personalizado en lugar derequests .

C. Empaqueta como funciones KQL reutilizables (alertas más limpias)

Guárdalas en tu workspace como funciones para que las reglas de alerta queden ordenadas:

// función detect_failed_signin_anomalies(sensitivity:int, exclude:dynamic)
.create-or-alter function with (folder = "detections") detect_failed_signin_anomalies(sensitivity:int=95, exclude:dynamic=dynamic(["svc_"])) {
  let lookback = 14d;
  let baseline = 7d;
  let binSize = 1h;
  SigninLogs
  | where TimeGenerated > ago(lookback) and ResultType != 0
  | extend UPN = tostring(UserPrincipalName)
  | where not(UPN has_any (exclude))
  | summarize failed=count() by UPN, bin(TimeGenerated, binSize)
  | make-series failedSeries = avg(failed) on TimeGenerated from ago(baseline) to now() step binSize by UPN
  | extend anomalies = series_decompose_anomalies(failedSeries, sensitivity, 24)
  | mv-expand TimeGenerated to typeof(datetime), failedSeries to typeof(double), anomalies to typeof(double)
  | where anomalies > 0
  | project TimeGenerated, UPN, failed=failedSeries, anomalyScore=anomalies
}

Y tu consulta de alerta queda así de simple:

detections.detect_failed_signin_anomalies(96, dynamic(["svc_","breakglass@"]))

7) Alertas: que sean accionables, con poco ruido y fáciles de enrutar

Aprovecha estas funciones de Azure Monitor:

  • Split-by dimensions: crea una alerta por UPN o por servicio (dimensión = UPN / cloud_RoleName). Así se evita el spam con varios sujetos y se enruta automáticamente al equipo o usuario correcto.
  • Frecuencia y lookback: empieza con frecuencia de 15 min y lookback de 60 min; pasa a 5 min cuando esté estable.
  • Número de violaciones: exige ≥2 evaluaciones consecutivas antes de disparar para evitar destellos de un solo bin.
  • Action Rules: silencia durante ventanas de mantenimiento o periodos ruidosos conocidos (por ejemplo, despliegues grandes).
  • Propiedades personalizadas: agrega UPN/servicio, anomaly score y la última IP/geo al payload de la alerta (excelente para Logic Apps).
  • Dos niveles: crea Severity 2 (paginar) para SLO burn y Severity 3–4 (notificar) para anomalías simples.

Campos a incluir en el payload de la alerta (custom details):

  • entity: UPN o service
  • signal: failed_signin_anomaly / latency_p95_anomaly / error_rate_spike
  • score: anomaly score
  • context: las últimas 3 IPs o países, o los endpoints con más fallos
  • runbook: enlace al documento de remediación

Conexión del Action Group:

  • Webhook a Logic App (principal)
  • Canal de Teams/Slack del equipo dueño (basado en dimensión)
  • Conector ITSM (creación automática de tickets)

Primeros pasos en Logic App (recomendado):

  1. Deduplica/silencia si ya existe un incidente activo para la misma combinación entity+signal.
  2. Enriquece (Graph para usuarios; Azure Resource Graph / GitOps para servicios).
  3. Enruta:
  • Bajo impacto → solo notificar y abrir ticket
  • Alto impacto → aprobación y luego acción (exigir MFA / bloquear; rollback/escalar a 0; revertir PR)

Pequeños ajustes de calidad de vida con gran impacto

  • Gráficas para revisores: agrega un workbook fijado o la línea render timechart a las consultas que triagueas a mano.
| project timestamp=TimeGenerated, failed=failedSeries
| render timechart
  • Guardarrieles contra ruido: en consultas de anomalías, agrega umbrales absolutos mínimos (por ejemplo, failedSeries >= 5) para que los destellos pequeños no disparen alertas.
  • Sensibilidad en feriados/fines de semana: si ves falsos positivos estacionales, baja sensitivity los fines de semana o excluye días festivos con una pequeña tabla de lookup.
  • Registro de cuentas de servicio: mantén una lista central y consúmela mediante externaldata() desde una URL de blob/gh raw para que los analistas puedan actualizarla sin tocar las consultas.
  1. Abre Azure MonitorLogs → ejecuta una consulta.
  2. Haz clic en New alert rule → define Resource = Log Analytics workspace.
  3. Condition = tu consulta con umbral en resultados > 0.
  4. Action group = email/SMS/ITSM + webhook de Logic App.
  5. Severity / Evaluation frequency = empieza conservador (por ejemplo, cada 15 min).

8) Automatización segura con Logic Apps / Functions

  • La Logic App recibe la alerta → enriquece el contexto (riesgo del usuario, geo, últimos éxitos, salud del servicio) → decide el camino:
  • Solo notificar (las primeras semanas) con ticket en ITSM.
  • Acción con guardarrieles (con aprobación) usando Microsoft Graph (por ejemplo, exigir MFA, bloqueo temporal de inicio de sesión) o cambios de RBAC en Kubernetes.
  • Todos los cambios deben ser auditables y limitados por privilegio mínimo (ver más abajo).

Cómo extenderlo a EKS/GKE

  • Reutiliza el Supervisor; instala Concierge en cada cluster.
  • Mantén un OpenTelemetry Collector por cluster; expórtalo al mismo workspace de Azure (etiqueta con cloud.provider).
  • Reutiliza las mismas consultas KQL entre nubes; pivotea por cloud_RoleName/k8s.cluster.name.

Riesgos y mitigaciones

  • Falsos positivos : empieza en modo notificación; itera la sensibilidad y las ventanas.
  • Radio de impacto de la automatización: exige aprobación manual para los pasos de alto impacto; rollout progresivo.
  • Exposición del Supervisor: colócalo detrás de un WAF, restringe los rangos de IP y aplica TLS y HSTS.
  • Privilegio mínimo: los scopes de Graph se limitan a las acciones que automatizas; el RBAC de Azure para la Function/Logic App sigue el principio de privilegio mínimo.

Costo y licenciamiento

  • Costo principal: ingesta y retención de logs. Contrólalo con sampling y retención de 30 a 90 días.
  • KQL ML: sin costo separado de infraestructura de ML.
  • Entra P2 (opcional): aporta señales de riesgo nativas; tus detecciones en KQL son un control compensatorio si no cuentas con P2.
  • Pinniped: liviano; el costo de infraestructura es despreciable comparado con el de los logs.

Snippets listos para copiar (apéndice)

FederationDomain (Supervisor ↔ Entra)

apiVersion: authentication.supervisor.pinniped.dev/v1alpha1
kind: FederationDomain
metadata:
  name: entra-domain
  namespace: pinniped-supervisor
spec:
  issuer: https://pinniped.<your-domain>
  identityProviders:
  - name: azuread
    type: OIDC
    oidc:
      clientID: "<CLIENT_ID>"
      clientSecret: { name: "pinniped-azure-secret" }
      issuer: "https://login.microsoftonline.com/<TENANT_ID>/v2.0"

OTel Collector mínimo (a Azure Monitor)

receivers:
  otlp:
    protocols:
      grpc: {}
      http: {}
processors:
  batch: {}
  resourcedetection:
    detectors: [env, k8snode, k8scluster, k8s]
exporters:
  azuremonitor:
    connection_string: "InstrumentationKey=<APP_INSIGHTS_KEY>;IngestionEndpoint=https://<region>.in.applicationinsights.azure.com/"
service:
  pipelines:
    traces:
      receivers: [otlp]
      processors: [batch, resourcedetection]
      exporters: [azuremonitor]
    metrics:
      receivers: [otlp]
      processors: [batch, resourcedetection]
      exporters: [azuremonitor]
    logs:
      receivers: [otlp]
      processors: [batch, resourcedetection]
      exporters: [azuremonitor]

KQL — anomalía de identidad

SigninLogs
| where TimeGenerated > ago(14d)
| where ResultType != 0
| summarize failed=count() by bin(TimeGenerated, 1h), UserPrincipalName
| make-series failedSeries=avg(failed) on TimeGenerated from ago(7d) to now() step 1h by UserPrincipalName
| extend anomalies = series_decompose_anomalies(failedSeries, 95, 7)
| where array_length(anomalies) > 0

KQL — anomalía de latencia de servicio

Perf
| where TimeGenerated > ago(7d) and CounterName == "request_duration_ms"
| summarize p95 = percentile(CounterValue, 95) by bin(TimeGenerated, 5m), ServiceName
| make-series p95Series=avg(p95) on TimeGenerated from ago(7d) to now() step 5m by ServiceName
| extend anomalies = series_decompose_anomalies(p95Series, 95, 7)
| where array_length(anomalies) > 0

Lo que te llevas listo para usar

  • Helm y YAML listos para copiar y pegar para levantar Supervisor/Concierge + OTel Collector
  • Dos consultas KQL listas para producción de detección de anomalías
  • Pasos claros para conectar alertas de Azure Monitor con Logic Apps

Opcional: política y automatización (por qué, cuándo y cómo)

Por qué sumarlo

  • Guardarrieles, no barreras: previene configuraciones riesgosas antes de que se ejecuten (admisión) y detecta drift en runtime (eventos de política a logs).
  • Cierra el ciclo: las alertas que solo paginan humanos alargan el MTTR. Una capa fina de automatización te permite enriquecer → decidir → (opcionalmente) actuar de forma segura.
  • Control auditable: cada acción automatizada pasa por Azure Monitor + Logic Apps con aprobaciones y registros.

Casos de uso reales

Pico sospechoso de inicios de sesión (identidad)

  • Señal: KQL sobre SigninLogs marca un outlier para un usuario o app.
  • Automatización: alerta → la Logic App enriquece con riesgo del usuario y geo → abre un ticket y (con aprobación) llama a Microsoft Graph para exigir step-up MFA o un bloqueo temporal de inicio de sesión.

Drift de pod privilegiado (runtime)

  • Señal: Kyverno/Gatekeeper reporta un pod con privileged: true o CAP_SYS_ADMIN.
  • Automatización: alerta → la Logic App publica en Slack/Teams, abre un incidente en JIRA y (con aprobación) escala el deployment a 0 en el namespace afectado.

Imagen sin firmar / registry incorrecto (cadena de suministro)

  • Señal: la política de admisión rechaza imágenes que no provienen de my-acr.azurecr.io o que no tienen una cosignature.
  • Automatización: crea una excepción de corta duración (24 h) solo si un aprobador da el visto bueno; registra la excepción en LA.

Namespace sin owner/labels (higiene operativa)

  • Señal: la admisión rechaza la creación del namespace por falta de owner / costCenter.
  • Automatización: la Logic App contacta al solicitante con un comando kubectl label prellenado.

Secretos en variables de entorno (mala configuración)

  • Señal: la política audita un manifiesto que referencia secretos en texto plano.
  • Automatización: convierte a Secret + montaje de volumen mediante un PR bot (flujo GitOps) y abre un ticket de seguridad.

Qué desplegar (lo mínimo)

  • Admisión (prevenir/mutar): Kyverno u OPA Gatekeeper.
  • Auditoría en runtime → logs: emite los resultados de las políticas a Log Analytics.
  • Decisión: reglas de alerta de Azure Monitor (a partir de KQL o eventos de política).
  • Orquestación: Logic Apps (o Azure Functions) con aprobación humana donde el impacto sea alto.

Políticas iniciales (Kyverno)

Denegar contenedores privilegiados:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: disallow-privileged
spec:
  validationFailureAction: enforce
  rules:
  - name: no-priv
    match: { resources: { kinds: ["Pod"] } }
    validate:
      message: "Privileged containers are not allowed."
      pattern:
        spec:
          containers:
          - securityContext:
              privileged: "false"

Exigir labels de owner y centro de costo:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: require-owner-labels
spec:
  validationFailureAction: enforce
  rules:
  - name: require-labels
    match: { resources: { kinds: ["Namespace","Deployment","StatefulSet"] } }
    validate:
      message: "owner and costCenter labels are required."
      pattern:
        metadata:
          labels:
            owner: "?*"
            costCenter: "?*"

Restringir las imágenes a tu ACR:

apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
  name: restrict-registries
spec:
  validationFailureAction: enforce
  rules:
  - name: only-acr
    match: { resources: { kinds: ["Pod"] } }
    validate:
      message: "Images must come from my-acr.azurecr.io"
      pattern:
        spec:
          containers:
          - image: "my-acr.azurecr.io/*"

Mejor en modo audit primero: configuravalidationFailureAction: audit durante una semana, ajusta y luego pasa aenforce .

Flujo de anomalía de identidad

  1. Consulta KQL sobre SigninLogs (la del post) → regla de alerta (cada 15 min).
  2. Action Group: webhook de Logic App + Teams/JIRA.
  3. Pasos de la Logic App:
  • Obtiene los detalles del usuario y su historial de inicios de sesión (Graph).
  • Verifica el último MFA exitoso reciente.
  • Bifurca:
  • Bajo impacto: notifica y crea ticket.
  • Alto impacto: aprobación (Service Owner) → actualización de conditionalAccess en Graph o bloqueo temporal.
  • Registra el resultado en Log Analytics.

Flujo de runtime/política

  1. Kyverno → envía los eventos de política a una tabla de Log Analytics (mediante OTel o diagnostics de AKS).
  2. Alerta KQL ante nuevos fallos de enforcement de disallow-privileged.
  3. Logic App:
  • Obtiene los metadatos del Deployment/Pod.
  • Notifica al service owner con el snippet de remediación.
  • Aprobación opcional → az aks command invoke o un PR de GitOps para revertir o parchar.

Cuándo dejarlo para después

  • Cluster único, equipo pequeño o cuando todavía no hay cultura de automatización on-call.
  • Si te faltan privilegios en Entra/Graph para aplicar conditional access, empieza con observabilidad + tickets y suma el enforcement más adelante.

¿Necesitas ayuda? Estamos para acompañarte.

Si estás evaluando esto para una prueba de concepto o planeando despliegues, DoiT puede ayudarte. Nuestro equipo de más de 100 expertos se especializa en soluciones cloud a la medida y está listo para acompañarte en el proceso y optimizar tu infraestructura para el cumplimiento y las demandas a futuro.

Conversemos sobre lo que tiene más sentido para tu empresa en esta etapa de aplicación de políticas, para que tu infraestructura cloud sea robusta, conforme y esté optimizada para el éxito. Contáctanos hoy.