defaultMethodOptions should exclude options method by default or at least have an option to do that.
LambdaRestApi has defaultMethodOption, when it is specified, it gets applied to all methods for given api resource. which is awesome but doesn't work when CORS is involved.
Take this simple example,
where I want all the methods to require x-api-key api key so I specify
defaultMethodOption: {
apiKeyRequired: true
}
On top of this I also want CORS to be enabled on all api resources so in LambdaRestApi I also specify this
defaultCorsPreflightOptions : {
... cors options
}
Because of defaultCorsPreflightOptions an additional method will be created for all my resources to support CORS but because defaultMethodOption applies on all methods, options will also have apiKeyRequired , which it shouldn't.
This is :bug: Bug Report
Thanks for filing this issue @whimzyLive.
The 'OPTIONS' method created as part of CORS preflight setup will have to be adjusted such that only properties in defaultMethodOptions that apply in the CORS context are filtered.
For the time being, you should be able to work around this using escape hatches.
I stumbled across this too.
For now, I have removed the API key requirement from the RestApi and added it to each resource instead. This isn't ideal though.
@asterikx until issue is resolved correctly, you can use cdk escape hatches as also suggested by @nija-at.
For my case I did
const corsMethods = this.apiV1.methods.filter(
method => method.httpMethod === 'OPTIONS'
);
corsMethods.forEach(method => {
const cfnMethod = method.node.defaultChild as CfnMethod;
cfnMethod.addPropertyOverride('ApiKeyRequired', false);
cfnMethod.addPropertyOverride('AuthorizationType', 'NONE');
cfnMethod.addPropertyDeletionOverride('AuthorizerId');
});
@whimzyLive thanks for sharing! I wasn't sure how to do this.
There are extra issues with this and proxy support AFAICT. For example, using LambdaRestApi:
const api = new apigateway.LambdaRestApi(stack, 'DummyApi', {
handler,
defaultMethodOptions: { authorizationType: apigateway.AuthorizationType.IAM },
defaultCorsPreflightOptions: { allowOrigins: apigateway.Cors.ALL_ORIGINS },
})
api.methods.filter(m => m.httpMethod === 'OPTIONS')
.forEach(m => m.node.defaultChild.addPropertyOverride('AuthorizationType', 'NONE'))
This works in so much as the OPTIONS methods are changed correctly in the CFN output to not use auth – but I still get 403s when sending OPTIONS requests – and I believe (although happy to be wrong) it's because the auth on the ANY methods from the proxy is overriding the (lack of) auth on the OPTIONS methods on the same resources.
EDIT: this was wrong. I believe the root cause is that the CDK is failing to see the addPropertyOverride changes when calculating the hash for the Deployment – so when the CloudFormation stack is updated after adding overrides, the deployment appears not to have changed, and so CloudFormation doesn't deploy the auth changes (because CloudFormation has no auto-deploy mechanism)
I'm guessing addPropertyOverride changes are out of scope for determining the deployment hash? If not, I'll file a separate issue for that. But definitely something people should be aware of – you'll need to trigger the deployment to change some other way to get the stack to update.
EDIT2: Added an issue for this over at https://github.com/aws/aws-cdk/issues/9086
I guess this is akin to the AddDefaultAuthorizerToCorsPreflight property in SAM
Another related issue here (if not exactly the same): https://github.com/aws/aws-cdk/issues/906#issuecomment-619426621
@mhart I am using lambdaRestApi with above workaround and all options are working fine. Only difference is: I am using it with custom Authorizer instead of IAM Authorizer. If you could share a part of your synthesised template that contains AWS::ApiGateway::Method I might be able to help.
Like I said, I'm pretty sure the generated CFN is actually right – I mean, it's removed the auth from OPTIONS – the problem is that (I suspect) the auth on the ANY method is overriding the non-auth on the OPTIONS method.
Anyway, here are the generated AWS::ApiGateway::Methods:
EDIT: Updated – I only had the two proxy methods in before, now have all four (two proxy + two root):
{
"DummyApiOPTIONSA607B51F": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "OPTIONS",
"ResourceId": {
"Fn::GetAtt": [
"DummyApi80F1E171",
"RootResourceId"
]
},
"RestApiId": {
"Ref": "DummyApi80F1E171"
},
"AuthorizationType": "NONE",
"Integration": {
"IntegrationResponses": [
{
"ResponseParameters": {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
"method.response.header.Access-Control-Allow-Origin": "'*'",
"method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'"
},
"StatusCode": "204"
}
],
"RequestTemplates": {
"application/json": "{ statusCode: 200 }"
},
"Type": "MOCK"
},
"MethodResponses": [
{
"ResponseParameters": {
"method.response.header.Access-Control-Allow-Headers": true,
"method.response.header.Access-Control-Allow-Origin": true,
"method.response.header.Access-Control-Allow-Methods": true
},
"StatusCode": "204"
}
]
}
},
"DummyApiproxyOPTIONSC056BEDA": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "OPTIONS",
"ResourceId": {
"Ref": "DummyApiproxy55B1C1CD"
},
"RestApiId": {
"Ref": "DummyApi80F1E171"
},
"AuthorizationType": "NONE",
"Integration": {
"IntegrationResponses": [
{
"ResponseParameters": {
"method.response.header.Access-Control-Allow-Headers": "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,X-Amz-User-Agent'",
"method.response.header.Access-Control-Allow-Origin": "'*'",
"method.response.header.Access-Control-Allow-Methods": "'OPTIONS,GET,PUT,POST,DELETE,PATCH,HEAD'"
},
"StatusCode": "204"
}
],
"RequestTemplates": {
"application/json": "{ statusCode: 200 }"
},
"Type": "MOCK"
},
"MethodResponses": [
{
"ResponseParameters": {
"method.response.header.Access-Control-Allow-Headers": true,
"method.response.header.Access-Control-Allow-Origin": true,
"method.response.header.Access-Control-Allow-Methods": true
},
"StatusCode": "204"
}
]
}
},
"DummyApiproxyANY1EBD4910": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "ANY",
"ResourceId": {
"Ref": "DummyApiproxy55B1C1CD"
},
"RestApiId": {
"Ref": "DummyApi80F1E171"
},
"AuthorizationType": "AWS_IAM",
"Integration": {
"IntegrationHttpMethod": "POST",
"Type": "AWS_PROXY",
"Uri": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"DummyFunction3BB5AE03",
"Arn"
]
},
"/invocations"
]
]
}
}
}
},
"DummyApiANYCEB66499": {
"Type": "AWS::ApiGateway::Method",
"Properties": {
"HttpMethod": "ANY",
"ResourceId": {
"Fn::GetAtt": [
"DummyApi80F1E171",
"RootResourceId"
]
},
"RestApiId": {
"Ref": "DummyApi80F1E171"
},
"AuthorizationType": "AWS_IAM",
"Integration": {
"IntegrationHttpMethod": "POST",
"Type": "AWS_PROXY",
"Uri": {
"Fn::Join": [
"",
[
"arn:",
{
"Ref": "AWS::Partition"
},
":apigateway:",
{
"Ref": "AWS::Region"
},
":lambda:path/2015-03-31/functions/",
{
"Fn::GetAtt": [
"DummyFunction3BB5AE03",
"Arn"
]
},
"/invocations"
]
]
}
}
}
}
}
}
@mhart Okay I see you are using Lambda proxy integration in particular. For Lambda proxy
your backend is responsible for returning the Access-Control-Allow-Origin and Access-Control-Allow-Headers headers, because a proxy integration doesn't return an integration response. - from docs
This is mentioned here, under Enabling CORS support for Lambda or HTTP proxy integrations.
That doesn't make sense – the 403 happens before the Lambda is even invoked.
This was the last thing I could think of. If you are getting 403 on options itself then we might have had different issue. Let me know if you find out something.
Found the issue – it was a complicated scenario with CFN stack updates and CDK deployment hashes not being updated. More info in the updated comment here: https://github.com/aws/aws-cdk/issues/8615#issuecomment-658478450
Most helpful comment
@asterikx until issue is resolved correctly, you can use cdk escape hatches as also suggested by @nija-at.
For my case I did