Skip to content

Commit

Permalink
docs: add CDK with SAR example (#5)
Browse files Browse the repository at this point in the history
  • Loading branch information
lmammino authored Dec 18, 2023
1 parent 58b61ee commit e9d84cf
Show file tree
Hide file tree
Showing 14 changed files with 5,154 additions and 25 deletions.
25 changes: 7 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ A high-performance token-based API Gateway authorizer Lambda that can validate O

## 🤌 Use case

This project provides a easy-to-install AWS Lambda function that can be used as a custom authorizer for AWS API Gateway. This authorizer can validate OIDC-issued JWT tokens and it can be used to secure your API endpoints using your OIDC provider of choice (e.g. Apple, Auth0, AWS Cognito, Azure AD / Micsosoft Entra ID, Facebook, GitLab, Google, Keycloak, LinkedIn, Okta, Salesforce, Twitch, etc.).
This project provides an easy-to-install AWS Lambda function that can be used as a custom authorizer for AWS API Gateway. This authorizer can validate OIDC-issued JWT tokens and it can be used to secure your API endpoints using your OIDC provider of choice (e.g. Apple, Auth0, AWS Cognito, Azure AD / Microsoft Entra ID, Facebook, GitLab, Google, Keycloak, LinkedIn, Okta, Salesforce, Twitch, etc.).

![A diagram illustrating how this project can be integrated. A user sends an authenticated request to API Gateway. API Gateway is configured to use a custom lambda as an authorizer (THIS PROJECT!). The lambda talks with your OIDC provider to get the public key to validate the user token and responds to API Gateway to Allow or Deny the request.](https://github.com/lmammino/oidc-authorizer/raw/main/docs/lovely-diagram.png)

Expand All @@ -39,20 +39,9 @@ This custom Lambda Authorizer is designed to be **easy to install and configure*

This project is meant to be integrated into existing applications (after all, an authorizer is useless without an API).

Different deployment options are available. Check out the [deployment docs](https://github.com/lmammino/oidc-authorizer/blob/main/docs/deploy.md) for an extensive explanation of all the possible approaches.


Different deployment options are available. Check out the [deployment docs](https://github.com/lmammino/oidc-authorizer/blob/main/docs/deploy.md) for an extensive explaination of all the possible approaches.

Alternatively, you can also consult some of the quick examples listed below:

- [Deploy from SAR (Serverless Application Repository) using SAM](https://github.com/lmammino/oidc-authorizer/blob/main/examples/sam-from-sar/template.yml)
- TODO: Deploy from SAR (Serverless Application Repository) using CDK
- TODO: build and package yourself
- TODO: use pre-published binaries and package yourself
- [Build yourself and deploy using SAM](https://github.com/lmammino/oidc-authorizer/blob/main/examples/sam/template.yml)
- TODO: use pre-published binaries and deploy using CDK
- TODO: use pre-published binaries and deploy using Terraform
- TODO: use pre-published binaries and deploy using CloudFormation one-click templates
Alternatively, you can also consult some of the quick examples in the [`examples` folder](https://github.com/lmammino/oidc-authorizer/blob/main/examples)

If you prefer, you can also learn [how to host your own SAR application](https://github.com/lmammino/oidc-authorizer/blob/main/docs/deploy.md#maintain-your-own-sar-application).

Expand All @@ -73,7 +62,7 @@ Here's a list of the configuration options that are supported:
### MinRefreshRate

- **Environment variable**: `MIN_REFRESH_RATE`
- **Description**: The minumum number of seconds to wait before keys are refreshed when the given key is not found.
- **Description**: The minimum number of seconds to wait before keys are refreshed when the given key is not found.
- **Mandatory**: No
- **Default value**: `"900"` (15 minutes)

Expand Down Expand Up @@ -133,11 +122,11 @@ The authorizer enriches the context of the request with the following values:
- `principalId`: the principal ID extracted from the token.
- `jwtClaims`: a JSON string containing the entire token payload (claims).

These values are injected into the context of the request and can be used to enrich your logging, tracing or to implement app-level authentication.
These values are injected into the context of the request and can be used to enrich your logging, tracing or to implement app-level authentication.

When you use the [Lambda-proxy integration](https://docs.aws.amazon.com/apigateway/latest/developerguide/set-up-lambda-proxy-integrations.html#api-gateway-create-api-as-simple-proxy) these values are made available under `event.requestContext.authorizer`.

For example this is how you can access the `principalId` and `jwtClaims` values in a Lambda function written in Python:
For example, this is how you can access the `principalId` and `jwtClaims` values in a Lambda function written in Python:

```python
import json
Expand All @@ -156,7 +145,7 @@ def handler(event, context):

## 🏃‍♂️ Benchmarks

Proper benchmarks are yet to be written (SORRY 😇), but for now, to prove that this Lambda is still reasonable fast, here's some data observed during some manual tests (128 MB Memory deployment):
Proper benchmarks are yet to be written (SORRY 😇), but for now, to prove that this Lambda is still reasonably fast, here's some data observed during some manual tests (128 MB Memory deployment):

- Cold start times: ~48ms
- Cold start requests (including fetching JWKS from Azure): 120-300ms
Expand Down
99 changes: 98 additions & 1 deletion docs/deploy.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,104 @@ A full example is available in the [`examples` folder](https://github.com/lmammi

### Use the SAR application with CDK:

TODO:...
The following snippet shows how to use the SAR application with CDK (using Typescript):

```typescript
// import the authorizer lambda for the Serverless Application Repository
const authorizerApp = new cdk.aws_sam.CfnApplication(this, 'AuthorizerApp', {
location: {
applicationId: 'arn:aws:serverlessrepo:eu-west-1:795006566846:applications/oidc-authorizer',
semanticVersion: '0.0.4' // 👀 CHANGE ME
},
parameters: {
// 👀 CHANGE THE FOLLOWING PARAMETERS
AcceptedAlgorithms: "",
AcceptedAudiences: "",
AcceptedIssuers: "",
DefaultPrincipalId: "unknown",
JwksUri: "https://login.microsoftonline.com/3e4abf5a-fdc9-485c-9853-af03c4a32976/discovery/v2.0/keys",
MinRefreshRate: "900",
PrincipalIdClaims: "preferred_username, sub",
// The amount of memory (in MB) to give to the authorizer Lambda.
LambdaMemorySize: "128",
// The timeout to give to the authorizer Lambda.
LambdaTimeout: "3"
}
})
const lambdaAuthorizer = aws_lambda.Function.fromFunctionAttributes(this, 'AuthorizerFunction', {
functionArn: authorizerApp.getAtt('Outputs.OidcAuthorizerArn').toString(),
sameEnvironment: true, // Note: this is important since the lambda is created in another stack we need to make sure CDK knows it's in the same region
})
// creates the authorizer definition
const authorizer = new aws_apigw.TokenAuthorizer(this, 'Authorizer', {
handler: lambdaAuthorizer,
identitySource: aws_apigw.IdentitySource.header('authorization'),
authorizerName: 'OidcAuthorizer',
});
// Your API is here
const apiGw = new aws_apigw.RestApi(this, 'api', {
restApiName: 'OIDC Authorizer Demo',
description: 'A demo app to test the OIDC authorizer',
deployOptions: {
stageName: 'prod',
},
defaultCorsPreflightOptions: {
allowOrigins: aws_apigw.Cors.ALL_ORIGINS,
allowMethods: aws_apigw.Cors.ALL_METHODS,
},
endpointTypes: [aws_apigw.EndpointType.REGIONAL],
deploy: true,
});
const sampleApiLambda1 = new aws_lambda.Function(this, 'sampleApiLambda1', {
runtime: aws_lambda.Runtime.PYTHON_3_9,
handler: 'index.handler',
code: aws_lambda.Code.fromInline(`
def handler(event, context):
return {'body': 'Hello from endpoint1!', 'statusCode': 200}
`)
});
const sampleApiLambda2 = new aws_lambda.Function(this, 'sampleApiLambda2', {
runtime: aws_lambda.Runtime.PYTHON_3_9,
handler: 'index.handler',
code: aws_lambda.Code.fromInline(`
def handler(event, context):
return {'body': 'Hello ' + event['requestContext']['authorizer']['principalId'] + ' from endpoint2!\nThese are your claims: ' + event['requestContext']['authorizer']['jwtClaims'], 'statusCode': 200}
`)
});
apiGw
.root
.addResource('1')
.addMethod('GET', new aws_apigw.LambdaIntegration(sampleApiLambda1), {
authorizer: authorizer,
authorizationType: aws_apigw.AuthorizationType.CUSTOM,
});
apiGw
.root
.addResource('2')
.addMethod('GET', new aws_apigw.LambdaIntegration(sampleApiLambda2), {
authorizer: authorizer,
authorizationType: aws_apigw.AuthorizationType.CUSTOM,
});
const apiGwEndpoint1Output = new cdk.CfnOutput(this, 'ApiEndpoint1', {
description: 'API Gateway endpoint 1',
value: `${apiGw.url}1`
});

const apiGwEndpoint2Output = new cdk.CfnOutput(this, 'ApiEndpoint2', {
description: 'API Gateway endpoint 2',
value: `${apiGw.url}2`
});
```

A full example is available in the [`examples` folder](https://github.com/lmammino/oidc-authorizer/blob/main/examples/cdk-from-sar/lib/cdk-stacks.ts).


> **Note**
Expand Down
8 changes: 8 additions & 0 deletions examples/cdk-from-sar/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
6 changes: 6 additions & 0 deletions examples/cdk-from-sar/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
10 changes: 10 additions & 0 deletions examples/cdk-from-sar/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Example of using the SAR application with CDK

## Useful commands

* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `npx cdk deploy` deploy this stack to your default AWS account/region
* `npx cdk diff` compare deployed stack with current state
* `npx cdk synth` emits the synthesized CloudFormation template
21 changes: 21 additions & 0 deletions examples/cdk-from-sar/bin/cdk.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from 'aws-cdk-lib';
import { CdkStack } from '../lib/cdk-stack';

const app = new cdk.App();
new CdkStack(app, 'CdkStack', {
/* If you don't specify 'env', this stack will be environment-agnostic.
* Account/Region-dependent features and context lookups will not work,
* but a single synthesized template can be deployed anywhere. */

/* Uncomment the next line to specialize this stack for the AWS Account
* and Region that are implied by the current CLI configuration. */
// env: { account: process.env.CDK_DEFAULT_ACCOUNT, region: process.env.CDK_DEFAULT_REGION },

/* Uncomment the next line if you know exactly what Account and Region you
* want to deploy the stack to. */
// env: { account: '123456789012', region: 'us-east-1' },

/* For more information, see https://docs.aws.amazon.com/cdk/latest/guide/environments.html */
});
64 changes: 64 additions & 0 deletions examples/cdk-from-sar/cdk.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
{
"app": "npx ts-node --prefer-ts-exts bin/cdk.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true,
"@aws-cdk/aws-iam:importedRoleStackSafeDefaultPolicyName": true,
"@aws-cdk/aws-s3:serverAccessLogsUseBucketPolicy": true,
"@aws-cdk/aws-route53-patters:useCertificate": true,
"@aws-cdk/customresources:installLatestAwsSdkDefault": false,
"@aws-cdk/aws-rds:databaseProxyUniqueResourceName": true,
"@aws-cdk/aws-codedeploy:removeAlarmsFromDeploymentGroup": true,
"@aws-cdk/aws-apigateway:authorizerChangeDeploymentLogicalId": true,
"@aws-cdk/aws-ec2:launchTemplateDefaultUserData": true,
"@aws-cdk/aws-secretsmanager:useAttachedSecretResourcePolicyForSecretTargetAttachments": true,
"@aws-cdk/aws-redshift:columnId": true,
"@aws-cdk/aws-stepfunctions-tasks:enableEmrServicePolicyV2": true,
"@aws-cdk/aws-ec2:restrictDefaultSecurityGroup": true,
"@aws-cdk/aws-apigateway:requestValidatorUniqueId": true,
"@aws-cdk/aws-kms:aliasNameRef": true,
"@aws-cdk/aws-autoscaling:generateLaunchTemplateInsteadOfLaunchConfig": true,
"@aws-cdk/core:includePrefixInUniqueNameGeneration": true,
"@aws-cdk/aws-efs:denyAnonymousAccess": true,
"@aws-cdk/aws-opensearchservice:enableOpensearchMultiAzWithStandby": true,
"@aws-cdk/aws-lambda-nodejs:useLatestRuntimeVersion": true,
"@aws-cdk/aws-efs:mountTargetOrderInsensitiveLogicalId": true,
"@aws-cdk/aws-rds:auroraClusterChangeScopeOfInstanceParameterGroupWithEachParameters": true,
"@aws-cdk/aws-appsync:useArnForSourceApiAssociationIdentifier": true,
"@aws-cdk/aws-rds:preventRenderingDeprecatedCredentials": true,
"@aws-cdk/aws-codepipeline-actions:useNewDefaultBranchForCodeCommitSource": true
}
}
Loading

0 comments on commit e9d84cf

Please sign in to comment.