Skip to content

Commit

Permalink
Merge pull request #6 from pharindoko/ref/update-beta-code
Browse files Browse the repository at this point in the history
feat(internal-apigatway): add new properties
  • Loading branch information
pharindoko authored Nov 4, 2022
2 parents e455a36 + 71235ab commit 5d666da
Show file tree
Hide file tree
Showing 7 changed files with 453 additions and 311 deletions.
36 changes: 31 additions & 5 deletions API.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

228 changes: 130 additions & 98 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,132 +1,164 @@
# CDK Internal Gateway

This CDK construct is used to create internal serverless applications and websites.

By default the aws api gateway endpoints are public and accessible from the internet.
Even in times of zero trust, for larger companies it`s not a bad idea to have an additional security layer for internal applications.
But it is still a huge of effort to configure and implement all aws components in a secure manner.

This construct tries to simplify the setup.
This CDK construct simplifies the setup of **internal serverless applications** for large companies and enterprises.

## Features

- no traffic will be routed over the internet
- restricts access to the internal network of your company
- create and attach custom domains and certificates
- create your internal websites and backends using api gateway integrations (see samples folder)
- creates all aws components needed to allow access only from the internal network of your company in a secure manner
- modularized approach with separate constructs
- add multiple internal api gateways to the same internal service to save costs and keep flexibility

---

## Requirements

- CDK V2
- VPC
- VPC Endpoint for execute-api
- CDK V2 (2.46.0)
- A VPC
- A VPC Endpoint for execute-api
- A Hosted Zone
- Internally accessible subnets (for the load balancer)

## Installation

Using Typescript for aws cdk

```bash
npm i
npm i cdk-internal-gateway
```

## Architecture

![cdk internal gateway](cdk-internal-gateway.drawio.png )
![cdk-internal-gateway-architecture](cdk-internal-gateway.drawio.png )

### Technical Details

## How to use it ?
- creates an internal application loadbalancer
- forwards traffic to VPC endpoint for execute-api
- redirect http to https
- provides a securely configured apigateway resource out of the box
- attach your aws components to the internal apigateway resource
- sets api gateway to PRIVATE mode
- sets resource policies to only allow traffic from vpc endpoint
- generates and attaches custom domains to the API Gateway
- generates and attaches certificates to the the API Gateway and the loadbalancer

Let`s assume we want to create a simple internal api for our company.
---

## Usage

> Let`s assume we create a simple internal api for our company and start with a single lambda function...
1. Create a file called `/lib/my-new-stack.ts`

```typescript
import { aws_apigateway as apigateway, aws_ec2 as ec2, aws_lambda as lambda, Stack, StackProps } from 'aws-cdk-lib';
import { HttpMethod } from 'aws-cdk-lib/aws-events';
import { InternalApiGatewayStack, InternalApiGatewayStackProps, InternalServiceStack } from 'cdk-internal-gateway';
import { Construct } from 'constructs';
import * as path from 'path';


// Create a new stack that inherits from the InternalApiGateway Construct
// Attach your lambda function to the internalApiGateway (member variable)
export class ServerlessStack extends InternalApiGatewayStack {
constructor(scope: Construct, id: string, props: InternalApiGatewayStackProps) {
super(scope, id, props);
this.internalApiGateway.root;
const defaultLambdaJavascript = this.internalApiGateway.root.resourceForPath("hey-js");
const defaultHandlerJavascript = new lambda.Function(
this,
`backendLambdaJavascript`,
{
functionName: `js-lambda`,
runtime: lambda.Runtime.NODEJS_14_X,
handler: "index.handler",
code: lambda.Code.fromAsset(path.join(__dirname, "../src")),
}
);

defaultLambdaJavascript.addMethod(
HttpMethod.GET,
new apigateway.LambdaIntegration(defaultHandlerJavascript)
);
```typescript
import { aws_apigateway as apigateway, aws_ec2 as ec2, aws_lambda as lambda, Stack, StackProps } from 'aws-cdk-lib';
import { HttpMethod } from 'aws-cdk-lib/aws-events';
import { InternalApiGatewayStack, InternalApiGatewayStackProps, InternalServiceStack } from 'cdk-internal-gateway';
import { Construct } from 'constructs';
import * as path from 'path';


// Create a new stack that inherits from the InternalApiGateway Construct
export class ServerlessStack extends InternalApiGatewayStack {
constructor(scope: Construct, id: string, props: InternalApiGatewayStackProps) {
super(scope, id, props);

// The internal api gateway is available as member variable
// Attach your lambda function to the this.internalApiGateway
const defaultLambdaJavascript = this.internalApiGateway.root.resourceForPath("hey-js");
const defaultHandlerJavascript = new lambda.Function(
this,
`backendLambdaJavascript`,
{
functionName: `js-lambda`,
runtime: lambda.Runtime.NODEJS_14_X,
handler: "index.handler",
code: lambda.Code.fromAsset(path.join(__dirname, "../src")),
}
);

defaultLambdaJavascript.addMethod(
HttpMethod.GET,
new apigateway.LambdaIntegration(defaultHandlerJavascript)
);
}
}
}

// Create a new stack that contains the whole service with all nested stacks
export class ServiceStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);

// get all parameters to create the internal service stack
const vpc = ec2.Vpc.fromLookup(this, 'vpcLookup', { vpcId: 'vpc-1234567890' });
const internalSubnetIds = ['subnet-0b1e1c6c7d8e9f0a2', 'subnet-0b1e1c6c7d8e9f0a3'];
const subnetSelection = {
subnets: internalSubnetIds.map((ip, index) =>
ec2.Subnet.fromSubnetId(this, `Subnet${index}`, ip),
),
};

// create the internal service stack
const internalServiceStack = new InternalServiceStack(this, 'InternalServiceStack', {
hostedZoneName: 'example.com',
subnetSelection: subnetSelection,
vpcEndpointId: 'vpce-1234567890',
vpcEndpointIPAddresses: ['192.168.2.1', '192.168.2.2'],
vpc: vpc,
subjectAlternativeNames: ['internal.example.com'],
subDomain: "internal-service"
})

// create your stack that inherits from the InternalApiGatewayStack
new ServerlessStack(this, 'MyProjectStack', {
domains: serviceStack.domains,
stage: "dev",
vpcEndpointId: serviceStack.vpcEndpointId,
})

// Create a new stack that contains the whole service with all nested stacks
export class ServiceStack extends Stack {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);

// get all parameters to create the internal service stack
const vpc = ec2.Vpc.fromLookup(this, 'vpcLookup', { vpcId: 'vpc-1234567890' });
const subnetSelection = {
subnets: ['subnet-0b1e1c6c7d8e9f0a2', 'subnet-0b1e1c6c7d8e9f0a3'].map((ip, index) =>
ec2.Subnet.fromSubnetId(this, `Subnet${index}`, ip),
),
};
const hostedZone = route53.HostedZone.fromLookup(stack, 'hostedzone', {
domainName: 'test.aws1234.com',
privateZone: true,
vpcId: vpc.vpcId,
});
const vpcEndpoint =
ec2.InterfaceVpcEndpoint.fromInterfaceVpcEndpointAttributes(
stack,
'vpcEndpoint',
{
port: 443,
vpcEndpointId: 'vpce-1234567890',
},
);

// create the internal service stack
const internalServiceStack = new InternalServiceStack(this, 'InternalServiceStack', {
hostedZone: hostedZone,
subnetSelection: subnetSelection,
vpcEndpointIPAddresses: ['192.168.2.1', '192.168.2.2'],
vpc: vpc,
subjectAlternativeNames: ['internal.example.com'],
subDomain: "internal-service"
})

// create your stack that inherits from the InternalApiGatewayStack
new ServerlessStack(this, 'MyProjectStack', {
domains: serviceStack.domains,
stage: "dev",
vpcEndpoint: vpcEndpoint,
})
}
}
}
```
```

2. Reference the newly created `ServiceStack` in the default `/bin/{project}.ts` file e.g. like this
1. Reference the newly created `ServiceStack` in the default `/bin/{project}.ts` file e.g. like this

```typescript
new ServiceStack(app, 'MyProjectStack', {
env:
{
account: process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION
}
```
```typescript
new ServiceStack(app, 'MyProjectStack', {
env:
{
account: process.env.CDK_DEPLOY_ACCOUNT || process.env.CDK_DEFAULT_ACCOUNT,
region: process.env.CDK_DEPLOY_REGION || process.env.CDK_DEFAULT_REGION
}
```

---

## Background

By default the aws api gateway endpoints are public and accessible from the internet.

Even in times of zero trust, for larger companies with a lof of engineering teams it makes sense to have an additional network security layer for internal applications to reduce the attack surface.

It is still a huge effort to configure and implement all aws components in a secure manner and this is why I created this construct.

## Costs

You have to expect basic infra costs for 2 components in this setup:

| Count | Type | Estimated Costs |
| Count | Type | Estimated Costs |
|---|---|---|
|1 x| application load balancer | 20 $ |
|1 x| application load balancer | 20 $ |
|2 x| network interfaces for the vpc endpoint | 16 $ |

A shared vpc can lower the costs as vpc endpoint and their network interfaces can be used together...
Loading

0 comments on commit 5d666da

Please sign in to comment.