Cloud Intelligence™Cloud Intelligence™

Cloud Intelligence™

Chega de Lutar com Permissões Complexas: um Alívio para o Seu Código

Esta página também está disponível em English, Deutsch, Español, Français, Italiano e 日本語.

By Dima KramskoyAug 5, 202612 min read
Dima Kramskoy

About Dima Kramskoy

Solutions architect who takes ideas from zero to proven. With over 20 years of software engineering, I build PoCs and MVPs on AWS that prove the path before you commit — spanning GenAI, DevOps, and FinOps. Off the clock: live-fire cooking, fishing, the outdoors, and applying Extreme Ownership to everything.

My personal page

PerfectScale™ for Kubernetes

Ready to optimize?

Get your free Kubernetes savings analysis

Visão geral

Este post apresenta o Amazon Verified Permissions (AVP) como uma solução prática para gerenciar autorizações complexas em aplicações, com um passo a passo prático usando o exemplo PetStore da AWS.

O problema

Já enfrentou uma complexidade absurda tentando gerenciar todos os possíveis papéis de autorização, usuários e atores do seu próprio produto SaaS? Conhece a dor das mudanças de requisitos de negócio, tentando aplicar os melhores princípios de segurança enquanto faz malabarismo com code reviews e testes A/B relacionados às mudanças que você está tentando implementar? E sim, você roda seu SaaS na nuvem AWS.

Acho que chegou a hora de respirar fundo e olhar o quadro geral!! Pense na sua solução, e repense de novo, porque eu talvez consiga te mostrar uma relativamente simples! Uma que pode te ajudar a gerenciar a complexidade e direcionar seus recursos e esforços para o que realmente importa: o seu negócio e as necessidades dele!!

Essa solução é o Amazon Verified Permissions.


Como você normalmente lida com autorização na sua aplicação web

Vamos destrinchar o sistema de autorização de uma aplicação web. A necessidade de um sistema assim surge com a evolução da aplicação. Você pode planejá-lo com antecedência (o que é melhor, como parte do design do seu sistema), mas ainda vai enfrentar mudanças conforme os requisitos de negócio evoluem. Você vai precisar alterar diferentes partes do sistema depois que ele já estiver em produção.

Por que precisamos de um sistema assim? Por vários motivos:

  • Você quer permitir que certos papéis vejam determinadas partes da aplicação
  • Você precisa restringir certas ações para papéis/usuários específicos da aplicação
  • Você precisa reforçar os controles de segurança e impedir acessos não autorizados
  • Sua lógica de permissões acaba espalhada pelo código, transformando a manutenção em um pesadelo

O sistema de permissões típico: um verdadeiro emaranhado

A maioria dos sistemas de autorização hoje se parece com isto — blocos de if/else espalhados pelo código:

// The typical nightmare in your Lambda or Node.js service:
boolean userAuthorizedForOrder(Order order, User user) {
if (user.storeId == order.storeId) {
if (user.roles.contains("store-owner")) {
return true; // store owners can access their own store's orders
} else if (user.roles.contains("customer")) {
if (user.id == order.customerId) {
return true; // customers can only see their own orders
}
}
}
return false;
}

E depois? Você adiciona outro papel. Depois mais um. Depois um caso especial para gerentes regionais. Depois uma regra sobre quem pode cancelar pedidos após 24 horas. Depois um novo requisito de negócio sobre acesso nos fins de semana. De repente, você está se afogando em condições aninhadas, seus code reviews viraram uma tortura, cada mudança de requisito de negócio vira um deploy, e metade do time está discutindo edge cases em vez de construir funcionalidades.

As dores reais

Explosão de papéis: Seus 2 papéis viram 5, depois 10, e aí você tem um gerente-regional-com-permissões-de-funcionário-de-loja-mas-só-nos-fins-de-semana, e ninguém consegue mais acompanhar quem deveria ter o quê.

Pesadelo de code review: Toda mudança de permissão exige mudança de código. Os devs passam horas debatendo lógica de políticas em PRs em vez de entregar funcionalidades.

Testar autorização: Como testar todas as combinações? Matriz de usuários × papéis × recursos × ações = caos. Você deixa passar edge cases e as coisas quebram em produção.

Trilha de auditoria: Boa sorte explicando para o time de compliance por que aquele usuário conseguia acessar aquele pedido. Está enterrado no histórico do git em 5 arquivos diferentes.

Zero flexibilidade: Quer mudar permissões numa sexta-feira? Azar o seu — isso é um deploy. Quer reverter uma política rapidamente? Faça deploy de novo.


Como gerenciar isso com o Amazon Verified Permissions

E se você tivesse a oportunidade de simplificar esse fardo? E se eu te dissesse que agora existe um serviço capaz de fazer a maior parte do trabalho de permissões da sua aplicação web por você?

Em vez de embutir a autorização no código, e se você a separasse completamente? E se as permissões fossem declarativas, versionadas, centralizadas, e você pudesse alterá-las sem fazer redeploy da aplicação?

Essa é a promessa do Amazon Verified Permissions (AVP).

A arquitetura

Veja como funciona (em alto nível):

  1. O usuário se autentica via AWS Cognito (ou qualquer IdP)
  2. Sua API recebe a requisição com um token JWT
  3. O Lambda Authorizer intercepta a requisição
  4. O Lambda monta uma consulta de autorização com: principal (quem), ação (o que está fazendo), recurso (sobre o que está agindo), contexto (atributos adicionais)
  5. O Lambda chama o AVP com essa consulta
  6. O AVP avalia as políticas Cedar (uma linguagem de políticas projetada para autorização)
  7. O AVP retorna ALLOW ou DENY
  8. O API Gateway aplica a decisão — a requisição prossegue ou recebe um 403

O insight principal: Toda a lógica de permissões vive nas políticas Cedar dentro do AVP. Seu código apenas pergunta "isso está autorizado?" e confia na resposta.

media

O exemplo do mundo real: PetStore

Usei o repositório PetStore da AWS como aplicação de exemplo, incluindo a maioria das funcionalidades descritas, mas com algumas pequenas mudanças, já que o schema fornecido não funcionava — provavelmente por conta de atualizações feitas pela AWS no serviço. Lembre-se de que é um serviço relativamente novo, e eu diria que em muitas áreas ainda é um trabalho em andamento.

Basta seguir os passos indicados e começar pelo deploy da aplicação Amplify. Ela pode pedir que você se identifique com sua conta do GitHub para baixar o repositório e fazer o deploy no seu ecossistema AWS. Depois do deploy, olhando o diagrama de arquitetura acima, conseguimos identificar os componentes e como eles se comunicam entre si.


Configurando tudo — o passo a passo

Passo 1: usuários e grupos no Cognito

Como provedor de identidade, usamos o AWS Cognito. Após o deploy, você pode acessar o dashboard do Amazon Cognito e deve encontrar o User pool que começa com "petstoresample" — esse é o seu pool, e é nele que você deve criar os usuários e grupos desta aplicação.

Crie três usuários:

Precisamos criar três usuários para esta PoC. Um deles precisa se chamar exatamente "abhi", pois esse nome está fixo no código da função Lambda. Os outros dois são mais flexíveis — eu os chamei de "owner" e "visitor". Ao criá-los:

  • Não envie verificações por e-mail
  • Marque-os como "endereço de e-mail verificado"
  • Use senhas simples (você não vai querer redefini-las depois da configuração)

Ao final, você deve ter algo assim:

media

Adicione atributos personalizados ao usuário owner:

Não esqueça de atualizar o usuário "owner" com o atributo personalizado employmentStoreCode. Ele será usado mais adiante como parte das políticas no Amazon Verified Permissions. Basta clicar em "Edit" e atualizar para que os atributos do seu usuário fiquem assim:

media

Crie grupos e atribua usuários:

Você precisa criar 2 grupos e adicionar os usuários a eles. Esses nomes de grupo ("Store-Owner-Role" e "Customer") fazem parte dos tokens emitidos pelo Cognito e serão usados para o controle de acesso baseado em papéis (RBAC).

media

O usuário "abhi" precisa ser adicionado ao grupo "Customer" junto com o outro usuário, "visitor":

media

E, obviamente, o "owner" com o atributo adicional que criamos vai para o grupo "Store-Owner-Role":

media

Passo 2: configuração do Amazon Verified Permissions

Essa parte está concluída; o próximo passo é revisar e configurar o Amazon Verified Permissions. Nesta etapa também vamos revisar o Lambda Authorizer e o API Gateway — eles foram implantados no mesmo deploy do Amplify, como parte da aplicação.

Basta navegar até o Amazon Verified Permissions digitando no campo de busca, e vamos primeiro criar um novo Policy store. No repositório eles recomendam começar pelo schema, mas a interface mudou — você precisa criar o Policy store primeiro. Pode preencher qualquer coisa se quiser, porque no final usaremos o schema que estou fornecendo. Então, para facilitar, especifique um Empty Policy Store e dê a ele o nome "PetStore".

Passo 3: defina seu schema

Isto é crucial. O schema informa ao AVP quais principals, recursos, ações e relacionamentos existem na sua aplicação. Use este schema:

{
"MyApplication": {
"actions": {
"GetOrder": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Order"]
}
},
"GetStoreInventory": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
},
"ListOrders": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
},
"PlaceOrder": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
},
"SearchPets": {
"appliesTo": {
"principalTypes": ["User"],
"resourceTypes": ["Application"]
}
}
},
"entityTypes": {
"Application": {
"memberOfTypes": [],
"shape": {
"attributes": {
"storeId": {"type": "String"}
},
"type": "Record"
}
},
"Order": {
"memberOfTypes": [],
"shape": {
"attributes": {
"owner": {
"name": "User",
"type": "Entity"
},
"storeId": {
"type": "String"
}
},
"type": "Record"
}
},
"Pet": {
"memberOfTypes": [],
"shape": {
"attributes": {
"owner": {
"name": "User",
"type": "Entity"
},
"storeId": {
"type": "String"
}
},
"type": "Record"
}
},
"Group": {
"memberOfTypes": [],
"shape": {
"attributes": {},
"type": "Record"
}
},
"User": {
"memberOfTypes": ["Group"],
"shape": {
"attributes": {
"employmentStoreCode": {"type": "String"}
},
"type": "Record"
}
}
}
}
}

O schema do projeto original tem um pequeno problema; então, em vez dele, copie este para o schema do seu Policy Store.

Passo 4: crie as políticas Cedar

Clique no Policy Store e, no painel esquerdo, você verá o link "Policies". Clique nele e depois em "Create Policy > Create static policy". O assistente de criação de políticas usa o schema que adicionamos. Estamos usando políticas permissivas — o escopo deve ser definido para o grupo de participantes, o escopo de recurso precisa ser o recurso específico onde você escolhe o que precisa, e o tipo de ação deve ser selecionado como um conjunto específico de ações.

Há um truque que você pode usar para executar a política totalmente permissiva: selecione "All principals", "All resources" e "All actions", salve a política, depois edite-a e simplesmente cole a política escrita na linguagem Cedar. É uma solução alternativa, mas você precisa conhecer a declaração da sua política.

Aqui estão as políticas necessárias para esta PoC:

Exemplo de RBAC — Clientes podem pesquisar pets e fazer pedidos:

permit(
principal in MyApplication::Group::"Customer",
action in [MyApplication::Action::"SearchPets", MyApplication::Action::"PlaceOrder"],
resource == MyApplication::Application::"main"
);

Exemplo de ABAC — Donos de loja só podem acessar o inventário da própria loja:

permit(
principal in MyApplication::Group::"Store-Owner-Role",
action == MyApplication::Action::"GetStoreInventory",
resource == MyApplication::Application::"main"
)
when {
principal.employmentStoreCode == resource.storeId
};

Exemplo de ABAC — Usuários só podem ver os próprios pedidos:

permit(
principal in MyApplication::Group::"Customer",
action == MyApplication::Action::"GetOrder",
resource is MyApplication::Order
)
when {
principal == resource.owner
};

Cloud bill shouldn't be a mystery

One platform for AI and Cloud optimization.

A integração com o Lambda Authorizer

Sua função Lambda recebe a requisição da API e precisa:

  1. Verificar o token JWT (extrair as informações do usuário)
  2. Montar uma consulta de autorização para o AVP
  3. Chamar a API isAuthorized do AVP
  4. Retornar ALLOW/DENY

Veja como isso fica (do repositório real do PetStore):

const AWS = require('aws-sdk');
const avp = new AWS.Service({
apiConfig: require('./verifiedpermissions.json')
});
const { CognitoJwtVerifier } = require("aws-jwt-verify");
const jwtVerifier = CognitoJwtVerifier.create({
userPoolId: process.env.USERPOOLID,
tokenUse: "id",
clientId: process.env.APPCLIENTID
});
const policyStoreId = process.env.POLICYSTOREID;
exports.handler = async (event) => {
// Extract and verify the JWT token
const token = event.headers["Authorization"];
let payload;
try {
payload = await jwtVerifier.verify(token);
var groups = payload["cognito:groups"] || [];
// Build the principal entity (the user)
var userEntity = {
"identifier": {
"entityType": "MyApplication::User",
"entityId": payload["cognito:username"]
},
"attributes": {
"employmentStoreCode": {
"string": payload["custom:employmentStoreCode"] == null ? "" : payload["custom:employmentStoreCode"]
}
},
"parents": []
};
// Add groups as parent entities (for group membership)
groups.forEach((group) => {
userEntity.parents.push({
"entityType": "MyApplication::Group",
"entityId": group
});
});
// Build the authorization query
let entities = {
entityList: [userEntity]
};
// Add resource entities based on the action
addResourceEntities(entities, actionMap[event.httpMethod + event.resource], event.pathParameters);
// Call AVP
let authQuery = {
policyStoreId,
principal: { "entityType": "MyApplication::User", "entityId": payload["cognito:username"] },
action: { "actionType": "MyApplication::Action", "actionId": actionMap[event.httpMethod + event.resource] },
resource: buildResource(actionMap[event.httpMethod + event.resource], event.pathParameters),
entities
};
const authResult = await avp.isAuthorized(authQuery).promise();
if (authResult.decision == 'ALLOW') {
return buildResponse(200, 'Authorized', authResult);
} else {
return buildResponse(403, "You are not authorized to perform this action.", authResult);
}
} catch(err) {
console.log(err);
return buildResponse(403, "Error: " + err);
}
};

Ponto-chave: Seu código não contém nenhuma lógica de permissões. Ele apenas passa o contexto para o AVP e confia na resposta. Essa é exatamente a ideia.


O verdadeiro benefício — mudanças sem redeploy

É aqui que a mágica acontece.

Cenário: O negócio diz: "A partir do próximo mês, usuários com Store-Owner-Role também podem cancelar pedidos, mas apenas pedidos feitos há mais de 24 horas."

Com permissões tradicionais fixas no código:

  • Você modifica o código do Lambda
  • Você adiciona uma função getOrderAge()
  • Você escreve testes para a nova lógica
  • Você faz o commit, cria o PR e espera o code review
  • Alguém questiona sua lógica, você itera
  • Você faz o merge, o deploy em staging e testa
  • Você faz o deploy em produção
  • Se houver um bug, você faz deploy de novo enquanto o negócio perde dinheiro

Com o Amazon Verified Permissions:

  • Você entra no console do AVP
  • Você adiciona uma nova política Cedar (leva 2 minutos):
permit(
principal in MyApplication::Group::"Store-Owner-Role",
action == MyApplication::Action::"CancelOrder",
resource is MyApplication::Order
)
when {
principal.employmentStoreCode == resource.storeId &&
resource.age > 1440 // minutes
};
  • Clica em salvar
  • Está no ar. Imediatamente. Sem deploy. Sem mudança de código. Sem redeploy do Lambda.

É isso. Seu código não mudou. Seu Lambda não passou por redeploy. A regra de autorização foi atualizada em tempo real.


RBAC + ABAC: o melhor dos dois mundos

O Cedar permite combinar as duas abordagens:

RBAC sozinho: "O usuário está no grupo Admin?" (binário, não considera os atributos do recurso)

ABAC sozinho: "O atributo department do usuário corresponde ao atributo department do recurso?" (granular, mas exige muitos atributos)

RBAC + ABAC (a força do Cedar): "O usuário está no grupo Store-Owner-Role E o employmentStoreCode dele corresponde ao storeId do recurso?"

permit(
principal in MyApplication::Group::"Store-Owner-Role",
action == MyApplication::Action::"ManageInventory",
resource is MyApplication::Application
)
when {
principal.employmentStoreCode == resource.storeId
};

Isso te dá o melhor dos dois mundos: agrupamento baseado em papéis + granularidade baseada em atributos.


Testes e deploy

Com as políticas configuradas, teste-as:

  1. Como usuário Customer: Consegue executar SearchPets? ✅ Sim. Consegue executar GetStoreInventory? ❌ Não.
  2. Como usuário Store-Owner-Role: Consegue executar GetStoreInventory para a própria loja? ✅ Sim. Para uma loja diferente? ❌ Não.
  3. Como owner com employmentStoreCode=petstore-london: Consegue acessar os pedidos da petstore-london? ✅ Sim.

Se uma política não estiver funcionando, verifique:

  • As entidades estão estruturadas corretamente no Lambda?
  • O JWT do usuário contém os cognito:groups corretos?
  • O atributo personalizado está no perfil do usuário?
  • Os tipos de entidade da sua consulta de autorização correspondem ao seu schema?

Conclusão: do encanamento ao propósito

Minha intenção com este post não era apresentar o Amazon Verified Permissions como a bala de prata definitiva para a sua aplicação, mas se você está no meio de um processo interminável de ajustar o código da sua aplicação web baseada em papéis porque os requisitos de negócio mudam com muita frequência, talvez valha a pena parar, olhar o quadro geral e repensar o que você está fazendo.

Talvez este serviço possa te ajudar a economizar tempo e recursos e liberar seu tempo para focar no negócio em vez do encanamento. No fim das contas, tudo isso parece encanamento — ele sustenta os objetivos de negócio da sua aplicação, mas não ajuda de verdade a aumentar o valor dela.

Mas, pelo menos com o Amazon Verified Permissions, você não fica consertando o encanamento toda semana.