AWS IAM SDK obtendo todos os documentos de política para uma função específica ao usar as funções do grupo Cognito
Estou usando grupos de grupos de usuários do AWS Cognito para gerenciar permissões para uma API do API Gateway. Acredito que este seja um uso válido para Grupos, pois a documentação diz isso aqui:https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html#using-groups-to-control-permission-with-amazon-api-gateway.
Infelizmente, a documentação é basicamente inexistente para este caso de uso, tanto quanto eu posso dizer (além desse pequeno parágrafo). Estou tentando descobrir como isso funciona com uma função Lambda do autorizador API Gateway personalizada. Eu criei uma função de teste e atribuí isso a um grupo de teste no Cognito. A função tem uma única política anexada a ela, mas no futuro as funções terão várias políticas.
Agora, no meu autorizador personalizado, já estou validando um token de acesso, etc., e tudo está funcionando bem. Agora estou tentando adicionar esse controle de acesso refinado usando os grupos/funções/políticas. Instalei o IAM SDK e tenho pesquisado para ver quais chamadas preciso fazer. Parece que não há uma maneira fácil de conseguir um papel e todas as suas políticas. O melhor que consegui é o seguinte:
public async Task<IEnumerable<string>> GetGroupPermissionsForUserAsync(Models.User user)
{
if (!user.UserAttributes.TryGetValue(UserAttributeName.UserPoolId, out var userPoolId))
{
return null;
}
var groups = await GetUserGroups(user.Username, userPoolId);
var groupRoleArn = groups.FirstOrDefault()?.RoleArn;
var policies = new List<string>();
if (!string.IsNullOrWhiteSpace(groupRoleArn))
{
var roleName = groupRoleArn.Substring(groupRoleArn.IndexOf('/') + 1);
var rolePoliciesResponse = await _iamClient.ListAttachedRolePoliciesAsync(new ListAttachedRolePoliciesRequest { RoleName = roleName });
foreach (var rolePolicy in rolePoliciesResponse.AttachedPolicies)
{
var policyVersionsResponse = await _iamClient.ListPolicyVersionsAsync(new ListPolicyVersionsRequest
{
PolicyArn = rolePolicy.PolicyArn
});
var latestPolicyVerson = policyVersionsResponse.Versions.OrderByDescending(x => x.CreateDate).LastOrDefault();
var policyVersionResponse = await _iamClient.GetPolicyVersionAsync(new GetPolicyVersionRequest
{
PolicyArn = rolePolicy.PolicyArn,
VersionId = latestPolicyVerson.VersionId
});
if (!string.IsNullOrWhiteSpace(policyVersionResponse?.PolicyVersion.Document))
{
policies.Add(HttpUtility.UrlDecode(policyVersionResponse.PolicyVersion.Document));
}
}
}
return policies;
}
private async Task<IEnumerable<GroupType>> GetUserGroups(string username, string userPoolId)
{
string nextToken = null;
var groups = new List<GroupType>();
do
{
var response = await _cognitoClient.AdminListGroupsForUserAsync(new AdminListGroupsForUserRequest
{
Username = username,
UserPoolId = userPoolId
});
groups.AddRange(response.Groups);
nextToken = response.NextToken;
} while (!string.IsNullOrWhiteSpace(nextToken));
return groups.OrderBy(x => x.Precedence);
}
Como você pode ver, tenho que fazer várias ligações apenas para obter as políticas para uma função.
_cognitoClient.AdminListGroupsForUserAsync
para obter os grupos do usuário do Cognito._iamClient.ListAttachedRolePoliciesAsync
para obter as políticas associadas às funções._iamClient.ListPolicyVersionsAsync
para obter as versões para cada política._iamClient.GetPolicyVersionAsync
para obter a versão da política individual que finalmente contém o documento da política.
ListPolicyVersionsAsync
retorna uma resposta com uma propriedade de documento, mas é sempre null por algum motivo, daí a necessidade da GetPolicyVersionAsync
chamada extra.
Isso tudo não apenas adiciona latência (que é um problema em uma função do autorizador em que todas as chamadas para a API serão executadas por meio desse código), mas também me deixa com apenas um monte de políticas individuais que preciso de alguma forma desduplicar antes de retornar ao API Gateway.
Não há realmente uma maneira mais fácil / rápida de fazer isso? Existe uma maneira de obter todas essas informações com menos chamadas e de simplificar as políticas caso suas regras se sobreponham?
Respostas
Existem tantas maneiras diferentes de configurar a autorização com o Cognito e o API Gateway que é difícil dizer o que será melhor para você sem saber quais recursos da AWS você está tentando acessar por meio dos métodos do API Gateway. Por exemplo, seus métodos do API Gateway estão se integrando aos métodos lambda que acessam informações do banco de dados ou estão chamando serviços diretamente?
Se bem entendi, você deseja combinar as permissões para os (potencialmente) vários grupos aos quais um usuário pode pertencer para criar um único conjunto de permissões para usar para essa solicitação específica. Portanto, a user
função pode permitir o acesso aos dados do usuário e admin
pode estender ainda mais o acesso a outros terminais administrativos?
Supondo que você tenha um cenário bastante padrão em que seus métodos do API Gateway estão se integrando a um método lambda que está acessando recursos subjacentes da AWS, como um banco de dados, você pode usar autorizadores personalizados da seguinte maneira:
Isso simplifica muito as coisas se os autorizadores personalizados apenas retornarem as permissões necessárias para executar o método de API solicitado:
{
"principalId": "sub-from-ID-token",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": [
"arn:aws:execute-api:eu-west-1:1234567890:qwerty:prod:GET/user/address"
]
}
]
}
}
Em um cenário padrão, uma solicitação autorizada com êxito executaria o lambda que contém a lógica da API. Esse lambda de apoio teria uma função de execução que define todas as permissões para acessar recursos protegidos, como dados em tabelas do DynamoDB etc. várias funções de grupo, mas, em vez disso, você gerencia essas permissões em um único local em seu sistema.
Com essa configuração, você pode ter um Autorizador personalizado bastante simples para cada área de sua API que precisa de proteção.
Por exemplo, para proteger seus /user/*
endpoints, você pode ter um autorizador personalizado que apenas verifica se o token de ID passado para o autorizador contém o user
grupo em sua cognito:groups
declaração. Em caso afirmativo, você retorna as permissões necessárias para executar o método de API solicitado ( execute-api:Invoke
). Você pode então ter outro autorizador personalizado que protege suas /admin/*
rotas, verificando se o admin
grupo está na declaração do token de ID cognito:groups
, etc.
Armazenamento em cache de respostas personalizadas do autorizador
Cada autorizador personalizado tem uma configuração de TTL opcional, que determina por quanto tempo sua resposta (para um token JWT específico) será armazenada em cache. Isso ajuda a reduzir o tempo de aquecimento do lambda ou o tempo gasto por chamadas adicionais no autorizador.
Se um autorizador enfrentar vários métodos, por exemplo:
- GET /usuário/endereço
- POST /usuário/endereço
- GET /usuário/móvel
então, é importante observar que uma resposta autorizada com êxito será armazenada em cache para todos esses métodos até que o tempo limite do armazenamento em cache se esgote. Portanto, a política retornada pelo autorizador customizado deve retornar uma lista de recursos que definem todos os métodos que o usuário pode executar ou usar curingas.
Exemplo de resposta para um autorizador que cobre todas as /user/*
rotas
{
"principalId": "sub-from-ID-token",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": [
"arn:aws:execute-api:eu-west-1:1234567890:qwerty:prod:GET/user/address",
"arn:aws:execute-api:eu-west-1:1234567890:qwerty:prod:POST/user/address",
"arn:aws:execute-api:eu-west-1:1234567890:qwerty:prod:GET/user/mobile"
]
}
]
}
}
Resposta de exemplo (usando curingas) para um autorizador que cobre todas as /user/*
rotas
{
"principalId": "sub-from-ID-token",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": [
"arn:aws:execute-api:eu-west-1:1234567890:qwerty:prod:*/user/*"
]
}
]
}
}