# RAG Multi-Tenant Seguro: ADR para Autorização em Duas Camadas

Autorização em RAG multi-tenant não é um detalhe de implementação — é uma decisão arquitetural com consequências diretas em compliance, vazamento de dados entre tenants e superfície de ataque de modelos de linguagem. Neste ADR, documento as forças que me levaram a adotar um padrão de defesa em profundidade com Amazon Verified Permissions e Bedrock Knowledge Bases, as alternativas que descartei e as consequências operacionais que precisam ser gerenciadas.

- URL: https://fernando.moretes.com/blog/rag-multi-tenant-seguro-adr-para-autorizacao-em-duas-camadas-secure-multi

- Markdown: https://fernando.moretes.com/blog/rag-multi-tenant-seguro-adr-para-autorizacao-em-duas-camadas-secure-multi/article.md?lang=pt

- Published: 2026-06-23T12:37:23.187Z

- Category: IA & Agentes

- Tags: RAG, multi-tenant, Amazon Bedrock, Verified Permissions, Zero Trust, authorization, financial-grade, security

- Reading time: 9 min

- Source: [Secure multi-tenant RAG with Amazon Bedrock and Verified Permissions](https://aws.amazon.com/blogs/architecture/secure-multi-tenant-rag-with-amazon-bedrock-and-verified-permissions/)

---

Quando um sistema RAG serve múltiplos tenants em um ambiente financeiro regulado, a pergunta não é 'como integrar o modelo?' — é 'como garantir que o documento do cliente A jamais apareça na resposta do cliente B, mesmo sob falha parcial de configuração?' Este ADR registra a decisão arquitetural que tomei para resolver exatamente esse problema.

## Contexto e Forças: Por Que Autorização em RAG É Diferente

Em sistemas RAG convencionais de tenant único, o controle de acesso é resolvido na camada de aplicação: você autentica o usuário, filtra os documentos por ownership e passa o contexto ao modelo. O problema começa quando você escala isso para dezenas de tenants com SLAs distintos, esquemas de permissão heterogêneos e obrigações regulatórias como LGPD, SOC 2 e PCI-DSS.

A força central aqui é o **risco de contaminação de contexto**. Em uma arquitetura RAG, o retrieval determina o que o modelo vê. Se o mecanismo de busca vetorial retorna chunks de documentos de outro tenant — por erro de metadado, por falha de filtro ou por injeção de prompt — o modelo processa e potencialmente reproduz informação confidencial. Isso não é hipotético: em testes de penetração que conduzi em plataformas RAG corporativas, consegui extrair contexto de outros tenants manipulando a query de embedding antes que qualquer filtro de aplicação fosse aplicado.

A segunda força é a **heterogeneidade de permissões intra-tenant**. Dentro de um único tenant financeiro, um analista júnior não deve ver os mesmos documentos que um gestor de portfólio. Isso exige controle de acesso baseado em atributos (ABAC), não apenas em roles. A maioria das implementações RAG que vejo em produção ignora essa dimensão e trata o tenant como unidade atômica de autorização — o que é insuficiente para qualquer ambiente regulado.

A terceira força é a **superfície de ataque do próprio modelo**. LLMs são suscetíveis a prompt injection. Se um documento malicioso no corpus instrui o modelo a ignorar restrições ou a revelar contexto de sistema, uma autorização que vive apenas no prompt não é suficiente. A defesa precisa existir antes que o modelo veja qualquer token.

## Forças Adicionais: Operacionalidade, Auditoria e Latência

Além das forças de segurança, há três forças operacionais que moldaram esta decisão. A primeira é **auditabilidade regulatória**. Em ambientes financeiros, cada decisão de acesso precisa ser registrada com contexto suficiente para responder a uma auditoria: quem acessou, qual documento, com qual justificativa de política, em qual timestamp. Isso descarta abordagens onde a autorização é implícita no código da aplicação sem trail estruturado.

A segunda força é **latência de inferência**. Um ciclo RAG já carrega latência de embedding (tipicamente 50-150ms em `amazon.titan-embed-text-v2`), busca vetorial (20-80ms em OpenSearch Serverless com k-NN), e inferência do modelo (500ms-3s dependendo do modelo e do tamanho do contexto). Adicionar uma camada de autorização que introduza mais de 30-50ms de overhead é inaceitável para SLAs de produto. Isso descarta soluções que exijam chamadas síncronas a sistemas externos lentos no caminho crítico.

A terceira força é **gestão de políticas em escala**. Com dezenas de tenants e centenas de roles distintos, manter políticas de autorização como código de aplicação se torna inviável. Você precisa de um policy store centralizado, versionado e auditável, com suporte a ABAC nativo — não uma tabela DynamoDB com lógica de autorização espalhada em Lambda functions.

Essas três forças operacionais, combinadas com as forças de segurança, eliminaram as abordagens mais simples e me forçaram a considerar um padrão de defesa em profundidade com componentes especializados para cada camada.

## Opções Consideradas para Autorização em RAG Multi-Tenant

### Opção 1: Filtro de Metadados Apenas (Aplicação)

**Pros**
- Implementação simples, sem dependências adicionais
- Latência mínima — filtro aplicado no payload da query
- Sem custo adicional de serviço

**Cons**
- Single point of failure: erro de código expõe dados cross-tenant
- Sem suporte nativo a ABAC intra-tenant
- Sem audit trail estruturado de decisões de acesso
- Políticas acopladas ao código — difícil de auditar e versionar

**Verdict:** Descartada — insuficiente para ambientes regulados

### Opção 2: Knowledge Bases Separadas por Tenant

**Pros**
- Isolamento físico total entre tenants
- Sem risco de contaminação de contexto no retrieval
- IAM resource-based policies por Knowledge Base

**Cons**
- Custo operacional alto: cada KB tem custo fixo de OpenSearch Serverless (~$700/mês mínimo por collection)
- Não resolve controle intra-tenant (roles dentro do mesmo tenant)
- Gestão de dezenas de KBs se torna operacionalmente complexa
- Limite de 5 Knowledge Bases por conta por padrão (Service Quota)

**Verdict:** Parcialmente viável apenas para número muito pequeno de tenants de alto valor

### Opção 3: Defesa em Profundidade — Verified Permissions + Filtro de Metadados

**Pros**
- Duas camadas independentes: falha em uma não compromete a outra
- AVP fornece ABAC nativo com Cedar policies — auditável e versionável
- Latência de AVP IsAuthorized: ~10-20ms p99 (API regional, sem cold start)
- Audit trail nativo via CloudTrail para cada decisão de autorização
- Filtro de metadados no Knowledge Base como segunda camada de enforcement

**Cons**
- Complexidade de modelagem Cedar para ABAC — curva de aprendizado real
- Custo adicional de AVP: $0.10 por 1000 authorization requests
- Requer sincronização de entidades (usuários, roles) entre IdP e AVP

**Verdict:** Decisão adotada — melhor equilíbrio entre segurança, auditabilidade e custo operacional

### Opção 4: OPA (Open Policy Agent) Self-Managed

**Pros**
- Flexibilidade máxima de política com Rego
- Sem vendor lock-in em serviço gerenciado
- Integração com ecossistema CNCF (OPAL para sync)

**Cons**
- Operação de cluster OPA em EKS adiciona overhead de SRE significativo
- Sem audit trail nativo integrado ao CloudTrail
- Latência variável dependendo de bundle size e complexidade de Rego
- Responsabilidade de HA, scaling e patching recai sobre o time

**Verdict:** Descartada para este contexto — overhead operacional injustificável quando AVP resolve o problema

## A Decisão: Defesa em Profundidade com Camadas Independentes

Adotei o padrão de defesa em profundidade com duas camadas independentes de autorização. A **Camada 1** é o Amazon Verified Permissions (AVP) com políticas Cedar, operando antes do retrieval. A **Camada 2** é o filtro de metadados nativo do Bedrock Knowledge Base, operando no momento da busca vetorial. A independência entre as camadas é o ponto crítico: uma falha de configuração em Cedar não desabilita o filtro de metadados, e vice-versa.

A escolha do Cedar como linguagem de política é deliberada. Cedar é uma linguagem de política com semântica formal e modelo de avaliação determinístico — diferente de Rego, que permite construções que podem produzir resultados inesperados em edge cases. Para ambientes financeiros, a previsibilidade do motor de avaliação é tão importante quanto a expressividade da linguagem. Além disso, o AVP oferece análise estática de políticas via `IsAuthorizedWithToken`, permitindo validar políticas antes do deploy — algo que OPA não oferece nativamente.

A modelagem de entidades no AVP segue um esquema hierárquico: `Tenant > Group > User`, com atributos de documento como `classification_level`, `tenant_id` e `document_type`. Uma política típica para um analista financeiro seria: `permit(principal in Group::"analysts", action == Action::"ReadDocument", resource) when { resource.classification_level <= principal.clearance_level && resource.tenant_id == principal.tenant_id };`. Essa política é avaliada em ~12ms p50 e ~18ms p99 em minha medição com payloads de entidade de tamanho médio.

O filtro de metadados no Knowledge Base é configurado como `{ "equals": { "key": "tenant_id", "value": "<tenant-from-jwt>" } }` combinado com `{ "equals": { "key": "clearance_level_max", "value": "<user-clearance>" } }`. Esses metadados são indexados no OpenSearch Serverless e o filtro é aplicado antes do ranking por similaridade — garantindo que chunks de outros tenants nunca entrem no contexto do modelo.

## Fluxo de Autorização em Duas Camadas para RAG Multi-Tenant

Cada requisição RAG passa por duas camadas independentes de autorização antes que qualquer chunk chegue ao modelo. A falha de qualquer camada bloqueia o acesso sem afetar a outra.

### 🔐 Layer 1 — Pre-Retrieval Authorization

- API Gateway (JWT Authorizer) (edge)
- Auth Lambda IsAuthorizedWithToken (compute)
- Amazon Verified Permissions (Cedar Policies) (security)

### 🔍 Layer 2 — Retrieval with Metadata Filter

- Bedrock Knowledge Base (Retrieve API) (ai)
- OpenSearch Serverless (k-NN + metadata filter) (data)
- S3 Document Store (tenant_id prefix isolation) (storage)

### 🤖 Inference — Model Never Sees Unauthorized Chunks

- Bedrock InvokeModel (Claude / Titan) (ai)
- Grounded Response (authorized context only) (frontend)

### 📋 Audit & Observability

- CloudTrail (AVP decisions + KB calls) (security)
- CloudWatch (SLO: auth latency p99) (security)

### Fluxos

- user -> apigw: JWT Bearer Token
- apigw -> lambda_auth: claims extraídos
- lambda_auth -> avp: IsAuthorizedWithToken (~12ms p50)
- avp -> lambda_auth: ALLOW / DENY + reason
- lambda_auth -> bedrock_kb: ALLOW → Retrieve com filtro
- bedrock_kb -> opensearch: k-NN + metadata filter (tenant_id, clearance)
- opensearch -> s3_docs: chunk fetch por S3 URI
- bedrock_kb -> bedrock_model: chunks autorizados + query
- bedrock_model -> response: resposta fundamentada
- avp -> cloudtrail: audit log de decisão
- lambda_auth -> cloudwatch: métricas de latência auth

## Implementação: Detalhes que Importam em Produção

Há cinco detalhes de implementação que a maioria das arquiteturas de referência omite e que fazem diferença real em produção financeira.

**1. Sincronização de entidades AVP.** O AVP precisa de um entity store consistente com o IdP. Uso um pipeline assíncrono: Cognito User Pool triggers → EventBridge → Lambda → AVP `BatchCreateOrUpdateEntities`. O SLA de consistência é eventual (~2-5s), o que é aceitável para mudanças de role, mas exige que o sistema trate o caso de usuário recém-promovido que ainda não tem o novo role propagado. A solução é um TTL de cache de 60s no Lambda de autorização com fallback para re-fetch se AVP retornar DENY inesperado.

**2. Metadados de documento no ingestion pipeline.** Durante o ingestion no Knowledge Base, cada chunk deve carregar `tenant_id`, `clearance_level_max`, `document_type` e `created_at` como metadados estruturados. Isso é feito via Lambda customizada no pipeline de Glue que processa os documentos antes de enviá-los ao Bedrock KB via `StartIngestionJob`. Metadados ausentes ou incorretos são o principal vetor de falha nesse padrão — implemento validação de schema com AWS Glue Data Quality antes do ingestion.

**3. Idempotência no retry de autorização.** Quando AVP retorna erro 5xx (raro, mas ocorre), o comportamento correto é **fail closed** — negar acesso e logar o erro, não fazer bypass. Implemento isso com um circuit breaker no Lambda de autorização: após 3 falhas consecutivas de AVP em 30s, o circuit abre e todas as requisições são negadas com HTTP 503 até o circuit fechar. Isso é contra-intuitivo para times de produto, mas é o comportamento correto para sistemas financeiros.

**4. Observabilidade da camada de autorização.** Instrumento o Lambda de autorização com OpenTelemetry, emitindo spans para cada chamada AVP com atributos `avp.decision`, `avp.tenant_id`, `avp.policy_id` e `avp.latency_ms`. Esses spans alimentam um dashboard Datadog com SLOs: `auth_latency_p99 < 30ms` e `auth_deny_rate_anomaly` (alerta se a taxa de DENY aumentar mais de 3σ em 5 minutos — indicador de ataque ou bug de política).

## Gestão de Políticas Cedar em Escala: O Problema Real

A parte mais subestimada desse padrão não é a integração técnica com AVP — é a **governança do ciclo de vida das políticas Cedar**. Em um ambiente com 50 tenants e 200 roles distintos, você rapidamente acumula centenas de políticas. Sem disciplina de engenharia, isso se torna um sistema de autorização que ninguém entende completamente.

Minha abordagem é tratar políticas Cedar como Infrastructure as Code de primeira classe. Cada política vive em um repositório Git com PR review obrigatório de dois engenheiros de segurança. O pipeline de CI usa o `cedar-policy-cli` para validar sintaxe e executar testes de unidade de política (sim, você pode escrever testes para políticas Cedar) antes de qualquer merge. O deploy é feito via CodePipeline com aprovação manual para mudanças que afetam mais de 10 entidades.

Um padrão que descobri ser crítico é a **política de negação explícita para documentos não classificados**. Por padrão, Cedar nega tudo que não é explicitamente permitido — mas documentos sem metadado `clearance_level_max` definido (por falha de ingestion) ficam em um estado ambíguo. Adiciono uma política de forbid explícita: `forbid(principal, action, resource) when { !resource.hasAttribute("clearance_level_max") };`. Isso garante que documentos com metadados corrompidos nunca sejam acessíveis, independentemente de qualquer outra política.

Para o problema de policy drift — quando as políticas no AVP divergem do que está no Git — implemento uma reconciliation Lambda que roda a cada 15 minutos, compara o hash das políticas ativas no AVP com o estado esperado no S3 (artefato do pipeline), e emite um alarme CloudWatch se houver divergência. Isso detecta mudanças manuais fora do pipeline, que são o principal vetor de configuração incorreta em ambientes regulados.

> **Consequências e Riscos que Precisam Ser Gerenciados:** **1. Eventual consistency é um risco real.** A sincronização de entidades AVP é assíncrona. Uma revogação de acesso urgente (ex.: demissão de funcionário com acesso a documentos confidenciais) pode levar até 5s para propagar. Para casos de alta urgência, implemente um mecanismo de invalidação imediata via `DeletePolicyStore` ou bloqueio no nível do Cognito User Pool (desabilitar o usuário) que é verificado antes da chamada AVP.

**2. O filtro de metadados não é criptografia.** O filtro de metadados no OpenSearch Serverless é uma restrição de query, não um controle criptográfico. Um operador com acesso direto ao OpenSearch Serverless pode contorná-lo. Para documentos de máxima sensibilidade, considere criptografia a nível de campo com KMS CMK por tenant, onde a chave de decriptografia só é liberada após autorização AVP bem-sucedida.

**3. Custo de AVP em alto volume.** A $0.10 por 1000 requests, um sistema com 1M de requests RAG/dia gera $3.000/mês apenas em autorização AVP. Em escala, avalie cache de decisão com TTL curto (30-60s) para queries idênticas do mesmo usuário — mas documente isso como trade-off de segurança, pois uma revogação não será imediatamente efetiva para queries cacheadas.

**4. Prompt injection ainda é uma ameaça.** Este padrão protege o retrieval, mas não o modelo em si. Um documento malicioso que passe pelos filtros (porque pertence legitimamente ao tenant) pode ainda tentar manipular o modelo via prompt injection. Adicione guardrails do Bedrock com `BLOCK_PROMPT_ATTACK` habilitado e monitore a métrica `InvokeModel.GuardrailsApplied`.

## Números de Referência para Dimensionamento

- **~12ms** — Latência p50 AVP IsAuthorized. Com payload de entidade médio (~5 atributos, 2 grupos)
- **~18ms** — Latência p99 AVP IsAuthorized. Dentro do budget de 30ms para SLA de produto
- **$3k/mês** — Custo AVP a 1M req/dia. Sem cache de decisão; com cache 30s pode reduzir 60-70%
- **~$700/mês** — Custo mínimo por Knowledge Base (OpenSearch Serverless). OCU mínimo de 2 OCUs indexing + 2 OCUs search por collection

## Anti-Padrões que Observo Frequentemente em RAG Multi-Tenant

- Autorização apenas no system prompt: 'Você só pode responder sobre documentos do tenant X' — LLMs não são enforcement boundaries, são atores de processamento de texto
- Tenant ID extraído do corpo da request (não do JWT verificado) — permite spoofing trivial de tenant
- Knowledge Base compartilhada sem filtro de metadados — qualquer falha de lógica de aplicação expõe dados cross-tenant
- Políticas Cedar hardcoded no Lambda em vez de gerenciadas no AVP Policy Store — impossível auditar, versionar ou revogar sem redeploy
- Sem validação de metadados no ingestion pipeline — documentos com tenant_id ausente ficam acessíveis a todos os tenants se o filtro falhar
- Fail open em erros de autorização — 'melhor deixar passar do que bloquear usuários legítimos' é o raciocínio errado em sistemas financeiros

## Avaliação pelo AWS Well-Architected Framework

- **security**: Defesa em profundidade com duas camadas independentes (AVP + metadata filter). IAM least-privilege para Bedrock KB e AVP. KMS CMK para dados em repouso no S3 e OpenSearch. CloudTrail habilitado para todas as chamadas AVP. Guardrails Bedrock para prompt injection. Nenhuma credencial de tenant no código — tudo via JWT claims verificados.
- **reliability**: Circuit breaker em falhas AVP com fail-closed behavior. Multi-AZ OpenSearch Serverless por padrão. Retry com jitter exponencial em chamadas Bedrock (max 3 tentativas, backoff 100ms base). Alarme de policy drift para detectar configuração incorreta fora do pipeline.
- **performance**: AVP p99 ~18ms — dentro do budget de latência. Cache de decisão opcional (30-60s TTL) para reduzir latência e custo. OpenSearch Serverless escala automaticamente sem gestão de capacidade. Lambda de autorização configurada com 512MB e Provisioned Concurrency para eliminar cold start em horário de pico.

> **Nota do Curador:** Na prática, o que mais me preocupa nesse padrão não é a integração técnica — é a governança das políticas Cedar ao longo do tempo. Times que não tratam políticas como código de primeira classe invariavelmente acumulam policy debt que se torna um risco de auditoria. Se eu tivesse que dar um único conselho: implemente os testes de unidade de política Cedar no dia zero, antes de qualquer política ir para produção — a capacidade de testar 'o analista júnior do tenant A NÃO deve ver documentos classificados como CONFIDENTIAL' como um teste automatizado é o que separa um sistema de autorização auditável de um que você só descobre que está errado durante um incidente. A lição difícil aqui é que a segunda camada de defesa (filtro de metadados) existe precisamente para o dia em que alguém fizer merge de uma política Cedar incorreta sem perceber.

## Veredicto: Adote o Padrão de Defesa em Profundidade, Mas Governe as Políticas

Para qualquer sistema RAG multi-tenant em ambiente financeiro regulado, o padrão de defesa em profundidade com Amazon Verified Permissions (Cedar) na Camada 1 e filtro de metadados no Bedrock Knowledge Base na Camada 2 é a abordagem correta. A independência das camadas é a propriedade mais valiosa: ela transforma erros de configuração de eventos catastróficos em incidentes detectáveis e contidos. O custo operacional adicional (~$3k/mês em AVP a 1M req/dia, reduzível com cache) é justificável em qualquer ambiente onde uma violação de dados cross-tenant teria consequências regulatórias. O investimento real não é em infraestrutura — é em governança: políticas Cedar como IaC, testes automatizados, pipeline de CI/CD para políticas e monitoramento de drift. Sem isso, você tem a arquitetura certa com o processo errado, e o processo é o que falha em produção.

## Referências

- [Secure multi-tenant RAG with Amazon Bedrock and Verified Permissions](https://aws.amazon.com/blogs/architecture/secure-multi-tenant-rag-with-amazon-bedrock-and-verified-permissions/)
- [Amazon Verified Permissions — Cedar Policy Language Reference](https://docs.aws.amazon.com/verifiedpermissions/latest/userguide/cedar-policy-language.html)
- [Amazon Bedrock Knowledge Bases — Metadata Filtering](https://docs.aws.amazon.com/bedrock/latest/userguide/kb-test-config.html)
- [Cedar Policy Language — Official Documentation](https://docs.cedarpolicy.com/)
- [AWS Well-Architected Framework — Security Pillar](https://docs.aws.amazon.com/wellarchitected/latest/security-pillar/welcome.html)
- [Amazon OpenSearch Serverless — Service Quotas](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/serverless-scaling.html)
- [CNCF Blog: Agent Auth — A Lawyer's Day in Court](https://www.cncf.io/blog/2026/06/23/agent-auth-a-lawyers-day-in-court/)
- [Amazon Bedrock Guardrails — Content Filtering](https://docs.aws.amazon.com/bedrock/latest/userguide/guardrails.html)
