AWS: Restrict Cognito Authorized User to specific Lambda Functions - amazon-web-services

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' ]

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));
}
});
}

Related

CognitoIdentityCredentials is not authorized to perform ssm:GetParameter on resource: using #aws-sdk v3

I am trying to get parameters in AWS Parameter Store using #aws-sdk v3 with cognito unauthenticated identity pool like this:
import { SSMClient, GetParameterCommand } from "#aws-sdk/client-ssm";
import { fromCognitoIdentityPool } from "#aws-sdk/credential-providers";
const REGION = 'us-east-1'
const ssm = new SSMClient({
region: REGION,
credentials: fromCognitoIdentityPool({
clientConfig: { region: REGION },
identityPoolId: 'us-east-1:xxxx...'
})
});
const input = {
Name: '/config'
}
const response = await ssm.send(new GetParameterCommand(param));
But I am receiving this message:
AccessDeniedException: User:
arn:aws:sts::xxxxxxxxxxxx:assumed-role/Cognito_CredentialsUnauth_Role/CognitoIdentityCredentials
is not authorized to perform: ssm:GetParameter on resource:
arn:aws:ssm:us-east-1:xxxxxxxxxxxx:parameter/config because no session
policy allows the ssm:GetParameter action
My cognito role is:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"mobileanalytics:PutEvents",
"cognito-sync:*",
"ssm:GetParameters",
"ssm:GetParameter"
],
"Resource": "*"
}
]
}
I talk to aws support and they sad that my role is right, and the problem is in my session policy. But I don't understand what I need to do

Identity Pool Role Can't Access S3 Bucket Access Point

Summary: I am using AWS Amplify Auth class with a pre-configured Cognito User Pool for authentication. After authentication, I am using the Cognito ID token to fetch identity pool credentials (using AWS CredentialProviders SDK) whose assumed role is given access to an S3 access point. I then attempt to fetch a known object from the bucket's access point using the AWS S3 SDK. The problem is that the request returns a response of 403 Forbidden instead of successfully getting the object, despite my role policy and bucket (access point) policy allowing the s3:GetObject action on the resource.
I am assuming something is wrong with the way my policies are set up. Code snippets below.
I am also concerned I'm not getting the right role back from the credentials provider, but I don't allow unauthenticated roles on the identity pool so I am not sure, and I don't know how to verify the role being sent back in the credentials' session token to check.
I also may not be configuring the sdk client objects properly, but I followed the documentation provided to the best of my understanding from the documentation (I am using AWS SDK v3, not v2, so slightly different syntax and uses modular imports)
Backend Configurations - IAM
Identity Pool: Authenticated Role Trust Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": [
"sts:AssumeRoleWithWebIdentity",
"sts:TagSession"
],
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "<MY_IDENTITY_POOL_ID>"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
Identity Pool: Authenticated Role S3 Access Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::<MY_ACCESS_POINT_NAME>/object/*"
}
]
}
Backend Configurations - S3
S3 Bucket and Access Points: Block All Public Access
S3 Bucket CORS Policy:
[
{
"AllowedHeaders": [
"*"
],
"AllowedMethods": [
"GET",
"PUT",
"HEAD"
],
"AllowedOrigins": [
"*"
],
"ExposeHeaders": [],
"MaxAgeSeconds": 300
}
]
S3 Bucket Policy (Delegates Access Control to Access Points):
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "DelegateAccessControlToAccessPoints",
"Effect": "Allow",
"Principal": {
"AWS": "*"
},
"Action": "*",
"Resource": [
"arn:aws:s3:::<MY_BUCKET_NAME>",
"arn:aws:s3:::<MY_BUCKET_NAME>/*"
],
"Condition": {
"StringEquals": {
"s3:DataAccessPointAccount": "<MY_ACCT_ID>"
}
}
}
]
}
Access Point Policy
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowAccessPointToGetObjects",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<ACCT_ID>:role/<MY_IDENTITY_POOL_AUTH_ROLE_NAME>"
},
"Action": [
"s3:GetObject",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:<REGION>:<ACCT_ID>:accesspoint/<MY_ACCESS_POINT_NAME>/object/*"
}
]
}
Front End AuthN & AuthZ
Amplify Configuration of User Pool Auth
Amplify.configure({
Auth: {
region: '<REGION>',
userPoolId: '<MY_USER_POOL_ID>',
userPoolWebClientId: '<MY_USER_POOL_APP_CLIENT_ID>'
}
})
User AuthZ process:
On user login event, call Amplify's Auth.signIn() which returns type CognitoUser:
// Log in user (error checking ommitted here for post)
const CognitoUser = await Auth.signIn(email, secret);
// Get ID Token JWT
const CognitoIdToken = CognitoUser.signInUserSession.getIdToken().getJwtToken();
// Use #aws-sdk/credentials-provider to get Identity Pool Credentials
const credentials = fromCognitoIdentityPool({
clientConfig: { region: '<REGION>' },
identityPoolId: '<MY_IDENTITY_POOL_ID>',
logins: {
'cognito-idp.<REGION>.amazonaws.com/<MY_USER_POOL_ID>': CognitoIdToken
}
})
// Create S3 SDK Client
client = new S3Client({
region: '<REGION>',
credentials
})
// Format S3 GetObjectCommand parameters for object to get from access point
const s3params = {
Bucket: '<MY_ACCESS_POINT_ARN>',
Key: '<MY_OBJECT_KEY>'
}
// Create S3 client command object
const getObjectCommand = new GetObjectCommand(s3params);
// Get object from access point (execute command)
const response = await client.send(getObjectCommand); // -> 403 FORBIDDEN

AWS Amplify Storage.get() - AWSAccessKeyId is undefined

I'm struggling with a react native project utilizing AWS amplify, both Auth and Storage.
Calling Storage from this service file in my project:
import Amplify, {Auth, Storage} from 'aws-amplify'
import config from './config'
Amplify.configure({
Auth: config.Auth,
Storage: config.Storage,
})
export {
Auth,
Storage,
}
config is:
AWS: {
Auth: {
region: 'us-east-X',
userPoolId: 'us-east-XXXXXXX',
userPoolWebClientId: 'XXXXXXXXX',
identityPoolId: 'us-east-X:XXXXXXXXXXXXXX',
},
Storage: {
AWSS3: {
bucket: 'XXXXXXX',
region: 'us-east-X',
},
},
},
By this point, the user has authenticated with Auth. Calling:
Storage.get('public/fileName.gif', { expires: 120 })
Results in a signed URL that appears to be missing the access key, which it should be generating from the IAM logged in user.
Here's an example signed URL it generates:
https://expyhealth-stg.s3.amazonaws.com/public/activityImages/Ankle%20Pumps.gif?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=undefined%2F20201020%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20201020T204616Z&X-Amz-Expires=120&X-Amz-Signature=f40bad142a9b190f9d9959bb9db0ad077c0cecab5171098f033a700cb9aa45b5&X-Amz-SignedHeaders=host&x-amz-user-agent=aws-sdk-js-v3-react-native-%40aws-sdk%2Fclient-s3%2F1.0.0-gamma.8%20aws-amplify%2F3.6.0%20react-native&x-id=GetObject"
Notice the X-Amz-Credential=undefined
I've been following the thread here for hours and keep coming up short. I cannot determine why it isn't generating the access key.
Using aws-amplify version 3.3.4
Here is the bucket policy:
{
"Version": "2012-10-17",
"Id": "Policy1599854584652",
"Statement": [
{
"Sid": "Stmt1599854581275",
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::XXXXXXXXXXXX:role/Cognito_ExpyHealthStagingAuth_Role"
},
"Action": "s3:GetObject",
"Resource": [
"arn:aws:s3:::XXXXXXXX",
"arn:aws:s3:::XXXXXXXX/*"
]
}
]
}
I solve a similar issue by editing the IAM trust relationship as follows:
Open the AWS console
Go to IAM
Go to roles
Select the role specified as the Authenticated role of your identity pool
Open "Trust Relationship" tab
Click "Edit trust relationship"
Paste the following:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "cognito-identity.amazonaws.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "eu-west-1:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
}
]
}
Tip: compare your configuration with the one generated by the AWS Amplify CLI.

IAM Policy variables not recognised - ${cognito-identity.amazonaws.com:sub}

I am trying to create a solution where every client that will use my service will have a sqs (which is in my AWS account). So in order that the client will be able to send messages and read messages from the queue, I want to use cognito with a single role that has variables, as there is a limitation on the number of roles that a single account can have.
I have created cognito user pool with an application, also created federated identity, role, policy and linked everything together.
the policy is
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:DeleteMessageBatch",
"sqs:SendMessageBatch"
],
"Resource": [
"arn:aws:sqs:us-east-1:XXXX:test-${cognito-identity.amazonaws.com:sub}",
"arn:aws:sqs:us-east-1:XXXX:test"
]
}
]
}
the test client code is
const cognitoUser = userPool.getCurrentUser();
cognitoUser.getSession((err, session) => {
console.log(`session token: ${session.getIdToken().getJwtToken()}`);
const paramsCredentials = {
IdentityPoolId: 'XXXX',
Logins: {}
};
AWS.config.region = 'XXXX';
paramsCredentials.Logins[
`cognito-idp.${AWS.config.region}.amazonaws.com/XXXX`
] = session.getIdToken().getJwtToken();
AWS.config.credentials = new AWS.CognitoIdentityCredentials(
paramsCredentials
);
AWS.config.credentials.get(err => {
if (err) {
console.log(`got error - getting credentials. error: ${err}`);
}
const id = AWS.config.credentials.identityId;
console.log('Cognito Identity ID ' + id);
const sqs = new AWS.SQS({
region: AWS.config.region
});
const params = {
QueueName: 'test-9ea2b895-2971-4ee2-b372-451bf2b19731'
};
sqs.getQueueUrl(params, (err, data) => {
if (err) {
console.log(`got error getting url for queue, error: ${err}`);
} else {
console.log(`SQS url = ${data.QueueUrl}`);
}
});
});
});
and I am getting an error of
AWS.SimpleQueueService.NonExistentQueue: The specified queue does not exist or you do not have access to it.
Blockquote
But when I change the queue to the test one, all is working fine. I have double checked the sub and it is the correct id
What did i do wrong?
${cognito-identity.amazonaws.com:sub} IAM policy variable will return region:uuid your queue name will be test-us-east-1:9ea2b895-2971-4ee2-b372-451bf2b19731 which is a invalid SQS queue name(colon not allowed). So, it is not possible to restrict access to a queue named after that identity but you can create a policy limited to only a specific set of users of your application
Here is a blog from AWS on Understanding Amazon Cognito Authentication Part 3: Roles and Policies
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"sqs:DeleteMessage",
"sqs:GetQueueUrl",
"sqs:DeleteMessageBatch",
"sqs:SendMessageBatch"
],
"Resource": [
"arn:aws:sqs:us-east-1:XXXX:test"
]
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:sub": [
"us-east-1:12345678-1234-1234-1234-123456790ab"
]
}
}
}
]
}

AWS IoT MQTT over Websocket with STS temporary credentials

I am having issues using the temporary credentials to initiate a connection to AWS IoT using STS temporary credentials, whilst keeping things secure.
I have already successfully connected embedded devices using certificates with policies.
But when I come to try connecting via the browser, using a pre-signed URL, I have hit a stumbling block.
Below is a code snippet from a Lambda function which first authenticates the request (not shown), and then builds the url using STS credentials via assumeRole.
Using my generated URL along with Paho javascript client, I have been successful up to the point of receiving a response of "101 Switching Protocols" in the browser. But the connection is terminated instead of switching to websockets.
Any help or guidance anyone out there can provide me with would be much appreciated.
const iot = new AWS.Iot();
const sts = new AWS.STS({region: 'eu-west-1'});
const params = {
DurationSeconds: 3600,
ExternalId: displayId,
Policy: JSON.stringify(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:*"
],
"Resource": [
"*"
]
},
/*{
"Effect": "Allow",
"Action": [
"iot:Connect"
],
"Resource": [
"arn:aws:iot:eu-west-1:ACCID:client/" + display._id
]
},
{
"Effect": "Allow",
"Action": [
"iot:Receive"
],
"Resource": [
"*"
]
}*/
]
}
),
RoleArn: "arn:aws:iam::ACCID:role/iot_websocket_url_role",
RoleSessionName: displayId + '-' + Date.now()
};
sts.assumeRole(params, function(err, stsData) {
if (err) {
fail(err, db);
return;
}
console.log(stsData);
const AWS_IOT_ENDPOINT_HOST = 'REDACTED.iot.eu-west-1.amazonaws.com';
var url = v4.createPresignedURL(
'GET',
AWS_IOT_ENDPOINT_HOST,
'/mqtt',
'iotdata',
crypto.createHash('sha256').update('', 'utf8').digest('hex'),
{
key: stsData.Credentials.AccessKeyId,
secret: stsData.Credentials.SecretAccessKey,
protocol: 'wss',
expires: 3600,
region: 'eu-west-1'
}
);
url += '&X-Amz-Security-Token=' + encodeURIComponent(stsData.Credentials.SessionToken);
console.log(url);
context.succeed({url: url});
});
Edit: If it helps, I just checked inside the "Frames" window in Chrome debugger, after selecting the request which returns a 101 code. It shows a single frame: "Binary Frame (Opcode 2, mask)".
Does this Opcode refer to MQTT control code 2 AKA "CONNACK"? I am not an expert at MQTT (yet!).
I realised my mistake by reading the docs on STS.
If you pass a policy to this operation, the temporary security credentials that are returned by the operation have the permissions that are allowed by both the access policy of the role that is being assumed, and the policy that you pass.
The RoleARN that is supplied must also allow the actions that you are requesting via STS assumeRole.
i.e. The RoleARN could allow iot:*, then when you assume role, you can narrow the permissions down to, for instance iot:Connect and for specific resources.