├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── alb-mtls ├── .gitignore ├── consumer │ ├── .gitignore │ ├── .npmignore │ ├── bin │ │ └── consumer.ts │ ├── cdk.json │ ├── jest.config.js │ ├── lib │ │ └── consumer-stack.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json └── producer │ ├── .gitignore │ ├── .npmignore │ ├── api │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json │ ├── authorizer │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json │ ├── bin │ └── producer.ts │ ├── ca │ └── Certificate.pem │ ├── cdk.context.json │ ├── cdk.json │ ├── configurePrivateDNS │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json │ ├── jest.config.js │ ├── lib │ └── producer-stack.ts │ ├── package-lock.json │ ├── package.json │ ├── rotation │ └── main.py │ ├── targetRegister │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json │ ├── test │ └── producer.test.ts │ └── tsconfig.json ├── architecture.png ├── mtls ├── Dockerfile ├── README.md └── nginx.conf ├── package-lock.json ├── pattern1-arch1.png ├── pattern1-arch2.png ├── pattern1-arch3.png ├── pattern1-arch4.png ├── pattern2-arch1.png └── vpc-endpoint ├── consumer ├── .gitignore ├── .npmignore ├── authorizer │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── bin │ └── consumer.ts ├── cdk.json ├── endpointPolicy │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── jest.config.js ├── lambdaConsumer │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── lib │ ├── consumer-api-stack.ts │ └── consumer-vpc-stack.ts ├── package-lock.json ├── package.json ├── rotation │ └── main.py ├── targetRegister │ ├── .eslintignore │ ├── .gitignore │ ├── .npmignore │ ├── app.ts │ ├── jest.config.ts │ ├── package-lock.json │ ├── package.json │ └── tsconfig.json ├── test │ └── consumer.test.ts └── tsconfig.json └── producer ├── .gitignore ├── .npmignore ├── api ├── .eslintignore ├── .gitignore ├── .npmignore ├── app.ts ├── jest.config.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── authorizer ├── .eslintignore ├── .gitignore ├── .npmignore ├── app.ts ├── jest.config.ts ├── package-lock.json ├── package.json └── tsconfig.json ├── bin └── producer.ts ├── cdk.json ├── jest.config.js ├── lib └── producer-stack.ts ├── package-lock.json ├── package.json ├── test └── producer.test.ts └── tsconfig.json /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check existing open, or recently closed, issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *main* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels (enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any 'help wanted' issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AWS Cross Account Private API Patterns 2 | 3 | This repository contains example patterns for consuming Private API Gateway endpoints across AWS Accounts. 4 | 5 | 6 | 7 | ## Getting Started :checkered_flag: 8 | 9 | The examples are provisioned using the Cloud Development Kit (CDK). To install the CDK locally, follow the instructions in the [CDK documentation](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html#getting_started_install): 10 | 11 | ``` 12 | npm install -g aws-cdk 13 | ``` 14 | 15 | There are 2 example implementations in this repository. The first uses a VPC endpoint to grant access to the consumer AWS account. The second uses mutual TLS (mTLS) and AWS PrivateLink to authorize access using a client certificate. 16 | 17 | 1. **[VPC Endpoint](https://github.com/aws-samples/cross-account-private-api-patterns/tree/main#pattern-1-cross-account-via-vpc-endpoint-electric_plug)** 18 | 19 | 2. **[mTLS via AWS PrivateLink](https://github.com/aws-samples/cross-account-private-api-patterns/tree/main#pattern-2-cross-account-using-mtls-closed_lock_with_key)** 20 | 21 | ## Pattern 1. Cross account via VPC endpoint :electric_plug: 22 | 23 | ![Architecture Diagram](./pattern1-arch1.png) 24 | 25 | This example uses a VPC endpoint to securely consume a private API from one account via a serverless application in another account. 26 | 27 | ``` 28 | cd vpc-endpoint 29 | ``` 30 | 31 | With CDK installed, there are then 3 stacks to deploy: 32 | 1. `ConsumerVpcStack` - the **consumer account** VPC & VPC Endpoint has to be created first. 33 | 2. `ProducerStack` - the **producer account** private API Gateway and Lambda function application is deployed next using the outputs from step 1. 34 | 3. `ConsumerApiStack` - finally the **consumer account** API is deployed for securely consuming the private API in the producer account. 35 | 36 | 37 | ### Step 1: Deploy the consumer account VPC [ConsumerVpcStack] 38 | 39 | *NOTE: Use the consumer account AWS credentials for this step.* 40 | 41 | ![Architecture Diagram](./pattern1-arch2.png) 42 | 43 | Bootstrap the CDK environment and install node modules. 44 | 45 | ``` 46 | cd consumer && npm i && cdk bootstrap 47 | ``` 48 | Deploy the ConsumerVpcStack containing core networking resources. Pass the `producer` AWS account id as a parameter. 49 | 50 | ``` 51 | cdk deploy ConsumerVpcStack --parameters producerAccountId=12345678910 52 | ``` 53 | Copy the `ConsumerVpcStack.ApiKeySecretArn` and `ConsumerVpcStack.ConsumerVPCe` outputs for the next step. 54 | 55 | ### Step 2: Deploy the producer private API Gateway application [ProducerStack] 56 | 57 | *NOTE: Use the producer account AWS credentials for this step.* 58 | 59 | ![Architecture Diagram](./pattern1-arch3.png) 60 | 61 | Bootstrap the CDK environment and install node modules. 62 | ``` 63 | cd ../producer && npm i && cdk bootstrap 64 | ``` 65 | 66 | Deploy the producer API application. Use the `ConsumerVpcStack.ApiKeySecretArn` and `ConsumerVpcStack.ConsumerVPCe` outputs from the first step to pass as parameters (this is used to lock down the trust policy on the private API to just this endpoint): 67 | 68 | ``` 69 | cdk deploy --parameters ConsumerVPCe=vpce-0e3ca9432b3e8cba6 --parameters ApiKeySecretArn=arn:aws:secretsmanager:eu-west-2:12345678910:secret:CrossAccountAPIKeyabc123-def456 70 | ``` 71 | 72 | Take note of the `ProducerStack.ApiUrl` output for the next step. 73 | 74 | ### Step 3: Deploy the consumer application [ConsumerApiStack] 75 | 76 | *NOTE: Use the consumer account AWS credentials for this step.* 77 | 78 | ![Architecture Diagram](./pattern1-arch4.png) 79 | 80 | Finally deploy the consumer API stack. This is deployed into the consumer AWS account. This will provision the API & Lambda functions that can be used to consume the private API in the producer account. Use the output API URL from the previous stack as a parameter in this final stack (e.g. `https://abc123def.execute-api.eu-west-2.amazonaws.com/prod/widgets`). Also provide the AWS Account ID of the producer account: 81 | 82 | ``` 83 | cd ../consumer 84 | ``` 85 | 86 | ``` 87 | cdk deploy ConsumerApiStack --parameters targetApiUrl=https://.execute-api..amazonaws.com/prod/widgets --parameters producerAccountId=12345678910 88 | ``` 89 | 90 | Note the `ConsumerApiStack.ConsumerApiEndpoint` URL to test in the next section. 91 | 92 | ### Testing 93 | 94 | Now the API can be tested using the following curl command. Note the Authorization header. The secret value used for this header can be retrieved from the Secrets Manager Secret `CrossAccountAPIKey` that was created in the consumer account as part of the CDK deployment. This implementation is used for simplicity for development and testing purposes. For production use, you should look at using a more secure authorization method such as AWS IAM or JWT authorization. 95 | 96 | ``` 97 | curl https://.execute-api..amazonaws.com/prod -H "Authorization: " 98 | ``` 99 | 100 | If everything works correctly you should see the below response: 101 | ``` 102 | {"id":"1","value":"4.99"} 103 | ``` 104 | 105 | You can test the Lambda implementation directly by navigating to the Lambda console in the consumer account and using the "Test" button with the default payload. The function name is `ConsumerApiStack-ConsumerFunction`. 106 | 107 | If everything works correctly you should see the below response: 108 | ``` 109 | { 110 | "statusCode": 200, 111 | "body": "{\"id\":\"1\",\"value\":\"4.99\"}" 112 | } 113 | ``` 114 | 115 | 116 | ## Pattern 2. Cross account using mTLS :closed_lock_with_key: 117 | 118 | ![Architecture Diagram](./pattern2-arch1.png) 119 | 120 | This example uses mTLS to authenticate communication between a producer and subscriber AWS account. The producer is a private API gateway, fronted by an application load balancer for mTLS resolution. The consumer in this case is just an EC2 instance deployed into another AWS account. 121 | 122 | ``` 123 | cd alb-mtls 124 | ``` 125 | 126 | ### Prerequisites 127 | 128 | To use a custom domain name for private DNS, you will need an existing [Route53 Hosted Zone](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/CreatingHostedZone.html) and a registered domain. 129 | 130 | This example uses an AWS Private Certificate Authority, but you could follow the same process for any external/3rd party certificate authority. To create a CA to use for mTLS, follow the following steps (can be in any AWS account): 131 | 132 | 1. Create ACM Private CA, specifying the CN (Common Name) as the domain (e.g. mtls.mydomain.com) 133 | 2. Download the generated Certificate.pem and copy it to the `./producer/ca/` folder in the repository 134 | 3. Generate client certificates against PCA as per: https://aws.amazon.com/blogs/security/use-acm-private-ca-for-amazon-api-gateway-mutual-tls/ e.g: 135 | 136 | ``` 137 | openssl req -new -newkey rsa:2048 -days 365 -keyout my_client.key -out my_client.csr 138 | ``` 139 | ``` 140 | aws acm-pca issue-certificate --certificate-authority-arn arn:aws:acm-pca:us-east-1:account_id:certificate-authority/certificate_authority_id --csr fileb://my_client.csr --signing-algorithm "SHA256WITHRSA" --validity Value=365,Type="DAYS" --template-arn arn:aws:acm-pca:::template/EndEntityCertificate/V1 141 | ``` 142 | ``` 143 | aws acm-pca get-certificate --certificate-authority-arn arn:aws:acm-pca:us-east-1:account_id:certificate-authority/certificate_authority_id --certificate-arn arn:aws:acm-pca:us-east-1:account_id:certificate-authority/certificate_authority_id/certificate/certificate_id --output text > my_client.pem 144 | ``` 145 | 146 | :pencil2: Note, you may have to separate the certificates onto separate lines in the resulting `my_client.pem` file e.g.: 147 | 148 | ``` 149 | ...Lw== 150 | -----END CERTIFICATE----- 151 | -----BEGIN CERTIFICATE----- 152 | MIIDKA... 153 | ``` 154 | 155 | (_optional_) - if you set a passphrase on the key you can export it so you don't need to input a passphrase for every request: 156 | 157 | ``` 158 | openssl rsa -in my_client.key -out client.key 159 | ``` 160 | 161 | With the CA configured, there are then 2 stacks to deploy. The producer has to be created first. The producer is the account which the API Gateway is deployed into. 162 | 163 | ### Step 1: Deploy the producer account stack [ProducerStack] 164 | 165 | *NOTE: Use the producer account AWS credentials for this step.* 166 | 167 | First bootstrap and deploy the producer stack. This is deployed into the producer AWS account and will create the core networking resources, including the mTLS configuration to be used by the consuming API. The consuming AWS account ID, a subdomain to host the service on, the domain name of the certificate authority and the route53 hosted zone ID for the domain must be provided as parameters: 168 | 169 | ``` 170 | cd producer && npm i && cdk bootstrap 171 | ``` 172 | 173 | ``` 174 | cdk deploy --parameters consumerAccountId=12345678910 --parameters subdomain=mtls --parameters domainName=mydomain.com --parameters hostedZoneId=ABCDEF123 175 | ``` 176 | 177 | Note the `ProducerStack.ServiceName` output for the next step. 178 | 179 | ### Step 2: Deploy the consumer account stack [ConsumerStack] 180 | 181 | *NOTE: Use the consumer account AWS credentials for this step.* 182 | 183 | Then bootstrap and deploy the consumer CDK application. This is deployed into the consumer AWS account. This will provision the VPC endpoint and EC2 instance that will be consume the API from the consumer account. Copy the ServiceName from the output of the first stack to pass into the consumer stack : 184 | 185 | ``` 186 | cd ../consumer && npm i && cdk bootstrap 187 | ``` 188 | 189 | ``` 190 | cdk deploy --parameters endpointService=com.amazonaws.vpce.region.vpce-svc-123456abcde 191 | ``` 192 | 193 | Once this stack is deployed, the request to connect with the VPC endpoint is automatically accepted by the producer AWS account. As an optional security control in production, you may want to manually verify consumers. 194 | 195 | Then the API can be tested from the EC2 instance in the consumer account. Connect to the instance using Systems Manager Session Manager. You can retrieve the auth token from AWS Secrets Manager in the producer account. The secret ARN is also output from the producer stack for convenience: 196 | 197 | ``` 198 | curl -H "Authorization: WjuAbc232QICxhjGNhPC12345" --key my_client.key --cert my_client.pem https://mtls.mydomain.com/widgets 199 | ``` 200 | 201 | If everything works correctly you should see the below response: 202 | ``` 203 | {"id":"1","value":"4.99"} 204 | ``` 205 | 206 | If you try and connect without passing a client certificate and key you will receive an error: 207 | ``` 208 | curl: (35) Recv failure: Connection reset by peer 209 | ``` 210 | 211 | ## Cleanup :moneybag: 212 | 213 | Don't forget to spin down all the resources when you are done testing to save costs 214 | 215 | ``` 216 | cdk destroy 217 | ``` 218 | -------------------------------------------------------------------------------- /alb-mtls/.gitignore: -------------------------------------------------------------------------------- 1 | *.crt 2 | *.key 3 | *.csr 4 | *.pem 5 | cdk.context.json 6 | -------------------------------------------------------------------------------- /alb-mtls/consumer/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | cdk.context.json -------------------------------------------------------------------------------- /alb-mtls/consumer/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /alb-mtls/consumer/bin/consumer.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "aws-cdk-lib"; 4 | import { ConsumerStack } from "../lib/consumer-stack"; 5 | import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; 6 | 7 | const app = new cdk.App(); 8 | const vpc = new ConsumerStack(app, "mtlsConsumerStack", { 9 | env: { 10 | //Required for ELBv2 access logging to work 11 | account: process.env.CDK_DEFAULT_ACCOUNT, 12 | region: process.env.CDK_DEFAULT_REGION, 13 | }, 14 | }); 15 | 16 | cdk.Aspects.of(app).add(new AwsSolutionsChecks()); 17 | NagSuppressions.addStackSuppressions(vpc, [ 18 | { 19 | id: "AwsSolutions-L1", 20 | reason: "Custom resource is currently hardcoded to NodeJS 22", 21 | }, 22 | { 23 | id: "AwsSolutions-IAM5", 24 | reason: 25 | "IAM policy resources not scoped where the resource ID is not known ahead of time", 26 | }, 27 | { 28 | id: "AwsSolutions-IAM4", 29 | reason: 30 | "AWS Managed Policies used in this solution are: AWSLambdaBasicExecutionRole", 31 | }, 32 | { 33 | id: "AwsSolutions-EC29", 34 | reason: 35 | "Standalone instace just for testing", 36 | }, 37 | { 38 | id: "AwsSolutions-SMG4", 39 | reason: "No rotation required for secret used in this solution" 40 | } 41 | ]); 42 | -------------------------------------------------------------------------------- /alb-mtls/consumer/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/consumer.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /alb-mtls/consumer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /alb-mtls/consumer/lib/consumer-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "aws-cdk-lib"; 2 | import { Construct } from "constructs"; 3 | import { 4 | Vpc, 5 | FlowLog, 6 | FlowLogResourceType, 7 | Instance, 8 | InstanceType, 9 | InstanceClass, 10 | InstanceSize, 11 | MachineImage, 12 | InterfaceVpcEndpoint, 13 | InterfaceVpcEndpointService, 14 | } from "aws-cdk-lib/aws-ec2"; 15 | import { CfnParameter } from "aws-cdk-lib"; 16 | 17 | 18 | export class ConsumerStack extends cdk.Stack { 19 | constructor(scope: Construct, id: string, props?: cdk.StackProps) { 20 | super(scope, id, props); 21 | 22 | const endpointService = new CfnParameter(this, "endpointService", { 23 | type: "String", 24 | description: "The Service Name of the producer VPC Endpoint Service.", 25 | }); 26 | 27 | const vpc = new Vpc(this, "ConsumerVPC", { 28 | vpcName: "ConsumerVPC", 29 | }); 30 | new FlowLog(this, "FlowLog", { 31 | resourceType: FlowLogResourceType.fromVpc(vpc), 32 | }); 33 | 34 | new Instance(this, 'consumerInstance', { 35 | vpc, 36 | detailedMonitoring: true, 37 | instanceType: InstanceType.of(InstanceClass.T3, InstanceSize.MICRO), 38 | machineImage: MachineImage.latestAmazonLinux2023(), 39 | ssmSessionPermissions: true, 40 | }); 41 | 42 | new InterfaceVpcEndpoint(this, 'VPCEndpoint', { 43 | vpc, 44 | service: new InterfaceVpcEndpointService(endpointService.valueAsString, 443), 45 | privateDnsEnabled: true, 46 | subnets: { 47 | availabilityZones: vpc.availabilityZones 48 | } 49 | }); 50 | 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /alb-mtls/consumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consumer", 3 | "version": "0.1.0", 4 | "bin": { 5 | "consumer": "bin/consumer.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.172.0", 18 | "esbuild": "^0.19.9", 19 | "jest": "^27.5.1", 20 | "ts-jest": "^27.1.4", 21 | "ts-node": "^10.9.1", 22 | "typescript": "~3.9.7" 23 | }, 24 | "dependencies": { 25 | "@aws-sdk/client-ec2": "^3.373.0", 26 | "@aws-sdk/client-elastic-load-balancing-v2": "^3.370.0", 27 | "@aws-sdk/client-secrets-manager": "^3.370.0", 28 | "aws-cdk-lib": "2.172.0", 29 | "axios": "^1.7.4", 30 | "cdk-nag": "^2.20.10", 31 | "constructs": "^10.0.0", 32 | "source-map-support": "^0.5.21" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /alb-mtls/consumer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /alb-mtls/producer/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /alb-mtls/producer/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /alb-mtls/producer/api/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /alb-mtls/producer/api/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /alb-mtls/producer/api/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /alb-mtls/producer/api/app.ts: -------------------------------------------------------------------------------- 1 | export interface GetWidgetEvent { 2 | } 3 | 4 | export const lambdaHandler = async (event: GetWidgetEvent) => { 5 | return { 6 | statusCode: 200, 7 | body: JSON.stringify({ 8 | "id": "1", 9 | "value": "4.99" 10 | }), 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /alb-mtls/producer/api/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /alb-mtls/producer/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "aws-sdk": "^2.1354.0" 11 | }, 12 | "scripts": { 13 | "unit": "jest", 14 | "lint": "eslint '*.ts' --quiet --fix", 15 | "compile": "tsc", 16 | "test": "npm run compile && npm run unit" 17 | }, 18 | "devDependencies": { 19 | "@types/aws-lambda": "^8.10.92", 20 | "@types/jest": "^27.4.0", 21 | "@types/node": "^17.0.13", 22 | "@types/pg": "^8.6.5", 23 | "@typescript-eslint/eslint-plugin": "^5.10.2", 24 | "@typescript-eslint/parser": "^5.10.2", 25 | "esbuild": "^0.14.14", 26 | "esbuild-jest": "^0.5.0", 27 | "eslint": "^8.8.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "jest": "^27.5.0", 31 | "prettier": "^2.5.1", 32 | "ts-node": "^10.4.0", 33 | "typescript": "^4.5.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /alb-mtls/producer/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetSecretValueCommand, 3 | SecretsManagerClient, 4 | } from "@aws-sdk/client-secrets-manager"; 5 | 6 | const secretManager = new SecretsManagerClient({}); 7 | 8 | export const lambdaHandler = async ( 9 | event: any, 10 | context: any, 11 | callback: any 12 | ) => { 13 | const token = event.authorizationToken; 14 | const input = { 15 | SecretId: process.env.API_KEY, 16 | }; 17 | const command = new GetSecretValueCommand(input); 18 | const response = await secretManager.send(command); 19 | 20 | switch (token) { 21 | case response.SecretString: 22 | callback(null, generatePolicy("user", "Allow", event.methodArn)); 23 | break; 24 | default: 25 | callback("Error: Invalid token"); 26 | } 27 | }; 28 | 29 | const generatePolicy = ( 30 | principalId: string, 31 | effect: string, 32 | resource: string 33 | ): any => { 34 | let authResponse: any = {}; 35 | 36 | authResponse.principalId = principalId; 37 | if (effect && resource) { 38 | let policyDocument: any = {}; 39 | policyDocument.Version = "2012-10-17"; 40 | policyDocument.Statement = []; 41 | let statementOne: any = {}; 42 | statementOne.Action = "execute-api:Invoke"; 43 | statementOne.Effect = effect; 44 | statementOne.Resource = resource; 45 | policyDocument.Statement[0] = statementOne; 46 | authResponse.policyDocument = policyDocument; 47 | } 48 | 49 | return authResponse; 50 | }; 51 | -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorizer", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-secrets-manager": "^3.637.0" 11 | }, 12 | "scripts": { 13 | "unit": "jest", 14 | "lint": "eslint '*.ts' --quiet --fix", 15 | "compile": "tsc", 16 | "test": "npm run compile && npm run unit" 17 | }, 18 | "devDependencies": { 19 | "@types/aws-lambda": "^8.10.92", 20 | "@types/jest": "^27.4.0", 21 | "@types/node": "^17.0.13", 22 | "@types/pg": "^8.6.5", 23 | "@typescript-eslint/eslint-plugin": "^5.10.2", 24 | "@typescript-eslint/parser": "^5.10.2", 25 | "esbuild": "^0.14.14", 26 | "esbuild-jest": "^0.5.0", 27 | "eslint": "^8.8.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "jest": "^27.5.0", 31 | "prettier": "^2.5.1", 32 | "ts-node": "^10.4.0", 33 | "typescript": "^4.5.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /alb-mtls/producer/authorizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /alb-mtls/producer/bin/producer.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "aws-cdk-lib"; 4 | import { ProducerStack } from "../lib/producer-stack"; 5 | import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; 6 | 7 | const app = new cdk.App(); 8 | const api = new ProducerStack(app, "mTLSProducerStack", { 9 | env: { 10 | //Required for ELBv2 access logging to work 11 | account: process.env.CDK_DEFAULT_ACCOUNT, 12 | region: process.env.CDK_DEFAULT_REGION, 13 | }, 14 | }); 15 | cdk.Aspects.of(app).add(new AwsSolutionsChecks()); 16 | NagSuppressions.addStackSuppressions(api, [ 17 | { 18 | id: "AwsSolutions-COG4", 19 | reason: 20 | "Token-based authorization is used in this solution rather than Cognito", 21 | }, 22 | { 23 | id: "AwsSolutions-IAM4", 24 | reason: 25 | "AWS Managed Policies used in this solution are: AWSLambdaBasicExecutionRole and AmazonAPIGatewayPushToCloudWatchLogs", 26 | }, 27 | { 28 | id: "AwsSolutions-IAM5", 29 | reason: 30 | "IAM policy resources not scoped where the resource ID is not known ahead of time", 31 | }, 32 | { 33 | id: "AwsSolutions-L1", 34 | reason: "Custom Resource is currently hardcoded to NodeJS 22", 35 | }, 36 | { 37 | id: "AwsSolutions-APIG2", 38 | reason: "Request validation not currently enabled. " 39 | }, 40 | { 41 | id: "AwsSolutions-SMG4", 42 | reason: "No rotation required for secret used in this solution" 43 | } 44 | ]); 45 | -------------------------------------------------------------------------------- /alb-mtls/producer/ca/Certificate.pem: -------------------------------------------------------------------------------- 1 | Replace this files contents with your certificate from your CA -------------------------------------------------------------------------------- /alb-mtls/producer/cdk.context.json: -------------------------------------------------------------------------------- 1 | { 2 | "availability-zones:account=329382086462:region=eu-west-1": [ 3 | "eu-west-1a", 4 | "eu-west-1b", 5 | "eu-west-1c" 6 | ] 7 | } 8 | -------------------------------------------------------------------------------- /alb-mtls/producer/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/producer.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | EC2Client, 3 | ModifyVpcEndpointServiceConfigurationCommand, 4 | StartVpcEndpointServicePrivateDnsVerificationCommand, 5 | DescribeVpcEndpointServiceConfigurationsCommand 6 | } from '@aws-sdk/client-ec2'; 7 | import { 8 | Route53Client, 9 | ChangeResourceRecordSetsCommand, 10 | ChangeAction, 11 | RRType 12 | } from '@aws-sdk/client-route-53'; 13 | 14 | const ec2 = new EC2Client({}); 15 | const route53 = new Route53Client({}); 16 | 17 | export const lambdaHandler = async (event: any, context: any) => { 18 | console.log('REQUEST RECEIVED:\n' + JSON.stringify(event)); 19 | if (event.RequestType == 'Delete') { 20 | await sendResponse(event, context, 'SUCCESS'); 21 | return; 22 | } 23 | try { 24 | const params = { 25 | ServiceId: process.env.SERVICE_ID, 26 | PrivateDnsName: process.env.DNS_NAME 27 | }; 28 | const modifyVpceCommand = new ModifyVpcEndpointServiceConfigurationCommand(params); 29 | await ec2.send(modifyVpceCommand); 30 | 31 | const describeParams = { 32 | ServiceIds: [process.env.SERVICE_ID!] 33 | } 34 | 35 | let dnsValue = "" 36 | let dnsName = "" 37 | 38 | while (true) { 39 | const describeCommand = new DescribeVpcEndpointServiceConfigurationsCommand(describeParams); 40 | const result = await ec2.send(describeCommand); 41 | if (result.ServiceConfigurations![0].ServiceState == 'Available') { 42 | dnsValue = result.ServiceConfigurations![0].PrivateDnsNameConfiguration?.Value!; 43 | dnsName = result.ServiceConfigurations![0].PrivateDnsNameConfiguration?.Name!; 44 | break; 45 | } 46 | await new Promise(resolve => setTimeout(resolve, 5000)); 47 | } 48 | 49 | const createRoute53RecordParams = { 50 | HostedZoneId: process.env.HOSTED_ZONE_ID!, 51 | ChangeBatch: { 52 | Changes: [ 53 | { 54 | Action: ChangeAction.CREATE, 55 | ResourceRecordSet: { 56 | Name: dnsName + "." + process.env.ROOT_DNS, 57 | Type: RRType.TXT, 58 | TTL: 300, 59 | ResourceRecords: [ 60 | { 61 | Value: "\"" + dnsValue + "\"" 62 | } 63 | ] 64 | } 65 | } 66 | ] 67 | } 68 | } 69 | const createRoute53RecordCommand = new ChangeResourceRecordSetsCommand(createRoute53RecordParams); 70 | await route53.send(createRoute53RecordCommand); 71 | 72 | const verifyDnsParams = { 73 | ServiceId: process.env.SERVICE_ID, 74 | } 75 | 76 | const startEndpointDnsVerificationCommand = new StartVpcEndpointServicePrivateDnsVerificationCommand(verifyDnsParams); 77 | await ec2.send(startEndpointDnsVerificationCommand); 78 | 79 | while (true) { 80 | const describeCommand = new DescribeVpcEndpointServiceConfigurationsCommand(describeParams); 81 | const result = await ec2.send(describeCommand); 82 | if (result.ServiceConfigurations![0].PrivateDnsNameConfiguration?.State! == 'verified') { 83 | break; 84 | 85 | } 86 | await new Promise(resolve => setTimeout(resolve, 5000)); 87 | } 88 | 89 | await sendResponse(event, context, 'SUCCESS'); 90 | } catch (e) { 91 | console.log(e); 92 | await sendResponse(event, context, 'FAILED'); 93 | } 94 | }; 95 | 96 | const sendResponse = async (event: any, context: any, responseStatus: string) => { 97 | const responseBody = JSON.stringify({ 98 | Status: responseStatus, 99 | Reason: 'See the details in CloudWatch Log Stream: ' + context.logStreamName, 100 | PhysicalResourceId: context.logStreamName, 101 | StackId: event.StackId, 102 | RequestId: event.RequestId, 103 | LogicalResourceId: event.LogicalResourceId 104 | }); 105 | 106 | console.log('RESPONSE BODY:\n', responseBody); 107 | 108 | const https = require('https'); 109 | const url = require('url'); 110 | 111 | const parsedUrl = url.parse(event.ResponseURL); 112 | const options = { 113 | hostname: parsedUrl.hostname, 114 | port: 443, 115 | path: parsedUrl.path, 116 | method: 'PUT', 117 | headers: { 118 | 'content-type': '', 119 | 'content-length': responseBody.length, 120 | }, 121 | }; 122 | 123 | console.log('SENDING RESPONSE...\n'); 124 | 125 | const request = https.request(options, function (response: any) { 126 | console.log('STATUS: ' + response.statusCode); 127 | console.log('HEADERS: ' + JSON.stringify(response.headers)); 128 | context.done(); 129 | }); 130 | 131 | request.on('error', function (error: any) { 132 | console.log('sendResponse Error:' + error); 133 | context.done(); 134 | }); 135 | request.write(responseBody); 136 | request.end(); 137 | }; 138 | -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern_3", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-ec2": "^3.664.0", 11 | "@aws-sdk/client-route-53": "^3.369.0", 12 | "aws-sdk": "^2.1354.0" 13 | }, 14 | "scripts": { 15 | "unit": "jest", 16 | "lint": "eslint '*.ts' --quiet --fix", 17 | "compile": "tsc", 18 | "test": "npm run compile && npm run unit" 19 | }, 20 | "devDependencies": { 21 | "@types/aws-lambda": "^8.10.92", 22 | "@types/jest": "^27.4.0", 23 | "@types/node": "^17.0.13", 24 | "@types/pg": "^8.6.5", 25 | "@typescript-eslint/eslint-plugin": "^5.10.2", 26 | "@typescript-eslint/parser": "^5.10.2", 27 | "esbuild": "^0.14.14", 28 | "esbuild-jest": "^0.5.0", 29 | "eslint": "^8.8.0", 30 | "eslint-config-prettier": "^8.3.0", 31 | "eslint-plugin-prettier": "^4.0.0", 32 | "jest": "^27.5.0", 33 | "prettier": "^2.5.1", 34 | "ts-node": "^10.4.0", 35 | "typescript": "^4.5.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /alb-mtls/producer/configurePrivateDNS/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /alb-mtls/producer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /alb-mtls/producer/lib/producer-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "aws-cdk-lib"; 2 | import { Construct } from "constructs"; 3 | import { CfnParameter, CustomResource, Duration } from "aws-cdk-lib"; 4 | import * as custom from "aws-cdk-lib/custom-resources"; 5 | import { LogGroup } from "aws-cdk-lib/aws-logs"; 6 | import { 7 | AccessLogFormat, 8 | CfnAccount, 9 | Deployment, 10 | DomainName, 11 | EndpointType, 12 | LambdaRestApi, 13 | LogGroupLogDestination, 14 | MethodLoggingLevel, 15 | RequestValidator, 16 | TokenAuthorizer, 17 | } from "aws-cdk-lib/aws-apigateway"; 18 | import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; 19 | import { 20 | AnyPrincipal, 21 | Effect, 22 | ManagedPolicy, 23 | PolicyDocument, 24 | PolicyStatement, 25 | Role, 26 | ServicePrincipal, 27 | AccountPrincipal 28 | } from "aws-cdk-lib/aws-iam"; 29 | import { Code, Runtime, Function } from "aws-cdk-lib/aws-lambda"; 30 | import { 31 | Vpc, 32 | FlowLog, 33 | FlowLogResourceType, 34 | InterfaceVpcEndpointAwsService, 35 | SecurityGroup, 36 | Port, 37 | Peer, 38 | CfnVPCEndpointService, 39 | CfnVPCEndpointServicePermissions, 40 | } from "aws-cdk-lib/aws-ec2"; 41 | import { 42 | NetworkLoadBalancer, 43 | Protocol, 44 | TargetType, 45 | ApplicationLoadBalancer, 46 | CfnListener, 47 | ApplicationTargetGroup, 48 | CfnTrustStore, 49 | ApplicationListener, 50 | ListenerAction 51 | } from "aws-cdk-lib/aws-elasticloadbalancingv2"; 52 | import { AlbListenerTarget } from 'aws-cdk-lib/aws-elasticloadbalancingv2-targets'; 53 | import { 54 | Bucket, 55 | BlockPublicAccess, 56 | BucketEncryption, 57 | ObjectOwnership, 58 | } from "aws-cdk-lib/aws-s3"; 59 | import { Secret } from "aws-cdk-lib/aws-secretsmanager"; 60 | import { Key } from "aws-cdk-lib/aws-kms"; 61 | import { BucketDeployment, Source } from "aws-cdk-lib/aws-s3-deployment" 62 | import { Certificate, CertificateValidation } from "aws-cdk-lib/aws-certificatemanager"; 63 | import { HostedZone } from "aws-cdk-lib/aws-route53"; 64 | 65 | export class ProducerStack extends cdk.Stack { 66 | constructor(scope: Construct, id: string, props?: cdk.StackProps) { 67 | super(scope, id, props); 68 | const resource = "widgets"; 69 | 70 | // Required input parameters 71 | const consumerAccountId = new CfnParameter(this, "consumerAccountId", { 72 | type: "String", 73 | description: "The AWS Account ID of the producer account.", 74 | }); 75 | 76 | const subdomain = new CfnParameter(this, "subdomain", { 77 | type: "String", 78 | description: "The domain name to create a default SSL certificate for the application load balancer e.g. mydomain.com", 79 | }); 80 | 81 | const domain = new CfnParameter(this, "domainName", { 82 | type: "String", 83 | description: "The domain name to create a subdomain for the service e.g. mtls.mydomain.com", 84 | }); 85 | 86 | const hostedZoneId = new CfnParameter(this, "hostedZoneId", { 87 | type: "String", 88 | description: "The hosted zone to create the subdomain.", 89 | }); 90 | 91 | // Basic networking setup 92 | const vpc = new Vpc(this, "mTLSVPC", { 93 | vpcName: "mTLSVPC", 94 | }); 95 | new FlowLog(this, "mTLSFlowLog", { 96 | resourceType: FlowLogResourceType.fromVpc(vpc), 97 | }); 98 | 99 | const privateApiEndpoint = vpc.addInterfaceEndpoint("mTLSApiEndpoint", { 100 | service: InterfaceVpcEndpointAwsService.APIGATEWAY, 101 | }); 102 | 103 | //API Key for basic auth 104 | const key = new Key(this, "APIKeyKMSKey", { 105 | enableKeyRotation: true, 106 | }); 107 | const apiSecret = new Secret(this, "CrossAccountAPIKey", { 108 | generateSecretString: { 109 | passwordLength: 32, 110 | excludePunctuation: true, 111 | }, 112 | encryptionKey: key, 113 | }); 114 | 115 | const rotationLambda = new Function(this, "RotationLambda", { 116 | code: Code.fromAsset("rotation"), 117 | runtime: Runtime.PYTHON_3_8, 118 | handler: "main.lambda_handler", 119 | }); 120 | 121 | apiSecret.addRotationSchedule("RotationSchedule", { 122 | automaticallyAfter: cdk.Duration.days(7), 123 | rotationLambda: rotationLambda, 124 | }); 125 | 126 | apiSecret.grantRead(new AccountPrincipal(consumerAccountId.valueAsString)); 127 | apiSecret.grantRead(new ServicePrincipal("lambda")); 128 | apiSecret.grantWrite(rotationLambda); 129 | 130 | const outputSecretArn = new cdk.CfnOutput(this, "ApiKeySecretArn", { 131 | value: apiSecret.secretFullArn!, 132 | exportName: "ApiKeySecretArn", 133 | }); 134 | 135 | //PrivateLink configuration 136 | const nlbAccessLogs = new Bucket(this, "NLBAccessLogsBucket", { 137 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 138 | enforceSSL: true, 139 | encryption: BucketEncryption.S3_MANAGED, 140 | serverAccessLogsPrefix: "logs", 141 | objectOwnership: ObjectOwnership.OBJECT_WRITER, 142 | }); 143 | 144 | const nlb = new NetworkLoadBalancer(this, "ConsumerNLB", { 145 | vpc, 146 | internetFacing: false, 147 | crossZoneEnabled: true, 148 | }); 149 | nlb.logAccessLogs(nlbAccessLogs, "consumerAccessLogs"); 150 | 151 | const cfnVPCEndpointService = new CfnVPCEndpointService(this, 'VPCEndpointService', { 152 | acceptanceRequired: false, // In a production environment, you may want to require manual acceptance 153 | networkLoadBalancerArns: [nlb.loadBalancerArn] 154 | }); 155 | 156 | const cfnVPCEndpointServicePermissions = new CfnVPCEndpointServicePermissions(this, 'VPCEndpointServicePermissions', { 157 | serviceId: cfnVPCEndpointService.getAtt("ServiceId").toString(), 158 | allowedPrincipals: ['arn:aws:iam::' + consumerAccountId.valueAsString + ':root'], 159 | }); 160 | 161 | const configurePrivateDNSRole = new Role(this, "configurePrivateDNSRole", { 162 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 163 | }); 164 | configurePrivateDNSRole.addToPolicy( 165 | new PolicyStatement({ 166 | resources: ["*"], 167 | actions: [ 168 | "ec2:DescribeVpcEndpoints", 169 | "ec2:DescribeNetworkInterfaces", 170 | "ec2:ModifyVpcEndpointServiceConfiguration", 171 | "ec2:DescribeVpcEndpointServiceConfigurations", 172 | "ec2:StartVpcEndpointServicePrivateDnsVerification", 173 | "route53:ChangeResourceRecordSets", 174 | "logs:CreateLogGroup", 175 | "logs:CreateLogStream", 176 | "logs:PutLogEvents", 177 | ], 178 | }) 179 | ); 180 | 181 | const configurePrivateDNSFn = new NodejsFunction( 182 | this, 183 | "configurePrivateDNSFunction", 184 | { 185 | runtime: Runtime.NODEJS_22_X, 186 | handler: "lambdaHandler", 187 | entry: "./configurePrivateDNS/app.ts", 188 | role: configurePrivateDNSRole, 189 | timeout: Duration.seconds(600), // domain verification process can take up to 10 minutes 190 | environment: { 191 | SERVICE_ID: cfnVPCEndpointService.getAtt("ServiceId").toString(), 192 | DNS_NAME: subdomain.valueAsString + "." + domain.valueAsString, 193 | ROOT_DNS: domain.valueAsString, 194 | HOSTED_ZONE_ID: hostedZoneId.valueAsString, 195 | }, 196 | } 197 | ); 198 | 199 | const configurePrivateDNSCRRole = new Role(this, "configurePrivateDNSCRRole", { 200 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 201 | }); 202 | configurePrivateDNSCRRole.addToPolicy( 203 | new PolicyStatement({ 204 | resources: ["*"], 205 | actions: [ 206 | "logs:CreateLogGroup", 207 | "logs:CreateLogStream", 208 | "logs:PutLogEvents", 209 | ], 210 | }) 211 | ); 212 | 213 | const configurePrivateDNSProvider = new custom.Provider( 214 | this, 215 | "configurePrivateDNSProvider", 216 | { 217 | onEventHandler: configurePrivateDNSFn, 218 | role: configurePrivateDNSCRRole, 219 | } 220 | ); 221 | 222 | const dnsService = new CustomResource(this, "configurePrivateDNS", { 223 | serviceToken: configurePrivateDNSProvider.serviceToken, 224 | }); 225 | 226 | const vpcEndpointServiceName = new custom.AwsCustomResource( 227 | this, 228 | "vpcEndpointServiceName", 229 | { 230 | onCreate: { 231 | service: "EC2", 232 | action: "describeVpcEndpointServiceConfigurations", 233 | parameters: { 234 | ServiceIds: [cfnVPCEndpointService.getAtt("ServiceId").toString()], 235 | }, 236 | physicalResourceId: custom.PhysicalResourceId.fromResponse( 237 | "ServiceConfigurations.0.ServiceName" 238 | ), 239 | outputPaths: ["ServiceConfigurations.0.ServiceName"], 240 | }, 241 | policy: custom.AwsCustomResourcePolicy.fromSdkCalls({ 242 | resources: custom.AwsCustomResourcePolicy.ANY_RESOURCE, 243 | }), 244 | } 245 | ); 246 | const serviceName = vpcEndpointServiceName.getResponseField( 247 | "ServiceConfigurations.0.ServiceName" 248 | ); 249 | 250 | const outputEndpointService = new cdk.CfnOutput(this, "ServiceName", { 251 | value: serviceName, 252 | exportName: "ServiceName", 253 | }); 254 | 255 | const outputNlb = new cdk.CfnOutput(this, "ConsumerNLBArnOutput", { 256 | value: nlb.loadBalancerArn, 257 | exportName: "ConsumerNLBArn", 258 | }); 259 | 260 | const nlbListener = nlb.addListener("HttpsListener", { 261 | port: 443, 262 | }); 263 | 264 | //mTLS configuration 265 | const albAccessLogs = new Bucket(this, "ALBAccessLogsBucket", { 266 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 267 | enforceSSL: true, 268 | encryption: BucketEncryption.S3_MANAGED, 269 | serverAccessLogsPrefix: "logs", 270 | objectOwnership: ObjectOwnership.OBJECT_WRITER, 271 | }); 272 | 273 | const albSecGroup = new SecurityGroup(this, "albSecGroup", { 274 | vpc, 275 | allowAllOutbound: true, 276 | }) 277 | 278 | const alb = new ApplicationLoadBalancer(this, "ConsumerALB", { 279 | vpc, 280 | internetFacing: false, 281 | securityGroup: albSecGroup, 282 | }) 283 | alb.logAccessLogs(albAccessLogs, "albAccessLogs") 284 | alb.connections.allowFrom(Peer.ipv4(vpc.vpcCidrBlock), Port.tcp(443)) 285 | 286 | const tg = new ApplicationTargetGroup(this, "ApiTargetGroup", { 287 | targetType: TargetType.IP, 288 | port: 443, 289 | healthCheck: { 290 | enabled: true, 291 | protocol: Protocol.HTTPS, 292 | path: "/ping", 293 | }, 294 | vpc, 295 | }); 296 | 297 | const trustStoreFnRole = new Role(this, "trustStoreFnRole", { 298 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 299 | }); 300 | trustStoreFnRole.addToPolicy( 301 | new PolicyStatement({ 302 | resources: ["*"], 303 | actions: [ 304 | "elasticloadbalancing:CreateTrustStore", 305 | "logs:CreateLogGroup", 306 | "logs:CreateLogStream", 307 | "logs:PutLogEvents", 308 | ], 309 | }) 310 | ); 311 | 312 | const caBucket = new Bucket(this, "CABucket", { 313 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 314 | enforceSSL: true, 315 | encryption: BucketEncryption.S3_MANAGED, 316 | serverAccessLogsPrefix: "logs", 317 | objectOwnership: ObjectOwnership.OBJECT_WRITER, 318 | }) 319 | 320 | const uploadCa = new BucketDeployment(this, "DeployCAFiles", { 321 | sources: [ 322 | Source.asset("./ca") 323 | ], 324 | destinationBucket: caBucket, 325 | }) 326 | 327 | const ts = new CfnTrustStore(this, 'TrustStore', { 328 | caCertificatesBundleS3Bucket: caBucket.bucketName, 329 | caCertificatesBundleS3Key: "Certificate.pem", 330 | name: "alb-trust-store", 331 | }) 332 | ts.node.addDependency(uploadCa) 333 | 334 | const hostedZone = HostedZone.fromHostedZoneAttributes(this, 'HostedZone', { 335 | zoneName: domain.valueAsString, 336 | hostedZoneId: hostedZoneId.valueAsString 337 | }); 338 | 339 | const cert = new Certificate(this, 'Certificate', { 340 | domainName: subdomain.valueAsString + "." + domain.valueAsString, 341 | validation: CertificateValidation.fromDns(hostedZone), 342 | }); 343 | 344 | 345 | 346 | // Create an ApplicationListener (L2 construct) 347 | const albListener = new ApplicationListener(this, 'ALBListener', { 348 | loadBalancer: alb, 349 | port: 443, 350 | certificates: [{certificateArn: cert.certificateArn}], 351 | defaultAction: ListenerAction.forward([tg]) 352 | }); 353 | 354 | // Add mutual authentication using the underlying L1 construct 355 | const cfnListener = albListener.node.defaultChild as CfnListener; 356 | cfnListener.mutualAuthentication = { 357 | mode: "verify", 358 | trustStoreArn: ts.getAtt("TrustStoreArn").toString() 359 | }; 360 | 361 | nlbListener.addTargets('ALBTargets', { 362 | targets: [new AlbListenerTarget(albListener)], 363 | port: 443, 364 | }); 365 | 366 | const ipTargetRegisterRole = new Role(this, "ipTargetRegisterRole", { 367 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 368 | }); 369 | ipTargetRegisterRole.addToPolicy( 370 | new PolicyStatement({ 371 | resources: ["*"], 372 | actions: [ 373 | "ec2:DescribeVpcEndpoints", 374 | "ec2:DescribeNetworkInterfaces", 375 | "elasticloadbalancing:RegisterTargets", 376 | "logs:CreateLogGroup", 377 | "logs:CreateLogStream", 378 | "logs:PutLogEvents", 379 | ], 380 | }) 381 | ); 382 | 383 | const ipTargetRegisterFn = new NodejsFunction( 384 | this, 385 | "IpTargetRegisterFunction", 386 | { 387 | runtime: Runtime.NODEJS_22_X, 388 | handler: "lambdaHandler", 389 | entry: "./targetRegister/app.ts", 390 | role: ipTargetRegisterRole, 391 | environment: { 392 | vpceId: privateApiEndpoint.vpcEndpointId, 393 | targetGroupArn: tg.targetGroupArn, 394 | }, 395 | } 396 | ); 397 | 398 | const customResourceRole = new Role(this, "customResourceRole", { 399 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 400 | }); 401 | customResourceRole.addToPolicy( 402 | new PolicyStatement({ 403 | resources: ["*"], 404 | actions: [ 405 | "logs:CreateLogGroup", 406 | "logs:CreateLogStream", 407 | "logs:PutLogEvents", 408 | ], 409 | }) 410 | ); 411 | 412 | const customIpRegisterProvider = new custom.Provider( 413 | this, 414 | "CustomIpRegisterProvider", 415 | { 416 | onEventHandler: ipTargetRegisterFn, 417 | role: customResourceRole, 418 | } 419 | ); 420 | 421 | new CustomResource(this, "IpTargetRegister", { 422 | serviceToken: customIpRegisterProvider.serviceToken, 423 | }); 424 | 425 | const apiResourcePolicy = new PolicyDocument({ 426 | statements: [ 427 | new PolicyStatement({ 428 | actions: ["execute-api:Invoke"], 429 | principals: [new AnyPrincipal()], 430 | effect: Effect.ALLOW, 431 | resources: ["execute-api:/*"], 432 | }), 433 | new PolicyStatement({ 434 | actions: ["execute-api:Invoke"], 435 | principals: [new AnyPrincipal()], 436 | effect: Effect.DENY, 437 | resources: ["execute-api:/*"], 438 | conditions: { 439 | StringNotEquals: { 440 | "aws:SourceVpce": privateApiEndpoint.vpcEndpointId, 441 | }, 442 | }, 443 | }), 444 | ], 445 | }); 446 | 447 | // Producer API configuration 448 | const apiHandler = new NodejsFunction(this, "ProducerApiFunction", { 449 | runtime: Runtime.NODEJS_22_X, 450 | handler: "lambdaHandler", 451 | entry: "./api/app.ts", 452 | }); 453 | 454 | const authorizerFnRole = new Role(this, "authorizerFnRole", { 455 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 456 | }); 457 | authorizerFnRole.addToPolicy( 458 | new PolicyStatement({ 459 | resources: ["*"], 460 | actions: [ 461 | "kms:Decrypt", 462 | "logs:CreateLogGroup", 463 | "logs:CreateLogStream", 464 | "logs:PutLogEvents", 465 | ], 466 | }) 467 | ); 468 | authorizerFnRole.addToPolicy( 469 | new PolicyStatement({ 470 | resources: [apiSecret.secretFullArn!], 471 | actions: ["secretsmanager:GetSecretValue"], 472 | }) 473 | ); 474 | 475 | const authorizerFn = new NodejsFunction(this, "AuthorizerFunction", { 476 | runtime: Runtime.NODEJS_22_X, 477 | handler: "lambdaHandler", 478 | entry: "./authorizer/app.ts", 479 | environment: { 480 | API_KEY: apiSecret.secretFullArn!, 481 | }, 482 | role: authorizerFnRole, 483 | }); 484 | 485 | const authorizer = new TokenAuthorizer(this, "Authorizer", { 486 | handler: authorizerFn, 487 | }); 488 | 489 | const prdLogGroup = new LogGroup(this, "PrdLogs"); 490 | const api = new LambdaRestApi(this, "ProducerApi", { 491 | handler: apiHandler, 492 | proxy: false, 493 | endpointConfiguration: { 494 | types: [EndpointType.PRIVATE], 495 | }, 496 | defaultMethodOptions: { 497 | authorizer, 498 | }, 499 | deployOptions: { 500 | accessLogDestination: new LogGroupLogDestination(prdLogGroup), 501 | accessLogFormat: AccessLogFormat.jsonWithStandardFields(), 502 | methodOptions: { 503 | "/*/*": { 504 | loggingLevel: MethodLoggingLevel.ERROR, 505 | }, 506 | }, 507 | }, 508 | policy: apiResourcePolicy, 509 | }); 510 | 511 | const items = api.root.addResource(resource); 512 | items.addMethod("GET"); 513 | 514 | const role = new Role(this, "CloudWatchRole", { 515 | assumedBy: new ServicePrincipal("apigateway.amazonaws.com"), 516 | managedPolicies: [ 517 | ManagedPolicy.fromAwsManagedPolicyName( 518 | "service-role/AmazonAPIGatewayPushToCloudWatchLogs" 519 | ), 520 | ], 521 | }); 522 | 523 | const cloudWatchAccount = new CfnAccount(this, "Account", { 524 | cloudWatchRoleArn: role.roleArn, 525 | }); 526 | 527 | api.node.addDependency(cloudWatchAccount); 528 | 529 | const requestValidator = new RequestValidator( 530 | this, 531 | "ConsumerRequestValidator", 532 | { 533 | restApi: api, 534 | requestValidatorName: "prdValidator", 535 | validateRequestBody: false, 536 | validateRequestParameters: false, 537 | } 538 | ); 539 | 540 | const deployment = new Deployment(this, "Deployment", { api }); 541 | 542 | const domainName = new DomainName(this, 'APIDomainName', { 543 | certificate: cert, 544 | domainName: subdomain.valueAsString + "." + domain.valueAsString 545 | }); 546 | 547 | domainName.addBasePathMapping(api, { 548 | stage: api.deploymentStage, 549 | }) 550 | domainName.node.addDependency(deployment) 551 | 552 | const outputAPI = new cdk.CfnOutput(this, "ApiUrl", { 553 | value: api.urlForPath(`/${resource}`), 554 | exportName: "ApiUrl", 555 | }); 556 | } 557 | } 558 | -------------------------------------------------------------------------------- /alb-mtls/producer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "producer", 3 | "version": "0.1.0", 4 | "bin": { 5 | "producer": "bin/producer.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.172.0", 18 | "esbuild": "^0.19.9", 19 | "jest": "^27.5.1", 20 | "ts-jest": "^27.1.4", 21 | "ts-node": "^10.9.1", 22 | "typescript": "~3.9.7" 23 | }, 24 | "dependencies": { 25 | "@aws-sdk/client-ec2": "^3.462.0", 26 | "@aws-sdk/client-elastic-load-balancing-v2": "^3.462.0", 27 | "@aws-sdk/client-route-53": "^3.369.0", 28 | "@aws-sdk/client-secrets-manager": "^3.369.0", 29 | "aws-cdk-lib": "2.172.0", 30 | "cdk-nag": "^2.27.205", 31 | "constructs": "^10.0.0", 32 | "source-map-support": "^0.5.21" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /alb-mtls/producer/rotation/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import boto3 5 | import logging 6 | import os 7 | 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | 11 | 12 | def lambda_handler(event, context): 13 | """Secrets Manager Rotation Template 14 | This is a template for creating an AWS Secrets Manager rotation lambda 15 | Args: 16 | event (dict): Lambda dictionary of event parameters. These keys must include the following: 17 | - SecretId: The secret ARN or identifier 18 | - ClientRequestToken: The ClientRequestToken of the secret version 19 | - Step: The rotation step (one of createSecret, setSecret, testSecret, or finishSecret) 20 | context (LambdaContext): The Lambda runtime information 21 | Raises: 22 | ResourceNotFoundException: If the secret with the specified arn and stage does not exist 23 | ValueError: If the secret is not properly configured for rotation 24 | KeyError: If the event parameters do not contain the expected keys 25 | """ 26 | arn = event['SecretId'] 27 | token = event['ClientRequestToken'] 28 | step = event['Step'] 29 | 30 | # Setup the client 31 | service_client = boto3.client( 32 | 'secretsmanager', endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT']) 33 | 34 | # Make sure the version is staged correctly 35 | metadata = service_client.describe_secret(SecretId=arn) 36 | if not metadata['RotationEnabled']: 37 | logger.error("Secret %s is not enabled for rotation" % arn) 38 | raise ValueError("Secret %s is not enabled for rotation" % arn) 39 | versions = metadata['VersionIdsToStages'] 40 | if token not in versions: 41 | logger.error( 42 | "Secret version %s has no stage for rotation of secret %s." % (token, arn)) 43 | raise ValueError( 44 | "Secret version %s has no stage for rotation of secret %s." % (token, arn)) 45 | if "AWSCURRENT" in versions[token]: 46 | logger.info( 47 | "Secret version %s already set as AWSCURRENT for secret %s." % (token, arn)) 48 | return 49 | elif "AWSPENDING" not in versions[token]: 50 | logger.error( 51 | "Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) 52 | raise ValueError( 53 | "Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) 54 | 55 | if step == "createSecret": 56 | create_secret(service_client, arn, token) 57 | 58 | elif step == "setSecret": 59 | set_secret(service_client, arn, token) 60 | 61 | elif step == "testSecret": 62 | test_secret(service_client, arn, token) 63 | 64 | elif step == "finishSecret": 65 | finish_secret(service_client, arn, token) 66 | 67 | else: 68 | raise ValueError("Invalid step parameter") 69 | 70 | 71 | def create_secret(service_client, arn, token): 72 | """Create the secret 73 | This method first checks for the existence of a secret for the passed in token. If one does not exist, it will generate a 74 | new secret and put it with the passed in token. 75 | Args: 76 | service_client (client): The secrets manager service client 77 | arn (string): The secret ARN or other identifier 78 | token (string): The ClientRequestToken associated with the secret version 79 | Raises: 80 | ResourceNotFoundException: If the secret with the specified arn and stage does not exist 81 | """ 82 | # Make sure the current secret exists 83 | service_client.get_secret_value(SecretId=arn, VersionStage="AWSCURRENT") 84 | 85 | # Now try to get the secret version, if that fails, put a new secret 86 | try: 87 | service_client.get_secret_value( 88 | SecretId=arn, VersionId=token, VersionStage="AWSPENDING") 89 | logger.info("createSecret: Successfully retrieved secret for %s." % arn) 90 | except service_client.exceptions.ResourceNotFoundException: 91 | # Get exclude characters from environment variable 92 | exclude_characters = os.environ['EXCLUDE_CHARACTERS'] if 'EXCLUDE_CHARACTERS' in os.environ else '/@"\'\\' 93 | # Generate a random password 94 | passwd = service_client.get_random_password( 95 | ExcludeCharacters=exclude_characters) 96 | 97 | # Put the secret 98 | service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, 99 | SecretString=passwd['RandomPassword'], VersionStages=['AWSPENDING']) 100 | logger.info( 101 | "createSecret: Successfully put secret for ARN %s and version %s." % (arn, token)) 102 | 103 | 104 | def set_secret(service_client, arn, token): 105 | """Set the secret 106 | This method should set the AWSPENDING secret in the service that the secret belongs to. For example, if the secret is a database 107 | credential, this method should take the value of the AWSPENDING secret and set the user's password to this value in the database. 108 | Args: 109 | service_client (client): The secrets manager service client 110 | arn (string): The secret ARN or other identifier 111 | token (string): The ClientRequestToken associated with the secret version 112 | """ 113 | # This is where the secret should be set in the service 114 | raise NotImplementedError 115 | 116 | 117 | def test_secret(service_client, arn, token): 118 | """Test the secret 119 | This method should validate that the AWSPENDING secret works in the service that the secret belongs to. For example, if the secret 120 | is a database credential, this method should validate that the user can login with the password in AWSPENDING and that the user has 121 | all of the expected permissions against the database. 122 | Args: 123 | service_client (client): The secrets manager service client 124 | arn (string): The secret ARN or other identifier 125 | token (string): The ClientRequestToken associated with the secret version 126 | """ 127 | # This is where the secret should be tested against the service 128 | raise NotImplementedError 129 | 130 | 131 | def finish_secret(service_client, arn, token): 132 | """Finish the secret 133 | This method finalizes the rotation process by marking the secret version passed in as the AWSCURRENT secret. 134 | Args: 135 | service_client (client): The secrets manager service client 136 | arn (string): The secret ARN or other identifier 137 | token (string): The ClientRequestToken associated with the secret version 138 | Raises: 139 | ResourceNotFoundException: If the secret with the specified arn does not exist 140 | """ 141 | # First describe the secret to get the current version 142 | metadata = service_client.describe_secret(SecretId=arn) 143 | current_version = None 144 | for version in metadata["VersionIdsToStages"]: 145 | if "AWSCURRENT" in metadata["VersionIdsToStages"][version]: 146 | if version == token: 147 | # The correct version is already marked as current, return 148 | logger.info( 149 | "finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn)) 150 | return 151 | current_version = version 152 | break 153 | 154 | # Finalize by staging the secret version current 155 | service_client.update_secret_version_stage( 156 | SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version) 157 | logger.info( 158 | "finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (token, arn)) 159 | -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/app.ts: -------------------------------------------------------------------------------- 1 | import { EC2Client, DescribeNetworkInterfacesCommand, DescribeVpcEndpointsCommand } from '@aws-sdk/client-ec2'; 2 | import { ElasticLoadBalancingV2Client, RegisterTargetsCommand } from '@aws-sdk/client-elastic-load-balancing-v2'; 3 | const ec2 = new EC2Client({}); 4 | const elb = new ElasticLoadBalancingV2Client({}); 5 | 6 | export const lambdaHandler = async (event: any, context: any) => { 7 | console.log('REQUEST RECEIVED:\n' + JSON.stringify(event)); 8 | if (event.RequestType == 'Delete') { 9 | await sendResponse(event, context, 'SUCCESS'); 10 | return; 11 | } 12 | try { 13 | const describeEndpointsParams = { 14 | VpcEndpointIds: [process.env.vpceId!], 15 | }; 16 | const describeEndpointsCommand = new DescribeVpcEndpointsCommand(describeEndpointsParams); 17 | const endpoints = await ec2.send(describeEndpointsCommand); 18 | const endpointArray: string[] = []; 19 | endpoints.VpcEndpoints![0].NetworkInterfaceIds?.map((it) => endpointArray.push(it)); 20 | console.log(endpointArray); 21 | 22 | const describeEniParams = { 23 | NetworkInterfaceIds: endpointArray, 24 | }; 25 | const describeEniCommand = new DescribeNetworkInterfacesCommand(describeEniParams); 26 | const enis = await ec2.send(describeEniCommand); 27 | console.log(enis); 28 | 29 | for (let ip = 0; ip < enis.NetworkInterfaces!.length!; ip++) { 30 | const addTargetParams = { 31 | TargetGroupArn: process.env.targetGroupArn!, 32 | Targets: [{ Id: enis.NetworkInterfaces![ip].PrivateIpAddress! }], 33 | }; 34 | const addTargetCommand = new RegisterTargetsCommand(addTargetParams); 35 | await elb.send(addTargetCommand); 36 | } 37 | await sendResponse(event, context, 'SUCCESS'); 38 | } catch (e) { 39 | console.log(e); 40 | await sendResponse(event, context, 'FAILED'); 41 | } 42 | }; 43 | 44 | const sendResponse = async (event: any, context: any, responseStatus: string) => { 45 | const responseBody = JSON.stringify({ 46 | Status: responseStatus, 47 | Reason: 'See the details in CloudWatch Log Stream: ' + context.logStreamName, 48 | PhysicalResourceId: context.logStreamName, 49 | StackId: event.StackId, 50 | RequestId: event.RequestId, 51 | LogicalResourceId: event.LogicalResourceId, 52 | }); 53 | 54 | console.log('RESPONSE BODY:\n', responseBody); 55 | 56 | const https = require('https'); 57 | const url = require('url'); 58 | 59 | const parsedUrl = url.parse(event.ResponseURL); 60 | const options = { 61 | hostname: parsedUrl.hostname, 62 | port: 443, 63 | path: parsedUrl.path, 64 | method: 'PUT', 65 | headers: { 66 | 'content-type': '', 67 | 'content-length': responseBody.length, 68 | }, 69 | }; 70 | 71 | console.log('SENDING RESPONSE...\n'); 72 | 73 | const request = https.request(options, function (response: any) { 74 | console.log('STATUS: ' + response.statusCode); 75 | console.log('HEADERS: ' + JSON.stringify(response.headers)); 76 | context.done(); 77 | }); 78 | 79 | request.on('error', function (error: any) { 80 | console.log('sendResponse Error:' + error); 81 | context.done(); 82 | }); 83 | request.write(responseBody); 84 | request.end(); 85 | }; 86 | -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern_3", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-ec2": "^3.631.0", 11 | "@aws-sdk/client-elastic-load-balancing-v2": "^3.631.0", 12 | "aws-sdk": "^2.1354.0" 13 | }, 14 | "scripts": { 15 | "unit": "jest", 16 | "lint": "eslint '*.ts' --quiet --fix", 17 | "compile": "tsc", 18 | "test": "npm run compile && npm run unit" 19 | }, 20 | "devDependencies": { 21 | "@types/aws-lambda": "^8.10.92", 22 | "@types/jest": "^27.4.0", 23 | "@types/node": "^17.0.13", 24 | "@types/pg": "^8.6.5", 25 | "@typescript-eslint/eslint-plugin": "^5.10.2", 26 | "@typescript-eslint/parser": "^5.10.2", 27 | "esbuild": "^0.14.14", 28 | "esbuild-jest": "^0.5.0", 29 | "eslint": "^8.8.0", 30 | "eslint-config-prettier": "^8.3.0", 31 | "eslint-plugin-prettier": "^4.0.0", 32 | "jest": "^27.5.0", 33 | "prettier": "^2.5.1", 34 | "ts-node": "^10.4.0", 35 | "typescript": "^4.5.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /alb-mtls/producer/targetRegister/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /alb-mtls/producer/test/producer.test.ts: -------------------------------------------------------------------------------- 1 | // import * as cdk from 'aws-cdk-lib'; 2 | // import { Template } from 'aws-cdk-lib/assertions'; 3 | // import * as Producer from '../lib/producer-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/producer-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Producer.ProducerStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /alb-mtls/producer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /architecture.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/cross-account-private-api-patterns/fd0ba88db91abf186ccd4f6f79862701871398ba/architecture.png -------------------------------------------------------------------------------- /mtls/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM nginx:alpine 2 | 3 | COPY ./nginx.conf /etc/nginx/conf.d/default.conf 4 | 5 | COPY ./server.key /etc/ssl/server.key 6 | COPY ./server.crt /etc/ssl/server.crt 7 | COPY ./client.crt /etc/ssl/client.crt 8 | -------------------------------------------------------------------------------- /mtls/README.md: -------------------------------------------------------------------------------- 1 | # NGINX Reverse Proxy with mTLS Termination 2 | 3 | This Dockerfile builds an nginx reverse proxy that acts as the termination for mTLS-based connections and will establish a standard TLS connection to the backend resource 4 | 5 | ## Instructions 6 | 7 | First generate the client and server SSL certificates to be used by the mTLS handshake. Be sure to specify the Common Name field for the domain you wish to verify (e.g. local.dev) 8 | 9 | ### Server 10 | 11 | ``` 12 | openssl genrsa -out server.key 2048 13 | openssl req -new -key server.key -out server.csr 14 | ``` 15 | 16 | ``` 17 | openssl x509 -req -days 3650 -in server.csr -signkey server.key -out server.crt 18 | ``` 19 | 20 | ### Client 21 | 22 | ``` 23 | openssl genrsa -out client.key 2048 24 | openssl req -new -key client.key -out client.csr 25 | ``` 26 | 27 | ``` 28 | openssl x509 -req -days 3650 -in client.csr -signkey client.key -out client.crt 29 | ``` 30 | 31 | ### NGINX 32 | 33 | Update the nginx.conf file to replace the proxy URL location with the URL of your backend resource. 34 | 35 | ### Docker 36 | 37 | Build the docker image which will copy the nginx config and ssl certs 38 | 39 | ``` 40 | docker build . -t mtls 41 | ``` 42 | 43 | ## Testing 44 | 45 | To test it locally, run an instance of the docker image built in the last step and make an HTTPS request to localhost supplying the client certificate. Make sure you have the DNS name you are using added to your hosts file (e.g. local.dev). 46 | 47 | ``` 48 | docker run -p 8080:443 mtls 49 | ``` 50 | 51 | ``` 52 | curl --key client.key --cert client.crt --cacert server.crt https://local.dev:8080 53 | ``` 54 | 55 | ## Deployment 56 | 57 | This container image can then be deployed on any container orchestration platform such as Amazon ECS or EKS. 58 | -------------------------------------------------------------------------------- /mtls/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 443 ssl; 3 | 4 | ssl_certificate /etc/ssl/server.crt; 5 | ssl_certificate_key /etc/ssl/server.key; 6 | ssl_protocols TLSv1.2; 7 | ssl_prefer_server_ciphers on; 8 | ssl_ciphers ECDH+AESGCM:ECDH+AES256:ECDH+AES128:DH+3DES:!ADH:!AECDH:!MD5; 9 | 10 | ssl_client_certificate /etc/ssl/client.crt; 11 | ssl_verify_client on; 12 | 13 | location / { 14 | proxy_pass https://replace-with-my-api-url.com; 15 | } 16 | } 17 | 18 | server { 19 | listen 80; 20 | 21 | location /health { 22 | add_header Content-Type text/plain; 23 | return 200 "OK"; 24 | } 25 | } -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cross-account-private-api-patterns", 3 | "lockfileVersion": 2, 4 | "requires": true, 5 | "packages": {} 6 | } 7 | -------------------------------------------------------------------------------- /pattern1-arch1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/cross-account-private-api-patterns/fd0ba88db91abf186ccd4f6f79862701871398ba/pattern1-arch1.png -------------------------------------------------------------------------------- /pattern1-arch2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/cross-account-private-api-patterns/fd0ba88db91abf186ccd4f6f79862701871398ba/pattern1-arch2.png -------------------------------------------------------------------------------- /pattern1-arch3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/cross-account-private-api-patterns/fd0ba88db91abf186ccd4f6f79862701871398ba/pattern1-arch3.png -------------------------------------------------------------------------------- /pattern1-arch4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/cross-account-private-api-patterns/fd0ba88db91abf186ccd4f6f79862701871398ba/pattern1-arch4.png -------------------------------------------------------------------------------- /pattern2-arch1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aws-samples/cross-account-private-api-patterns/fd0ba88db91abf186ccd4f6f79862701871398ba/pattern2-arch1.png -------------------------------------------------------------------------------- /vpc-endpoint/consumer/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | cdk.context.json -------------------------------------------------------------------------------- /vpc-endpoint/consumer/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetSecretValueCommand, 3 | SecretsManagerClient, 4 | } from "@aws-sdk/client-secrets-manager"; 5 | 6 | const secretManager = new SecretsManagerClient({}); 7 | 8 | export const lambdaHandler = async ( 9 | event: any, 10 | context: any, 11 | callback: any 12 | ) => { 13 | const token = event.authorizationToken; 14 | const input = { 15 | SecretId: process.env.API_KEY, 16 | }; 17 | const command = new GetSecretValueCommand(input); 18 | const response = await secretManager.send(command); 19 | 20 | switch (token) { 21 | case response.SecretString: 22 | callback(null, generatePolicy("user", "Allow", event.methodArn)); 23 | break; 24 | default: 25 | callback("Error: Invalid token"); 26 | } 27 | }; 28 | 29 | const generatePolicy = ( 30 | principalId: string, 31 | effect: string, 32 | resource: string 33 | ): any => { 34 | let authResponse: any = {}; 35 | 36 | authResponse.principalId = principalId; 37 | if (effect && resource) { 38 | let policyDocument: any = {}; 39 | policyDocument.Version = "2012-10-17"; 40 | policyDocument.Statement = []; 41 | let statementOne: any = {}; 42 | statementOne.Action = "execute-api:Invoke"; 43 | statementOne.Effect = effect; 44 | statementOne.Resource = resource; 45 | policyDocument.Statement[0] = statementOne; 46 | authResponse.policyDocument = policyDocument; 47 | } 48 | 49 | return authResponse; 50 | }; 51 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorizer", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-secrets-manager": "^3.370.0" 11 | }, 12 | "scripts": { 13 | "unit": "jest", 14 | "lint": "eslint '*.ts' --quiet --fix", 15 | "compile": "tsc", 16 | "test": "npm run compile && npm run unit" 17 | }, 18 | "devDependencies": { 19 | "@types/aws-lambda": "^8.10.92", 20 | "@types/jest": "^27.4.0", 21 | "@types/node": "^17.0.13", 22 | "@types/pg": "^8.6.5", 23 | "@typescript-eslint/eslint-plugin": "^5.10.2", 24 | "@typescript-eslint/parser": "^5.10.2", 25 | "esbuild": "^0.14.14", 26 | "esbuild-jest": "^0.5.0", 27 | "eslint": "^8.8.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "jest": "^27.5.0", 31 | "prettier": "^2.5.1", 32 | "ts-node": "^10.4.0", 33 | "typescript": "^4.5.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/authorizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /vpc-endpoint/consumer/bin/consumer.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "aws-cdk-lib"; 4 | import { ConsumerApiStack } from "../lib/consumer-api-stack"; 5 | import { ConsumerVpcStack } from "../lib/consumer-vpc-stack"; 6 | import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; 7 | 8 | const app = new cdk.App(); 9 | const vpc = new ConsumerVpcStack(app, "ConsumerVpcStack", { 10 | env: { 11 | //Required for ELBv2 access logging to work 12 | account: process.env.CDK_DEFAULT_ACCOUNT, 13 | region: process.env.CDK_DEFAULT_REGION, 14 | }, 15 | }); 16 | 17 | const api = new ConsumerApiStack(app, "ConsumerApiStack", { 18 | env: { 19 | //Required for Vpc.FromLookup to work 20 | account: process.env.CDK_DEFAULT_ACCOUNT, 21 | region: process.env.CDK_DEFAULT_REGION, 22 | }, 23 | }); 24 | cdk.Aspects.of(app).add(new AwsSolutionsChecks()); 25 | NagSuppressions.addStackSuppressions(vpc, [ 26 | { 27 | id: "AwsSolutions-L1", 28 | reason: "Custom resource is currently hardcoded to NodeJS 14", 29 | }, 30 | { 31 | id: "AwsSolutions-IAM5", 32 | reason: 33 | "IAM policy resources not scoped where the resource ID is not known ahead of time", 34 | }, 35 | { 36 | id: "AwsSolutions-IAM4", 37 | reason: 38 | "AWS Managed Policies used in this solution are: AWSLambdaBasicExecutionRole", 39 | }, 40 | { 41 | id: "AwsSolutions-SMG4", 42 | reason: "No rotation required for secret used in this solution" 43 | } 44 | ]); 45 | NagSuppressions.addStackSuppressions(api, [ 46 | { 47 | id: "AwsSolutions-COG4", 48 | reason: 49 | "Token-based authorization is used in this solution rather than Cognito", 50 | }, 51 | { 52 | id: "AwsSolutions-IAM4", 53 | reason: 54 | "AWS Managed Policies used in this solution are: AWSLambdaBasicExecutionRole, AWSLambdaVPCAccessExecutionRole, AmazonAPIGatewayPushToCloudWatchLogs", 55 | }, 56 | { 57 | id: "AwsSolutions-IAM5", 58 | reason: "Custom Resource IAM Policy", 59 | }, 60 | { 61 | id: "AwsSolutions-L1", 62 | reason: "Custom Resource is currently hardcoded to NodeJS 14", 63 | }, 64 | { 65 | id: "AwsSolutions-SMG4", 66 | reason: "No rotation required for secret used in this solution" 67 | } 68 | ]); 69 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/consumer.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/app.ts: -------------------------------------------------------------------------------- 1 | import { EC2Client, ModifyVpcEndpointCommand} from '@aws-sdk/client-ec2'; 2 | const ec2 = new EC2Client({}); 3 | 4 | export const lambdaHandler = async (event: any, context: any) => { 5 | console.log('REQUEST RECEIVED:\n' + JSON.stringify(event)); 6 | if (event.RequestType == 'Delete') { 7 | await sendResponse(event, context, 'SUCCESS'); 8 | return; 9 | } 10 | try { 11 | const apiId = process.env.API?.split(".")[0].replace("https://", ""); 12 | const params = { 13 | VpcEndpointId: process.env.VPCE, 14 | PolicyDocument: `{\"Statement\":[{\"Action\":\"execute-api:Invoke\",\"Effect\":\"Allow\",\"Principal\":{\"AWS\":\"*\"},\"Resource\":\"arn:aws:execute-api:${process.env.REGION}:${process.env.ACCOUNT}:${apiId}/*\"}],\"Version\":\"2012-10-17\"}` 15 | }; 16 | const modifyVpceCommand = new ModifyVpcEndpointCommand(params); 17 | await ec2.send(modifyVpceCommand); 18 | 19 | await sendResponse(event, context, 'SUCCESS'); 20 | } catch (e) { 21 | console.log(e); 22 | await sendResponse(event, context, 'FAILED'); 23 | } 24 | }; 25 | 26 | const sendResponse = async (event: any, context: any, responseStatus: string) => { 27 | const responseBody = JSON.stringify({ 28 | Status: responseStatus, 29 | Reason: 'See the details in CloudWatch Log Stream: ' + context.logStreamName, 30 | PhysicalResourceId: context.logStreamName, 31 | StackId: event.StackId, 32 | RequestId: event.RequestId, 33 | LogicalResourceId: event.LogicalResourceId, 34 | }); 35 | 36 | console.log('RESPONSE BODY:\n', responseBody); 37 | 38 | const https = require('https'); 39 | const url = require('url'); 40 | 41 | const parsedUrl = url.parse(event.ResponseURL); 42 | const options = { 43 | hostname: parsedUrl.hostname, 44 | port: 443, 45 | path: parsedUrl.path, 46 | method: 'PUT', 47 | headers: { 48 | 'content-type': '', 49 | 'content-length': responseBody.length, 50 | }, 51 | }; 52 | 53 | console.log('SENDING RESPONSE...\n'); 54 | 55 | const request = https.request(options, function (response: any) { 56 | console.log('STATUS: ' + response.statusCode); 57 | console.log('HEADERS: ' + JSON.stringify(response.headers)); 58 | context.done(); 59 | }); 60 | 61 | request.on('error', function (error: any) { 62 | console.log('sendResponse Error:' + error); 63 | context.done(); 64 | }); 65 | request.write(responseBody); 66 | request.end(); 67 | }; 68 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern_3", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-ec2": "^3.664.0", 11 | "@aws-sdk/client-elastic-load-balancing-v2": "^3.664.0", 12 | "aws-sdk": "^2.1354.0" 13 | }, 14 | "scripts": { 15 | "unit": "jest", 16 | "lint": "eslint '*.ts' --quiet --fix", 17 | "compile": "tsc", 18 | "test": "npm run compile && npm run unit" 19 | }, 20 | "devDependencies": { 21 | "@types/aws-lambda": "^8.10.92", 22 | "@types/jest": "^27.4.0", 23 | "@types/node": "^17.0.13", 24 | "@types/pg": "^8.6.5", 25 | "@typescript-eslint/eslint-plugin": "^5.10.2", 26 | "@typescript-eslint/parser": "^5.10.2", 27 | "esbuild": "^0.14.14", 28 | "esbuild-jest": "^0.5.0", 29 | "eslint": "^8.8.0", 30 | "eslint-config-prettier": "^8.3.0", 31 | "eslint-plugin-prettier": "^4.0.0", 32 | "jest": "^27.5.0", 33 | "prettier": "^2.5.1", 34 | "ts-node": "^10.4.0", 35 | "typescript": "^4.5.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/endpointPolicy/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /vpc-endpoint/consumer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/app.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { 3 | GetSecretValueCommand, 4 | SecretsManagerClient, 5 | } from "@aws-sdk/client-secrets-manager"; 6 | 7 | const secretManager = new SecretsManagerClient({}); 8 | 9 | export interface GetWidgetEvent {} 10 | 11 | export const lambdaHandler = async (event: GetWidgetEvent) => { 12 | const input = { 13 | SecretId: process.env.API_KEY, 14 | }; 15 | const command = new GetSecretValueCommand(input); 16 | const secretResponse = await secretManager.send(command); 17 | const response = await axios.get(process.env.apiUrl!, { 18 | headers: { 19 | 'Authorization': secretResponse.SecretString 20 | } 21 | }); 22 | return { 23 | statusCode: 200, 24 | body: JSON.stringify(response.data), 25 | }; 26 | }; 27 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern_3", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "aws-sdk": "^2.1354.0", 11 | "axios": "^1.7.4", 12 | "@aws-sdk/client-secrets-manager": "^3.370.0" 13 | }, 14 | "scripts": { 15 | "unit": "jest", 16 | "lint": "eslint '*.ts' --quiet --fix", 17 | "compile": "tsc", 18 | "test": "npm run compile && npm run unit" 19 | }, 20 | "devDependencies": { 21 | "@types/aws-lambda": "^8.10.92", 22 | "@types/jest": "^27.4.0", 23 | "@types/node": "^17.0.13", 24 | "@types/pg": "^8.6.5", 25 | "@typescript-eslint/eslint-plugin": "^5.10.2", 26 | "@typescript-eslint/parser": "^5.10.2", 27 | "esbuild": "^0.14.14", 28 | "esbuild-jest": "^0.5.0", 29 | "eslint": "^8.8.0", 30 | "eslint-config-prettier": "^8.3.0", 31 | "eslint-plugin-prettier": "^4.0.0", 32 | "jest": "^27.5.0", 33 | "prettier": "^2.5.1", 34 | "ts-node": "^10.4.0", 35 | "typescript": "^4.5.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lambdaConsumer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lib/consumer-api-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "aws-cdk-lib"; 2 | import { Construct } from "constructs"; 3 | import { CfnParameter, CustomResource, Fn } from "aws-cdk-lib"; 4 | import { NetworkLoadBalancer } from "aws-cdk-lib/aws-elasticloadbalancingv2"; 5 | import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; 6 | import { LogGroup } from "aws-cdk-lib/aws-logs"; 7 | import { 8 | AccessLogFormat, 9 | CfnAccount, 10 | ConnectionType, 11 | Deployment, 12 | Integration, 13 | IntegrationType, 14 | LogGroupLogDestination, 15 | MethodLoggingLevel, 16 | RequestValidator, 17 | RestApi, 18 | TokenAuthorizer, 19 | VpcLink, 20 | } from "aws-cdk-lib/aws-apigateway"; 21 | import { 22 | Vpc, 23 | SecurityGroup, 24 | Peer, 25 | Port, 26 | SubnetType, 27 | } from "aws-cdk-lib/aws-ec2"; 28 | import { 29 | Role, 30 | ServicePrincipal, 31 | ManagedPolicy, 32 | PolicyStatement, 33 | } from "aws-cdk-lib/aws-iam"; 34 | import { Runtime } from "aws-cdk-lib/aws-lambda"; 35 | import { Provider } from "aws-cdk-lib/custom-resources"; 36 | 37 | export class ConsumerApiStack extends cdk.Stack { 38 | constructor(scope: Construct, id: string, props: cdk.StackProps) { 39 | super(scope, id, props); 40 | 41 | const targetApiUrl = new CfnParameter(this, "targetApiUrl", { 42 | type: "String", 43 | description: "The URI of the target private API to be invoked.", 44 | }); 45 | 46 | const producerAccountId = new CfnParameter(this, "producerAccountId", { 47 | type: "String", 48 | description: "The AWS Account ID of the producer account.", 49 | }); 50 | 51 | const nlbArn = Fn.importValue("ConsumerNLBArn"); 52 | const vpce = Fn.importValue("ConsumerVPCe"); 53 | const secretArn = Fn.importValue("ApiKeySecretArn"); 54 | 55 | const nlb = NetworkLoadBalancer.fromNetworkLoadBalancerAttributes( 56 | this, 57 | "ALB", 58 | { 59 | loadBalancerArn: nlbArn, 60 | } 61 | ); 62 | 63 | const modifyPolicyRole = new Role(this, "modifyPolicyRole", { 64 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 65 | }); 66 | modifyPolicyRole.addToPolicy( 67 | new PolicyStatement({ 68 | resources: ["*"], 69 | actions: [ 70 | "ec2:ModifyVpcEndpoint", 71 | "logs:CreateLogGroup", 72 | "logs:CreateLogStream", 73 | "logs:PutLogEvents", 74 | ], 75 | }) 76 | ); 77 | 78 | const modifyPolicyRoleFn = new NodejsFunction( 79 | this, 80 | "ModifyPolicyRoleFunction", 81 | { 82 | runtime: Runtime.NODEJS_22_X, 83 | handler: "lambdaHandler", 84 | entry: "./endpointPolicy/app.ts", 85 | role: modifyPolicyRole, 86 | environment: { 87 | VPCE: vpce, 88 | ACCOUNT: producerAccountId.valueAsString, 89 | REGION: this.region, 90 | API: targetApiUrl.valueAsString, 91 | }, 92 | } 93 | ); 94 | 95 | const customResourceRole = new Role(this, "customResourceRole", { 96 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 97 | }); 98 | customResourceRole.addToPolicy( 99 | new PolicyStatement({ 100 | resources: ["*"], 101 | actions: [ 102 | "logs:CreateLogGroup", 103 | "logs:CreateLogStream", 104 | "logs:PutLogEvents", 105 | ], 106 | }) 107 | ); 108 | 109 | const customVpcePolicyUpdate = new Provider( 110 | this, 111 | "CustomVpcePolicyUpdate", 112 | { 113 | onEventHandler: modifyPolicyRoleFn, 114 | role: customResourceRole, 115 | } 116 | ); 117 | 118 | new CustomResource(this, "VpcePolicyUpdate", { 119 | serviceToken: customVpcePolicyUpdate.serviceToken, 120 | }); 121 | 122 | const link = new VpcLink(this, "ConsumerVPCLink", { 123 | targets: [nlb], 124 | }); 125 | 126 | const integration = new Integration({ 127 | type: IntegrationType.HTTP_PROXY, 128 | integrationHttpMethod: "ANY", 129 | options: { 130 | connectionType: ConnectionType.VPC_LINK, 131 | vpcLink: link, 132 | }, 133 | uri: targetApiUrl, 134 | }); 135 | 136 | const authorizerFnRole = new Role(this, "authorizerFnRole", { 137 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 138 | }); 139 | authorizerFnRole.addToPolicy( 140 | new PolicyStatement({ 141 | resources: ["*"], 142 | actions: [ 143 | "kms:Decrypt", 144 | "logs:CreateLogGroup", 145 | "logs:CreateLogStream", 146 | "logs:PutLogEvents", 147 | ], 148 | }) 149 | ); 150 | authorizerFnRole.addToPolicy( 151 | new PolicyStatement({ 152 | resources: [secretArn], 153 | actions: ["secretsmanager:GetSecretValue"], 154 | }) 155 | ); 156 | 157 | const authorizerFn = new NodejsFunction(this, "AuthorizerFunction", { 158 | runtime: Runtime.NODEJS_22_X, 159 | handler: "lambdaHandler", 160 | entry: "./authorizer/app.ts", 161 | environment: { 162 | API_KEY: secretArn, 163 | }, 164 | role: authorizerFnRole, 165 | }); 166 | 167 | const authorizer = new TokenAuthorizer(this, "Authorizer", { 168 | handler: authorizerFn, 169 | }); 170 | 171 | const prdLogGroup = new LogGroup(this, "PrdLogs"); 172 | const api = new RestApi(this, "ConsumerApi", { 173 | defaultIntegration: integration, 174 | defaultMethodOptions: { 175 | authorizer, 176 | }, 177 | deployOptions: { 178 | accessLogDestination: new LogGroupLogDestination(prdLogGroup), 179 | accessLogFormat: AccessLogFormat.jsonWithStandardFields(), 180 | methodOptions: { 181 | "/*/*": { 182 | loggingLevel: MethodLoggingLevel.ERROR, 183 | }, 184 | }, 185 | }, 186 | }); 187 | api.root.addMethod("ANY"); 188 | 189 | const role = new Role(this, "CloudWatchRole", { 190 | assumedBy: new ServicePrincipal("apigateway.amazonaws.com"), 191 | managedPolicies: [ 192 | ManagedPolicy.fromAwsManagedPolicyName( 193 | "service-role/AmazonAPIGatewayPushToCloudWatchLogs" 194 | ), 195 | ], 196 | }); 197 | 198 | const cloudWatchAccount = new CfnAccount(this, "Account", { 199 | cloudWatchRoleArn: role.roleArn, 200 | }); 201 | 202 | api.node.addDependency(cloudWatchAccount); 203 | 204 | const deployment = new Deployment(this, "Deployment", { api }); 205 | 206 | const requestValidator = new RequestValidator( 207 | this, 208 | "ProducerRequestValidator", 209 | { 210 | restApi: api, 211 | requestValidatorName: "prdValidator", 212 | validateRequestBody: false, 213 | validateRequestParameters: false, 214 | } 215 | ); 216 | 217 | const vpc = Vpc.fromLookup(this, "VPC", { 218 | vpcName: "ConsumerVPC", 219 | }); 220 | 221 | const lambdaSecurityGroup = new SecurityGroup(this, "lambdaSecurityGroup", { 222 | vpc, 223 | allowAllOutbound: true, 224 | disableInlineRules: true, 225 | }); 226 | lambdaSecurityGroup.addIngressRule( 227 | Peer.ipv4(vpc.vpcCidrBlock), 228 | Port.allTraffic() 229 | ); 230 | 231 | const lambdaConsumerRole = new Role(this, "lambdaConsumerRole", { 232 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 233 | }); 234 | lambdaConsumerRole.addToPolicy( 235 | new PolicyStatement({ 236 | resources: ["*"], 237 | actions: [ 238 | "ec2:CreateNetworkInterface", 239 | "ec2:DescribeNetworkInterfaces", 240 | "ec2:DeleteNetworkInterface", 241 | "ec2:AssignPrivateIpAddresses", 242 | "ec2:UnassignPrivateIpAddresses", 243 | "kms:Decrypt", 244 | "logs:CreateLogGroup", 245 | "logs:CreateLogStream", 246 | "logs:PutLogEvents", 247 | ], 248 | }) 249 | ); 250 | lambdaConsumerRole.addToPolicy( 251 | new PolicyStatement({ 252 | resources: [secretArn], 253 | actions: ["secretsmanager:GetSecretValue"], 254 | }) 255 | ); 256 | 257 | const lambdaConsumer = new NodejsFunction(this, "ConsumerFunction", { 258 | runtime: Runtime.NODEJS_22_X, 259 | handler: "lambdaHandler", 260 | entry: "./lambdaConsumer/app.ts", 261 | environment: { 262 | apiUrl: targetApiUrl.valueAsString, 263 | API_KEY: secretArn, 264 | }, 265 | role: lambdaConsumerRole, 266 | vpc: vpc, 267 | vpcSubnets: vpc.selectSubnets({ 268 | subnetType: SubnetType.PRIVATE_WITH_EGRESS, 269 | }), 270 | securityGroups: [lambdaSecurityGroup], 271 | }); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/lib/consumer-vpc-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "aws-cdk-lib"; 2 | import { Construct } from "constructs"; 3 | import * as custom from "aws-cdk-lib/custom-resources"; 4 | import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; 5 | import { CfnParameter, CustomResource } from "aws-cdk-lib"; 6 | import { 7 | Vpc, 8 | FlowLog, 9 | FlowLogResourceType, 10 | InterfaceVpcEndpointAwsService, 11 | } from "aws-cdk-lib/aws-ec2"; 12 | import { 13 | NetworkLoadBalancer, 14 | NetworkTargetGroup, 15 | Protocol, 16 | TargetType, 17 | } from "aws-cdk-lib/aws-elasticloadbalancingv2"; 18 | import { 19 | Role, 20 | ServicePrincipal, 21 | PolicyStatement, 22 | AccountPrincipal, 23 | } from "aws-cdk-lib/aws-iam"; 24 | import { Code, Runtime, Function } from "aws-cdk-lib/aws-lambda"; 25 | import { 26 | Bucket, 27 | BlockPublicAccess, 28 | BucketEncryption, 29 | ObjectOwnership, 30 | } from "aws-cdk-lib/aws-s3"; 31 | import { Secret } from "aws-cdk-lib/aws-secretsmanager"; 32 | import { Key } from "aws-cdk-lib/aws-kms"; 33 | 34 | export class ConsumerVpcStack extends cdk.Stack { 35 | constructor(scope: Construct, id: string, props?: cdk.StackProps) { 36 | super(scope, id, props); 37 | 38 | const producerAccountId = new CfnParameter(this, "producerAccountId", { 39 | type: "String", 40 | description: "The AWS Account ID of the producer account.", 41 | }); 42 | 43 | const vpc = new Vpc(this, "ConsumerVPC", { 44 | vpcName: "ConsumerVPC", 45 | }); 46 | new FlowLog(this, "FlowLog", { 47 | resourceType: FlowLogResourceType.fromVpc(vpc), 48 | }); 49 | 50 | const privateApiEndpoint = vpc.addInterfaceEndpoint("ConsumerApiEndpoint", { 51 | service: InterfaceVpcEndpointAwsService.APIGATEWAY, 52 | }); 53 | 54 | const outputVPCe = new cdk.CfnOutput(this, "ConsumerVPCe", { 55 | value: privateApiEndpoint.vpcEndpointId, 56 | exportName: "ConsumerVPCe", 57 | }); 58 | 59 | const key = new Key(this, "APIKeyKMSKey", { 60 | enableKeyRotation: true, 61 | }); 62 | const apiSecret = new Secret(this, "CrossAccountAPIKey", { 63 | generateSecretString: { 64 | passwordLength: 32, 65 | excludePunctuation: true, 66 | }, 67 | encryptionKey: key, 68 | }); 69 | 70 | const rotationLambda = new Function(this, "RotationLambda", { 71 | code: Code.fromAsset("rotation"), 72 | runtime: Runtime.PYTHON_3_8, 73 | handler: "main.lambda_handler", 74 | }); 75 | 76 | apiSecret.addRotationSchedule("RotationSchedule", { 77 | automaticallyAfter: cdk.Duration.days(7), 78 | rotationLambda: rotationLambda, 79 | }); 80 | 81 | apiSecret.grantRead(new AccountPrincipal(producerAccountId.valueAsString)); 82 | apiSecret.grantRead(new ServicePrincipal("lambda")); 83 | apiSecret.grantWrite(rotationLambda); 84 | 85 | const outputSecretArn = new cdk.CfnOutput(this, "ApiKeySecretArn", { 86 | value: apiSecret.secretFullArn!, 87 | exportName: "ApiKeySecretArn", 88 | }); 89 | 90 | const nlbAccessLogs = new Bucket(this, "NLBAccessLogsBucket", { 91 | blockPublicAccess: BlockPublicAccess.BLOCK_ALL, 92 | enforceSSL: true, 93 | encryption: BucketEncryption.S3_MANAGED, 94 | serverAccessLogsPrefix: "logs", 95 | objectOwnership: ObjectOwnership.OBJECT_WRITER, 96 | }); 97 | 98 | const nlb = new NetworkLoadBalancer(this, "ConsumerNLB", { 99 | vpc, 100 | internetFacing: false, 101 | crossZoneEnabled: true, 102 | }); 103 | nlb.logAccessLogs(nlbAccessLogs, "consumerAccessLogs"); 104 | 105 | const outputNlb = new cdk.CfnOutput(this, "ConsumerNLBArn", { 106 | value: nlb.loadBalancerArn, 107 | exportName: "ConsumerNLBArn", 108 | }); 109 | 110 | const listener = nlb.addListener("HttpsListener", { 111 | port: 443, 112 | }); 113 | 114 | const tg = new NetworkTargetGroup(this, "ApiTargetGroup", { 115 | targetType: TargetType.IP, 116 | port: 443, 117 | protocol: Protocol.TCP, 118 | healthCheck: { 119 | enabled: true, 120 | protocol: Protocol.HTTPS, 121 | path: "/ping", 122 | }, 123 | vpc, 124 | }); 125 | 126 | const ipTargetRegisterRole = new Role(this, "ipTargetRegisterRole", { 127 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 128 | }); 129 | ipTargetRegisterRole.addToPolicy( 130 | new PolicyStatement({ 131 | resources: ["*"], 132 | actions: [ 133 | "ec2:DescribeVpcEndpoints", 134 | "ec2:DescribeNetworkInterfaces", 135 | "elasticloadbalancing:RegisterTargets", 136 | "logs:CreateLogGroup", 137 | "logs:CreateLogStream", 138 | "logs:PutLogEvents", 139 | ], 140 | }) 141 | ); 142 | 143 | const ipTargetRegisterFn = new NodejsFunction( 144 | this, 145 | "IpTargetRegisterFunction", 146 | { 147 | runtime: Runtime.NODEJS_22_X, 148 | handler: "lambdaHandler", 149 | entry: "./targetRegister/app.ts", 150 | role: ipTargetRegisterRole, 151 | environment: { 152 | vpceId: privateApiEndpoint.vpcEndpointId, 153 | targetGroupArn: tg.targetGroupArn, 154 | }, 155 | } 156 | ); 157 | 158 | const customResourceRole = new Role(this, "customResourceRole", { 159 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 160 | }); 161 | customResourceRole.addToPolicy( 162 | new PolicyStatement({ 163 | resources: ["*"], 164 | actions: [ 165 | "logs:CreateLogGroup", 166 | "logs:CreateLogStream", 167 | "logs:PutLogEvents", 168 | ], 169 | }) 170 | ); 171 | 172 | const customIpRegisterProvider = new custom.Provider( 173 | this, 174 | "CustomIpRegisterProvider", 175 | { 176 | onEventHandler: ipTargetRegisterFn, 177 | role: customResourceRole, 178 | } 179 | ); 180 | 181 | new CustomResource(this, "IpTargetRegister", { 182 | serviceToken: customIpRegisterProvider.serviceToken, 183 | }); 184 | 185 | listener.addTargetGroups("AddApiTargetGroup", tg); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "consumer", 3 | "version": "0.1.0", 4 | "bin": { 5 | "consumer": "bin/consumer.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.172.0", 18 | "jest": "^27.5.1", 19 | "ts-jest": "^27.1.4", 20 | "ts-node": "^10.9.1", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "aws-cdk-lib": "2.172.0", 25 | "constructs": "^10.0.0", 26 | "source-map-support": "^0.5.21", 27 | "axios": "^1.7.4", 28 | "cdk-nag": "^2.20.10", 29 | "@aws-sdk/client-ec2": "^3.373.0", 30 | "@aws-sdk/client-elastic-load-balancing-v2": "^3.370.0", 31 | "@aws-sdk/client-secrets-manager": "^3.370.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/rotation/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved. 2 | # SPDX-License-Identifier: MIT-0 3 | 4 | import boto3 5 | import logging 6 | import os 7 | 8 | logger = logging.getLogger() 9 | logger.setLevel(logging.INFO) 10 | 11 | 12 | def lambda_handler(event, context): 13 | """Secrets Manager Rotation Template 14 | This is a template for creating an AWS Secrets Manager rotation lambda 15 | Args: 16 | event (dict): Lambda dictionary of event parameters. These keys must include the following: 17 | - SecretId: The secret ARN or identifier 18 | - ClientRequestToken: The ClientRequestToken of the secret version 19 | - Step: The rotation step (one of createSecret, setSecret, testSecret, or finishSecret) 20 | context (LambdaContext): The Lambda runtime information 21 | Raises: 22 | ResourceNotFoundException: If the secret with the specified arn and stage does not exist 23 | ValueError: If the secret is not properly configured for rotation 24 | KeyError: If the event parameters do not contain the expected keys 25 | """ 26 | arn = event['SecretId'] 27 | token = event['ClientRequestToken'] 28 | step = event['Step'] 29 | 30 | # Setup the client 31 | service_client = boto3.client( 32 | 'secretsmanager', endpoint_url=os.environ['SECRETS_MANAGER_ENDPOINT']) 33 | 34 | # Make sure the version is staged correctly 35 | metadata = service_client.describe_secret(SecretId=arn) 36 | if not metadata['RotationEnabled']: 37 | logger.error("Secret %s is not enabled for rotation" % arn) 38 | raise ValueError("Secret %s is not enabled for rotation" % arn) 39 | versions = metadata['VersionIdsToStages'] 40 | if token not in versions: 41 | logger.error( 42 | "Secret version %s has no stage for rotation of secret %s." % (token, arn)) 43 | raise ValueError( 44 | "Secret version %s has no stage for rotation of secret %s." % (token, arn)) 45 | if "AWSCURRENT" in versions[token]: 46 | logger.info( 47 | "Secret version %s already set as AWSCURRENT for secret %s." % (token, arn)) 48 | return 49 | elif "AWSPENDING" not in versions[token]: 50 | logger.error( 51 | "Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) 52 | raise ValueError( 53 | "Secret version %s not set as AWSPENDING for rotation of secret %s." % (token, arn)) 54 | 55 | if step == "createSecret": 56 | create_secret(service_client, arn, token) 57 | 58 | elif step == "setSecret": 59 | set_secret(service_client, arn, token) 60 | 61 | elif step == "testSecret": 62 | test_secret(service_client, arn, token) 63 | 64 | elif step == "finishSecret": 65 | finish_secret(service_client, arn, token) 66 | 67 | else: 68 | raise ValueError("Invalid step parameter") 69 | 70 | 71 | def create_secret(service_client, arn, token): 72 | """Create the secret 73 | This method first checks for the existence of a secret for the passed in token. If one does not exist, it will generate a 74 | new secret and put it with the passed in token. 75 | Args: 76 | service_client (client): The secrets manager service client 77 | arn (string): The secret ARN or other identifier 78 | token (string): The ClientRequestToken associated with the secret version 79 | Raises: 80 | ResourceNotFoundException: If the secret with the specified arn and stage does not exist 81 | """ 82 | # Make sure the current secret exists 83 | service_client.get_secret_value(SecretId=arn, VersionStage="AWSCURRENT") 84 | 85 | # Now try to get the secret version, if that fails, put a new secret 86 | try: 87 | service_client.get_secret_value( 88 | SecretId=arn, VersionId=token, VersionStage="AWSPENDING") 89 | logger.info("createSecret: Successfully retrieved secret for %s." % arn) 90 | except service_client.exceptions.ResourceNotFoundException: 91 | # Get exclude characters from environment variable 92 | exclude_characters = os.environ['EXCLUDE_CHARACTERS'] if 'EXCLUDE_CHARACTERS' in os.environ else '/@"\'\\' 93 | # Generate a random password 94 | passwd = service_client.get_random_password( 95 | ExcludeCharacters=exclude_characters) 96 | 97 | # Put the secret 98 | service_client.put_secret_value(SecretId=arn, ClientRequestToken=token, 99 | SecretString=passwd['RandomPassword'], VersionStages=['AWSPENDING']) 100 | logger.info( 101 | "createSecret: Successfully put secret for ARN %s and version %s." % (arn, token)) 102 | 103 | 104 | def set_secret(service_client, arn, token): 105 | """Set the secret 106 | This method should set the AWSPENDING secret in the service that the secret belongs to. For example, if the secret is a database 107 | credential, this method should take the value of the AWSPENDING secret and set the user's password to this value in the database. 108 | Args: 109 | service_client (client): The secrets manager service client 110 | arn (string): The secret ARN or other identifier 111 | token (string): The ClientRequestToken associated with the secret version 112 | """ 113 | # This is where the secret should be set in the service 114 | raise NotImplementedError 115 | 116 | 117 | def test_secret(service_client, arn, token): 118 | """Test the secret 119 | This method should validate that the AWSPENDING secret works in the service that the secret belongs to. For example, if the secret 120 | is a database credential, this method should validate that the user can login with the password in AWSPENDING and that the user has 121 | all of the expected permissions against the database. 122 | Args: 123 | service_client (client): The secrets manager service client 124 | arn (string): The secret ARN or other identifier 125 | token (string): The ClientRequestToken associated with the secret version 126 | """ 127 | # This is where the secret should be tested against the service 128 | raise NotImplementedError 129 | 130 | 131 | def finish_secret(service_client, arn, token): 132 | """Finish the secret 133 | This method finalizes the rotation process by marking the secret version passed in as the AWSCURRENT secret. 134 | Args: 135 | service_client (client): The secrets manager service client 136 | arn (string): The secret ARN or other identifier 137 | token (string): The ClientRequestToken associated with the secret version 138 | Raises: 139 | ResourceNotFoundException: If the secret with the specified arn does not exist 140 | """ 141 | # First describe the secret to get the current version 142 | metadata = service_client.describe_secret(SecretId=arn) 143 | current_version = None 144 | for version in metadata["VersionIdsToStages"]: 145 | if "AWSCURRENT" in metadata["VersionIdsToStages"][version]: 146 | if version == token: 147 | # The correct version is already marked as current, return 148 | logger.info( 149 | "finishSecret: Version %s already marked as AWSCURRENT for %s" % (version, arn)) 150 | return 151 | current_version = version 152 | break 153 | 154 | # Finalize by staging the secret version current 155 | service_client.update_secret_version_stage( 156 | SecretId=arn, VersionStage="AWSCURRENT", MoveToVersionId=token, RemoveFromVersionId=current_version) 157 | logger.info( 158 | "finishSecret: Successfully set AWSCURRENT stage to version %s for secret %s." % (token, arn)) 159 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/app.ts: -------------------------------------------------------------------------------- 1 | import { EC2Client, DescribeNetworkInterfacesCommand, DescribeVpcEndpointsCommand } from '@aws-sdk/client-ec2'; 2 | import { ElasticLoadBalancingV2Client, RegisterTargetsCommand } from '@aws-sdk/client-elastic-load-balancing-v2'; 3 | const ec2 = new EC2Client({}); 4 | const elb = new ElasticLoadBalancingV2Client({}); 5 | 6 | export const lambdaHandler = async (event: any, context: any) => { 7 | console.log('REQUEST RECEIVED:\n' + JSON.stringify(event)); 8 | if (event.RequestType == 'Delete') { 9 | await sendResponse(event, context, 'SUCCESS'); 10 | return; 11 | } 12 | try { 13 | const describeEndpointsParams = { 14 | VpcEndpointIds: [process.env.vpceId!], 15 | }; 16 | const describeEndpointsCommand = new DescribeVpcEndpointsCommand(describeEndpointsParams); 17 | const endpoints = await ec2.send(describeEndpointsCommand); 18 | const endpointArray: string[] = []; 19 | endpoints.VpcEndpoints![0].NetworkInterfaceIds?.map((it) => endpointArray.push(it)); 20 | console.log(endpointArray); 21 | 22 | const describeEniParams = { 23 | NetworkInterfaceIds: endpointArray, 24 | }; 25 | const describeEniCommand = new DescribeNetworkInterfacesCommand(describeEniParams); 26 | const enis = await ec2.send(describeEniCommand); 27 | console.log(enis); 28 | 29 | for (let ip = 0; ip < enis.NetworkInterfaces!.length!; ip++) { 30 | const addTargetParams = { 31 | TargetGroupArn: process.env.targetGroupArn!, 32 | Targets: [{ Id: enis.NetworkInterfaces![ip].PrivateIpAddress! }], 33 | }; 34 | const addTargetCommand = new RegisterTargetsCommand(addTargetParams); 35 | await elb.send(addTargetCommand); 36 | } 37 | await sendResponse(event, context, 'SUCCESS'); 38 | } catch (e) { 39 | console.log(e); 40 | await sendResponse(event, context, 'FAILED'); 41 | } 42 | }; 43 | 44 | const sendResponse = async (event: any, context: any, responseStatus: string) => { 45 | const responseBody = JSON.stringify({ 46 | Status: responseStatus, 47 | Reason: 'See the details in CloudWatch Log Stream: ' + context.logStreamName, 48 | PhysicalResourceId: context.logStreamName, 49 | StackId: event.StackId, 50 | RequestId: event.RequestId, 51 | LogicalResourceId: event.LogicalResourceId, 52 | }); 53 | 54 | console.log('RESPONSE BODY:\n', responseBody); 55 | 56 | const https = require('https'); 57 | const url = require('url'); 58 | 59 | const parsedUrl = url.parse(event.ResponseURL); 60 | const options = { 61 | hostname: parsedUrl.hostname, 62 | port: 443, 63 | path: parsedUrl.path, 64 | method: 'PUT', 65 | headers: { 66 | 'content-type': '', 67 | 'content-length': responseBody.length, 68 | }, 69 | }; 70 | 71 | console.log('SENDING RESPONSE...\n'); 72 | 73 | const request = https.request(options, function (response: any) { 74 | console.log('STATUS: ' + response.statusCode); 75 | console.log('HEADERS: ' + JSON.stringify(response.headers)); 76 | context.done(); 77 | }); 78 | 79 | request.on('error', function (error: any) { 80 | console.log('sendResponse Error:' + error); 81 | context.done(); 82 | }); 83 | request.write(responseBody); 84 | request.end(); 85 | }; 86 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pattern_3", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-ec2": "^3.629.0", 11 | "@aws-sdk/client-elastic-load-balancing-v2": "^3.629.0", 12 | "aws-sdk": "^2.1354.0" 13 | }, 14 | "scripts": { 15 | "unit": "jest", 16 | "lint": "eslint '*.ts' --quiet --fix", 17 | "compile": "tsc", 18 | "test": "npm run compile && npm run unit" 19 | }, 20 | "devDependencies": { 21 | "@types/aws-lambda": "^8.10.92", 22 | "@types/jest": "^27.4.0", 23 | "@types/node": "^17.0.13", 24 | "@types/pg": "^8.6.5", 25 | "@typescript-eslint/eslint-plugin": "^5.10.2", 26 | "@typescript-eslint/parser": "^5.10.2", 27 | "esbuild": "^0.14.14", 28 | "esbuild-jest": "^0.5.0", 29 | "eslint": "^8.8.0", 30 | "eslint-config-prettier": "^8.3.0", 31 | "eslint-plugin-prettier": "^4.0.0", 32 | "jest": "^27.5.0", 33 | "prettier": "^2.5.1", 34 | "ts-node": "^10.4.0", 35 | "typescript": "^4.5.5" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/targetRegister/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /vpc-endpoint/consumer/test/consumer.test.ts: -------------------------------------------------------------------------------- 1 | // import * as cdk from 'aws-cdk-lib'; 2 | // import { Template } from 'aws-cdk-lib/assertions'; 3 | // import * as Consumer from '../lib/consumer-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/consumer-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Consumer.ConsumerStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /vpc-endpoint/consumer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/.gitignore: -------------------------------------------------------------------------------- 1 | *.js 2 | !jest.config.js 3 | *.d.ts 4 | node_modules 5 | 6 | # CDK asset staging directory 7 | .cdk.staging 8 | cdk.out 9 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/.npmignore: -------------------------------------------------------------------------------- 1 | *.ts 2 | !*.d.ts 3 | 4 | # CDK asset staging directory 5 | .cdk.staging 6 | cdk.out 7 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/app.ts: -------------------------------------------------------------------------------- 1 | export interface GetWidgetEvent { 2 | } 3 | 4 | export const lambdaHandler = async (event: GetWidgetEvent) => { 5 | return { 6 | statusCode: 200, 7 | body: JSON.stringify({ 8 | "id": "1", 9 | "value": "4.99" 10 | }), 11 | }; 12 | }; 13 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "api", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "aws-sdk": "^2.1354.0" 11 | }, 12 | "scripts": { 13 | "unit": "jest", 14 | "lint": "eslint '*.ts' --quiet --fix", 15 | "compile": "tsc", 16 | "test": "npm run compile && npm run unit" 17 | }, 18 | "devDependencies": { 19 | "@types/aws-lambda": "^8.10.92", 20 | "@types/jest": "^27.4.0", 21 | "@types/node": "^17.0.13", 22 | "@types/pg": "^8.6.5", 23 | "@typescript-eslint/eslint-plugin": "^5.10.2", 24 | "@typescript-eslint/parser": "^5.10.2", 25 | "esbuild": "^0.14.14", 26 | "esbuild-jest": "^0.5.0", 27 | "eslint": "^8.8.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "jest": "^27.5.0", 31 | "prettier": "^2.5.1", 32 | "ts-node": "^10.4.0", 33 | "typescript": "^4.5.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/api/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .aws-sam -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/.gitignore: -------------------------------------------------------------------------------- 1 | 2 | # Created by https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 3 | # Edit at https://www.toptal.com/developers/gitignore?templates=osx,node,linux,windows,sam 4 | 5 | ### Linux ### 6 | *~ 7 | 8 | # temporary files which can be created if a process still has a handle open of a deleted file 9 | .fuse_hidden* 10 | 11 | # KDE directory preferences 12 | .directory 13 | 14 | # Linux trash folder which might appear on any partition or disk 15 | .Trash-* 16 | 17 | # .nfs files are created when an open file is removed but is still being accessed 18 | .nfs* 19 | 20 | ### Node ### 21 | # Logs 22 | logs 23 | *.log 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | lerna-debug.log* 28 | 29 | # Diagnostic reports (https://nodejs.org/api/report.html) 30 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 31 | 32 | # Runtime data 33 | pids 34 | *.pid 35 | *.seed 36 | *.pid.lock 37 | 38 | # Directory for instrumented libs generated by jscoverage/JSCover 39 | lib-cov 40 | 41 | # Coverage directory used by tools like istanbul 42 | coverage 43 | *.lcov 44 | 45 | # nyc test coverage 46 | .nyc_output 47 | 48 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 49 | .grunt 50 | 51 | # Bower dependency directory (https://bower.io/) 52 | bower_components 53 | 54 | # node-waf configuration 55 | .lock-wscript 56 | 57 | # Compiled binary addons (https://nodejs.org/api/addons.html) 58 | build/Release 59 | 60 | # Dependency directories 61 | node_modules/ 62 | jspm_packages/ 63 | 64 | # TypeScript v1 declaration files 65 | typings/ 66 | 67 | # TypeScript cache 68 | *.tsbuildinfo 69 | 70 | # Optional npm cache directory 71 | .npm 72 | 73 | # Optional eslint cache 74 | .eslintcache 75 | 76 | # Optional stylelint cache 77 | .stylelintcache 78 | 79 | # Microbundle cache 80 | .rpt2_cache/ 81 | .rts2_cache_cjs/ 82 | .rts2_cache_es/ 83 | .rts2_cache_umd/ 84 | 85 | # Optional REPL history 86 | .node_repl_history 87 | 88 | # Output of 'npm pack' 89 | *.tgz 90 | 91 | # Yarn Integrity file 92 | .yarn-integrity 93 | 94 | # dotenv environment variables file 95 | .env 96 | .env.test 97 | .env*.local 98 | 99 | # parcel-bundler cache (https://parceljs.org/) 100 | .cache 101 | .parcel-cache 102 | 103 | # Next.js build output 104 | .next 105 | 106 | # Nuxt.js build / generate output 107 | .nuxt 108 | dist 109 | 110 | # Storybook build outputs 111 | .out 112 | .storybook-out 113 | storybook-static 114 | 115 | # rollup.js default build output 116 | dist/ 117 | 118 | # Gatsby files 119 | .cache/ 120 | # Comment in the public line in if your project uses Gatsby and not Next.js 121 | # https://nextjs.org/blog/next-9-1#public-directory-support 122 | # public 123 | 124 | # vuepress build output 125 | .vuepress/dist 126 | 127 | # Serverless directories 128 | .serverless/ 129 | 130 | # FuseBox cache 131 | .fusebox/ 132 | 133 | # DynamoDB Local files 134 | .dynamodb/ 135 | 136 | # TernJS port file 137 | .tern-port 138 | 139 | # Stores VSCode versions used for testing VSCode extensions 140 | .vscode-test 141 | 142 | # Temporary folders 143 | tmp/ 144 | temp/ 145 | 146 | ### OSX ### 147 | # General 148 | .DS_Store 149 | .AppleDouble 150 | .LSOverride 151 | 152 | # Icon must end with two \r 153 | Icon 154 | 155 | 156 | # Thumbnails 157 | ._* 158 | 159 | # Files that might appear in the root of a volume 160 | .DocumentRevisions-V100 161 | .fseventsd 162 | .Spotlight-V100 163 | .TemporaryItems 164 | .Trashes 165 | .VolumeIcon.icns 166 | .com.apple.timemachine.donotpresent 167 | 168 | # Directories potentially created on remote AFP share 169 | .AppleDB 170 | .AppleDesktop 171 | Network Trash Folder 172 | Temporary Items 173 | .apdisk 174 | 175 | ### SAM ### 176 | # Ignore build directories for the AWS Serverless Application Model (SAM) 177 | # Info: https://aws.amazon.com/serverless/sam/ 178 | # Docs: https://docs.aws.amazon.com/serverless-application-model/latest/developerguide/serverless-sam-reference.html 179 | 180 | **/.aws-sam 181 | 182 | ### Windows ### 183 | # Windows thumbnail cache files 184 | Thumbs.db 185 | Thumbs.db:encryptable 186 | ehthumbs.db 187 | ehthumbs_vista.db 188 | 189 | # Dump file 190 | *.stackdump 191 | 192 | # Folder config file 193 | [Dd]esktop.ini 194 | 195 | # Recycle Bin used on file shares 196 | $RECYCLE.BIN/ 197 | 198 | # Windows Installer files 199 | *.cab 200 | *.msi 201 | *.msix 202 | *.msm 203 | *.msp 204 | 205 | # Windows shortcuts 206 | *.lnk 207 | 208 | # End of https://www.toptal.com/developers/gitignore/api/osx,node,linux,windows,sam 209 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/.npmignore: -------------------------------------------------------------------------------- 1 | tests/* 2 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/app.ts: -------------------------------------------------------------------------------- 1 | import { 2 | GetSecretValueCommand, 3 | SecretsManagerClient, 4 | } from "@aws-sdk/client-secrets-manager"; 5 | 6 | const secretManager = new SecretsManagerClient({}); 7 | 8 | export const lambdaHandler = async ( 9 | event: any, 10 | context: any, 11 | callback: any 12 | ) => { 13 | const token = event.authorizationToken; 14 | const input = { 15 | SecretId: process.env.API_KEY, 16 | }; 17 | const command = new GetSecretValueCommand(input); 18 | const response = await secretManager.send(command); 19 | 20 | switch (token) { 21 | case response.SecretString: 22 | callback(null, generatePolicy("user", "Allow", event.methodArn)); 23 | break; 24 | default: 25 | callback("Error: Invalid token"); 26 | } 27 | }; 28 | 29 | const generatePolicy = ( 30 | principalId: string, 31 | effect: string, 32 | resource: string 33 | ): any => { 34 | let authResponse: any = {}; 35 | 36 | authResponse.principalId = principalId; 37 | if (effect && resource) { 38 | let policyDocument: any = {}; 39 | policyDocument.Version = "2012-10-17"; 40 | policyDocument.Statement = []; 41 | let statementOne: any = {}; 42 | statementOne.Action = "execute-api:Invoke"; 43 | statementOne.Effect = effect; 44 | statementOne.Resource = resource; 45 | policyDocument.Statement[0] = statementOne; 46 | authResponse.policyDocument = policyDocument; 47 | } 48 | 49 | return authResponse; 50 | }; 51 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/jest.config.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * For a detailed explanation regarding each configuration property and type check, visit: 3 | * https://jestjs.io/docs/configuration 4 | */ 5 | 6 | export default { 7 | transform: { 8 | '^.+\\.ts?$': 'esbuild-jest', 9 | }, 10 | clearMocks: true, 11 | collectCoverage: true, 12 | coverageDirectory: 'coverage', 13 | coverageProvider: 'v8', 14 | testMatch: ['**/tests/unit/*.test.ts'], 15 | }; 16 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "authorizer", 3 | "version": "1.0.0", 4 | "description": "hello world sample for NodeJS", 5 | "main": "app.js", 6 | "repository": "https://github.com/awslabs/aws-sam-cli/tree/develop/samcli/local/init/templates/cookiecutter-aws-sam-hello-nodejs", 7 | "author": "SAM CLI", 8 | "license": "MIT", 9 | "dependencies": { 10 | "@aws-sdk/client-secrets-manager": "^3.369.0" 11 | }, 12 | "scripts": { 13 | "unit": "jest", 14 | "lint": "eslint '*.ts' --quiet --fix", 15 | "compile": "tsc", 16 | "test": "npm run compile && npm run unit" 17 | }, 18 | "devDependencies": { 19 | "@types/aws-lambda": "^8.10.92", 20 | "@types/jest": "^27.4.0", 21 | "@types/node": "^17.0.13", 22 | "@types/pg": "^8.6.5", 23 | "@typescript-eslint/eslint-plugin": "^5.10.2", 24 | "@typescript-eslint/parser": "^5.10.2", 25 | "esbuild": "^0.14.14", 26 | "esbuild-jest": "^0.5.0", 27 | "eslint": "^8.8.0", 28 | "eslint-config-prettier": "^8.3.0", 29 | "eslint-plugin-prettier": "^4.0.0", 30 | "jest": "^27.5.0", 31 | "prettier": "^2.5.1", 32 | "ts-node": "^10.4.0", 33 | "typescript": "^4.5.5" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/authorizer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2020", 4 | "strict": true, 5 | "preserveConstEnums": true, 6 | "noEmit": true, 7 | "sourceMap": false, 8 | "module":"es2015", 9 | "moduleResolution":"node", 10 | "esModuleInterop": true, 11 | "skipLibCheck": true, 12 | "forceConsistentCasingInFileNames": true, 13 | }, 14 | "exclude": ["node_modules", "**/*.test.ts"] 15 | } -------------------------------------------------------------------------------- /vpc-endpoint/producer/bin/producer.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | import "source-map-support/register"; 3 | import * as cdk from "aws-cdk-lib"; 4 | import { ProducerStack } from "../lib/producer-stack"; 5 | import { AwsSolutionsChecks, NagSuppressions } from "cdk-nag"; 6 | 7 | const app = new cdk.App(); 8 | const api = new ProducerStack(app, "ProducerStack", {}); 9 | cdk.Aspects.of(app).add(new AwsSolutionsChecks()); 10 | NagSuppressions.addStackSuppressions(api, [ 11 | { 12 | id: "AwsSolutions-COG4", 13 | reason: 14 | "Token-based authorization is used in this solution rather than Cognito", 15 | }, 16 | { 17 | id: "AwsSolutions-IAM4", 18 | reason: 19 | "AWS Managed Policies used in this solution are: AWSLambdaBasicExecutionRole and AmazonAPIGatewayPushToCloudWatchLogs", 20 | }, 21 | { 22 | id: "AwsSolutions-IAM5", 23 | reason: 24 | "IAM policy resources not scoped where the resource ID is not known ahead of time", 25 | }, 26 | ]); 27 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/cdk.json: -------------------------------------------------------------------------------- 1 | { 2 | "app": "npx ts-node --prefer-ts-exts bin/producer.ts", 3 | "watch": { 4 | "include": [ 5 | "**" 6 | ], 7 | "exclude": [ 8 | "README.md", 9 | "cdk*.json", 10 | "**/*.d.ts", 11 | "**/*.js", 12 | "tsconfig.json", 13 | "package*.json", 14 | "yarn.lock", 15 | "node_modules", 16 | "test" 17 | ] 18 | }, 19 | "context": { 20 | "@aws-cdk/aws-apigateway:usagePlanKeyOrderInsensitiveId": true, 21 | "@aws-cdk/core:stackRelativeExports": true, 22 | "@aws-cdk/aws-rds:lowercaseDbIdentifier": true, 23 | "@aws-cdk/aws-lambda:recognizeVersionProps": true, 24 | "@aws-cdk/aws-lambda:recognizeLayerVersion": true, 25 | "@aws-cdk/aws-cloudfront:defaultSecurityPolicyTLSv1.2_2021": true, 26 | "@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true, 27 | "@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true, 28 | "@aws-cdk/core:checkSecretUsage": true, 29 | "@aws-cdk/aws-iam:minimizePolicies": true, 30 | "@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true, 31 | "@aws-cdk/core:validateSnapshotRemovalPolicy": true, 32 | "@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true, 33 | "@aws-cdk/aws-s3:createDefaultLoggingPolicy": true, 34 | "@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true, 35 | "@aws-cdk/aws-apigateway:disableCloudWatchRole": true, 36 | "@aws-cdk/core:enablePartitionLiterals": true, 37 | "@aws-cdk/core:target-partitions": [ 38 | "aws", 39 | "aws-cn" 40 | ] 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | testEnvironment: 'node', 3 | roots: ['/test'], 4 | testMatch: ['**/*.test.ts'], 5 | transform: { 6 | '^.+\\.tsx?$': 'ts-jest' 7 | } 8 | }; 9 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/lib/producer-stack.ts: -------------------------------------------------------------------------------- 1 | import * as cdk from "aws-cdk-lib"; 2 | import { Construct } from "constructs"; 3 | import { CfnParameter } from "aws-cdk-lib"; 4 | import { LogGroup } from "aws-cdk-lib/aws-logs"; 5 | import { 6 | AccessLogFormat, 7 | CfnAccount, 8 | Deployment, 9 | EndpointType, 10 | LambdaRestApi, 11 | LogGroupLogDestination, 12 | MethodLoggingLevel, 13 | RequestValidator, 14 | TokenAuthorizer, 15 | } from "aws-cdk-lib/aws-apigateway"; 16 | import { NodejsFunction } from "aws-cdk-lib/aws-lambda-nodejs"; 17 | import { 18 | AnyPrincipal, 19 | Effect, 20 | ManagedPolicy, 21 | PolicyDocument, 22 | PolicyStatement, 23 | Role, 24 | ServicePrincipal, 25 | } from "aws-cdk-lib/aws-iam"; 26 | import { Runtime } from "aws-cdk-lib/aws-lambda"; 27 | 28 | export class ProducerStack extends cdk.Stack { 29 | constructor(scope: Construct, id: string, props?: cdk.StackProps) { 30 | super(scope, id, props); 31 | const resource = "widgets"; 32 | 33 | const vpce = new CfnParameter(this, "ConsumerVPCe", { 34 | type: "String", 35 | description: "ID of the VPC Endpoint in the consumer account", 36 | }); 37 | 38 | const secretArn = new CfnParameter(this, "ApiKeySecretArn", { 39 | type: "String", 40 | description: "ARN of the Secrets Manager secret used for authentication", 41 | }); 42 | 43 | const apiResourcePolicy = new PolicyDocument({ 44 | statements: [ 45 | new PolicyStatement({ 46 | actions: ["execute-api:Invoke"], 47 | principals: [new AnyPrincipal()], 48 | effect: Effect.ALLOW, 49 | resources: ["execute-api:/*"], 50 | }), 51 | new PolicyStatement({ 52 | actions: ["execute-api:Invoke"], 53 | principals: [new AnyPrincipal()], 54 | effect: Effect.DENY, 55 | resources: ["execute-api:/*"], 56 | conditions: { 57 | StringNotEquals: { 58 | "aws:SourceVpce": vpce.valueAsString, 59 | }, 60 | }, 61 | }), 62 | ], 63 | }); 64 | 65 | const apiHandler = new NodejsFunction(this, "ProducerApiFunction", { 66 | runtime: Runtime.NODEJS_22_X, 67 | handler: "lambdaHandler", 68 | entry: "./api/app.ts", 69 | }); 70 | 71 | const authorizerFnRole = new Role(this, "authorizerFnRole", { 72 | assumedBy: new ServicePrincipal("lambda.amazonaws.com").withSessionTags(), 73 | }); 74 | authorizerFnRole.addToPolicy( 75 | new PolicyStatement({ 76 | resources: ["*"], 77 | actions: [ 78 | "kms:Decrypt", 79 | "logs:CreateLogGroup", 80 | "logs:CreateLogStream", 81 | "logs:PutLogEvents", 82 | ], 83 | }) 84 | ); 85 | authorizerFnRole.addToPolicy( 86 | new PolicyStatement({ 87 | resources: [secretArn.valueAsString], 88 | actions: ["secretsmanager:GetSecretValue"], 89 | }) 90 | ); 91 | 92 | const authorizerFn = new NodejsFunction(this, "AuthorizerFunction", { 93 | runtime: Runtime.NODEJS_22_X, 94 | handler: "lambdaHandler", 95 | entry: "./authorizer/app.ts", 96 | environment: { 97 | API_KEY: secretArn.valueAsString, 98 | }, 99 | role: authorizerFnRole, 100 | }); 101 | 102 | const authorizer = new TokenAuthorizer(this, "Authorizer", { 103 | handler: authorizerFn, 104 | }); 105 | 106 | const prdLogGroup = new LogGroup(this, "PrdLogs"); 107 | const api = new LambdaRestApi(this, "ProducerApi", { 108 | handler: apiHandler, 109 | proxy: false, 110 | endpointConfiguration: { 111 | types: [EndpointType.PRIVATE], 112 | }, 113 | defaultMethodOptions: { 114 | authorizer, 115 | }, 116 | deployOptions: { 117 | accessLogDestination: new LogGroupLogDestination(prdLogGroup), 118 | accessLogFormat: AccessLogFormat.jsonWithStandardFields(), 119 | methodOptions: { 120 | "/*/*": { 121 | loggingLevel: MethodLoggingLevel.ERROR, 122 | }, 123 | }, 124 | }, 125 | policy: apiResourcePolicy, 126 | }); 127 | 128 | const items = api.root.addResource(resource); 129 | items.addMethod("GET"); 130 | 131 | const role = new Role(this, "CloudWatchRole", { 132 | assumedBy: new ServicePrincipal("apigateway.amazonaws.com"), 133 | managedPolicies: [ 134 | ManagedPolicy.fromAwsManagedPolicyName( 135 | "service-role/AmazonAPIGatewayPushToCloudWatchLogs" 136 | ), 137 | ], 138 | }); 139 | 140 | const cloudWatchAccount = new CfnAccount(this, "Account", { 141 | cloudWatchRoleArn: role.roleArn, 142 | }); 143 | 144 | api.node.addDependency(cloudWatchAccount); 145 | 146 | const requestValidator = new RequestValidator( 147 | this, 148 | "ConsumerRequestValidator", 149 | { 150 | restApi: api, 151 | requestValidatorName: "prdValidator", 152 | validateRequestBody: false, 153 | validateRequestParameters: false, 154 | } 155 | ); 156 | 157 | const deployment = new Deployment(this, "Deployment", { api }); 158 | 159 | const outputAPI = new cdk.CfnOutput(this, "ApiUrl", { 160 | value: api.urlForPath(`/${resource}`), 161 | exportName: "ApiUrl", 162 | }); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "producer", 3 | "version": "0.1.0", 4 | "bin": { 5 | "producer": "bin/producer.js" 6 | }, 7 | "scripts": { 8 | "build": "tsc", 9 | "watch": "tsc -w", 10 | "test": "jest", 11 | "cdk": "cdk" 12 | }, 13 | "devDependencies": { 14 | "@types/jest": "^27.5.2", 15 | "@types/node": "10.17.27", 16 | "@types/prettier": "2.6.0", 17 | "aws-cdk": "2.172.0", 18 | "jest": "^27.5.1", 19 | "ts-jest": "^27.1.4", 20 | "ts-node": "^10.9.1", 21 | "typescript": "~3.9.7" 22 | }, 23 | "dependencies": { 24 | "aws-cdk-lib": "2.172.0", 25 | "cdk-nag": "^2.20.10", 26 | "constructs": "^10.0.0", 27 | "source-map-support": "^0.5.21", 28 | "@aws-sdk/client-secrets-manager": "^3.369.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/test/producer.test.ts: -------------------------------------------------------------------------------- 1 | // import * as cdk from 'aws-cdk-lib'; 2 | // import { Template } from 'aws-cdk-lib/assertions'; 3 | // import * as Producer from '../lib/producer-stack'; 4 | 5 | // example test. To run these tests, uncomment this file along with the 6 | // example resource in lib/producer-stack.ts 7 | test('SQS Queue Created', () => { 8 | // const app = new cdk.App(); 9 | // // WHEN 10 | // const stack = new Producer.ProducerStack(app, 'MyTestStack'); 11 | // // THEN 12 | // const template = Template.fromStack(stack); 13 | 14 | // template.hasResourceProperties('AWS::SQS::Queue', { 15 | // VisibilityTimeout: 300 16 | // }); 17 | }); 18 | -------------------------------------------------------------------------------- /vpc-endpoint/producer/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2018", 4 | "module": "commonjs", 5 | "lib": [ 6 | "es2018" 7 | ], 8 | "declaration": true, 9 | "strict": true, 10 | "noImplicitAny": true, 11 | "strictNullChecks": true, 12 | "noImplicitThis": true, 13 | "alwaysStrict": true, 14 | "noUnusedLocals": false, 15 | "noUnusedParameters": false, 16 | "noImplicitReturns": true, 17 | "noFallthroughCasesInSwitch": false, 18 | "inlineSourceMap": true, 19 | "inlineSources": true, 20 | "experimentalDecorators": true, 21 | "strictPropertyInitialization": false, 22 | "typeRoots": [ 23 | "./node_modules/@types" 24 | ] 25 | }, 26 | "exclude": [ 27 | "node_modules", 28 | "cdk.out" 29 | ] 30 | } 31 | --------------------------------------------------------------------------------