AutorizaciΓ³n como cΓ³digo Β· API REST + Kubernetes + Terraform Β· Generado por CULTIVA IA
| 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 | β | β | β | β |
input.path[1] != input.user.id antes de permitir.
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)] ) }
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
# 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
# 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
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"
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);
| 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 |
default allow := false (deny-first)opa test policy/ -vreasons setteam + environment obligatoriosrunAsNonRoot: trueterraform apply