AWS: Restrict Cognito Authorized User to specific

2019-03-04 12:27发布

问题:

I'm working with AWS and I've the following setup: UserPool; API Gateway, Lambda Functions

The api gateway is using a UserPool authorizer to protect the lambda functions. This is working so far. Now I want to restrict every lambda function to a specific group of users. Therefore I've created two user groups in the CognitoPool (user and admin) and I've assigned a specific role to each group with a policy. Afterwards I've created a user in the UserPool and added him to the user group. That user is still able to submit requests to each route/lambda function.

How do I submit a request?

  • Postman
  • set IdToken (of the authenticated user) in the Authorization header
  • without Authorization header the response is a 401 (as expected)
  • with Authorization header every lambda function can be triggered (not expected)

Configuration of the UserPool Groups:

  • Group User:

    • Arn: Role ARN: arn:aws:iam::xxxxxx:role/User
    • UserRole is specified as

      {
        "Version": "2012-10-17",
        "Statement": [
          "Action": [
            "lambda:InvokeFunction",
            "lambda:InvokeAsync"
          ],
         "Resource": [
           "arn:aws:lambda:region:xxxxxx:function:api-dev-getItems
          ],
          "Effect": "Allow"
        ]
      }
      
  • Group Admin:

    • Arn: Role ARN: arn:aws:iam::xxxxxx:role/Admin
    • AdminRole is specified as

      {
        "Version": "2012-10-17",
        "Statement": [
            "Action": [
            "lambda:InvokeFunction",
            "lambda:InvokeAsync"
          ],
          "Resource": [
             "arn:aws:lambda:region:xxxxxx:function:api-dev-getItems
             "arn:aws:lambda:region:xxxxxx:function:api-dev-getUsers
          ],
          "Effect": "Allow"
        ]
      }
      

The payload of the id token also contains: 'cognito:roles': [ 'arn:aws:iam::xxxxxx:role/User' ]

回答1:

So I've found a solution to my problem. Here is the summary of my experiences:

  • Cognito Authorizer is more like a yes/no authorizer (authenticated or not; user groups are not evaluated)
  • Therefore I went with AWS IAM Authorizer in the API Gateway, which will evaluate the user group roles
  • Instead of a JWT the AWS signature v4 authorization has to be passed (there is a plugin for postman and several packages on npm)
  • Since I am using an API Gateway I had to change the role policy resources to execute-api:Invoke

In detail:

UserRole:

{
  "Version": "2012-10-17",
  "Statement": [
    "Action": [
      "lambda:InvokeFunction",
      "lambda:InvokeAsync"
    ],
   "Resource": [
     "arn:aws:execute-api:region:accountid:api-id/stage/GET/items
    ],
    "Effect": "Allow"
  ]
}

AdminRole:

{
  "Version": "2012-10-17",
  "Statement": [
    "Action": [
      "lambda:InvokeFunction",
      "lambda:InvokeAsync"
    ],
   "Resource": [
     "arn:aws:execute-api:region:accountid:api-id/stage/GET/items
     "arn:aws:execute-api:region:accountid:api-id/stage/*/users
    ],
    "Effect": "Allow"
  ]
}

Instead of passing the ID Token into the Authorization header, I had to use Postman AWS Signature, which requires at least an AccessKey and a SecretKey. Those two can be retrieved when I sign in my user using the aws-sdk. aws-sdk-js with TypeScript as example:

import { CognitoUserPool, CognitoUser, AuthenticationDetails } from 'amazon-cognito-identity-js';

const userPool = new CognitoUserPool({
  UserPoolId: 'my pool id',
  ClientId: 'my client id'
});

function signIn(username: string, password: string) {
  const authData = {
    Username: username,
    Password: password,
  };

  const authDetails = new AuthenticationDetails(authData);
  const userData = {
    Username: username,
    Pool: userPool,
  };

  const cognitoUser = new CognitoUser(userData);
  cognitoUser.authenticateUser(authDetails, {
    onSuccess: (result) => {
      const cognitoIdpKey = `cognito-idp.${region}.amazonaws.com/${userPool.getUserPoolId()}`;

      const credentials = new AWS.CognitoIdentityCredentials({
        IdentityPoolId: 'identity pool id,
        Logins: {
          [cognitoIdpKey]: result.getIdToken().getJwtToken(),
        }
      });
      AWS.config.update({
        credentials,
      });

      credentials.refreshPromise()
        .then(() => {
          console.log('Success refresh. Required data:', (credentials as any).data.Credentials);
        })
        .catch(err => console.error('credentials refresh', err));
    }
  });
}