okigan / awscurl

curl-like access to AWS resources with AWS Signature Version 4 request signing.
MIT License
755 stars 94 forks source link

"The request signature we calculated does not match the signature you provided" for a Private API with IAM_AUTH enabled using public DNS names #68

Open bhatiasuraj opened 5 years ago

bhatiasuraj commented 5 years ago

Created a private API with API Gateway and enabled AWS_IAM auth on a GET method for a resource. When invoking using the default Stage URL (using private DNS name), the request works fine as below -

awscurl -v https://API-ID.execute-api.REGION.amazonaws.com/STAGE/RESOURCE

However, making a request using the public DNS names leads to the signature calculation error. We are passing the Host header here for the VPC endpoint to be able to identify which API to hit.

awscurl -v https://vpce-ID.execute-api.REGION.vpce.amazonaws.com/STAGE/RESOURCE -H "Host: API-ID.execute-api.REGION.amazonaws.com"

If we replace Host header with x-api-gw-id as suggested here https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-api-test-invoke-url.html#w20aac13c16c28c11

The request works fine -

awscurl -v https://vpce-ID.execute-api.REGION.vpce.amazonaws.com/STAGE/RESOURCE -H "x-apigw-api-id: API-ID"

I suspect the issue is with how the host header value is used in the signature calculation. Can you please confirm if format is right?

okigan commented 5 years ago

AFAIR, HOST header value is not utilized in signature calculation

On Aug 28, 2019, at 9:34 AM, Suraj Bhatia notifications@github.com wrote:

Created a private API with API Gateway and enabled AWS_IAM auth on a GET method for a resource. When invoking using the default Stage URL (using private DNS name), the request works fine as below -

awscurl -v https://API-ID.execute-api.REGION.amazonaws.com/STAGE/RESOURCE

However, making a request using the public DNS names leads to the signature calculation error. We are passing the Host header here for the VPC endpoint to be able to identify which API to hit.

awscurl -v https://vpce-ID.execute-api.REGION.vpce.amazonaws.com/STAGE/RESOURCE -H "Host: API-ID.execute-api.REGION.amazonaws.com"

If we replace Host header with x-api-gw-id as suggested here https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-api-test-invoke-url.html#w20aac13c16c28c11

The request works fine -

awscurl -v https://vpce-ID.execute-api.REGION.vpce.amazonaws.com/STAGE/RESOURCE -H "x-apigw-api-id: API-ID"

I suspect the issue is with how the host header value is used in the signature calculation. Can you please confirm if format is right?

— You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub, or mute the thread.

bhatiasuraj commented 5 years ago

Why is there a signature calculation error in that case then?

bhatiasuraj commented 5 years ago
# If the host was specified in the HTTP header, ensure that the canonical
    # headers are set accordingly
    if 'host' in headers:
        fullhost = headers['host']
    else:
        fullhost = host + ':' + port if port else host
okigan commented 5 years ago

i see -- is that case sensitivity behavior, what happens if you specify with lower case?

bhatiasuraj commented 5 years ago

Voila! Using 'host' instead of 'Host' leads to 200 response. Not cool though, right?

okigan commented 5 years ago

prioritizing to get you going, so it's cool you are unblocked.

so now should decide what's the right way going forward -- do you want to take a crack at that?

bhatiasuraj commented 5 years ago

Yeah, fortunately x-api-gw-id works so I'm unblocked. I can take a look it though

okigan commented 5 years ago

Have a look - too heads are better than one

On Aug 28, 2019, at 11:33 AM, Suraj Bhatia notifications@github.com wrote:

Yeah, fortunately x-api-gw-id works so I'm unblocked. I can take a look it though

— You are receiving this because you commented. Reply to this email directly, view it on GitHub, or mute the thread.

bhatiasuraj commented 5 years ago

@okigan This change should be sufficient (line85 on wards) -

if 'Host' in headers:
      host = headers['Host']
else:
      host = uri_dict['host']

Basically checking if a "Host" header was explicitly passed in the URL, if so - override 'host' with that value instead of picking it from the URL dictionary.

Tested with below URLs -

1. Private DNS name of the API and no Host header explicitly passed $ awscurl https://API-ID.execute-api.REGION.amazonaws.com/STAGE/RESOURCE Success

2. Public DNS name of the API with Host header specified as "Host" $ awscurl https://vpce-ID.execute-api.REGION.vpce.amazonaws.com/STAGE/RESOURCE -H "Host: API-ID.execute-api.REGION.amazonaws.com" Success

3. Public DNS name of the API with Host header specified as "host" $ awscurl https://vpce-ID.execute-api.REGION.vpce.amazonaws.com/STAGE/RESOURCE -H "host: API-ID.execute-api.REGION.amazonaws.com" Success

okigan commented 5 years ago

i think using dictionary with case insensitive lookup is more full prof -- requests library includes one - what do you think about using that one?

bhatiasuraj commented 5 years ago

That would require more changes - currently host is being picked up from the URL and not the headers.

okigan commented 5 years ago

i forget - would this be sufficient, or it's missing something?

https://github.com/okigan/awscurl/pull/72

okigan commented 4 years ago

@bhatiasuraj bumping this up -- check out #72