I am using AWS Cognito User Pool groups to manage permissions for an API Gateway API. I believe this is a valid use for Groups as the documentation says so here: https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-user-groups.html#using-groups-to-control-permission-with-amazon-api-gateway.
Unfortunately, the documentation is basically non-existent for this use case as far as I can tell (apart from that small paragraph). I am trying to figure out how this works with a custom API Gateway authoriser Lambda function. I have created a test role and assigned this to a test group in Cognito. The role has a single policy attached to it but in future the roles will have several policies.
Now, in my custom authoriser I am validating an access token etc already and that's all working fine. I am now trying to add this fine-grained access control in using the groups/roles/policies. I have installed the IAM SDK and have been digging around to see which calls I need to make. It seems that there is no easy way to get a role and all it's policies. The best I have come up with is the following:
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);
}
As you can see, I have to make a bunch of calls just to get the policies for a role.
_cognitoClient.AdminListGroupsForUserAsyncto get the user's groups from Cognito._iamClient.ListAttachedRolePoliciesAsyncto get the policies associated with the roles._iamClient.ListPolicyVersionsAsyncto get the versions for each policy._iamClient.GetPolicyVersionAsyncto get the individual policy version which finally has the policy document.
ListPolicyVersionsAsync returns a response with a document property but it's always null for some reason, hence the need for the extra GetPolicyVersionAsync call.
Not only does this all add latency (which is an issue on an authoriser function where every call to the API is going to be running through this code) but it leaves me with just a bunch of individual policies which I need to somehow de-dupe before returning to API Gateway.
It there really no easier/faster way to do this? Is there a way to get all this information with fewer calls and is there a way to flatten the policies in case their rules overlap?