Home > Blog > Desenvolvimento Web
Desenvolvimento Web
JavaScript
Programação

OPA no Node.js

Atualizado em: 18 de setembro de 2026

Rack de servidores processando fluxos de dados no Node.js

O OPA no Node.js permite separar decisões de política do código da aplicação. Em vez de manter regras de autorização, compliance e segurança espalhadas em condicionais, a API envia um documento JSON ao Open Policy Agent e recebe uma decisão estruturada.

OPA, sigla de Open Policy Agent, usa a linguagem declarativa Rego. Ele pode decidir se uma requisição é permitida, quais campos precisam ser ocultados, quais limites se aplicam a um tenant ou se uma configuração de infraestrutura viola uma regra. A aplicação Node.js continua responsável por autenticar, coletar contexto e aplicar a decisão.

Neste guia, você aprenderá a executar OPA como sidecar ou serviço, escrever políticas Rego, consultar a API HTTP, integrar com Express, testar policies, distribuir bundles, aplicar fail-closed e evitar que dados controlados pelo cliente sejam tratados como fatos confiáveis.

O que é Open Policy Agent?

A documentação oficial do Open Policy Agent define OPA como um policy engine geral que separa decisão de enforcement. O projeto é graduado pela CNCF e pode ser usado em microserviços, Kubernetes, CI/CD, gateways e infraestrutura.

As políticas são escritas em Rego, linguagem projetada para avaliar dados estruturados. O repositório oficial está em open-policy-agent/opa.

Decisão versus enforcement

OPA decide:

{
  "allow": true,
  "reason": "project_editor",
  "fields": ["id", "name", "status"]
}

A aplicação aplica:

if (!decision.allow) {
  throw new ForbiddenError(decision.reason);
}

return pick(document, decision.fields);

OPA não intercepta automaticamente a requisição quando usado diretamente pela aplicação. O middleware precisa respeitar o resultado.

Executando OPA com Docker

docker run --rm \
  --name opa \
  -p 8181:8181 \
  -v "$PWD/policy:/policy:ro" \
  openpolicyagent/opa:1.8.0 \
  run --server /policy

Fixe uma versão específica. Uma atualização de Rego ou built-ins deve passar por testes antes de chegar à produção.

Primeira policy

Crie policy/authz.rego:

package codigofacil.authz

import rego.v1

default allow := false

allow if {
  input.user.authenticated
  input.action == "read"
  input.resource.tenant_id == input.user.tenant_id
}

allow if {
  "admin" in input.user.roles
  input.resource.tenant_id == input.user.tenant_id
}

A regra nega por padrão. Acesso é concedido somente quando alguma condição explícita é satisfeita.

Consultando pela linha de comando

opa eval \
  --data policy/authz.rego \
  --input input.json \
  'data.codigofacil.authz.allow'

Use --fail ou --fail-defined em scripts de validação conforme o resultado esperado.

API HTTP

Com OPA em modo servidor:

POST http://localhost:8181/v1/data/codigofacil/authz

Corpo:

{
  "input": {
    "user": {
      "id": "user-42",
      "authenticated": true,
      "tenant_id": "tenant-a",
      "roles": ["editor"]
    },
    "action": "read",
    "resource": {
      "type": "document",
      "id": "doc-7",
      "tenant_id": "tenant-a"
    }
  }
}

Resposta:

{
  "result": {
    "allow": true
  }
}

Cliente Node.js simples

async function evaluatePolicy(input, signal) {
  const response = await fetch(
    'http://opa:8181/v1/data/codigofacil/authz',
    {
      method: 'POST',
      headers: {
        'content-type': 'application/json'
      },
      body: JSON.stringify({ input }),
      signal
    }
  );

  if (!response.ok) {
    throw new Error(`OPA respondeu ${response.status}`);
  }

  const body = await response.json();

  if (!body.result) {
    throw new Error('Decisão OPA ausente');
  }

  return body.result;
}

Timeout

const decision = await evaluatePolicy(
  input,
  AbortSignal.timeout(500)
);

Política de autorização não pode bloquear indefinidamente. Defina timeout menor que o orçamento total da requisição.

Fail-closed

let decision;

try {
  decision = await evaluatePolicy(input, AbortSignal.timeout(500));
} catch (error) {
  logger.error({ error }, 'OPA indisponível');
  throw new ServiceUnavailableError();
}

if (decision.allow !== true) {
  throw new ForbiddenError();
}

Em operações sensíveis, erro, resposta ausente ou tipo inesperado devem negar. Não converta indisponibilidade em acesso permitido.

Middleware Express

function requirePolicy(action, resourceLoader) {
  return async function policyMiddleware(req, res, next) {
    try {
      const resource = await resourceLoader(req);

      const input = {
        user: {
          id: req.auth.userId,
          tenant_id: req.auth.tenantId,
          roles: req.auth.roles,
          authenticated: true
        },
        action,
        resource
      };

      const decision = await evaluatePolicy(
        input,
        AbortSignal.timeout(500)
      );

      if (decision.allow !== true) {
        return res.status(403).json({
          type: 'https://api.example.com/problems/forbidden',
          title: 'Acesso negado',
          status: 403
        });
      }

      req.policy = decision;
      next();
    } catch (error) {
      next(error);
    }
  };
}

Consulte Problem Details no Node.js.

Não confie no corpo da requisição

Este input é perigoso:

{
  "user": {
    "roles": req.body.roles,
    "tenant_id": req.body.tenantId
  }
}

Roles e tenant precisam vir de sessão validada, token verificado ou banco. O recurso deve ser carregado pelo backend, não descrito apenas pelo cliente.

Decisões estruturadas

OPA pode retornar mais que boolean:

package codigofacil.documents

import rego.v1

default decision := {
  "allow": false,
  "reason": "denied",
  "fields": []
}

decision := {
  "allow": true,
  "reason": "owner",
  "fields": ["id", "name", "content", "created_at"]
} if {
  input.user.id == input.resource.owner_id
}

A aplicação precisa validar o formato do resultado antes de usar campos ou filtros.

ABAC com OPA

Rego expressa regras baseadas em atributos:

allow if {
  input.user.department == input.resource.department
  input.resource.classification != "restricted"
  input.context.hour >= 8
  input.context.hour < 18
}

Veja ABAC no Node.js.

RBAC com OPA

role_permissions := {
  "viewer": {"document:read"},
  "editor": {"document:read", "document:update"},
  "admin": {"document:read", "document:update", "document:delete"}
}

allow if {
  some role in input.user.roles
  sprintf("%s:%s", [input.resource.type, input.action]) in role_permissions[role]
}

Consulte RBAC no Node.js.

OPA ou OpenFGA?

OPA é um policy engine geral para atributos, contexto, infraestrutura e decisões arbitrárias. OpenFGA é especializado em autorização baseada em relações e navegação por objetos.

Os dois podem ser combinados: OpenFGA resolve relações; OPA aplica condições adicionais, como tenant, risco, horário ou classificação. Veja OpenFGA no Node.js.

Dados externos

OPA pode carregar data documents, mas não deve fazer chamadas HTTP arbitrárias em cada decisão. Prefira:

  • input fornecido pela aplicação;
  • bundles distribuídos pelo control plane;
  • dados replicados e versionados;
  • cache local;
  • decisões pequenas e determinísticas.

Bundles

Um bundle contém policies e data:

bundle/
├── policies/
│   └── authz.rego
└── data.json

Empacote:

tar -czf bundle.tar.gz -C bundle .

OPA pode buscar bundles periodicamente de um servidor configurado. Assine e proteja o canal de distribuição.

Configuração de bundle

services:
  policy_control_plane:
    url: https://policies.example.com
    credentials:
      bearer:
        token: ${POLICY_BUNDLE_TOKEN}

bundles:
  authz:
    service: policy_control_plane
    resource: bundles/authz.tar.gz
    polling:
      min_delay_seconds: 10
      max_delay_seconds: 30

Não inclua tokens diretamente no arquivo versionado.

Versão da política

Inclua metadata no bundle ou data:

{
  "policy_version": "authz-2026-09-18.1"
}

Retorne a versão na decisão e registre nos logs. Isso ajuda a explicar por que um acesso foi permitido ou negado.

Testes Rego

Crie authz_test.rego:

package codigofacil.authz_test

import rego.v1
import data.codigofacil.authz

test_same_tenant_reader if {
  authz.allow with input as {
    "user": {
      "authenticated": true,
      "tenant_id": "tenant-a",
      "roles": []
    },
    "action": "read",
    "resource": {
      "tenant_id": "tenant-a"
    }
  }
}

test_cross_tenant_denied if {
  not authz.allow with input as {
    "user": {
      "authenticated": true,
      "tenant_id": "tenant-a",
      "roles": ["admin"]
    },
    "action": "read",
    "resource": {
      "tenant_id": "tenant-b"
    }
  }
}

Execute:

opa test policy -v

Coverage

opa test policy --coverage --format=json

Cobertura ajuda a encontrar regras não exercitadas, mas não garante que todas as combinações de privilégio estão corretas.

Lint com Regal

Regal é um linter do ecossistema OPA:

regal lint policy

Fixe a versão no CI e trate warnings conforme a política do projeto.

CI

- run: opa fmt --fail policy
- run: opa check --strict policy
- run: opa test policy -v
- run: regal lint policy

Veja CI para Node.js com GitHub Actions.

Policy diff

Uma alteração de policy pode ampliar acesso sem mudar o código. Pull requests precisam mostrar:

  • regras alteradas;
  • casos permitidos novos;
  • casos negados removidos;
  • versão do bundle;
  • testes adicionados;
  • impacto por tenant ou recurso.

Performance

Decisões locais por sidecar tendem a ser rápidas, mas policies complexas podem degradar. Meça:

  • tempo de avaliação;
  • tamanho do input;
  • número de regras;
  • uso de built-ins custosos;
  • latência de rede;
  • tempo de atualização do bundle.

Partial evaluation

OPA pode pré-computar partes da policy quando alguns dados são conhecidos. Isso é útil para gerar filtros ou reduzir trabalho repetido. A técnica exige modelagem cuidadosa e testes do resultado.

Data filtering

Em vez de checar cada registro, a policy pode produzir uma condição usada na consulta. Nunca concatene SQL retornado diretamente. Traduza uma estrutura validada para parâmetros e operadores permitidos.

Multi-tenancy

Inclua tenant no input e aplique proteção no banco. OPA não substitui Row-Level Security nem filtros de query.

Consulte Multi-Tenancy no Node.js.

Cache de decisões

Cache pode reduzir latência, mas a chave precisa incluir todos os atributos relevantes e a versão da policy. Uma decisão baseada em horário, risco ou estado mutável pode não ser segura para cache longo.

Auditoria

logger.info({
  userId: req.auth.userId,
  action,
  resourceType: resource.type,
  resourceId: resource.id,
  allowed: decision.allow,
  reason: decision.reason,
  policyVersion: decision.policy_version
}, 'Decisão de política');

Não registre input completo quando contém dados pessoais.

Observabilidade do OPA

Monitore:

  • latência de decisão;
  • erros e respostas indefinidas;
  • bundle ativo;
  • falhas de download;
  • uso de memória;
  • taxa de allow e deny;
  • timeouts na aplicação;
  • decisões por versão.

Undefined não é allow

Quando uma query não produz resultado, trate como erro ou deny:

if (typeof body.result?.allow !== 'boolean') {
  throw new Error('Decisão OPA inválida');
}

Segurança da API do OPA

  • Não exponha OPA diretamente à internet.
  • Use rede privada ou sidecar.
  • Proteja management APIs.
  • Use TLS quando atravessa hosts.
  • Restrinja quem publica bundles.
  • Valide tamanho do input.
  • Não permita escrita de policies pela aplicação comum.

Erros comuns

  • Dados do cliente como verdade: atacante inventa roles.
  • Fail-open: indisponibilidade concede acesso.
  • Policy sem testes: privilégio aumenta silenciosamente.
  • Input gigante: decisão fica lenta.
  • OPA exposto: management API pode ser abusada.
  • Bundle sem versão: auditoria não explica decisões.
  • Cache sem contexto: decisão de outro tenant é reutilizada.
  • Confundir decisão com enforcement: código ignora o resultado.

Conclusão

O OPA no Node.js centraliza policies e separa decisão de aplicação. Rego expressa regras de autorização, compliance e segurança sobre documentos JSON, enquanto a API Node.js coleta contexto e aplica a resposta.

Negue por padrão, use input derivado de fontes confiáveis, distribua bundles versionados e teste policies no CI. Execute OPA próximo da aplicação, defina timeout e falha fechada. Assim, políticas podem evoluir com revisão e auditoria sem ficarem escondidas em dezenas de condicionais.

10 melhores cursos de programação em 2026

Descubra os melhores cursos de programação. Aprenda a escolher o curso ideal para iniciar ou avançar na carreira de desenvolvedor

POSTS RELACIONADOS

Ver todos

Seta para a direita