OPA: o policy engine universal
Open Policy Agent (OPA) é um engine de policy genérico criado pela Styra em 2016, CNCF Graduated em 2021. A pergunta que ele responde é simples: "dado este JSON de input, esta decisão é permitida?". O domínio é livre — pode ser autorização de API, admission control em Kubernetes, validação de Terraform plans, CI/CD gates, banco de dados row-level — qualquer lugar onde uma decisão estruturada precisa ser tomada.
OPA é "policy-as-code" — diferente de SpiceDB/OpenFGA ("policy-as-data"). Você escreve regras textuais versionadas em git, com testes, lint, code review. É excelente para infra e regras de negócio condicionais; complemento (não substituto) de ReBAC para sharing per-resource.
Rego em 5 minutos
Rego é uma linguagem declarativa. A unidade básica é a regra: cabeça + corpo. A regra é verdadeira quando o corpo é satisfeito.
package authz
import future.keywords.in
# Default deny — fundamental
default allow := false
# Admin pode tudo
allow {
"admin" in input.subject.roles
}
# Owner pode editar próprio recurso
allow {
input.action == "update"
input.resource.owner_id == input.subject.id
}
# Viewer pode ler dentro do horário comercial COM MFA
allow {
"viewer" in input.subject.roles
input.action == "read"
input.subject.mfa == true
business_hours
}
# Helper rule
business_hours {
hour := time.clock(time.now_ns())[0]
hour >= 9
hour < 18
}
# Regra que retorna OBJETO (não só boolean)
decision := {
"allow": allow,
"reason": reason,
}
reason := "admin override" {
"admin" in input.subject.roles
}
reason := "owner of resource" {
input.resource.owner_id == input.subject.id
}
reason := "outside business hours" {
not business_hours
}Testar policies como código de produção
package authz_test
import data.authz
test_admin_allowed {
authz.allow with input as {
"subject": { "roles": ["admin"], "id": "u1" },
"resource": { "owner_id": "u2" },
"action": "delete"
}
}
test_viewer_outside_hours_denied {
not authz.allow with input as {
"subject": { "roles": ["viewer"], "id": "u1", "mfa": true },
"resource": { "owner_id": "u2" },
"action": "read"
}
with time.now_ns as 1700000000000000000 # 19:30 UTC — fora horário
}
test_owner_can_update {
authz.allow with input as {
"subject": { "roles": [], "id": "u1" },
"resource": { "owner_id": "u1" },
"action": "update"
}
}# CLI nativo
opa test -v ./policies/
# →
# PASS: 3/3 tests
# authz_test.test_admin_allowed (1.2ms)
# authz_test.test_viewer_outside_hours_denied (0.9ms)
# authz_test.test_owner_can_update (0.8ms)
# Coverage
opa test --coverage --format=json ./policies/ | jq '.coverage'Testabilidade é o diferencial crítico de OPA contra ABAC ad-hoc. Cada policy é uma unit testable, lintable e coverable. Em ambientes regulados (banco, saúde), isso transforma autorização em código com SLA — não em "se quiser saber, leia o admin panel".
Deployment: sidecar é a regra
services:
bundles:
url: https://policies.corp.example/bundles
credentials:
bearer:
token: ${OPA_BUNDLE_TOKEN}
bundles:
authz:
service: bundles
resource: authz.tar.gz
polling:
min_delay_seconds: 30
max_delay_seconds: 60
signing:
keyid: prod-bundle-key
decision_logs:
console: true
reporting:
min_delay_seconds: 5
max_delay_seconds: 10Kubernetes Gatekeeper: a aplicação canônica
OPA virou padrão em K8s admission control via Gatekeeper. Você instala um ValidatingWebhook que delega ao OPA antes de cada recurso ser persistido em etcd.
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8srequiredlabels
spec:
crd:
spec:
names: { kind: K8sRequiredLabels }
validation:
openAPIV3Schema:
properties:
labels: { type: array, items: { type: string } }
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8srequiredlabels
violation[{"msg": msg, "details": {"missing_labels": missing}}] {
provided := {label | input.review.object.metadata.labels[label]}
required := {label | label := input.parameters.labels[_]}
missing := required - provided
count(missing) > 0
msg := sprintf("missing required labels: %v", [missing])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sRequiredLabels
metadata: { text: ns-must-have-owner }
spec:
match:
kinds: [{ apiGroups: [""], kinds: ["Namespace"] }]
parameters:
labels: ["owner", "cost-center"] Qualquer kubectl apply de Namespace sem labels owner e cost-center é rejeitado pelo API server, com a mensagem do violation block. Compliance enforced no ponto de entrada — não em scan posterior.
Envoy ext_authz: API authz no service mesh
O segundo grande uso de OPA é como autorizador externo do Envoy (Istio, Consul, AWS App Mesh, Cloud Run). Filtro ext_authz intercepta cada request HTTP/gRPC e consulta um endpoint externo. OPA responde via protocolo gRPC ExtAuthz com permit/deny e obligations (headers a injetar).
# Policy para Envoy ext_authz
package envoy.authz
default allow := false
# Token JWT parsed do header
claims := payload {
[_, payload, _] := io.jwt.decode(input.attributes.request.http.headers.authorization)
}
# Allow se token válido e path autorizado
allow {
claims.sub
some path
path := input.attributes.request.http.path
glob.match(allowed_paths[_], ["/"], path)
}
allowed_paths := ["/api/public/*", "/api/users/me", "/healthz"]Beyond authz: Terraform, CI/CD, SQL row filters
OPA vs ReBAC: complemento, não substituto
📋 SaaS com sharing per-doc (Drive-like) + policies de infra (K8s, Terraform) + condições dinâmicas (hora, MFA, IP)
ReBAC modela RELAÇÕES (quem é editor deste doc); OPA modela CONDIÇÕES (pode fazer isso AGORA, com este contexto, em K8s/TF). São ortogonais — empresas sérias usam ambos.
Alt: Só OPA —
Alt: Só SpiceDB —
Alt: Híbrido OPA + ReBAC —
| Aspecto | OPA / Rego | SpiceDB / OpenFGA |
|---|---|---|
| Paradigma | Policy-as-code (texto) | Policy-as-data (tuplas) |
| Forte em | Infra, condições, RBAC + ABAC | Sharing per-resource (ReBAC) |
| Reverse query "quem tem acesso?" | Pouco escalável | Nativo (LookupSubjects) |
| K8s admission | Padrão de fato (Gatekeeper) | Não é o domínio |
| Compliance audit | Logs de decisão + policy versionada | Tuplas + zedtokens + audit |
| Storage | Stateless + bundle pull | Postgres / MySQL / CRDB |
Padrões avançados: partial evaluation
Recurso poderoso e subutilizado: OPA pode avaliar parcialmente uma policy, gerando uma expressão residual. Aplicado: gerar predicado SQL filtrado a partir de policy.
# Policy: "user lê docs do mesmo departamento"
# Partial eval contra subject=alice (dept=eng), sem definir resource
opa eval --partial --data policy.rego --input subject.json \
'data.authz.allow'
# Saída (simplificada): "input.resource.dept == 'eng'"
# Esse expression é traduzido para SQL: WHERE resource.dept = 'eng'Styra DAS comercializa isso como "row-level security driven by OPA" — você descreve a policy uma vez em Rego, e o engine gera o WHERE clause apropriado para Postgres/MySQL. Elimina policy lógica duplicada entre app e DB.
Anti-patterns frequentes
- Centralized OPA server compartilhado: vira gargalo e SPOF. Use sidecar.
- Sem bundle signing: bundles em produção devem ser assinados (cosign/keyid) — caso contrário, qualquer comprometimento do CDN/repo pode injetar policy.
- Decisões muito grandes no objeto: retorne só o necessário (allow + reason + obligations); evite serializar grafos enormes.
- Sem decision logs: compliance e debug ficam impossíveis. sempre ligado em produção.
- Reescrever ReBAC em Rego: caso você esteja modelando "quem tem acesso ao doc X" com tuplas em Rego data + iteração, está reinventando SpiceDB mal. Use a ferramenta certa.
Resumo executivo
- OPA = engine universal de policy. CNCF Graduated 2021. Padrão de fato em K8s, Envoy, Terraform.
- Rego é Datalog estendida — declarativa, não Turing-complete, testável ().
- Deployment: sidecar com Bundle API + decision logs + Prometheus. Latência sub-ms.
- Gatekeeper (K8s admission) e Envoy ext_authz (service mesh) são os dois usos mais maduros.
- Forte em: RBAC + ABAC + infra policies. Fraco em: reverse queries de sharing per-resource.
- Complemento de SpiceDB/OpenFGA, não substituto. Híbrido é o padrão maduro 2026.
- Partial evaluation permite gerar WHERE SQL a partir de Rego — row-level security driven by policy.