OPA β€” Open Policy Agent Β· DataFlow Analytics

AutorizaciΓ³n como cΓ³digo Β· API REST + Kubernetes + Terraform Β· Generado por CULTIVA IA

Policy as Code Rego v1 Gatekeeper Terraform
7
Endpoints protegidos
4
Roles de usuario
3
Constraints K8s
3
Reglas Terraform
πŸ—‚ Matriz de Permisos β€” API REST DataFlow
Endpoint DescripciΓ³n viewer analyst manager admin
GET/reports Listar todos los informes βœ“ βœ“ βœ“ βœ“
POST/reports Crear nuevo informe βœ— βœ“ βœ“ βœ“
DELETE/reports/:id Eliminar informe βœ— βœ— βœ— βœ“
GET/exports/:reportId Descargar exportaciΓ³n βœ— βœ— βœ“ βœ“
POST/exports Crear exportaciΓ³n masiva βœ— βœ— βœ— βœ“
PATCH/users/:id/role Cambiar rol (no el propio) βœ— βœ— βœ— βœ“*
GET/billing Ver facturaciΓ³n βœ— βœ— βœ— βœ“
⚠ βœ“* Admin puede cambiar el rol de otros usuarios, pero nunca el suyo propio. La polΓ­tica verifica que input.path[1] != input.user.id antes de permitir.
βš™ PolΓ­tica Rego β€” policy/dataflow_authz.rego
πŸ“„ policy/dataflow_authz.rego Rego v1
package dataflow.authz

import rego.v1

# ─────────────────────────────────────────────────────
# DataFlow Analytics β€” PolΓ­tica de autorizaciΓ³n central
# Roles: admin | manager | analyst | viewer
# ─────────────────────────────────────────────────────

# Denegar por defecto (principio de mΓ­nimo privilegio)
default allow := false

# ── Admins tienen acceso total ──────────────────────
allow if {
    input.user.role == "admin"
}

# ── GET /reports β€” todos los roles ──────────────────
allow if {
    input.method == "GET"
    input.path[0] == "reports"
    count(input.path) == 1
    input.user.role in {"viewer", "analyst", "manager", "admin"}
}

# ── POST /reports β€” analyst, manager, admin ──────────
allow if {
    input.method == "POST"
    input.path[0] == "reports"
    input.user.role in {"analyst", "manager", "admin"}
}

# ── DELETE /reports/:id β€” solo admin ─────────────────
allow if {
    input.method == "DELETE"
    input.path[0] == "reports"
    input.user.role == "admin"
}

# ── GET /exports/:reportId β€” manager, admin ──────────
allow if {
    input.method == "GET"
    input.path[0] == "exports"
    input.user.role in {"manager", "admin"}
}

# ── POST /exports β€” solo admin ────────────────────────
allow if {
    input.method == "POST"
    input.path[0] == "exports"
    input.user.role == "admin"
}

# ── PATCH /users/:id/role β€” admin, no puede cambiarse a sΓ­ mismo ──
allow if {
    input.method == "PATCH"
    input.path[0] == "users"
    input.path[2] == "role"
    input.user.role == "admin"
    input.path[1] != input.user.id  # Nunca cambiar el propio rol
}

# ── GET /billing β€” solo admin ─────────────────────────
allow if {
    input.method == "GET"
    input.path[0] == "billing"
    input.user.role == "admin"
}

# ── Razones de denegaciΓ³n para audit log ─────────────
reasons contains msg if {
    not allow
    msg := sprintf(
        "[DENIED] user=%s role=%s method=%s path=%s",
        [input.user.id, input.user.role, input.method, concat("/", input.path)]
    )
}
πŸ§ͺ policy/dataflow_authz_test.rego Rego Test
package dataflow.authz_test

import rego.v1

# Viewer puede listar informes
test_viewer_can_list_reports if {
    allow with input as {
        "method": "GET", "path": ["reports"],
        "user": {"id": "u1", "role": "viewer"}
    }
}

# Viewer NO puede crear informes
test_viewer_cannot_create_reports if {
    not allow with input as {
        "method": "POST", "path": ["reports"],
        "user": {"id": "u1", "role": "viewer"}
    }
}

# Admin NO puede cambiar su propio rol
test_admin_cannot_change_own_role if {
    not allow with input as {
        "method": "PATCH", "path": ["users", "admin-42", "role"],
        "user": {"id": "admin-42", "role": "admin"}
    }
}

# Manager puede descargar exportaciones
test_manager_can_download_export if {
    allow with input as {
        "method": "GET", "path": ["exports", "rpt-99"],
        "user": {"id": "mgr-7", "role": "manager"}
    }
}

# Ejecutar: opa test policy/ -v
☸ Kubernetes Gatekeeper β€” Admission Control
πŸ“‹ k8s/required-labels.yaml YAML + Rego
# Labels obligatorios en todos los Deployments
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8srequiredlabels
spec:
  crd:
    spec:
      names:
        kind: K8sRequiredLabels
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8srequiredlabels
      import rego.v1
      violation contains {"msg": msg} if {
        provided := {l |
          input.review.object
            .metadata.labels[l]}
        required := {l |
          l := input.parameters.labels[_]}
        missing := required - provided
        count(missing) > 0
        msg := sprintf(
          "Labels requeridos ausentes: %v",
          [missing])
      }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata:
  name: dataflow-required-labels
spec:
  match:
    kinds:
    - apiGroups: ["apps"]
      kinds: ["Deployment"]
  parameters:
    labels:
    - app.kubernetes.io/name
    - app.kubernetes.io/managed-by
    - team
    - environment
πŸ”’ k8s/no-root-containers.yaml YAML + Rego
# Bloquear contenedores que corran como root
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
  name: k8snoroot
spec:
  crd:
    spec:
      names:
        kind: K8sNoRoot
  targets:
  - target: admission.k8s.gatekeeper.sh
    rego: |
      package k8snoroot
      import rego.v1
      violation contains {"msg": msg} if {
        c := input.review.object
               .spec.containers[_]
        not c.securityContext.runAsNonRoot
        msg := sprintf(
          "Container %s: runAsNonRoot=true requerido",
          [c.name])
      }
      violation contains {"msg": msg} if {
        c := input.review.object
               .spec.containers[_]
        c.securityContext.runAsUser == 0
        msg := sprintf(
          "Container %s no puede correr como root (UID 0)",
          [c.name])
      }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sNoRoot
metadata:
  name: dataflow-no-root
spec:
  match:
    kinds:
    - apiGroups: [""]
      kinds: ["Pod"]
    - apiGroups: ["apps"]
      kinds:
      - Deployment
      - StatefulSet
πŸ— ValidaciΓ³n Terraform β€” policy/terraform_dataflow.rego
πŸ“„ policy/terraform_dataflow.rego Rego v1
package terraform.dataflow

import rego.v1

# ── Bloquear S3 buckets pΓΊblicos ─────────────────────
deny contains msg if {
    resource := input.resource_changes[_]
    resource.type == "aws_s3_bucket"
    resource.change.after.acl == "public-read"
    msg := sprintf(
        "[BLOQUEADO] S3 bucket %s no puede ser pΓΊblico (violaciΓ³n SOC 2)",
        [resource.address]
    )
}

# ── Cifrado obligatorio en RDS ────────────────────────
deny contains msg if {
    resource := input.resource_changes[_]
    resource.type == "aws_db_instance"
    not resource.change.after.storage_encrypted
    msg := sprintf(
        "[BLOQUEADO] RDS %s debe tener storage_encrypted=true (ISO 27001)",
        [resource.address]
    )
}

# ── Tipos de instancia permitidos (control de costes) ─
deny contains msg if {
    resource := input.resource_changes[_]
    resource.type == "aws_instance"
    not resource.change.after.instance_type in allowed_types
    msg := sprintf(
        "[BLOQUEADO] EC2 %s usa %s β€” permitidos: %v",
        [resource.address,
         resource.change.after.instance_type,
         allowed_types]
    )
}

allowed_types := {"t3.micro", "t3.small", "t3.medium", "m5.large"}

# Validar: terraform plan -out=plan.tfplan
# terraform show -json plan.tfplan > plan.json
# opa eval -d policy/ -i plan.json "data.terraform.dataflow.deny"
πŸ”— Middleware Express β€” src/middleware/opa-authz.ts
πŸ“„ src/middleware/opa-authz.ts TypeScript
import type { Request, Response, NextFunction } from "express";

const OPA_URL = process.env.OPA_URL ?? "http://opa:8181";
const POLICY  = "v1/data/dataflow/authz/allow";

interface OpaInput {
  method : string;
  path   : string[];
  user   : { id: string; role: string; teams?: string[] };
  body  ?: unknown;
}

export async function authorize(
  req: Request, res: Response, next: NextFunction
): Promise<void> {
  const input: OpaInput = {
    method : req.method,
    path   : req.path.split("/").filter(Boolean),
    user   : {
      id   : req.user?.id   ?? "anonymous",
      role : req.user?.role ?? "viewer",
      teams: req.user?.teams ?? [],
    },
    body: req.body,
  };

  const response = await fetch(`${OPA_URL}/${POLICY}`, {
    method : "POST",
    headers: { "Content-Type": "application/json" },
    body   : JSON.stringify({ input }),
  });

  const { result } = await response.json();

  if (result) {
    next();
  } else {
    // Obtener razΓ³n del audit log
    const rRes = await fetch(
      `${OPA_URL}/v1/data/dataflow/authz/reasons`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ input }),
      }
    );
    const { result: reasons } = await rRes.json();
    console.warn("[OPA]", reasons?.[0] ?? "Access denied");
    res.status(403).json({
      error  : "Forbidden",
      message: "No tienes permisos para realizar esta acciΓ³n"
    });
  }
}

// Uso en app.ts:
// import { authorize } from "./middleware/opa-authz";
// app.use(authorize);
πŸ“Š Audit Log de Decisiones β€” muestra en tiempo real
Timestamp Usuario Rol MΓ©todo Ruta DecisiΓ³n PolΓ­tica
2026-06-16 09:01:03 sara.gomez analyst POST /reports ALLOW dataflow.authz
2026-06-16 09:04:17 carlos.v viewer POST /exports DENY dataflow.authz
2026-06-16 09:07:55 admin-42 admin PATCH /users/admin-42/role DENY self-role-block
2026-06-16 09:12:30 laia.mas manager GET /exports/rpt-001 ALLOW dataflow.authz
2026-06-16 09:19:44 pedro.s viewer DELETE /reports/rpt-002 DENY dataflow.authz
2026-06-16 09:22:01 admin-01 admin DELETE /reports/rpt-002 ALLOW dataflow.authz
βœ… Checklist de ImplementaciΓ³n β€” DataFlow
PolΓ­tica Rego
  • βœ“ default allow := false (deny-first)
  • βœ“ Tests unitarios con opa test policy/ -v
  • βœ“ Razones de denegaciΓ³n en reasons set
  • βœ“ Versionado en Git junto al cΓ³digo de app
  • ⚠ Revisar en PRs (policy review separado)
  • ⚠ Empaquetar como bundle para distribuciΓ³n multi-servicio
Kubernetes + Terraform
  • βœ“ Gatekeeper instalado (v3.15+)
  • βœ“ Labels team + environment obligatorios
  • βœ“ Todos los pods con runAsNonRoot: true
  • βœ“ OPA valida plan antes de terraform apply
  • ⚠ Activar audit log OPA β†’ SIEM (CloudWatch / Elastic)
  • ⚠ AΓ±adir constraint: imΓ‘genes solo de registry privado
βœ“ Beneficio para ISO 27001 / SOC 2: todas las decisiones de autorizaciΓ³n son auditables, la polΓ­tica estΓ‘ en Git con historial de cambios, y la validaciΓ³n de infraestructura bloquea configuraciones no seguras antes del despliegue β€” evidencia directa para controles A.9 (Control de acceso) y A.12 (Seguridad operacional).