{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
}
I. Current issue
💥 A guy with same problem posted a question at this link:
https://repost.aws/questions/QUbHCI9AfyRdaUPCCo_3XKMQ/lambda-function-url-behind-cloudfront-invalidsignatureexception-only-on-post
Describe:
-
He using CloudFront in front of Lambda Function URL, but he can only using GET method, POST/PUT request is rejected
Analysis problem:
⚠️ Lambda URL have 2 type of authentication:
-
NONE
: anyone with URL can access function -
AWS_IAM
: require signed-header
If you are using AWS_IAM, all user can’t directly access Lambda URL. It require signed-header as x-amz-content-sha256
II. Solution
Solution 1: Create signed-header with boto3 session
The custom header is add manually from client side.
Note that with this solution, using CloudFront is optional as we do nothing to sign header at CloudFront side, we do it from client side.
CloudFront in this case only for CDN purpose.
Using CloudFront, you can using GET method even though you don’t have
x-amz-content-sha256
in header. But if you call directly to Lambda URL, it will reject all METHODs immediately.
Here is how you can sign request:
Step 1. Create boto3 session to sign header
import boto3
from botocore import crt, awsrequest
class SigV4ASign:
def __init__(self, boto3_session=boto3.Session()):
self.session = boto3_session
def get_headers(self, service, region, aws_request_config):
sigV4A = crt.auth.CrtS3SigV4AsymAuth(self.session.get_credentials(), service, region)
request = awsrequest.AWSRequest(**aws_request_config)
sigV4A.add_auth(request)
prepped = request.prepare()
return prepped.headers
def get_headers_basic(self, service, region, method, url):
sigV4A = crt.auth.CrtS3SigV4AsymAuth(self.session.get_credentials(), service, region)
request = awsrequest.AWSRequest(method=method, url=url)
sigV4A.add_auth(request)
prepped = request.prepare()
return prepped.headers
Step 2: Using header in our request
from sigv4a_sign import SigV4ASign
import requests
service = 'lambda'
region = '*'
method = 'GET'
url = 'https://4xmze5deqxjjy4ltw2ze3h7gr40tlvcp.lambda-url.us-east-1.on.aws'
headers = SigV4ASign().get_headers_basic(service, region, method, url)
r = requests.get(url, headers=headers)
print(f'status_code: {r.status_code} \nobject text: {r.text}')
Solution 2: Bypass signed-header by create LambdaEdge to assign token at CloudFront
With this solution, all traffic is signed at CloudFront by LambdaEdge no matter who send the request. It means that everyone can call Lambda URL through CloudFront distribution domain.
It only helpful to prevent traffic go directly to Lambda function, but not validate if a user go through CloudFront.
You can implement a solution to sign custom header by follow this document:
Futher Read - Why only GET method can bypass CloudFront to invoke Lambda URL
⁉️ We know that Lambda URL requires all methods to have signed header, but why GET method can go through CloudFront to invoke function?
Here is message when I test with GET method at CloudFront, as you see it includes the Headers, and it included x-amz-content-sha256
.
{
"message": "Hello from Lambda!",
"headers": {
"x-amz-content-sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"cloudfront-is-android-viewer": "false",
"content-length": "0",
"x-amzn-tls-version": "TLSv1.3",
"cloudfront-viewer-country": "US",
"postman-token": "2b178115-dd65-4567-8cfb-07b95ed45d6e",
"x-amz-source-account": "058264411535",
"cloudfront-viewer-tls": "TLSv1.3:TLS_AES_128_GCM_SHA256:sessionResumed",
"x-forwarded-port": "443",
"x-amz-security-token": "IQoJb3JpZ2luX2VjED0aCXVzLWVhc3QtMSJHMEUCIQDVVeJvqePhgQHPp2rQwVkuB1eNg7lIwpqLd7UpIw4ihwIgBjkJsp9pna8XikOc8Oo0sIW5wsKteL1gc/2PvgVwcu8qtQIIVhAAGgw4NTYzNjkwNTMxODEiDOcCEWCBKujgcCE8KCqSAk3VAG17dDtr7yyyni1Eclx3bOChaK3Lq8ZKhz3LDFZ9KwHErLm5CVFMLg7fHmqHbxYPFxRft6rFJfnePq3XV2x12r8WVNiPMr4rrrNevzB7Qyn2n2CdwOalsFYm7I3fLB4oT5hR7CNN61tzJpupy5XnzbkQJ1HmjnlXRmbQTnkWjpooWuZXZF5hmNKpOAH7+nrn+L97+9xSkYKGs5Yj66BD/TxeoUKBD1extNk8Dz49yvyX3Le9O7rUwWuMof8Qih6aABL8IRQQs5qo2tpuCIODe+nRi2F1j7CJ85YW5MPp2FVvCLOCi0y7ApAiIpzn+g3fQCd830DqEwA1FJ3Mc+eDDy+SZ3j0oDK97lOgAJFtOMQw5JXFtgY6jwGzvIJEeEV29B3yHCpalJciUNzGxWtt2Vw7f1Btv02KgbnGyVzFvs4Dh3Ia6ldLAWevM6ZB/h62870hj0XKBUw5Q/sZR+LqJGDFyD/UfMWb6zSbv6mCc3xtbO/HoxUW7qwNiBGwqDvxvls9383cnpTT8rHPviOGv78QAnNF7qGmKGVPlGWignUvEzYx2Tur8g==",
"via": "1.1 eb8674b99d3dfcc6867fb20af353442a.cloudfront.net (CloudFront)",
"x-amzn-tls-cipher-suite": "TLS_AES_128_GCM_SHA256",
"cloudfront-viewer-asn": "14618",
"cloudfront-is-desktop-viewer": "true",
"host": "4xmze5deqxjjy4ltw2ze3h7gr40tlvcp.lambda-url.us-east-1.on.aws",
"cache-control": "no-cache",
"cloudfront-viewer-city": "Ashburn",
"cloudfront-viewer-http-version": "1.1",
"cloudfront-viewer-address": "54.86.50.139:4863",
"x-amz-date": "20240830T061644Z",
"x-forwarded-proto": "https",
"cloudfront-is-ios-viewer": "false",
"x-forwarded-for": "54.86.50.139",
"accept": "*/*",
"x-amz-source-arn": "arn:aws:cloudfront::058264411535:distribution/E1ZAD106XSWJF5",
"cloudfront-is-smarttv-viewer": "false",
"x-amzn-trace-id": "Self=1-66d163cc-0bda717d14f2a84850a6eed2;Root=1-66d163cc-711a9a2d2cad036f1be882a4",
"cloudfront-is-tablet-viewer": "false",
"cloudfront-forwarded-proto": "https",
"accept-encoding": "gzip, deflate, br",
"x-amz-cf-id": "89c8mF_r2r7fOOOaYREIJ5ZZW4mfurh81xCmzYA5VHbyDTy56i0Crg==",
"user-agent": "PostmanRuntime/7.41.2",
"cloudfront-is-mobile-viewer": "false"
}
}
I did not sign the request before, so we can imagine that CloudFront automatically sign request header for requests that using GET method.
But when I call POST method it show error:
{
"message": "The request signature we calculated does not match the signature you provided. Check your AWS Secret Access Key and signing method. Consult the service documentation for details."
}
It clearly that the POST method is not signed by default by CloudFront, we need to create signed token to header.
In this document it saids about why GET is enable by default ( https://community.aws/content/2fuBTcoVg7nnRIVLnqjIsIC8LAi/enhancing-security-for-lambda-function-urls?lang=en ).
To summary, it saids that the solution is for easier access if we enable for GET requests at CloudFront, POST requests still require signed payloads.
In my view, bypass GET can help us with less effort to sign request header, and we know that GET is very common method so if every request need to be signed will take a lot of cost and time. Disadvantage is less secure.
Refs
To read more about solution, I have some helpful link below:
https://github.com/vuongbachdoan/sigv4a-signing-examples/tree/main/python