Feature/custom cfn resource for storing cognito jkws ssm#53
Merged
phitoduck merged 2 commits intoMay 2, 2022
Merged
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## trunk #53 +/- ##
=======================================
Coverage 73.70% 73.70%
=======================================
Files 42 42
Lines 1289 1289
Branches 183 183
=======================================
Hits 950 950
Misses 317 317
Partials 22 22 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Collaborator
Author
|
The docs fail to build for this PR. This is due to a need to bump the version of AWS CDK used in parts of the rootski infrastructure unrelated to this feature. The feature itself works and the tests pass, so we will fix the docs build in a later PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Custom CloudFormation Resource that puts Cognito JSON Web Keys into SSM Parameter Store
Context
What is AWS Cognito? How does rootski use it?
Rootski personalizes the experience for users by having users sign up and log in.
Rather than write a user registration system ourselves, we use a service in AWS called
AWS Cognito. Cognito takes care of sign up, sign in, password recovery, deregistering users,
and enforcing strong passwords. Cognito even exposes a nice UI to do all these things:
When users successfully sign into rootski in the browser,
AWS Cognito grants the user an authorization token (a JWT token) which the browser
places in the headers of HTTP requests sent to the rootski backend.
When the rootski backend API receives an incoming request, it checks the JWT token in
the headers to verify that the request is authorized. To do this, rootski downloads a key
called a JSON Web Key (JWK) which it uses to decrypt the token and make sure that
the token came from the rootski AWS Cognito instance--not some hacker on the internet.
The JSON Web Keys for a Cognito User Pool can be fetched using this endpoint:
"https://cognito-idp.{cognito_aws_region}.amazonaws.com/{cognito_user_pool_id}/.well-known/jwks.json"This is what the JWKs look like.
{ "keys": [ { "alg": "RS256", "e": "AQAB", "kid": "vBU9jC18VYmhB09UOHVOChs9A15t/8+2TvAJkR6+gjk=", "kty": "RSA", "n": "sURVgjgib4xdf2b-bIDwotk3Qvph32D447PeDri5GtRBIxUBcMG_ODHqnBlq61adRptop1XlPAcYCjD6sWzpLbiCibrtuli0ZB2OyOU4ZTNnDPBlLavd17dxWqW7QN0lh1zBYaNvLiBbkVGIfwxsRcMrWiel3rTGQAYTIIuytuPvWqMdat0J86auN-vqkG7MoM460U9HsfwSfKt_GDmSgE8soO6BM7K2a80lww2qtTz-R--blwoTmB3nHLKL6X125OMWdELX6d7xrlRkXGItnRexgDsJKBpsxDTNPieD42mo26Qo3vZA77myaevg_YM53XuX8buwsMwxN5eUkdAUKQ", "use": "sig" }, { "alg": "RS256", "e": "AQAB", "kid": "lTFNZ2kMaKhtTYe+PyzGAnF3U1TcW0VKJtybTxkTwmE=", "kty": "RSA", "n": "1k3quCmSUFGJHR5IcuDoHv155h4yuWVe35J4PTDguL65yeSIVUrmcyc4S4pYcOiShLQn2kUuUgxqywCVa6aRZVMOXLct2Rcn8yd-jhaa_Tz21JHsODeRefbTHc9L39YebTCCydgN9EC4QNqnDC6xC4h7gqenSC9MU45aO1Sl74hwBMaW6QINcE5tSM671UWoRUXR8yjM6SBoX3qbJwITInEYKlFiKWywx7addhMw6ZyNaPpsfnM5StKIOwg2n-suhmDpq9jAkHe3MBuBL0s-W1nhamuDVsiBYNBvbc24XBStTPDjJRHpnM-_WdWuhxb0R7tJAB5mcSDCIucIOfGgPw", "use": "sig" } ] }What is the problem this PR is solving?
The backend rootski API runs in an isolated VPC network without internet
access. Due to this, the backend is not able to hit the public AWS endpoint
to fetch the JSON Web Keys used to validate JWT tokens issued by the rootski
Cognito User Pool.
However, the rootski API is able to access AWS SSM, including AWS SSM Parameter Store.
This lambda function defines a custom resource that performs an HTTP request to
the cognito URL to fetch the JWKs and then stores those in an SSM parameter.
Thanks to this, the rootski API code running in AWS Lambda can fetch the JWKs from SSM.
What is the solution proposed in this PR?
The rootski AWS Cognito User Pool is created using AWS CDK code. This code existed prior to this PR.
This PR creates a "custom CloudFormation resource" called
Custom::Rootski-CognitoJWKsInSSMthatis created using AWS CDK right after the Cognito User Pool.
The job of this custom resource is:
/rootski/cognito/jwks.jsonThat's what the PR implements! Here's the finished product:
Implementation
To create the
Custom::Rootski-CognitoJWKsInSSMresource, we needed to write an AWS Lambda Function that is invoked by CloudFormation.The concept is pretty straightforward: when you create your stack, CloudFormation sends a
Createevent to your Lambda function. The expectation is that when the Lambda function finishes running, the custom resource AKA our SSM Parameter containing JWKs will exist.When the CDK/CloudFormation stack is destroyed, CloudFormation sends a
Deleteevent to the lambda function. When this finishes, the resource should be fully deleted.The
CreateandDeleteevents are identical except for theRequestTypefield. Here is an exampleCreateevent payload that came up while testing the custom resource lambda for this PR:{ "RequestType": "Create", "ServiceToken": "arn:aws:lambda:us-west-2:091910621680:function:Cognito-JWKs-In-SSM-Param-SSMParameterWithCognitoJ-GGOkyUUvA1f4", "ResponseURL": "https://cloudformation-custom-resource-response-uswest2.s3-us-west-2.amazonaws.com/arn%3Aaws%3Acloudformation%3Aus-west-2%3A091910621680%3Astack/Cognito-JWKs-In-SSM-Parameter-Custom-Resource-CF/36444d20-c8e3-11ec-81f0-06fa347afb33%7CSSMParameterWithCognitoJWKs%7C471031d4-5160-496c-9f00-6126aa96ee3d?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Date=20220501T001223Z&X-Amz-SignedHeaders=host&X-Amz-Expires=7200&X-Amz-Credential=AKIA54RCMT6SJTABWA2S%2F20220501%2Fus-west-2%2Fs3%2Faws4_request&X-Amz-Signature=7b01a55a52ea74a68a3bd1075cfc25804bead538503a4f05c844005546325451", "StackId": "arn:aws:cloudformation:us-west-2:091910621680:stack/Cognito-JWKs-In-SSM-Parameter-Custom-Resource-CF/36444d20-c8e3-11ec-81f0-06fa347afb33", "RequestId": "471031d4-5160-496c-9f00-6126aa96ee3d", "LogicalResourceId": "SSMParameterWithCognitoJWKs", "ResourceType": "Custom::Rootski-CognitoJWKsInSSM", "ResourceProperties": { "ServiceToken": "arn:aws:lambda:us-west-2:091910621680:function:Cognito-JWKs-In-SSM-Param-SSMParameterWithCognitoJ-GGOkyUUvA1f4", "SSMParameterPath": "/rootski/cognito/jwks.json", "CognitoUserPoolId": "us-west-2_NMATFlcVJ", "CognitoUserPoolRegion": "us-west-2" } }High-level Table of Contents for the PR
cognito/resources/jwk_custom_resource_lambda/which contains the lambda functionSSMParameterWithCognitoJWKsStackwhich