S3 Signed Url's expiring before argument passed - amazon-web-services

I am trying to generate a signed URL for S3 bucket objects with the maximum expiration of 604800 seconds or 7 days. However, after testing I discovered that the links expire in under 24hrs. Doing some digging I came across this article claiming that the 7 day expiration is only available if the aws-sdk is authorized with an IAM user and the s3 library is making use of AWS Signature v4.
I am definitely using v4: exports.getS3 = () => new AWS.S3({region : 'us-east-1', signatureVersion: 'v4'})
Additionally, as far as I can tell, the lambdas deployed via serverless should default to my IAM user credentials when making use of the sdk without any other manipulation: const AWS = require('aws-sdk')
Here is the aforementioned article : https://aws.amazon.com/premiumsupport/knowledge-center/presigned-url-s3-bucket-expiration/
I also defined the IAM role delegated to my user to enable access to s3 iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:*
Resource: "*"
- Effect: Allow
Action:
- ssm:*
Resource: "*"
- Effect: Allow
Action:
- s3:*
Resource: "*"
I've verified that it is not something as asinine as passing the wrong argument exports.getSignedURL = (key,bucket,method,expiration) =>{
console.log(`GETTING SIGNED URL WITH EXPIRATION ${expiration}`)
return new Promise((resolve, reject) => {
exports.getS3().getSignedUrl(method,{
Bucket: bucket,
Key : key,
Expires : expiration
},(err,url)=>err?reject(err):resolve(url))
});
}
Has anybody encountered this issue or have any ideas what may be causing my problem? Is there some configuration I am missing?

Lambda functions deployed with serverless do not default to your IAM user credentials, as far as I know. They use the IAM role/policy that you supply in serverless.yml, plus basic CloudWatch Logs permissions which are auto-generated by serverless
The problem is that your Lambda function is using temporary credentials from STS (via an assumed IAM role) to generate the pre-signed URL. The URL will expire when the temporary session token expires (or earlier if you explicitly indicate an earlier timeout).
If you use IAM user credentials, rather than temporary credentials via an IAM role, you can extend the expiration to 7 days (with signature v4) or end of epoch (with the potentially deprecated signature v2). So, you need to supply your Lambda function with IAM user credentials, possibly through environment variables or AWS Parameter Store or AWS Secrets Manager.
For more, see Why is my presigned URL for an Amazon S3 bucket expiring before the expiration time that I specified?
Also, there are a couple of minor coding issues here:
all AWS methods have a .promise() option to return a promise, so no need to use callbacks and no need to manually create Promise objects
while the getSignedUrl method offers an asynchronous option, the operation itself is synchronous so you should simply run const url = s3.getSignedUrl(...)

Related

Executing AWS Lambda with assumed role with web identity

Is it possible for Lambda to be executed via API Gateway with an assumed role on behalf of a Cognito authenticated user?
Right now I'm doing the role assumption manually, from within the Lambda code:
const assumedRole = await sts.send(new AssumeRoleWithWebIdentityCommand({
RoleArn: 'some role ARN',
RoleSessionName: event.requestContext.authorizer.jwt.claims['cognito:username'],
WebIdentityToken: event.headers.authorization
}));
But I would like to avoid that, and have the Lambda be executed with this assumed role already.
What I am having difficulty with is figuring out what IAM Role and policy is required to achieve that.
Thanks!
You can pass cognito token from header of your request to the lambda and then make a get get credentials for identity call. That would be the best approach for assuming role of your cognito user dynamically. If you have configured cognito to grant role based on token you don't even need to explicitely provide a role arn in the call.

AWS Lambda/Secrets Manager permission no longer working (Pulumi)

This stack was working at one point... I'm not sure what's going on. This permission is no longer doing what it did before, or has become invalid.
I have a Lambda function that rotates a Secret, so naturally it must be triggered by Secrets Manager. So I built up the Permission as follows
import * as aws from '#pulumi/aws'
export const accessTokenSecret = new aws.secretsmanager.Secret('accessTokenSecret', {});
export const smPermission = new aws.lambda.Permission(`${lambdaName}SecretsManagerPermission`, {
action: 'lambda:InvokeFunction',
function: rotateKnacklyAccessTokenLambda.name,
principal: 'secretsmanager.amazonaws.com',
sourceArn: accessTokenSecret.arn,
})
And the Policy,
{
Action: [
'secretsmanager:GetResourcePolicy',
'secretsmanager:GetSecretValue',
'secretsmanager:DescribeSecret',
'secretsmanager:ListSecrets',
'secretsmanager:RotateSecret',
],
Resource: 'arn:aws:secretsmanager:*:*:*',
Effect: 'Allow',
},
Running pulumi up -y yields
aws:secretsmanager:SecretRotation (knacklyAccessTokenRotation):
error: 1 error occurred:
* error enabling Secrets Manager Secret "" rotation: AccessDeniedException: Secrets Manager cannot invoke the specified Lambda function. Ensure that the function policy grants access to the principal secretsmanager.amazonaws.com.
This error confuses me, because the Policy created for the Lambda will not accept the Principal param (which makes sense, the same behaviour happens in the AWS Console), so I'm sure they mean Permission instead of Policy.
Based on the log I can tell that the Permission is being created way after the Lambda/Secrets Manager is, I'm not sure if this is a Pulumi issue similar to how it destroys stacks in the incorrect order (Roles and Policies for example).
I can see the Permission in the AWS Lambda configuration section, so maybe it's ok?

AWS S3 signed url - X-Amz-Security-Token expires too early

I am in this situation where I need to have a pre-signed url to live for around a month. And since the signature v4 isn't able to deliver this, I've decided to use the V2 for now.
I have set the expiraten to one month but for some reason it expires
after 1 day? (don't know the exact time it expires could be within the same day)
<Code>ExpiredToken</Code>
<Message>The provided token has expired.</Message>
And as I digged further into this, It looked like the issue could be with the X-Amz-Security-Token which expires too early. But I've no idea how to set a value to this header? (couldnt find anything about it)
Setup:
Its a lambda function which generates a signed url to fetch a file from the S3. Everything is done through cloudformation. And done with the JavaScript SDK
const s3 = new AWS.S3({
signatureVersion: 'v2',
region: 'eu-west-1'
});
const bucketParam = {
Bucket: 'test-bucket',
Key: 'testFile-1111-2222',
Expires: 2592000
};
Any help would be appreciated
I believe the IAM role used by Lambda is using temporary credentials, which expire before the link. According to AWS, you need to generate the presigned URL with an IAM user and signatureVersion = 4 for the link to expire after 7 days:
To create a presigned URL that's valid for up to 7 days, first designate IAM user credentials (the access key and secret access key) to the SDK that you're using. Then, generate a presigned URL using AWS Signature Version 4.
See Why is my presigned URL for an Amazon S3 bucket expiring before the expiration time that I specified? for more details
You should try creating an IAM user to generate those URLs, and actually use its credential and assume its role (using STS) in the Lambda function in order to generate the URL. And don't forget to use signatureVersion='s3v4'.
Hope this helps
The policy "expiration" field cannot be more than 7 days beyond the "x-amz-date" field.
I found no way around this. This seems broken or at least poorly documented.
The workaround seems to be to set "x-amz-date" in the future. While not intuitive this seems to be allowed, which enables you to set the expiration further in the future.

How to get the identity ID of the Cognito users in AWS Lambda

In my AWS project, I use API Gateway to create APIs that call lambda functions. The APIs are called by an Android application. I use a Cognito user pool and a Cognito Identity pool to manage my users (authenticated and unauthenticated).
In my lambda functions, I need to get the identity ID of the users. After some research, I saw that to achieve that, I need to check Invoke with caller credentials in the Integration Request of my API.
Unfortunately, when I call my API, I got an error 500, with the following log: Execution failed due to configuration error: Invalid permissions on Lambda function. Apparently, it's because the Cognito identity pool role doesn't have the lambda invoke permissions for the backend lambda.
So in order to be able to get the identity id in my lambda function, how can I add those permissions and, if possible, what is the CloudFormation syntax to add those permissions?
Thanks for your help.
Select the AWS Lambda (in AWS UI) which you are trying to fix and scroll down to the Execution role part. You need to create a new role that has permission to access the Cognito Identity pool and sett that role for Lambda and save it.
In AWS API, have you added the authorizer which sets permission (allow/deny) to access the API.
If your users are in Cognito User Pool, there is no need of API gateway, Lambda, etc. Simply use AWS Amplify and everything is baked into the framework.
https://aws-amplify.github.io/docs/js/authentication
Robin
Here is what was missing in my CloudFormation template, just add it in the Cognito Identity Pool policy (or policies if you're dealing with unauthenticated users):
- Effect: 'Allow'
Action:
- 'lambda:InvokeFunction'
Resource:
Fn::Join:
- ''
-
- 'arn:aws:lambda:'
- Ref: AWS::Region
- ':'
- Ref: AWS::AccountId
- ':function:*'

AWS Lambda : Even after STS:AssumeRole was successful, the lambda function still uses the old IAM role

I have created a new IAM role, which has access (scan/Query) to specific DynamoDb tables.
I am trying to use STS Assume Role API call from my lambda function, so that the lambda function gets access to the specific Dynamo Db tables.
The Assume Role call was successful, I got the role ID, AccesskeyId, Secret Access Key and Session Token.
When I make a call from my lambda function, to access the Dynamo DB, I am getting an error
AccessDeniedException: User:
arn:aws:sts::>:assumed-role/ota-dev-us-east-1-lambdaRole/
is not authorized to perform: dynamodb:Scan on resource:
arn:aws:dynamodb:us-east-1:>:table/<>
My question is, even after the Role Assume call was successful in the Lambda function, why the lambda function was still using the older role to access the Dynamo DB?
I was expecting the Lambda function to assume the new role, but from the logs it looks like, it is using still the older role.
Looks like I am missing some steps in between.
The STS AssumeRole call, depending how you trigger it, does not automatically refresh credentials in the AWS.config global object of the SDK.
You need to retrieve the access key, session key and session token returned by AssumeRole and pass it to your global AWS credentials SDK object.
The exact code will depend on the programming language you are using, here is the doc for Python's Boto3
https://boto3.amazonaws.com/v1/documentation/api/latest/guide/configuration.html
On a side note, I wonder why you do not give permanent access to your DynamoDB table in the Lambda execution role. Is this to limit the function reach and give fine grained access control at runtime, based on caller's identity ?