Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CDK version of existing pattern - multi-account-private-apigw #2591

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
# Multi-Account centralized API Gateway with Private APIs as integration targets

This CDK template implements an Amazon API Gateway REST API, as a front door for private API Gateway services residing in separate AWS accounts. This pattern allows for centralized governance and security and provides flexibility to development teams to develop their applications in their own accounts.

Learn more about this pattern at Serverless Land Patterns: << Add the live URL here >>

Important: this application uses various AWS services and there are costs associated with these services after the Free Tier usage - please see the [AWS Pricing page](https://aws.amazon.com/pricing/) for details. You are responsible for any AWS costs incurred. No warranty is implied in this example.

## Architecture Overview

![alt text](src/images/architecture_diagram.png)

- The `Central API Account` hosts an API Gateway Regional API which acts as the central API for services in the two workload accounts. A VPC link is created in this account to enable the backend integration to the Private APIs in the workload accounts. The VPC link connectivity is established using an NLB, which has a target group consisting of the Elastic Network Interfaces (ENIs) for the API Gateway Interface VPC Endpoint.

- In `Workload Account A`, one of the Private APIs is hosted with a Fargate service and an ALB as the backend integration. A VPC link and NLB are created to allow connectivity to the ALB of the Fargate service. The NLB is required as [only an NLB is supported as a target for the VPC link when using API Gateway REST APIs](https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-vs-rest.html#http-api-vs-rest.differences.integrations).

- In `Workload Account B`, the other Private API is hosted with a Fargate service and an NLB as the backend integration. A VPC link is created to allow connectivity to the NLB of the Fargate service.

- For simplicity, a [public image for a Demo API](https://gallery.ecr.aws/bstraehle/rest-api) was used for both Fargate services.

## Requirements

* [Three AWS accounts](https://portal.aws.amazon.com/gp/aws/developer/registration/index.html) if you do not already have one and log in. The IAM user that you use must have sufficient permissions to make necessary AWS service calls and manage AWS resources.
* [AWS CLI](https://docs.aws.amazon.com/cli/latest/userguide/install-cliv2.html) installed and configured
* [Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git)
* [Node and NPM](https://nodejs.org/en/download)
* [AWS Cloud Development Kit (AWS CDK)](https://docs.aws.amazon.com/cdk/v2/guide/getting_started.html)

## Deployment Instructions

1. Create a new directory, navigate to that directory in a terminal and clone the GitHub repository:
```
git clone https://github.com/aws-samples/serverless-patterns
```
1. Change directory to the pattern directory:
```
cd multi-account-central-api-private-apigw-targets-cdk-typescript
```
1. Install dependencies:
```
npm install
```

1. Open `src/config.json` and modify the cidrs if you require:
```
...
"cidrs": {
"serviceA": "10.5.0.0/16",
"serviceB": "10.6.0.0/16",
"centralAPI": "10.7.0.0/16"
}
...
```

1. Set Environment Variables:
```
export REGION=ca-central-1
export CENTRAL_API_ACCOUNT_NUMBER=XXXXXXXXXXXX
export SERVICE_A_ACCOUNT_NUMBER=XXXXXXXXXXXX
export SERVICE_B_ACCOUNT_NUMBER=XXXXXXXXXXXX
```

1. Configure AWS CDK for each AWS account:
```
cdk bootstrap --profile <your-profile-name> $CENTRAL_API_ACCOUNT_NUMBER/$REGION
cdk bootstrap --profile <your-profile-name> $SERVICE_A_ACCOUNT_NUMBER/$REGION
cdk bootstrap --profile <your-profile-name> $SERVICE_B_ACCOUNT_NUMBER/$REGION
```

1. Deploy using AWS CDK:
```
cdk deploy CentralApiStack --profile <your-profile-name>
cdk deploy ServiceAStack --profile <your-profile-name>
cdk deploy ServiceBStack --profile <your-profile-name>
cdk deploy CentralApiResourcesStack --profile <your-profile-name>
```


## How it works

The CDK application consists of the following 4 stacks.

### CentralApiStack

![alt text](src/images/central-api-stack.png)


In this stack, the central Regional REST API is deployed with the VPC, VPC Link, Network Load Balancer and API Gateway VPC Endpoint. In addition to these core components, an Advanced Parameter and an AWS Resource Manager Share are created to store the API Gateway VPC Endpoint ID and share it to the workload accounts. The workload accounts need the endpoint ID to use in their API Gateway resource policies.

### ServiceAStack

![alt text](src/images/service-a-stack.png)

In this stack, the following are deployed: a Private REST API with a resource policy, Fargate, ALB, NLB, and VPC Link. In addition to these core components, an Advanced Parameter and an AWS Resource Manager Share are created to store the API Gateway Endpoint URL and share it to the `Central API` Account. The Central API Account needs this URL to setup the HTTP_Proxy for this service.

### ServiceBStack

![alt text](src/images/service-b-stack.png)

In this stack, the following are deployed: a Private REST API with a resource policy, Fargate, NLB, and VPC Link. In addition to these core components, an Advanced Parameter and an AWS Resource Manager Share are created to store the API Gateway Endpoint URL and share it to the `Central API` Account. The Central API Account needs this URL to setup the HTTP_Proxy for this service.

### CentralApiResourcesStack

![alt text](src/images/central-api-resources-stack.png)

In this stack, the API resources and integrations to the workload accounts are created using the shared API Gateway Endpoint URLs.

## Testing

Use the following commands to get the Endpoint URL for Service A and Service B.

```
export SERVICE_A_URL=$(aws cloudformation describe-stacks --stack-name CentralApiResourcesStack --query "Stacks[0].Outputs[?ExportName=='ServiceAEndpoint'].OutputValue" --output text --profile <your-profile-name>)

export SERVICE_B_URL=$(aws cloudformation describe-stacks --stack-name CentralApiResourcesStack --query "Stacks[0].Outputs[?ExportName=='ServiceBEndpoint'].OutputValue" --output text --profile <your-profile-name>)
```


The APIs for both Service A and Service B are the same. Here is a list of available endpoints:

```
GET /swagger (This is for the API definition)
GET /Demo
POST /Demo
GET /Demo/{id}
PUT /Demo/{id}
DELETE /Demo/{id}
```

Here are example commands that can be used:


1. Get a list of items
```
curl $SERVICE_A_URL/Demo
curl $SERVICE_B_URL/Demo
```

1. Add an item to the data store
```
curl -X POST $SERVICE_A_URL/Demo -d '{"id":"1234", "name":"Service A test"}' -H "Content-Type: application/json"
curl -X POST $SERVICE_B_URL/Demo -d '{"id":"1234", "name":"Service B test"}' -H "Content-Type: application/json"
```

1. Get the item added in step 2
```
curl $SERVICE_A_URL/Demo/1234
curl $SERVICE_B_URL/Demo/1234
```

1. Update the item added in step 2
```
curl -X PUT $SERVICE_A_URL/Demo/1234 -d '{"id":"1234", "name":"This is a test for Service A"}' -H "Content-Type: application/json"
curl -X PUT $SERVICE_B_URL/Demo/1234 -d '{"id":"1234", "name":"This is a test for Service B"}' -H "Content-Type: application/json"
```

1. Delete the item added in step 2
```
curl -X DELETE $SERVICE_A_URL/Demo/1234
curl -X DELETE $SERVICE_B_URL/Demo/1234
```

## Cleanup

1. Destroy the stacks

```
cdk destroy CentralApiResourcesStack --profile <your AWS config profile>
cdk destroy ServiceBStack --profile <your AWS config profile>
cdk destroy ServiceAStack --profile <your AWS config profile>
cdk destroy CentralApiStack --profile <your AWS config profile>
```

1. Wait for the resources to delete and confirm their removal
----
Copyright 2024 Amazon.com, Inc. or its affiliates. All Rights Reserved.

SPDX-License-Identifier: MIT-0
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"title": "Multi-Account centralized API Gateway with Private APIs as integration targets",
"description": "Create Private REST API Gateway in multiple accounts and integrate with the central account's API Gateway",
"language": "TypeScript",
"level": "300",
"framework": "CDK",
"introBox": {
"headline": "How it works",
"text": [
"This sample project demonstrates how to enable secure, centralized API communications across multiple AWS accounts using a centralized Amazon API Gateway. It facilitates east/west communication between services while keeping traffic within the AWS network.",
"The architecture utilizes key AWS services such as Amazon API Gateway (Private), VPC links, Network Load Balancers (NLBs), and Execute-API VPC Endpoints. These services work together to securely route requests between multiple AWS accounts and their respective APIs.",
"This pattern deploys three separate AWS accounts: a central account hosting the main API Gateway and routing components, and two other accounts with an ECS Fargate service behind a private API Gateway. One of the Fargate services is fronted by an NLB and the other is fronted by an Application Load Balancer (ALB)."
]
},
"gitHub": {
"template": {
"repoURL": "https://github.com/aws-samples/serverless-patterns/tree/main/multi-account-central-api-private-apigw-targets-cdk-typescript",
"templateURL": "serverless-patterns/multi-account-central-api-private-apigw-targets-cdk-typescript",
"projectFolder": "multi-account-central-api-private-apigw-targets-cdk-typescript",
"templateFile": "src/bin/central-api-private-targets.ts"
}
},
"resources": {
"bullets": [
{
"text": "Amazon API Gateway (Private)",
"link": "https://docs.aws.amazon.com/apigateway/latest/developerguide/apigateway-private-apis.html"
},
{
"text": "Execute-API VPC Endpoint",
"link": "https://docs.aws.amazon.com/vpc/latest/privatelink/create-interface-endpoint.html"
},
{
"text": "VPC Links",
"link": "https://docs.aws.amazon.com/vpc/latest/userguide/endpoint-services-overview.html"
},
{
"text": "Network Load Balancer (NLB)",
"link": "https://docs.aws.amazon.com/elasticloadbalancing/latest/network/introduction.html"
},
{
"text": "Application Load Balancer (ALB)",
"link": "https://docs.aws.amazon.com/elasticloadbalancing/latest/application/introduction.html"
},
{
"text": "Amazon ECS Fargate",
"link": "https://docs.aws.amazon.com/AmazonECS/latest/developerguide/AWS_Fargate.html"
}
]
},
"deploy": {
"text": ["cdk deploy"]
},
"testing": {
"text": ["See the GitHub repo for detailed testing instructions."]
},
"cleanup": {
"text": ["Delete the stack: <code>cdk delete</code>."]
},
"authors": [
{
"name": "Jevon Liburd",
"image": "https://media.licdn.com/dms/image/v2/C5603AQGQeKZyhyvcpw/profile-displayphoto-shrink_200_200/profile-displayphoto-shrink_200_200/0/1605745311041?e=1742428800&v=beta&t=zYr5hwUCsfljHyeDSMrWTmIrrfMu7LJpV0qD_nsjVYc",
"bio": "Jevon is a Technical Account Manager at Amazon Web Services.",
"linkedin": "jevon-liburd-a436b315",
"twitter": "Jevon_EL"
}
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules

# CDK asset staging directory
.cdk.staging
cdk.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*.ts
!*.d.ts

# CDK asset staging directory
.cdk.staging
cdk.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
#!/usr/bin/env node
import * as cdk from 'aws-cdk-lib';
import { CentralApiStack } from '../lib/central-api-stack';
import { ServiceAStack } from '../lib/service-a-stack';
import { ServiceBStack } from '../lib/service-b-stack';
import config from '../config.json';
import { CentralApiResourcesStack } from '../lib/central-api-resources-stack';
import { Tags } from 'aws-cdk-lib';

const app = new cdk.App();
const centralApiAccount = config.stacks.centralAPI.account || process.env.CENTRAL_API_ACCOUNT_NUMBER;
const serviceAAccount = config.stacks.serviceA.account || process.env.SERVICE_A_ACCOUNT_NUMBER;
const serviceBAccount = config.stacks.serviceB.account || process.env.SERVICE_B_ACCOUNT_NUMBER;
const region = config.region || process.env.REGION;

// Stop execution if the account numbers are not defined
if (!centralApiAccount || !serviceAAccount || !serviceBAccount || !region) {
throw new Error('Account numbers and/or region for stacks are not defined. Define them in the config file or as environment variables as mentioned in the README');
}

const centralApi = new CentralApiStack(app, 'CentralApiStack', {
cidr: config.cidrs.centralAPI,
accountsForIEPShare: [serviceAAccount, serviceBAccount],
stage: config.stacks.centralAPI.stage,
apiDescription: config.stacks.centralAPI.apiDescription,
iepParam: config.params.interfaceEp.param,
iepParamId: config.params.interfaceEp.paramId,
env: {
account: centralApiAccount,
region: region,
},
});
const centralApiResources = new CentralApiResourcesStack(app, 'CentralApiResourcesStack', {
svcAUriParam: config.params.svcAUri.param,
svcAUriParamId: config.params.svcAUri.paramId,
svcBUriParam: config.params.svcBUri.param,
svcBUriParamId: config.params.svcBUri.paramId,
svcAAccount: serviceAAccount,
svcBAccount: serviceBAccount,
stage: config.stacks.centralAPI.stage,
env: {
account: centralApiAccount,
region: region,
},
});
const serviceA = new ServiceAStack(app, 'ServiceAStack', {
cidr: config.cidrs.serviceA,
iepParam: config.params.interfaceEp.param,
iepParamId: config.params.interfaceEp.paramId,
uriParam: config.params.svcAUri.param,
uriParamId: config.params.svcAUri.paramId,
centralApiAccount: centralApiAccount,
stage: config.stacks.serviceA.stage,
apiDescription: config.stacks.serviceA.apiDescription,
env: {
account: serviceAAccount,
region: region,
},
});
const serviceB = new ServiceBStack(app, 'ServiceBStack', {
cidr: config.cidrs.serviceB,
iepParam: config.params.interfaceEp.param,
iepParamId: config.params.interfaceEp.paramId,
uriParam: config.params.svcBUri.param,
uriParamId: config.params.svcBUri.paramId,
centralApiAccount: centralApiAccount,
stage: config.stacks.serviceB.stage,
apiDescription: config.stacks.serviceB.apiDescription,
env: {
account: serviceBAccount,
region: region,
},
});

// Setting tags for all the taggable resoruces in the stacks
const tags = config.tags;
tags.forEach((tag) => {
Tags.of(centralApi).add(tag.key, tag.value);
Tags.of(centralApiResources).add(tag.key, tag.value);
Tags.of(serviceA).add(tag.key, tag.value);
Tags.of(serviceB).add(tag.key, tag.value);
});


Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"availability-zones:account=239641027929:region=ca-central-1": [
"ca-central-1a",
"ca-central-1b",
"ca-central-1d"
],
"availability-zones:account=211125558039:region=ca-central-1": [
"ca-central-1a",
"ca-central-1b",
"ca-central-1d"
],
"availability-zones:account=785168607513:region=ca-central-1": [
"ca-central-1a",
"ca-central-1b",
"ca-central-1d"
]
}
Loading