Como projetar sistemas de autorização baseados em políticas

1. Fundamentos da Autorização Baseada em Políticas (PBA)

1.1. Definição e diferenças entre RBAC, ABAC e PBA

A autorização baseada em políticas (PBA) representa uma evolução em relação aos modelos tradicionais de controle de acesso. Enquanto o RBAC (Role-Based Access Control) gerencia permissões através de papéis predefinidos e o ABAC (Attribute-Based Access Control) utiliza atributos do usuário, recurso e ambiente, o PBA combina o melhor de ambos ao permitir que decisões sejam tomadas com base em políticas declarativas e flexíveis.

No RBAC, um gerente sempre pode acessar relatórios financeiros. No ABAC, um gerente pode acessar relatórios apenas durante o horário comercial. No PBA, a política pode ser: "Permitir acesso a relatórios financeiros se o usuário for gerente E o horário for comercial E o dispositivo for corporativo E a localização for a sede da empresa". A diferença fundamental é que o PBA externaliza completamente a lógica de decisão da aplicação.

1.2. Componentes essenciais

Um sistema PBA maduro é composto por quatro componentes arquiteturais principais:

  • Policy Enforcement Point (PEP): Intercepta requisições na aplicação e delega a decisão ao PDP.
  • Policy Decision Point (PDP): Motor de decisão que avalia políticas e retorna permit/deny.
  • Policy Information Point (PIP): Fornece atributos contextuais (localização, horário, nível de risco).
  • Policy Administration Point (PAP): Interface para gerenciamento e versionamento de políticas.

1.3. Vantagens da separação entre lógica de decisão e lógica de aplicação

Ao externalizar a autorização, ganhamos:
- Auditabilidade centralizada: Todas as decisões são registradas em um único ponto.
- Atualização sem deploy: Políticas podem ser alteradas sem reimplantar microsserviços.
- Consistência entre serviços: A mesma política vale para APIs, filas e bancos de dados.

2. Modelagem de Políticas: Estrutura e Linguagens

2.1. Estrutura típica de uma política

Toda política PBA segue o padrão: Sujeito + Recurso + Ação + Condição. Exemplo:

Permitir que [usuário: admin] [ação: deletar] [recurso: usuários] se [condição: departamento == "TI"]

2.2. Linguagens de definição de políticas

As principais linguagens são:

  • XACML: Padrão OASIS, verboso mas completo.
  • Rego (OPA): Declarativa, amplamente adotada em cloud-native.
  • ALFA: Abstração sobre XACML, mais legível.

Exemplo em Rego:

package authz

default allow = false

allow {
    input.method == "GET"
    input.path == "/api/users"
    input.user.role == "admin"
}

2.3. Hierarquia e combinação de políticas

Algoritmos de combinação definem o resultado quando múltiplas políticas se aplicam:

  • Deny-overrides: Se qualquer política negar, a decisão final é negar.
  • Permit-overrides: Se qualquer política permitir, a decisão final é permitir.
  • First-applicable: A primeira política que corresponder define a decisão.

3. Arquitetura de um Sistema de Autorização Baseado em Políticas

3.1. Fluxo de requisição

O fluxo típico é:

  1. Cliente faz requisição ao serviço A.
  2. PEP no serviço A intercepta e monta o contexto (usuário, ação, recurso).
  3. PEP envia requisição de decisão ao PDP.
  4. PDP consulta PIP para atributos adicionais (ex: score de confiança).
  5. PDP avalia políticas e retorna allow ou deny.
  6. PEP executa ou bloqueia a ação original.

3.2. Armazenamento e distribuição de políticas

Políticas podem ser armazenadas em:
- Bundles estáticos: Arquivos locais versionados no deploy.
- Serviços centralizados: OPA como servidor com APIs de gerenciamento.
- Bancos de dados: Para políticas dinâmicas em tempo real.

3.3. Cache de decisões e estratégias de invalidação

Cache é crítico para desempenho. Estratégias comuns:
- TTL fixo: Decisões expiram após N segundos.
- Invalidation por evento: Quando uma política muda, caches são limpos.
- Cache por chave composta: (usuário + recurso + ação) como chave.

4. Implementação com Open Policy Agent (OPA)

4.1. Instalação e configuração básica do OPA como PDP

# Baixar e iniciar OPA como servidor
wget https://openpolicyagent.org/downloads/v0.58.0/opa_linux_amd64
chmod +x opa_linux_amd64
./opa_linux_amd64 run --server --addr :8181

4.2. Exemplo de política em Rego

Política para controle de acesso a endpoints de API:

package api.authz

# Regra padrão: negar acesso
default allow = false

# Permitir acesso GET a /public para qualquer usuário autenticado
allow {
    input.method == "GET"
    input.path == "/api/public"
    input.user.authenticated == true
}

# Permitir acesso POST a /admin apenas para admins
allow {
    input.method == "POST"
    startswith(input.path, "/api/admin")
    input.user.role == "admin"
}

# Permitir acesso ao próprio perfil
allow {
    input.method == "GET"
    input.path == "/api/users/" + input.user.id
}

Testando a política via API:

# Requisição de decisão
curl -X POST http://localhost:8181/v1/data/api/authz/allow \
  -d '{
    "input": {
      "method": "GET",
      "path": "/api/admin/config",
      "user": {
        "id": "123",
        "role": "admin",
        "authenticated": true
      }
    }
  }'
# Resposta: {"result": true}

4.3. Integração do OPA com aplicações via sidecar ou API REST

Em Kubernetes, o OPA pode rodar como sidecar no mesmo pod da aplicação:

# Configuração de sidecar no deployment
containers:
  - name: app
    image: minha-app:latest
  - name: opa
    image: openpolicyagent/opa:latest
    args:
      - "run"
      - "--server"
      - "--addr=:8181"
      - "/policies/authz.rego"

A aplicação consulta o sidecar via localhost:8181.

5. Considerações de Desempenho e Escalabilidade

5.1. Otimização de consultas ao PDP

  • Pré-calcular decisões: Para fluxos conhecidos, cacheie resultados.
  • Reduzir atributos: Envie apenas atributos relevantes ao PDP.
  • Usar índices: OPA suporta índices em regras para busca mais rápida.

5.2. Particionamento de políticas por domínio ou tenant

Para sistemas multi-tenant, cada inquilino pode ter seu próprio pacote de políticas:

package tenant_a.authz
package tenant_b.authz

O PDP decide qual pacote consultar baseado no tenant do usuário.

5.3. Monitoramento e logging de decisões

Habilite logs estruturados no OPA:

./opa run --server --addr :8181 --log-level debug --log-format json

Cada decisão gera um log com input, resultado e tempo de avaliação.

6. Segurança e Boas Práticas na Definição de Políticas

6.1. Prevenção de vazamento de informações

Evite expor dados sensíveis em mensagens de erro:

# Ruim: retorna motivo detalhado
deny["Usuário não tem cargo de gerente"] { ... }

# Bom: retorna negação genérica
default deny = false

6.2. Versionamento e testes de políticas

Testes unitários em Rego são essenciais:

package test.authz

test_admin_access {
    allow with input as {"method": "GET", "path": "/api/admin", "user": {"role": "admin"}}
}

test_deny_regular_user {
    not allow with input as {"method": "GET", "path": "/api/admin", "user": {"role": "user"}}
}

Execute com: opa test ./policies/

6.3. Controle de acesso ao próprio sistema de políticas

O PAP (Policy Administration Point) deve ser protegido com autenticação forte. Apenas administradores de segurança devem poder alterar políticas.

7. Casos de Uso e Exemplos Práticos

7.1. Autorização em microsserviços

Política centralizada para múltiplos serviços:

package gateway.authz

# Serviço de pedidos: apenas usuários com conta ativa
allow {
    input.service == "orders"
    input.user.status == "active"
}

# Serviço de pagamentos: apenas financeiro
allow {
    input.service == "payments"
    input.user.department == "finance"
}

7.2. Controle de acesso a dados sensíveis baseado em atributos contextuais

package data.access

allow {
    input.action == "read_pii"
    input.user.clearance >= 3
    input.location.country == "BR"
    time.clock(input.timestamp).hour >= 8
    time.clock(input.timestamp).hour <= 18
}

7.3. Políticas dinâmicas para ambientes multi-tenant

package tenants

# Cada tenant tem regras próprias
allow {
    input.tenant_id == "acme_corp"
    input.user.role == "manager"
    input.resource.type == "reports"
}

allow {
    input.tenant_id == "startup_inc"
    input.user.role == "developer"
    input.resource.type == "logs"
}

Referências