-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Add a built-in AWS_IAM authorizer for HTTP APIs #1878
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a built-in AWS_IAM authorizer for HTTP APIs #1878
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@harrisonhjones So I am having a hard time parsing what is going on here and how it fits into what we already have. Can you explain what you are trying to add here? I know it is IAM Auth for HttpApi, but it's hard to understand how this works at a high level, why this is different than our other Auth's, what defaults (if any) are changing, etc. The issue doesn't seem to have any approach explained and bouncing between two different PRs to try to understand what is different/being added makes things difficult to understand. I don't think we have a design template within SAM, like we do in SAM CLI, but thinking it could be worth it for features that are more involved or require some context.
# To remain backwards compatible add the built-in "AWS_IAM" security scheme _before_ adding the authorizers defined | ||
# in the template so that if the template already has an authorized named "AWS_IAM" it will override the built-in one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why wouldn't we add this only if a customer wants AWS_IAM auth?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal here is that if a customer somehow already has an authorizer called AWS_IAM
we wouldn't want to clobber it.
Add Auth configuration to the OAS file, if necessary | ||
Add Auth configuration to the OAS file. In order to support the built-in AWS_IAM authorizer it is always added. | ||
""" | ||
if not self.auth: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removing this changes behavior. That is the self.definition_body = open_api_editor.openapi
is set, which will risk breaking existing customers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My goal here was to allow customers to specify the IAM_AUTH
authorizer even if they didn't have any auth defined in their serverless http api. If you look at my first example, in the converstaion tab, I illustrated turning on IAM auth for a serverless function using the default serverless http api:
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS SAM template with a simple API definition
Transform: AWS::Serverless-2016-10-31
Resources:
#######
# Serverless functions that use the automatically-created AWS::Serverless::HttpApi called "ServerlessHttpApi".
#######
# Should have no auth set.
HttpApiFunctionDefaultApiDefaultAuth:
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: HttpApi
Properties:
Path: /default-api/default-auth
Method: GET
Handler: index.handler
InlineCode:
"def handler(event, context):\n return {'body':
'HttpApiFunctionDefaultApiDefaultAuth', 'statusCode': 200}\n"
Runtime: python3.8
# Should have IAM auth set.
HttpApiFunctionDefaultApiIAMAuth:
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: HttpApi
Properties:
Path: /default-api/iam-auth
Method: GET
Auth:
Authorizer: AWS_IAM
Handler: index.handler
InlineCode:
"def handler(event, context):\n return {'body':
'HttpApiFunctionDefaultApiIAMAuth', 'statusCode': 200}\n"
Runtime: python3.8
Now, we could keep the if not self.auth
checks. If we did that customers that wanted to use the IAM authorizer would need to define any other authorizer and potentially leave it unused to gain access to the IAM authorizer. That seems fairly hacky to me which I'd like to avoid.
if not self.auth: | ||
return | ||
|
||
if self.auth and not self.definition_body: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same here. Why remove self.auth
check?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my response above.
return | ||
|
||
if not authorizers.get(default_authorizer): | ||
# The AWS_IAM authorizer is built-in and does not need to be defined in the template as an authorizer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We require this for all other Auths. Why should AWS_IAM be any different?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because AWS_IAM is a "built in" auth type. It should not be customized and there only ever needs to be a single one of them. That said I did take the approach you are suggesting in my alternate implementation: #1876
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And we already have a precedent for another built-in authorizer: NONE
. The NONE
authorizer could have been implemented as some kind of authorizer you had to define but which did nothing, which would make it like all the other authorizers, but instead it was defined as a special built-in authorizer. I implemented something similar with the AWS_IAM authorizer.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@harrisonhjones So I am having a hard time parsing what is going on here and how it fits into what we already have. Can you explain what you are trying to add here? I know it is IAM Auth for HttpApi, but it's hard to understand how this works at a high level, why this is different than our other Auth's, what defaults (if any) are changing, etc. The issue doesn't seem to have any approach explained and bouncing between two different PRs to try to understand what is different/being added makes things difficult to understand. I don't think we have a design template within SAM, like we do in SAM CLI, but thinking it could be worth it for features that are more involved or require some context.
Sure thing. The goal of this and PR #1876 is to add AWS IAM authorizer support to Serverless functions and HTTP APIs. By "support" I mean customers will be able to configure their individual Serverless functions to use AWS IAM auth and their Serverless HTTP APIs to use AWS IAM auth by default. The two PRs do it in two different ways:
PR 1876
PR 1876 accomplishes the goal by defining a new authorizer property, is_aws_iam_authorizer
which is set using the new IamAuthorizer
key. If set on an authorizer, regardless of any other properties, the authorizer is setup as an IAM authorizer. Customers have to define the authorizer themselves if they are using a custom Serverless HTTP (not the default on). They can call it whatever they want and to use it they need to reference it in their Serverless functions. For example:
HttpApiFunctionCustomApiWithDefaultIamAuthIamAuth:
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: HttpApi
Properties:
Path: /custom-api-with-default-iam-auth/iam-auth
Method: GET
ApiId: !Ref CustomServerlessHttpApiWithDefaultIamAuth
Auth:
Authorizer: CustomIamAuthorizer
... other properties ...
CustomServerlessHttpApiWithDefaultIamAuth:
Type: AWS::Serverless::HttpApi
Properties:
Auth:
DefaultAuthorizer: CustomIamAuthorizer
Authorizers:
CustomIamAuthorizer:
IamAuthorizer: True
The PR then goes a bit further to add an IAM authorizer to the default Serverless HTTP API (called the ImplicitHttpApiResource
) so that customers that use that with their Serverless functions can enable IAM auth without having to define their own Serverless HTTP API.
PR 1878 (this one)
PR 1878 works differently. It always adds a AWS_IAM
security scheme to all generated Serverless HTTP APIs. Customers do not need to define any authorizers to use IAM auth, they simply need to reference AWS_IAM
in their Serverless function definitions or as the default auth for their Serverless HTTP API definitions.
Closing
I hope that helps explain my approaches. If it would help move things quicker I'm happy to jump on a call to discuss.
Add Auth configuration to the OAS file, if necessary | ||
Add Auth configuration to the OAS file. In order to support the built-in AWS_IAM authorizer it is always added. | ||
""" | ||
if not self.auth: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My goal here was to allow customers to specify the IAM_AUTH
authorizer even if they didn't have any auth defined in their serverless http api. If you look at my first example, in the converstaion tab, I illustrated turning on IAM auth for a serverless function using the default serverless http api:
AWSTemplateFormatVersion: "2010-09-09"
Description: AWS SAM template with a simple API definition
Transform: AWS::Serverless-2016-10-31
Resources:
#######
# Serverless functions that use the automatically-created AWS::Serverless::HttpApi called "ServerlessHttpApi".
#######
# Should have no auth set.
HttpApiFunctionDefaultApiDefaultAuth:
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: HttpApi
Properties:
Path: /default-api/default-auth
Method: GET
Handler: index.handler
InlineCode:
"def handler(event, context):\n return {'body':
'HttpApiFunctionDefaultApiDefaultAuth', 'statusCode': 200}\n"
Runtime: python3.8
# Should have IAM auth set.
HttpApiFunctionDefaultApiIAMAuth:
Type: AWS::Serverless::Function
Properties:
Events:
ApiEvent:
Type: HttpApi
Properties:
Path: /default-api/iam-auth
Method: GET
Auth:
Authorizer: AWS_IAM
Handler: index.handler
InlineCode:
"def handler(event, context):\n return {'body':
'HttpApiFunctionDefaultApiIAMAuth', 'statusCode': 200}\n"
Runtime: python3.8
Now, we could keep the if not self.auth
checks. If we did that customers that wanted to use the IAM authorizer would need to define any other authorizer and potentially leave it unused to gain access to the IAM authorizer. That seems fairly hacky to me which I'd like to avoid.
if not self.auth: | ||
return | ||
|
||
if self.auth and not self.definition_body: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my response above.
# To remain backwards compatible add the built-in "AWS_IAM" security scheme _before_ adding the authorizers defined | ||
# in the template so that if the template already has an authorized named "AWS_IAM" it will override the built-in one. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The goal here is that if a customer somehow already has an authorizer called AWS_IAM
we wouldn't want to clobber it.
"Unable to add Auth configuration because 'DefinitionBody' does not contain a valid OpenApi definition.", | ||
) | ||
|
||
open_api_editor = OpenApiEditor(self.definition_body) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking back at this line I'm now wondering if this line populates the open_api_editor
with the self.definition_body
. If so then my new code, specifically the open_api_editor.security_schemes["AWS_IAM"] = {
line could still clobber an existing definition which I'd like to avoid. Likely we'll need to add something like:
if not "AWS_IAM" in open_api_editor.security_schemes {
open_api_editor.security_schemes["AWS_IAM"] = {
...
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure whether this information helps or not - but in my pursuit of trying to work how to enable AWS_IAM via OpenAPI this morning, (obviously I did not succeed (edit: I did later)), I did the following.
- Created an API (HttpApi) with no auth, via an OpenAPI file.
- Added IAM auth to an endpoint
- Exported the OpenAPI template
Under security schemes, it generated this-
securitySchemes:
sigv4:
type: "apiKey"
name: "Authorization"
in: "header"
x-amazon-apigateway-authtype: "awsSigv4"
The affected path now looked like this
/mypath:
post:
operationId: "MyPath"
responses:
default:
description: "Default response for POST /mypath"
security:
- sigv4: []
x-amazon-apigateway-integration:
payloadFormatVersion: "2.0"
type: "aws_proxy"
httpMethod: "POST"
uri: <redacted>"
connectionType: "INTERNET"
Similarly I got errors when I tried to recreate using the exported template. An Authorizer cannot be used with AuthorizationType AWS_IAM. Ignoring.
So it seems like by default it calls it an IAM authorizer in the scheme as sigv4, with the awsSigv4 type.
EDIT: I just tried attaching an IAM authorizer via OpenAPI and it worked - so it likes like you can configure an IAM authorizer by using Open API. I'm not sure what I did differently to this morning....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EDIT: I just tried attaching an IAM authorizer via OpenAPI and it worked - so it likes like you can configure an IAM authorizer by using Open API. I'm not sure what I did differently to this morning....
Yep, you can do it that way. This PR is proposing a way to do it without having to touch OpenAPI.
return | ||
|
||
if not authorizers.get(default_authorizer): | ||
# The AWS_IAM authorizer is built-in and does not need to be defined in the template as an authorizer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because AWS_IAM is a "built in" auth type. It should not be customized and there only ever needs to be a single one of them. That said I did take the approach you are suggesting in my alternate implementation: #1876
return | ||
|
||
if not authorizers.get(default_authorizer): | ||
# The AWS_IAM authorizer is built-in and does not need to be defined in the template as an authorizer. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And we already have a precedent for another built-in authorizer: NONE
. The NONE
authorizer could have been implemented as some kind of authorizer you had to define but which did nothing, which would make it like all the other authorizers, but instead it was defined as a special built-in authorizer. I implemented something similar with the AWS_IAM authorizer.
Closing in favor of #1924 |
This is a work in progress and does not yet contain tests. I am sending it out at this stage to get feedback on my approach. This is an alternate approach from the other PR I sent out, #1876. The main difference between that PR and this one is that this code change adds a new special
Authorizer
built in calledAWS_IAM
that cannot be defined in theAuthorizers
section of an HTTP API.Issue #, if available: #1731
Description of changes:
This code change does one thing:
AWS_IAM
, to be used by HTTP APIs.Description of how you validated changes:
I created the following test template:
I then compiled it using:
I then manually deployed it using CloudFormation in my personal AWS account.
Once deployed I validated all the routes had the expected auth type:
Checklist:
make pr
passesExamples?
Please reach out in the comments, if you want to add an example. Examples will be
added to
sam init
through https://github.com/awslabs/aws-sam-cli-app-templates/By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.