6

I'm trying to generate a presigned link to API Gateway (that uses IAM authentication), so client may access one of my Lambda functions behind this API Gateway without authenticating request. This is mostly for client convenience, so it may use some links from response transparently, whether they points to the same authenticated API Gateway, some S3 bucket or any arbitrary URL in the Internet.

To do so, I crafting API signature v4 using query parameters (see docs and example)

So, if I try to sign following link scoped for us-west-2 region and execute-api service:

https://example.com/some/path?some=params

I will get following result (using Node.js aws4 library, but it doesn't matter here):

https://example.com/some/path?some=params&
X-Amz-Security-Token=<Session Token Removed>
X-Amz-Date=20210330T180303Z&
X-Amz-Algorithm=AWS4-HMAC-SHA256&
X-Amz-Credential=<Access Key Removed>%2F20210330%2Fus-west-2%2Fexecute-api%2Faws4_request&
X-Amz-SignedHeaders=host&
X-Amz-Signature=884f132ad6f0c7a850e6b1d22b5fed169c13e2189b6e0d0d568d11f967f4a8bd

And it works! But only first 5 minutes after generation…

After five minutes passed, I will get following error in response:

{"message":"Signature expired: 20210330T175821Z is now earlier than 20210330T180403Z (20210330T180903Z - 5 min.)"}

See this response to this question for more details.

I've tried to add X-Amz-Expires query parameter mentioned in the docs with various values (both less and greater than 300 seconds), but with no luck: behavior doesn't change.

I need at least a few hours, up to allowed 6 hours for links generated by IAM instance credentials, as links are being signed by another Lambda function.

Is there any way to increase pre-signed link validity duration for API Gateway access?

Envek
  • 4,426
  • 3
  • 34
  • 42

2 Answers2

13

This is a very interesting question! At first, I thought it is clearly documented in the S3 docs that X-Amz-Expires is supported by all services (including API Gateway). [1][2]

After some more research, it turned out that it is not so clear at all if services other than S3 support the X-Amz-Expires parameter.

There are various sources claiming that only S3 is respecting the parameter. The following is a statement by an AWS employee working on the aws-sdk for go:

The expires time is only relevant for the S3 service. Other services have their own fixed expiration time. Generally this is 15 minutes, but it looks like IoT data service uses a 5 minute expiration time. [3]

They followed up with:

The SDK doesn't have any metadata data available providing which services do or do not use the expiry value. [4]

Then adding a note into the corresponding source code on GitHub:

All other AWS services will use a fixed expiration time of 15 minutes. [5]

There are a ton of examples that show that AWS is using the parameter for the S3 service, e.g. [1][6]. However, there are also examples from AWS docs that show the use of the parameter for the IAM service, e.g. [7][8]. That is very confusing.

There is a comment by an SDE at AWS which is dated back to 2018 in which he makes the same confusing observation [9]:

If S3 is the only service that supports this header I agree that the SDK's documentation should be updated to reflect that - including a note in the description for this header in S3's SigV4 documentation stating that this header is exclusive to presigned URLs for this service would also be helpful.

FWIW I spoke to some folks from AWS Auth and the only service they know of using the header is S3 (interesting that you found a code sample using IAM). They suggested that the 15 minute expiration for STS presigned URLs would not be changing.

Another former AWS employee further noticed:

I was able to reproduce this behavior both on the AWS SDKs for Go and PHP with presigned URLs for EC2, IAM, STS, and Route 53. The only service I observed that invalidated a presigned URL after the time specified in the "x-amz-expires" header (instead of the default 15 minutes) was S3.

Thus, I guess it is not possible to increase pre-signed link validity duration for API Gateway access. I think that AWS did not design the signature signing algorithm to support your use case. I think that the S3 presigned URL action is one of the rare exceptions for which AWS allows an extended expiry period.

When looking at their motivation behind creating the signing algorithm the way it is, I noticed that they try to minimize the attack surface for replay attacks:

Protect against potential replay attacks
In most cases, a request must reach AWS within five minutes of the time stamp in the request. Otherwise, AWS denies the request. [10]

There are some more resources [11][12] that lead to the conclusion that letting customers choose lengthy expiry values would undermine the original security purpose of that parameter.

I think there is no generic way to create a presigned URL towards an AWS service's REST API and execute it far in the future.

If I were in your place, I would implement a custom authentication strategy using JWTs and API Gateway Lambda authorizers. [13] That way you can control the signing algorithm and particulary its expiration time on your own. I want to add that JWTs are URL-safe in the same way AWS signature query string parameters are. [14]

[1] https://docs.aws.amazon.com/AmazonS3/latest/API/sigv4-query-string-auth.html
[2] https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
[3] https://github.com/aws/aws-sdk-go/issues/2304#issuecomment-441755864
[4] https://github.com/aws/aws-sdk-go/issues/2304#issuecomment-441758599
[5] https://github.com/aws/aws-sdk-go/blob/6212dfa8032336d438c526c086918c8d2ceb6432/aws/request/request.go#L310
[6] https://github.com/mhart/aws4/blob/master/aws4.js#L130
[7] https://docs.aws.amazon.com/general/latest/gr/sigv4-signed-request-examples.html
[8] https://docs.aws.amazon.com/general/latest/gr/sigv4-add-signature-to-request.html
[9] https://github.com/aws/aws-sdk-go/issues/2167#issuecomment-428764319
[10] https://docs.aws.amazon.com/general/latest/gr/signing_aws_api_requests.html
[11] https://aws.amazon.com/de/articles/making-secure-requests-to-amazon-web-services/?nc1=h_ls (section "Replay Attacks")
[12] https://stackoverflow.com/a/12267408/10473469
[13] https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-use-lambda-authorizer.html
[14] https://stackoverflow.com/a/56273952/10473469

Martin Löper
  • 6,471
  • 1
  • 16
  • 40
  • What an amazing research! Thank you! Sadly that this just confirms that there is no solution, probably. – Envek Apr 06 '21 at 14:13
  • At least no built-in solution ;) If you want, we could chat about your specific use case and how a "Cognito - JWT - Lambda Authorizer" solution might look like. I believe your use case can be covered by bringing some more AWS services into the game than just plain IAM and STS. – Martin Löper Apr 06 '21 at 14:46
  • Lambda also uses 5 minute expiration time. – jellycsc Apr 06 '23 at 02:34
3

I was struggling with the same for one of the use-cases. It was like a user wanted to access a CSV file within S3 for some limited amount of time.

I ended up building the following architecture:

  • One lambda function to handle the "user access request". This was sitting behind an API gateway, and took care of putting the CSV file into S3 (this piece is probably not needed for you). And the Lambda was generating a URL with some hashing in place that triggered another lambda (#2) behind API GW.
  • The second lambda I had was behind API GW too, and could be triggered by the URL generated from the first lambda. This was where the user came into place, because the URL from the first lambda was given to the actual user to click on it. What the second lambda did is simple. It generated a 6h long pre-signed URL on the fly for the S3 object, and sent back an HTTP 302 with the pre-signed URL, so the user's browser immediately downloads the CSV.

This way, pretty much the lambda generated URL could be shared with anyone, and the URL would work until the S3 object is there.

If you need to have some kind of policy to have the URL accessible for 30 days or something, you could set up a bucket policy to get rid of the object afterwards.

Arnold Galovics
  • 3,246
  • 3
  • 22
  • 33