Repository: pharindoko/cdk-internal-gateway (language: python)

Issue:
feature: add static webhosting construct



Discussion / hints:

Fix patch:
diff --git a/API.md b/API.md
index 509b966..749b7d0 100644
--- a/API.md
+++ b/API.md
@@ -213,6 +213,105 @@ List of domains created by the internal service stack and shared with the api ga
 ---
 
 
+### InternalWebsite <a name="InternalWebsite" id="cdk-internal-gateway.InternalWebsite"></a>
+
+#### Initializers <a name="Initializers" id="cdk-internal-gateway.InternalWebsite.Initializer"></a>
+
+```typescript
+import { InternalWebsite } from 'cdk-internal-gateway'
+
+new InternalWebsite(scope: Construct, id: string, props: InternalWebsiteProps)
+```
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#cdk-internal-gateway.InternalWebsite.Initializer.parameter.scope">scope</a></code> | <code>constructs.Construct</code> | *No description.* |
+| <code><a href="#cdk-internal-gateway.InternalWebsite.Initializer.parameter.id">id</a></code> | <code>string</code> | *No description.* |
+| <code><a href="#cdk-internal-gateway.InternalWebsite.Initializer.parameter.props">props</a></code> | <code><a href="#cdk-internal-gateway.InternalWebsiteProps">InternalWebsiteProps</a></code> | *No description.* |
+
+---
+
+##### `scope`<sup>Required</sup> <a name="scope" id="cdk-internal-gateway.InternalWebsite.Initializer.parameter.scope"></a>
+
+- *Type:* constructs.Construct
+
+---
+
+##### `id`<sup>Required</sup> <a name="id" id="cdk-internal-gateway.InternalWebsite.Initializer.parameter.id"></a>
+
+- *Type:* string
+
+---
+
+##### `props`<sup>Required</sup> <a name="props" id="cdk-internal-gateway.InternalWebsite.Initializer.parameter.props"></a>
+
+- *Type:* <a href="#cdk-internal-gateway.InternalWebsiteProps">InternalWebsiteProps</a>
+
+---
+
+#### Methods <a name="Methods" id="Methods"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#cdk-internal-gateway.InternalWebsite.toString">toString</a></code> | Returns a string representation of this construct. |
+
+---
+
+##### `toString` <a name="toString" id="cdk-internal-gateway.InternalWebsite.toString"></a>
+
+```typescript
+public toString(): string
+```
+
+Returns a string representation of this construct.
+
+#### Static Functions <a name="Static Functions" id="Static Functions"></a>
+
+| **Name** | **Description** |
+| --- | --- |
+| <code><a href="#cdk-internal-gateway.InternalWebsite.isConstruct">isConstruct</a></code> | Checks if `x` is a construct. |
+
+---
+
+##### ~~`isConstruct`~~ <a name="isConstruct" id="cdk-internal-gateway.InternalWebsite.isConstruct"></a>
+
+```typescript
+import { InternalWebsite } from 'cdk-internal-gateway'
+
+InternalWebsite.isConstruct(x: any)
+```
+
+Checks if `x` is a construct.
+
+###### `x`<sup>Required</sup> <a name="x" id="cdk-internal-gateway.InternalWebsite.isConstruct.parameter.x"></a>
+
+- *Type:* any
+
+Any object.
+
+---
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#cdk-internal-gateway.InternalWebsite.property.node">node</a></code> | <code>constructs.Node</code> | The tree node. |
+
+---
+
+##### `node`<sup>Required</sup> <a name="node" id="cdk-internal-gateway.InternalWebsite.property.node"></a>
+
+```typescript
+public readonly node: Node;
+```
+
+- *Type:* constructs.Node
+
+The tree node.
+
+---
+
+
 ## Structs <a name="Structs" id="Structs"></a>
 
 ### InternalApiGatewayProps <a name="InternalApiGatewayProps" id="cdk-internal-gateway.InternalApiGatewayProps"></a>
@@ -439,5 +538,144 @@ SSLPolicy attached to the load balancer listener.
 
 ---
 
+### InternalWebsiteProps <a name="InternalWebsiteProps" id="cdk-internal-gateway.InternalWebsiteProps"></a>
+
+Properties for InternalService.
+
+#### Initializer <a name="Initializer" id="cdk-internal-gateway.InternalWebsiteProps.Initializer"></a>
+
+```typescript
+import { InternalWebsiteProps } from 'cdk-internal-gateway'
+
+const internalWebsiteProps: InternalWebsiteProps = { ... }
+```
+
+#### Properties <a name="Properties" id="Properties"></a>
+
+| **Name** | **Type** | **Description** |
+| --- | --- | --- |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.domains">domains</a></code> | <code>aws-cdk-lib.aws_apigateway.IDomainName[]</code> | List of custom domains names to be used for the API Gateway. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.stage">stage</a></code> | <code>string</code> | Stage name  used for all cloudformation resource names and internal aws resource names. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.vpcEndpoint">vpcEndpoint</a></code> | <code>aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint</code> | VPC endpoint id of execute-api vpc endpoint. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.apiBasePathMappingPath">apiBasePathMappingPath</a></code> | <code>string</code> | Path for custom domain base path mapping that will be attached to the api gateway. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.binaryMediaTypes">binaryMediaTypes</a></code> | <code>string[]</code> | Binary media types for the internal api gateway. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.minimumCompressionSize">minimumCompressionSize</a></code> | <code>number</code> | minimum compression size for the internal api gateway. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.sourcePath">sourcePath</a></code> | <code>string</code> | Path of website folder containing the website`s sources. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.bucketName">bucketName</a></code> | <code>string</code> | Name of s3 bucket to use for the website deployment. |
+| <code><a href="#cdk-internal-gateway.InternalWebsiteProps.property.websiteIndexDocument">websiteIndexDocument</a></code> | <code>string</code> | Name of html index document used for the website. |
+
+---
+
+##### `domains`<sup>Required</sup> <a name="domains" id="cdk-internal-gateway.InternalWebsiteProps.property.domains"></a>
+
+```typescript
+public readonly domains: IDomainName[];
+```
+
+- *Type:* aws-cdk-lib.aws_apigateway.IDomainName[]
+
+List of custom domains names to be used for the API Gateway.
+
+---
+
+##### `stage`<sup>Required</sup> <a name="stage" id="cdk-internal-gateway.InternalWebsiteProps.property.stage"></a>
+
+```typescript
+public readonly stage: string;
+```
+
+- *Type:* string
+
+Stage name  used for all cloudformation resource names and internal aws resource names.
+
+---
+
+##### `vpcEndpoint`<sup>Required</sup> <a name="vpcEndpoint" id="cdk-internal-gateway.InternalWebsiteProps.property.vpcEndpoint"></a>
+
+```typescript
+public readonly vpcEndpoint: IInterfaceVpcEndpoint;
+```
+
+- *Type:* aws-cdk-lib.aws_ec2.IInterfaceVpcEndpoint
+
+VPC endpoint id of execute-api vpc endpoint.
+
+This endpoint will be used to forward requests from the load balancer`s target group to the api gateway.
+
+---
+
+##### `apiBasePathMappingPath`<sup>Optional</sup> <a name="apiBasePathMappingPath" id="cdk-internal-gateway.InternalWebsiteProps.property.apiBasePathMappingPath"></a>
+
+```typescript
+public readonly apiBasePathMappingPath: string;
+```
+
+- *Type:* string
+
+Path for custom domain base path mapping that will be attached to the api gateway.
+
+---
+
+##### `binaryMediaTypes`<sup>Optional</sup> <a name="binaryMediaTypes" id="cdk-internal-gateway.InternalWebsiteProps.property.binaryMediaTypes"></a>
+
+```typescript
+public readonly binaryMediaTypes: string[];
+```
+
+- *Type:* string[]
+
+Binary media types for the internal api gateway.
+
+---
+
+##### `minimumCompressionSize`<sup>Optional</sup> <a name="minimumCompressionSize" id="cdk-internal-gateway.InternalWebsiteProps.property.minimumCompressionSize"></a>
+
+```typescript
+public readonly minimumCompressionSize: number;
+```
+
+- *Type:* number
+
+minimum compression size for the internal api gateway.
+
+---
+
+##### `sourcePath`<sup>Required</sup> <a name="sourcePath" id="cdk-internal-gateway.InternalWebsiteProps.property.sourcePath"></a>
+
+```typescript
+public readonly sourcePath: string;
+```
+
+- *Type:* string
+
+Path of website folder containing the website`s sources.
+
+---
+
+##### `bucketName`<sup>Optional</sup> <a name="bucketName" id="cdk-internal-gateway.InternalWebsiteProps.property.bucketName"></a>
+
+```typescript
+public readonly bucketName: string;
+```
+
+- *Type:* string
+
+Name of s3 bucket to use for the website deployment.
+
+---
+
+##### `websiteIndexDocument`<sup>Optional</sup> <a name="websiteIndexDocument" id="cdk-internal-gateway.InternalWebsiteProps.property.websiteIndexDocument"></a>
+
+```typescript
+public readonly websiteIndexDocument: string;
+```
+
+- *Type:* string
+- *Default:* index.html
+
+Name of html index document used for the website.
+
+---
+
 
 
diff --git a/README.md b/README.md
index 75e4cca..deebaa7 100644
--- a/README.md
+++ b/README.md
@@ -29,17 +29,34 @@ pip install pharindoko.cdk-internal-gateway
 
 ### Technical Details
 
+Modularized approach with separate constructs
+
+- attach multiple InternalApiGateway and InternalWebsite constructs to the same Internal Service to save costs and keep flexibility
+
+**Internal Service Construct (mandatory construct):**
+
 - creates an internal application loadbalancer
   - forwards traffic to VPC endpoint for execute-api
   - redirect http to https
+- generates custom domains for the API Gateway
+- generates certificates for the loadbalancer listener
+
+**Internal Api Gateway Construct:**
+
 - 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
-- modularized approach with separate constructs
-  - add multiple internal api gateways to the same internal service to save costs and keep flexibility
+- attaches custom domains to the API Gateway
+- attaches certificates to the the API Gateway and the loadbalancer
+
+**Internal Website Construct:**
+
+- makes your website internally accessible
+- redeploys your website with a single cdk deploy
+- provides a securely configured private s3 bucket out of box
+- works with SPA applications (written with Vue, Angular) and static websites
+- is an extension of the InternalApiGateway Construct
 
 ## Requirements
 
@@ -133,7 +150,7 @@ pip install pharindoko.cdk-internal-gateway
             })
 
             // create another stack that inherits from the InternalApiGateway
-            ... 
+            ...
             ...
         }
     }
@@ -152,11 +169,11 @@ pip install pharindoko.cdk-internal-gateway
 
 ## Costs
 
-You have to expect basic infra costs for 2 components in this setup:  
+You have to expect basic infra costs for 2 components in this setup:
 
 | Count |  Type |  Estimated Costs |
 |---|---|---|
 |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...  
+A shared vpc can lower the costs as vpc endpoint and their network interfaces can be used together...
diff --git a/cdk-internal-gateway.drawio.png b/cdk-internal-gateway.drawio.png
index 57b60d4..cb821d4 100644
Binary files a/cdk-internal-gateway.drawio.png and b/cdk-internal-gateway.drawio.png differ
diff --git a/src/index.ts b/src/index.ts
index 0f625b0..a494fa8 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,2 +1,3 @@
 export * from "./internal-apigateway";
 export * from "./internal-service";
+export * from "./internal-website";
diff --git a/src/internal-website.ts b/src/internal-website.ts
new file mode 100644
index 0000000..8771216
--- /dev/null
+++ b/src/internal-website.ts
@@ -0,0 +1,143 @@
+import * as path from "path";
+import {
+  aws_apigateway as apigateway,
+  aws_iam as iam,
+  aws_s3 as s3,
+  aws_s3_deployment as s3deploy,
+  RemovalPolicy,
+} from "aws-cdk-lib";
+import { BlockPublicAccess } from "aws-cdk-lib/aws-s3";
+import { Construct } from "constructs";
+import {
+  InternalApiGateway,
+  InternalApiGatewayProps,
+} from "./internal-apigateway";
+
+/**
+ * Properties for InternalService
+ */
+export interface InternalWebsiteProps extends InternalApiGatewayProps {
+  /**
+   * Path of website folder containing the website`s sources
+   */
+  readonly sourcePath: string;
+
+  /**
+   * Name of s3 bucket to use for the website deployment
+   */
+  readonly bucketName?: string;
+
+  /**
+   * Name of html index document used for the website
+   *
+   * @default index.html
+   */
+  readonly websiteIndexDocument?: string;
+}
+
+export class InternalWebsite extends InternalApiGateway {
+  constructor(scope: Construct, id: string, props: InternalWebsiteProps) {
+    super(scope, id, props);
+
+    const bucket = new s3.Bucket(this, `WebsiteBucket-${id}`, {
+      versioned: true,
+      bucketName: props.bucketName || undefined,
+      publicReadAccess: false,
+      blockPublicAccess: BlockPublicAccess.BLOCK_ALL,
+      removalPolicy: RemovalPolicy.DESTROY,
+      websiteIndexDocument: props.websiteIndexDocument || "index.html",
+      autoDeleteObjects: true,
+    });
+
+    new s3deploy.BucketDeployment(this, `WebsiteDeployment-${id}`, {
+      sources: [s3deploy.Source.asset(path.normalize(props.sourcePath))],
+      destinationBucket: bucket,
+    });
+
+    const role = new iam.Role(this, `ApiGatewayReadOnlyRole-${id}`, {
+      assumedBy: new iam.ServicePrincipal("apigateway.amazonaws.com"),
+    });
+
+    role.addToPolicy(
+      new iam.PolicyStatement({
+        resources: [`${bucket.bucketArn}`, `${bucket.bucketArn}/*`],
+        actions: ["s3:GetObject", "s3:GetObjectAcl", "s3:ListBucket"],
+      })
+    );
+
+    const proxyIntegration = new apigateway.AwsIntegration({
+      service: "s3",
+      integrationHttpMethod: "GET",
+      path: `${bucket.bucketName}/{proxy}`,
+      options: {
+        credentialsRole: role,
+        requestParameters: {
+          "integration.request.path.proxy": "method.request.path.proxy",
+        },
+        integrationResponses: [
+          {
+            statusCode: "200",
+            selectionPattern: "2..",
+            responseParameters: {
+              "method.response.header.Content-Type":
+                "integration.response.header.Content-Type",
+            },
+          },
+          {
+            statusCode: "403",
+            selectionPattern: "4..",
+          },
+        ],
+      },
+    });
+
+    const defaultIntegration = new apigateway.AwsIntegration({
+      service: "s3",
+      integrationHttpMethod: "GET",
+      path: `${bucket.bucketName}/${
+        props.websiteIndexDocument || "index.html"
+      }`,
+      options: {
+        credentialsRole: role,
+        integrationResponses: [
+          {
+            statusCode: "200",
+            selectionPattern: "2..",
+            responseParameters: {
+              "method.response.header.Content-Type":
+                "integration.response.header.Content-Type",
+            },
+          },
+          {
+            statusCode: "403",
+            selectionPattern: "4..",
+          },
+        ],
+      },
+    });
+
+    const proxyMethodOptions = {
+      methodResponses: [
+        {
+          statusCode: "200",
+          responseParameters: {
+            "method.response.header.Content-Type": true,
+          },
+        },
+      ],
+      requestParameters: {
+        "method.request.path.proxy": true,
+      },
+    };
+
+    const proxyResource = this.apiGateway.root.addProxy({
+      anyMethod: false,
+      defaultIntegration: proxyIntegration,
+      defaultMethodOptions: proxyMethodOptions,
+    });
+    const defaultResource = this.apiGateway.root.resourceForPath("/");
+
+    defaultResource.addMethod("ANY", defaultIntegration, proxyMethodOptions);
+    proxyResource.addMethod("ANY", proxyIntegration, proxyMethodOptions);
+  }
+}


Test patch:
diff --git a/test/internal-website.test.ts b/test/internal-website.test.ts
new file mode 100644
index 0000000..aaaac9e
--- /dev/null
+++ b/test/internal-website.test.ts
@@ -0,0 +1,1117 @@
+import {
+  App,
+  aws_ec2 as ec2,
+  aws_route53 as route53,
+  Stack,
+} from "aws-cdk-lib";
+import { Match, Template } from "aws-cdk-lib/assertions";
+import { InternalService, InternalWebsite } from "../src";
+
+let app: App;
+let stack: Stack;
+let internalServiceStack: InternalService;
+let vpcEndpointId: ec2.IInterfaceVpcEndpoint;
+
+beforeEach(() => {
+  app = new App();
+  stack = new Stack(app, "test", {
+    env: {
+      account: "123456789012",
+      region: "us-east-1",
+    },
+  });
+
+  const vpc = ec2.Vpc.fromLookup(stack, "vpc", { vpcId: "vpc-1234567" });
+  const internalSubnetIds = ["subnet-1234567890", "subnet-1234567890"];
+  const hostedZone = route53.HostedZone.fromLookup(stack, "hostedzone", {
+    domainName: "test.aws1234.com",
+    privateZone: true,
+    vpcId: vpc.vpcId,
+  });
+
+  internalServiceStack = new InternalService(stack, "internalServiceStack", {
+    vpc: vpc,
+    subnetSelection: {
+      subnets: internalSubnetIds.map((ip, index) =>
+        ec2.Subnet.fromSubnetId(stack, `Subnet${index}`, ip)
+      ),
+    },
+    vpcEndpointIPAddresses: ["192.168.2.1", "192.168.2.2"],
+    subjectAlternativeNames: [
+      "internalservice-dev.test.com",
+      "internalservice-dev.test2.com",
+    ],
+    hostedZone: hostedZone,
+    subDomain: "internalservice-dev",
+  });
+  vpcEndpointId = ec2.InterfaceVpcEndpoint.fromInterfaceVpcEndpointAttributes(
+    stack,
+    "vpcEndpoint",
+    {
+      port: 443,
+      vpcEndpointId: "vpce-1234567890",
+    }
+  );
+});
+
+test("Internal Website provider - set default values", () => {
+  new InternalWebsite(stack, "internalGatewayStack", {
+    stage: "dev",
+    domains: internalServiceStack.domains,
+    vpcEndpoint: vpcEndpointId,
+    sourcePath: "./test/website-sample",
+  });
+
+  const template = Template.fromStack(stack);
+  expect(template).toMatchInlineSnapshot(`
+    Object {
+      "Outputs": Object {
+        "internalGatewayStackGatewayinternalGatewayStackEndpoint4102CA04": Object {
+          "Value": Object {
+            "Fn::Join": Array [
+              "",
+              Array [
+                "https://",
+                Object {
+                  "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+                },
+                ".execute-api.us-east-1.",
+                Object {
+                  "Ref": "AWS::URLSuffix",
+                },
+                "/",
+                Object {
+                  "Ref": "internalGatewayStackGatewayinternalGatewayStackDeploymentStagedev5F843F8F",
+                },
+                "/",
+              ],
+            ],
+          },
+        },
+        "internalServiceStackDomainUrlinternalServiceStackB921B240": Object {
+          "Description": "service url",
+          "Export": Object {
+            "Name": "-DomainUrl",
+          },
+          "Value": "https://internalservice-dev.test.aws1234.com",
+        },
+      },
+      "Parameters": Object {
+        "BootstrapVersion": Object {
+          "Default": "/cdk-bootstrap/hnb659fds/version",
+          "Description": "Version of the CDK Bootstrap resources in this environment, automatically retrieved from SSM Parameter Store. [cdk:skip]",
+          "Type": "AWS::SSM::Parameter::Value<String>",
+        },
+      },
+      "Resources": Object {
+        "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536": Object {
+          "DependsOn": Array [
+            "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF",
+            "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
+          ],
+          "Properties": Object {
+            "Code": Object {
+              "S3Bucket": "cdk-hnb659fds-assets-123456789012-us-east-1",
+              "S3Key": "6ddcf10002539818a9256eff3fb2b22aa09298d8f946e26ba121c175a600c44e.zip",
+            },
+            "Handler": "index.handler",
+            "Layers": Array [
+              Object {
+                "Ref": "internalGatewayStackWebsiteDeploymentinternalGatewayStackAwsCliLayer57780F1C",
+              },
+            ],
+            "Role": Object {
+              "Fn::GetAtt": Array [
+                "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
+                "Arn",
+              ],
+            },
+            "Runtime": "python3.9",
+            "Timeout": 900,
+          },
+          "Type": "AWS::Lambda::Function",
+        },
+        "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265": Object {
+          "Properties": Object {
+            "AssumeRolePolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": "sts:AssumeRole",
+                  "Effect": "Allow",
+                  "Principal": Object {
+                    "Service": "lambda.amazonaws.com",
+                  },
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+            "ManagedPolicyArns": Array [
+              Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+                  ],
+                ],
+              },
+            ],
+          },
+          "Type": "AWS::IAM::Role",
+        },
+        "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF": Object {
+          "Properties": Object {
+            "PolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": Array [
+                    "s3:GetObject*",
+                    "s3:GetBucket*",
+                    "s3:List*",
+                  ],
+                  "Effect": "Allow",
+                  "Resource": Array [
+                    Object {
+                      "Fn::Join": Array [
+                        "",
+                        Array [
+                          "arn:",
+                          Object {
+                            "Ref": "AWS::Partition",
+                          },
+                          ":s3:::cdk-hnb659fds-assets-123456789012-us-east-1",
+                        ],
+                      ],
+                    },
+                    Object {
+                      "Fn::Join": Array [
+                        "",
+                        Array [
+                          "arn:",
+                          Object {
+                            "Ref": "AWS::Partition",
+                          },
+                          ":s3:::cdk-hnb659fds-assets-123456789012-us-east-1/*",
+                        ],
+                      ],
+                    },
+                  ],
+                },
+                Object {
+                  "Action": Array [
+                    "s3:GetObject*",
+                    "s3:GetBucket*",
+                    "s3:List*",
+                    "s3:DeleteObject*",
+                    "s3:PutObject",
+                    "s3:PutObjectLegalHold",
+                    "s3:PutObjectRetention",
+                    "s3:PutObjectTagging",
+                    "s3:PutObjectVersionTagging",
+                    "s3:Abort*",
+                  ],
+                  "Effect": "Allow",
+                  "Resource": Array [
+                    Object {
+                      "Fn::GetAtt": Array [
+                        "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                        "Arn",
+                      ],
+                    },
+                    Object {
+                      "Fn::Join": Array [
+                        "",
+                        Array [
+                          Object {
+                            "Fn::GetAtt": Array [
+                              "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                              "Arn",
+                            ],
+                          },
+                          "/*",
+                        ],
+                      ],
+                    },
+                  ],
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+            "PolicyName": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRoleDefaultPolicy88902FDF",
+            "Roles": Array [
+              Object {
+                "Ref": "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756CServiceRole89A01265",
+              },
+            ],
+          },
+          "Type": "AWS::IAM::Policy",
+        },
+        "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F": Object {
+          "DependsOn": Array [
+            "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092",
+          ],
+          "Properties": Object {
+            "Code": Object {
+              "S3Bucket": "cdk-hnb659fds-assets-123456789012-us-east-1",
+              "S3Key": "6babbac1f25446ab4660ead0ad5972e3a7742f50c6d8326af98a8bcd5d485335.zip",
+            },
+            "Description": Object {
+              "Fn::Join": Array [
+                "",
+                Array [
+                  "Lambda function for auto-deleting objects in ",
+                  Object {
+                    "Ref": "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                  },
+                  " S3 bucket.",
+                ],
+              ],
+            },
+            "Handler": "__entrypoint__.handler",
+            "MemorySize": 128,
+            "Role": Object {
+              "Fn::GetAtt": Array [
+                "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092",
+                "Arn",
+              ],
+            },
+            "Runtime": "nodejs14.x",
+            "Timeout": 900,
+          },
+          "Type": "AWS::Lambda::Function",
+        },
+        "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092": Object {
+          "Properties": Object {
+            "AssumeRolePolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": "sts:AssumeRole",
+                  "Effect": "Allow",
+                  "Principal": Object {
+                    "Service": "lambda.amazonaws.com",
+                  },
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+            "ManagedPolicyArns": Array [
+              Object {
+                "Fn::Sub": "arn:\${AWS::Partition}:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole",
+              },
+            ],
+          },
+          "Type": "AWS::IAM::Role",
+        },
+        "internalGatewayStackApiGatewayReadOnlyRoleinternalGatewayStackCB025027": Object {
+          "Properties": Object {
+            "AssumeRolePolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": "sts:AssumeRole",
+                  "Effect": "Allow",
+                  "Principal": Object {
+                    "Service": "apigateway.amazonaws.com",
+                  },
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+          },
+          "Type": "AWS::IAM::Role",
+        },
+        "internalGatewayStackApiGatewayReadOnlyRoleinternalGatewayStackDefaultPolicy6443C420": Object {
+          "Properties": Object {
+            "PolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": Array [
+                    "s3:GetObject",
+                    "s3:GetObjectAcl",
+                    "s3:ListBucket",
+                  ],
+                  "Effect": "Allow",
+                  "Resource": Array [
+                    Object {
+                      "Fn::GetAtt": Array [
+                        "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                        "Arn",
+                      ],
+                    },
+                    Object {
+                      "Fn::Join": Array [
+                        "",
+                        Array [
+                          Object {
+                            "Fn::GetAtt": Array [
+                              "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                              "Arn",
+                            ],
+                          },
+                          "/*",
+                        ],
+                      ],
+                    },
+                  ],
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+            "PolicyName": "internalGatewayStackApiGatewayReadOnlyRoleinternalGatewayStackDefaultPolicy6443C420",
+            "Roles": Array [
+              Object {
+                "Ref": "internalGatewayStackApiGatewayReadOnlyRoleinternalGatewayStackCB025027",
+              },
+            ],
+          },
+          "Type": "AWS::IAM::Policy",
+        },
+        "internalGatewayStackGatewayinternalGatewayStack16717335": Object {
+          "Properties": Object {
+            "Description": "This service serves an internal api gateway",
+            "EndpointConfiguration": Object {
+              "Types": Array [
+                "PRIVATE",
+              ],
+              "VpcEndpointIds": Array [
+                "vpce-1234567890",
+              ],
+            },
+            "Name": "Gateway-internalGatewayStack",
+            "Policy": Object {
+              "Statement": Array [
+                Object {
+                  "Action": "execute-api:Invoke",
+                  "Condition": Object {
+                    "StringNotEquals": Object {
+                      "aws:sourceVpce": "vpce-1234567890",
+                    },
+                  },
+                  "Effect": "Deny",
+                  "Principal": Object {
+                    "AWS": "*",
+                  },
+                  "Resource": "execute-api:/*/*/*",
+                },
+                Object {
+                  "Action": "execute-api:Invoke",
+                  "Condition": Object {
+                    "StringEquals": Object {
+                      "aws:sourceVpce": "vpce-1234567890",
+                    },
+                  },
+                  "Effect": "Allow",
+                  "Principal": Object {
+                    "AWS": "*",
+                  },
+                  "Resource": "execute-api:/*/*/*",
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+          },
+          "Type": "AWS::ApiGateway::RestApi",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackANY270EDB4A": Object {
+          "Properties": Object {
+            "AuthorizationType": "NONE",
+            "HttpMethod": "ANY",
+            "Integration": Object {
+              "Credentials": Object {
+                "Fn::GetAtt": Array [
+                  "internalGatewayStackApiGatewayReadOnlyRoleinternalGatewayStackCB025027",
+                  "Arn",
+                ],
+              },
+              "IntegrationHttpMethod": "GET",
+              "IntegrationResponses": Array [
+                Object {
+                  "ResponseParameters": Object {
+                    "method.response.header.Content-Type": "integration.response.header.Content-Type",
+                  },
+                  "SelectionPattern": "2..",
+                  "StatusCode": "200",
+                },
+                Object {
+                  "SelectionPattern": "4..",
+                  "StatusCode": "403",
+                },
+              ],
+              "Type": "AWS",
+              "Uri": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":apigateway:us-east-1:s3:path/",
+                    Object {
+                      "Ref": "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                    },
+                    "/index.html",
+                  ],
+                ],
+              },
+            },
+            "MethodResponses": Array [
+              Object {
+                "ResponseParameters": Object {
+                  "method.response.header.Content-Type": true,
+                },
+                "StatusCode": "200",
+              },
+            ],
+            "RequestParameters": Object {
+              "method.request.path.proxy": true,
+            },
+            "ResourceId": Object {
+              "Fn::GetAtt": Array [
+                "internalGatewayStackGatewayinternalGatewayStack16717335",
+                "RootResourceId",
+              ],
+            },
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+          },
+          "Type": "AWS::ApiGateway::Method",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackAccountE55E37CC": Object {
+          "DeletionPolicy": "Retain",
+          "DependsOn": Array [
+            "internalGatewayStackGatewayinternalGatewayStack16717335",
+          ],
+          "Properties": Object {
+            "CloudWatchRoleArn": Object {
+              "Fn::GetAtt": Array [
+                "internalGatewayStackGatewayinternalGatewayStackCloudWatchRoleA7C069E8",
+                "Arn",
+              ],
+            },
+          },
+          "Type": "AWS::ApiGateway::Account",
+          "UpdateReplacePolicy": "Retain",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackCloudWatchRoleA7C069E8": Object {
+          "DeletionPolicy": "Retain",
+          "Properties": Object {
+            "AssumeRolePolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": "sts:AssumeRole",
+                  "Effect": "Allow",
+                  "Principal": Object {
+                    "Service": "apigateway.amazonaws.com",
+                  },
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+            "ManagedPolicyArns": Array [
+              Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs",
+                  ],
+                ],
+              },
+            ],
+          },
+          "Type": "AWS::IAM::Role",
+          "UpdateReplacePolicy": "Retain",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackDeployment754882BEf8dca1f7e640b3bb960d81e0ff81b7e9": Object {
+          "DependsOn": Array [
+            "internalGatewayStackGatewayinternalGatewayStackproxyANYFEAB94E3",
+            "internalGatewayStackGatewayinternalGatewayStackproxy9B658BE0",
+            "internalGatewayStackGatewayinternalGatewayStackANY270EDB4A",
+          ],
+          "Properties": Object {
+            "Description": "This service serves an internal api gateway",
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+          },
+          "Type": "AWS::ApiGateway::Deployment",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackDeploymentStagedev5F843F8F": Object {
+          "DependsOn": Array [
+            "internalGatewayStackGatewayinternalGatewayStackAccountE55E37CC",
+          ],
+          "Properties": Object {
+            "DeploymentId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStackDeployment754882BEf8dca1f7e640b3bb960d81e0ff81b7e9",
+            },
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+            "StageName": "dev",
+          },
+          "Type": "AWS::ApiGateway::Stage",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackproxy9B658BE0": Object {
+          "Properties": Object {
+            "ParentId": Object {
+              "Fn::GetAtt": Array [
+                "internalGatewayStackGatewayinternalGatewayStack16717335",
+                "RootResourceId",
+              ],
+            },
+            "PathPart": "{proxy+}",
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+          },
+          "Type": "AWS::ApiGateway::Resource",
+        },
+        "internalGatewayStackGatewayinternalGatewayStackproxyANYFEAB94E3": Object {
+          "Properties": Object {
+            "AuthorizationType": "NONE",
+            "HttpMethod": "ANY",
+            "Integration": Object {
+              "Credentials": Object {
+                "Fn::GetAtt": Array [
+                  "internalGatewayStackApiGatewayReadOnlyRoleinternalGatewayStackCB025027",
+                  "Arn",
+                ],
+              },
+              "IntegrationHttpMethod": "GET",
+              "IntegrationResponses": Array [
+                Object {
+                  "ResponseParameters": Object {
+                    "method.response.header.Content-Type": "integration.response.header.Content-Type",
+                  },
+                  "SelectionPattern": "2..",
+                  "StatusCode": "200",
+                },
+                Object {
+                  "SelectionPattern": "4..",
+                  "StatusCode": "403",
+                },
+              ],
+              "RequestParameters": Object {
+                "integration.request.path.proxy": "method.request.path.proxy",
+              },
+              "Type": "AWS",
+              "Uri": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "arn:",
+                    Object {
+                      "Ref": "AWS::Partition",
+                    },
+                    ":apigateway:us-east-1:s3:path/",
+                    Object {
+                      "Ref": "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                    },
+                    "/{proxy}",
+                  ],
+                ],
+              },
+            },
+            "MethodResponses": Array [
+              Object {
+                "ResponseParameters": Object {
+                  "method.response.header.Content-Type": true,
+                },
+                "StatusCode": "200",
+              },
+            ],
+            "RequestParameters": Object {
+              "method.request.path.proxy": true,
+            },
+            "ResourceId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStackproxy9B658BE0",
+            },
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+          },
+          "Type": "AWS::ApiGateway::Method",
+        },
+        "internalGatewayStackWebsiteBucketinternalGatewayStackAutoDeleteObjectsCustomResource27685893": Object {
+          "DeletionPolicy": "Delete",
+          "DependsOn": Array [
+            "internalGatewayStackWebsiteBucketinternalGatewayStackPolicy1913132B",
+          ],
+          "Properties": Object {
+            "BucketName": Object {
+              "Ref": "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+            },
+            "ServiceToken": Object {
+              "Fn::GetAtt": Array [
+                "CustomS3AutoDeleteObjectsCustomResourceProviderHandler9D90184F",
+                "Arn",
+              ],
+            },
+          },
+          "Type": "Custom::S3AutoDeleteObjects",
+          "UpdateReplacePolicy": "Delete",
+        },
+        "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09": Object {
+          "DeletionPolicy": "Delete",
+          "Properties": Object {
+            "PublicAccessBlockConfiguration": Object {
+              "BlockPublicAcls": true,
+              "BlockPublicPolicy": true,
+              "IgnorePublicAcls": true,
+              "RestrictPublicBuckets": true,
+            },
+            "Tags": Array [
+              Object {
+                "Key": "aws-cdk:auto-delete-objects",
+                "Value": "true",
+              },
+              Object {
+                "Key": "aws-cdk:cr-owned:917df6a0",
+                "Value": "true",
+              },
+            ],
+            "VersioningConfiguration": Object {
+              "Status": "Enabled",
+            },
+            "WebsiteConfiguration": Object {
+              "IndexDocument": "index.html",
+            },
+          },
+          "Type": "AWS::S3::Bucket",
+          "UpdateReplacePolicy": "Delete",
+        },
+        "internalGatewayStackWebsiteBucketinternalGatewayStackPolicy1913132B": Object {
+          "Properties": Object {
+            "Bucket": Object {
+              "Ref": "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+            },
+            "PolicyDocument": Object {
+              "Statement": Array [
+                Object {
+                  "Action": Array [
+                    "s3:GetBucket*",
+                    "s3:List*",
+                    "s3:DeleteObject*",
+                  ],
+                  "Effect": "Allow",
+                  "Principal": Object {
+                    "AWS": Object {
+                      "Fn::GetAtt": Array [
+                        "CustomS3AutoDeleteObjectsCustomResourceProviderRole3B1BD092",
+                        "Arn",
+                      ],
+                    },
+                  },
+                  "Resource": Array [
+                    Object {
+                      "Fn::GetAtt": Array [
+                        "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                        "Arn",
+                      ],
+                    },
+                    Object {
+                      "Fn::Join": Array [
+                        "",
+                        Array [
+                          Object {
+                            "Fn::GetAtt": Array [
+                              "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+                              "Arn",
+                            ],
+                          },
+                          "/*",
+                        ],
+                      ],
+                    },
+                  ],
+                },
+              ],
+              "Version": "2012-10-17",
+            },
+          },
+          "Type": "AWS::S3::BucketPolicy",
+        },
+        "internalGatewayStackWebsiteDeploymentinternalGatewayStackAwsCliLayer57780F1C": Object {
+          "Properties": Object {
+            "Content": Object {
+              "S3Bucket": "cdk-hnb659fds-assets-123456789012-us-east-1",
+              "S3Key": "c409e6c5845f1f349df8cd84e160bf6f1c35d2b060b63e1f032f9bd39d4542cc.zip",
+            },
+            "Description": "/opt/awscli/aws",
+          },
+          "Type": "AWS::Lambda::LayerVersion",
+        },
+        "internalGatewayStackWebsiteDeploymentinternalGatewayStackCustomResourceCE904C61": Object {
+          "DeletionPolicy": "Delete",
+          "Properties": Object {
+            "DestinationBucketName": Object {
+              "Ref": "internalGatewayStackWebsiteBucketinternalGatewayStackBF609A09",
+            },
+            "Prune": true,
+            "ServiceToken": Object {
+              "Fn::GetAtt": Array [
+                "CustomCDKBucketDeployment8693BB64968944B69AAFB0CC9EB8756C81C01536",
+                "Arn",
+              ],
+            },
+            "SourceBucketNames": Array [
+              "cdk-hnb659fds-assets-123456789012-us-east-1",
+            ],
+            "SourceObjectKeys": Array [
+              "387c754ea2daf522842e23f74232b2c455937afe2e90e7c8d173275b0706342a.zip",
+            ],
+          },
+          "Type": "Custom::CDKBucketDeployment",
+          "UpdateReplacePolicy": "Delete",
+        },
+        "internalGatewayStacktestinternalServiceStackApiGatewayCustomDomaininternalServiceStackCFCBFC75": Object {
+          "Properties": Object {
+            "BasePath": "",
+            "DomainName": Object {
+              "Ref": "internalServiceStackApiGatewayCustomDomaininternalServiceStack836326F5",
+            },
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+            "Stage": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStackDeploymentStagedev5F843F8F",
+            },
+          },
+          "Type": "AWS::ApiGateway::BasePathMapping",
+        },
+        "internalGatewayStacktestinternalServiceStackDomaininternalservicedevtest2cominternalServiceStackB33D4C7F": Object {
+          "Properties": Object {
+            "BasePath": "",
+            "DomainName": Object {
+              "Ref": "internalServiceStackDomaininternalservicedevtest2cominternalServiceStack5EFB1D61",
+            },
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+            "Stage": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStackDeploymentStagedev5F843F8F",
+            },
+          },
+          "Type": "AWS::ApiGateway::BasePathMapping",
+        },
+        "internalGatewayStacktestinternalServiceStackDomaininternalservicedevtestcominternalServiceStack590A0ACC": Object {
+          "Properties": Object {
+            "BasePath": "",
+            "DomainName": Object {
+              "Ref": "internalServiceStackDomaininternalservicedevtestcominternalServiceStackB6868874",
+            },
+            "RestApiId": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStack16717335",
+            },
+            "Stage": Object {
+              "Ref": "internalGatewayStackGatewayinternalGatewayStackDeploymentStagedev5F843F8F",
+            },
+          },
+          "Type": "AWS::ApiGateway::BasePathMapping",
+        },
+        "internalServiceStackApiGatewayCustomDomaininternalServiceStack836326F5": Object {
+          "Properties": Object {
+            "DomainName": "internalservice-dev.test.aws1234.com",
+            "EndpointConfiguration": Object {
+              "Types": Array [
+                "REGIONAL",
+              ],
+            },
+            "RegionalCertificateArn": Object {
+              "Ref": "internalServiceStackSSLCertificateinternalServiceStack283B9A17",
+            },
+            "SecurityPolicy": "TLS_1_2",
+          },
+          "Type": "AWS::ApiGateway::DomainName",
+        },
+        "internalServiceStackApplicationLoadBalancerinternalServiceStackA9484EF3": Object {
+          "Properties": Object {
+            "LoadBalancerAttributes": Array [
+              Object {
+                "Key": "deletion_protection.enabled",
+                "Value": "false",
+              },
+            ],
+            "Scheme": "internal",
+            "SecurityGroups": Array [
+              Object {
+                "Fn::GetAtt": Array [
+                  "internalServiceStackLoadBalancerSecurityGroupinternalServiceStackB6066852",
+                  "GroupId",
+                ],
+              },
+            ],
+            "Subnets": Array [
+              "subnet-1234567890",
+              "subnet-1234567890",
+            ],
+            "Type": "application",
+          },
+          "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
+        },
+        "internalServiceStackApplicationLoadBalancerinternalServiceStackListenerinternalServiceStackAC542223": Object {
+          "Properties": Object {
+            "Certificates": Array [
+              Object {
+                "CertificateArn": Object {
+                  "Ref": "internalServiceStackSSLCertificateinternalServiceStack283B9A17",
+                },
+              },
+            ],
+            "DefaultActions": Array [
+              Object {
+                "TargetGroupArn": Object {
+                  "Ref": "internalServiceStackTargetGroupinternalServiceStackB131C63B",
+                },
+                "Type": "forward",
+              },
+            ],
+            "LoadBalancerArn": Object {
+              "Ref": "internalServiceStackApplicationLoadBalancerinternalServiceStackA9484EF3",
+            },
+            "Port": 443,
+            "Protocol": "HTTPS",
+            "SslPolicy": "ELBSecurityPolicy-FS-1-2-Res-2020-10",
+          },
+          "Type": "AWS::ElasticLoadBalancingV2::Listener",
+        },
+        "internalServiceStackApplicationLoadBalancerinternalServiceStackRedirect80To4439382502D": Object {
+          "Properties": Object {
+            "DefaultActions": Array [
+              Object {
+                "RedirectConfig": Object {
+                  "Port": "443",
+                  "Protocol": "HTTPS",
+                  "StatusCode": "HTTP_301",
+                },
+                "Type": "redirect",
+              },
+            ],
+            "LoadBalancerArn": Object {
+              "Ref": "internalServiceStackApplicationLoadBalancerinternalServiceStackA9484EF3",
+            },
+            "Port": 80,
+            "Protocol": "HTTP",
+          },
+          "Type": "AWS::ElasticLoadBalancingV2::Listener",
+        },
+        "internalServiceStackDomaininternalservicedevtest2cominternalServiceStack5EFB1D61": Object {
+          "Properties": Object {
+            "DomainName": "internalservice-dev.test2.com",
+            "EndpointConfiguration": Object {
+              "Types": Array [
+                "REGIONAL",
+              ],
+            },
+            "RegionalCertificateArn": Object {
+              "Ref": "internalServiceStackSSLCertificateinternalServiceStack283B9A17",
+            },
+            "SecurityPolicy": "TLS_1_2",
+          },
+          "Type": "AWS::ApiGateway::DomainName",
+        },
+        "internalServiceStackDomaininternalservicedevtestcominternalServiceStackB6868874": Object {
+          "Properties": Object {
+            "DomainName": "internalservice-dev.test.com",
+            "EndpointConfiguration": Object {
+              "Types": Array [
+                "REGIONAL",
+              ],
+            },
+            "RegionalCertificateArn": Object {
+              "Ref": "internalServiceStackSSLCertificateinternalServiceStack283B9A17",
+            },
+            "SecurityPolicy": "TLS_1_2",
+          },
+          "Type": "AWS::ApiGateway::DomainName",
+        },
+        "internalServiceStackLoadBalancerSecurityGroupinternalServiceStackB6066852": Object {
+          "Properties": Object {
+            "GroupDescription": "security group for a load balancer",
+            "SecurityGroupEgress": Array [
+              Object {
+                "CidrIp": "0.0.0.0/0",
+                "Description": "Allow all outbound traffic by default",
+                "IpProtocol": "-1",
+              },
+            ],
+            "SecurityGroupIngress": Array [
+              Object {
+                "CidrIp": "0.0.0.0/0",
+                "Description": "allow HTTPS traffic from anywhere",
+                "FromPort": 443,
+                "IpProtocol": "tcp",
+                "ToPort": 443,
+              },
+              Object {
+                "CidrIp": "0.0.0.0/0",
+                "Description": "Allow from anyone on port 80",
+                "FromPort": 80,
+                "IpProtocol": "tcp",
+                "ToPort": 80,
+              },
+            ],
+            "VpcId": "vpc-12345",
+          },
+          "Type": "AWS::EC2::SecurityGroup",
+        },
+        "internalServiceStackRoute53RecordinternalServiceStack0A03BF43": Object {
+          "Properties": Object {
+            "AliasTarget": Object {
+              "DNSName": Object {
+                "Fn::Join": Array [
+                  "",
+                  Array [
+                    "dualstack.",
+                    Object {
+                      "Fn::GetAtt": Array [
+                        "internalServiceStackApplicationLoadBalancerinternalServiceStackA9484EF3",
+                        "DNSName",
+                      ],
+                    },
+                  ],
+                ],
+              },
+              "HostedZoneId": Object {
+                "Fn::GetAtt": Array [
+                  "internalServiceStackApplicationLoadBalancerinternalServiceStackA9484EF3",
+                  "CanonicalHostedZoneID",
+                ],
+              },
+            },
+            "HostedZoneId": "DUMMY",
+            "Name": "internalservice-dev.test.aws1234.com.",
+            "Type": "A",
+          },
+          "Type": "AWS::Route53::RecordSet",
+        },
+        "internalServiceStackSSLCertificateinternalServiceStack283B9A17": Object {
+          "Properties": Object {
+            "DomainName": "internalservice-dev.test.aws1234.com",
+            "SubjectAlternativeNames": Array [
+              "internalservice-dev.test.com",
+              "internalservice-dev.test2.com",
+            ],
+            "Tags": Array [
+              Object {
+                "Key": "Name",
+                "Value": "test/internalServiceStack/SSLCertificate-internalServiceStack",
+              },
+            ],
+            "ValidationMethod": "DNS",
+          },
+          "Type": "AWS::CertificateManager::Certificate",
+        },
+        "internalServiceStackTargetGroupinternalServiceStackB131C63B": Object {
+          "Properties": Object {
+            "Port": 443,
+            "Protocol": "HTTPS",
+            "TargetGroupAttributes": Array [
+              Object {
+                "Key": "stickiness.enabled",
+                "Value": "false",
+              },
+            ],
+            "TargetType": "ip",
+            "Targets": Array [
+              Object {
+                "Id": "192.168.2.1",
+              },
+              Object {
+                "Id": "192.168.2.2",
+              },
+            ],
+            "VpcId": "vpc-12345",
+          },
+          "Type": "AWS::ElasticLoadBalancingV2::TargetGroup",
+        },
+      },
+      "Rules": Object {
+        "CheckBootstrapVersion": Object {
+          "Assertions": Array [
+            Object {
+              "Assert": Object {
+                "Fn::Not": Array [
+                  Object {
+                    "Fn::Contains": Array [
+                      Array [
+                        "1",
+                        "2",
+                        "3",
+                        "4",
+                        "5",
+                      ],
+                      Object {
+                        "Ref": "BootstrapVersion",
+                      },
+                    ],
+                  },
+                ],
+              },
+              "AssertDescription": "CDK bootstrap stack version 6 required. Please run 'cdk bootstrap' with a recent version of the CDK CLI.",
+            },
+          ],
+        },
+      },
+    }
+  `);
+});
+
+test("Api Gateway Stack provider - set optional parameters", () => {
+  new InternalWebsite(stack, "internalApiGatewayStackOptionalParameters", {
+    stage: "dev",
+    domains: internalServiceStack.domains,
+    vpcEndpoint: vpcEndpointId,
+    sourcePath: "./test/website-sample",
+    websiteIndexDocument: "test.html",
+    bucketName: "test-bucket",
+  });
+
+  const template = Template.fromStack(stack);
+  template.hasResourceProperties(
+    "AWS::S3::Bucket",
+    Match.objectLike({
+      WebsiteConfiguration: {
+        IndexDocument: "test.html",
+      },
+    })
+  );
+
+  template.hasResourceProperties(
+    "AWS::S3::Bucket",
+    Match.objectLike({
+      BucketName: "test-bucket",
+    })
+  );
+
+  template.hasResourceProperties(
+    "AWS::ApiGateway::Method",
+    Match.objectLike({
+      Integration: {
+        Uri: {
+          "Fn::Join": [
+            "",
+            [
+              "arn:",
+              {
+                Ref: "AWS::Partition",
+              },
+              ":apigateway:us-east-1:s3:path/",
+              {
+                Ref: "internalApiGatewayStackOptionalParametersWebsiteBucketinternalApiGatewayStackOptionalParameters2269B1C9",
+              },
+              "/test.html",
+            ],
+          ],
+        },
+      },
+    })
+  );
+});
diff --git a/test/website-sample/index.html b/test/website-sample/index.html
new file mode 100644
index 0000000..7d8ae2e
--- /dev/null
+++ b/test/website-sample/index.html
@@ -0,0 +1,5 @@
+<html>
+  <body>
+    Hello World
+  </body>
+</html>