Voltar para os textos

Quem pode dar poder a quem

Usar uma capacidade e concedê-la são autoridades diferentes. Modele as duas.

Em quase todo painel administrativo existe uma propriedade que significa, ao mesmo tempo, usar o produto, cadastrar pessoas, conceder privilégios e atravessar fronteiras. Geralmente ela se chama admin. É conveniente até o dia em que alguém pergunta: um administrador do cliente pode criar outro administrador igual a ele?

Separação de deveres parte da ideia de que certas autoridades não deveriam vir juntas só por conveniência. No catálogo, isso vira permissions distintas:

users.write                -- cadastrar pessoas
authz.roles.assign_tenant  -- atribuir roles do tenant
authz.grants.direct        -- conceder exceções temporárias
platform.tenant.bypass     -- atravessar tenants

Por que “só concede o que possui” falha

A regra mais comum é: um usuário só pode conceder permissions que ele mesmo tem. Parece segura e tem dois defeitos.

O primeiro é a auto-replicação. Se o administrador possui todas as permissions de admin, o subset check também permite que ele conceda admin a qualquer um. A política “o admin do tenant cadastra pessoas e atribui papéis operacionais, mas não cria outro admin” simplesmente não é expressável.

O segundo é o inverso: a regra obriga quem só precisa conceder uma capacidade a possuí-la. Um gestor que distribui acesso a microdados passa a ler microdados, mesmo sem nenhuma razão para isso.

A matriz de autoridade

A saída é modelar a autoridade de concessão diretamente:

role_grantable(grantor_role, target_role)

tenant_admin   -> viewer
tenant_admin   -> analyst
platform_admin -> tenant_admin
platform_admin -> viewer
platform_admin -> analyst

Repare no que não está ali: tenant_admin -> tenant_admin. A regra de negócio deixa de ser efeito colateral de uma comparação de conjuntos e vira uma linha inspecionável. Para grants diretos vale o mesmo raciocínio, com outra matriz:

permission_grantable(grantor_role, permission_code)

tenant_admin -> analysis.run
tenant_admin -> data.query
tenant_admin -> content.share

E acima das duas matrizes fica um hard deny: nenhuma autoridade de tenant concede permission de plataforma, independentemente do que a matriz disser. Se alguém configurar errado, a fronteira continua de pé.

O predicado, com os parênteses no lugar

can_assign(actor, target, role) :=
  (
      tenant_branch(actor, target)
   OR platform_branch(actor)
  )
  AND role_is_grantable(actor, role)
  AND target_role_content_is_allowed(actor, role)

Parece pedantismo, mas aqui precedência é segurança. Sem os parênteses, A OR B AND C AND D vira A OR (B AND C AND D), e qualquer ator que satisfaça o ramo de tenant pula as checagens de matriz. É um bypass completo escondido num operador. A implementação deve espelhar a fórmula, ou usar early returns equivalentes que não deixem dúvida.

O último termo, target_role_content_is_allowed, importa quando o tenant pode criar roles próprias: a validação olha o conteúdo da role, não o nome. Uma role custom chamada “Leitor” que contém authz.roles.assign_tenant não é leitora.

Cadastro não é concessão

POST /users              -- cria a identidade
POST /users/{id}/roles   -- concede autoridade

Dois endpoints, dois serviços, duas trilhas. Juntar os dois num formulário de “novo usuário” com um dropdown de role faz de todo cadastro uma concessão, e a auditoria passa a ter que separar o que a arquitetura juntou.

Bloquear autoatribuição, actor == target, é uma salvaguarda razoável. Mas é só isso: o controle real é a matriz. Se a matriz permite a escalada, impedir que alguém a faça em si mesmo só obriga a pedir para um colega.

Grants diretos com prazo

Exceções temporárias existem, e é melhor modelá-las do que fingir que não. Mas grant direto não pode virar uma segunda implementação descontrolada de RBAC. As regras que eu sigo:

  • expires_at obrigatório. Não existe grant eterno.
  • Justificativa obrigatória, gravada junto.
  • Concessão auditada na mesma transação.
  • O vencimento entra no frescor da sessão.
  • Recorrência monitorada: o mesmo grant renovado todo mês é uma role que ninguém quis criar.
  • Permissions de plataforma indelegáveis por qualquer mecanismo de tenant.

Proveniência

Para cada permission efetiva, o sistema deve conseguir dizer de onde ela veio: role de sistema, role custom ou grant direto, quem concedeu, quando e até quando. Isso transforma “por que essa pessoa consegue fazer isso?” numa consulta, e não numa investigação manual de logs.

Este texto faz parte de uma série que começa em Autorização em quatro perguntas.