Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Private Serverless Web app deployment
AWSTemplateFormatVersion: "2010-09-09"
Transform: "AWS::Serverless-2016-10-31"
Description: "VPC contrained Serverless React App"
Parameters:
pDeploymentEnvironment:
Type: String
Description: Name of the environment
Default: dev
pOrganizationName:
Type: String
Default: ""
pDeploymentName:
Type: String
Description: Name of the service
pArtifactsBucket:
Description: "S3 Artifacts bucket"
Type: String
Default: ""
pApiDomain:
Description: "Domain name for the client"
Type: String
Default: "ui.local"
pDomainCert:
Description: "ACM Certificate ARN for our domain name"
Type: String
Default: ""
pDeploymentRolePrefix:
Description: "prefix for default ADFS roles"
Type: String
pVPCCidrRange:
Description: "VPC Cidr range"
Type: String
pVPCId:
Description: VPC ID
Type: String
Resources:
rApolloSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub apollo-${pDeploymentName}-${pDeploymentEnvironment}-m-sg
GroupDescription: !Sub apollo-${pDeploymentName}-${pDeploymentEnvironment}-m-sg
VpcId:
Fn::ImportValue: !Ref pVPCId
SecurityGroupIngress:
- Description: Allow USers access
IpProtocol: tcp
FromPort: 443
ToPort: 443
CidrIp: '10.0.0.0/16'
- Description: Allow Users redirect to https
IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: '10.27.0.0/16'
SecurityGroupEgress:
- Description: Allow access out to the VPC
IpProtocol: 'tcp'
FromPort: 443
ToPort: 443
CidrIp:
Fn::ImportValue: !Ref pVPCCidrRange
rApolloLoadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: !Sub apollo-${pDeploymentName}-${pDeploymentEnvironment}-alb
Scheme: internal
Subnets:
- Fn::ImportValue: !Sub subnet-a
- Fn::ImportValue: !Sub subnet-b
- Fn::ImportValue: !Sub subnet-c
SecurityGroups:
- !Ref rApolloSecurityGroup
rApolloRedirectListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref rApolloLoadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- Type: "redirect"
RedirectConfig:
Protocol: "HTTPS"
Port: "443"
Host: "#{host}"
Path: "/#{path}"
Query: "#{query}"
StatusCode: "HTTP_301"
rApolloPrimaryListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref rApolloLoadBalancer
Port: 443
Protocol: HTTPS
Certificates:
- CertificateArn: !Ref pDomainCert
DefaultActions:
- Type: forward
TargetGroupArn: !Ref rApolloTargetGroup
rApolloTargetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: !Sub ${pDeploymentName}-${pDeploymentEnvironment}-tg
VpcId: !Ref pVPCId
TargetType: ip
Protocol: HTTPS
Port: 443
Targets:
- Id: !GetAtt rVpcEndpointExecuteAPIPrivateIPs.IP0
Port: 443
- Id: !GetAtt rVpcEndpointExecuteAPIPrivateIPs.IP1
Port: 443
- Id: !GetAtt rVpcEndpointExecuteAPIPrivateIPs.IP2
Port: 443
Matcher:
HttpCode: 200-403
HealthCheckIntervalSeconds: 60
HealthCheckPath: /
HealthCheckProtocol: HTTPS
HealthCheckTimeoutSeconds: 20
HealthyThresholdCount: 2
UnhealthyThresholdCount: 5
TargetGroupAttributes:
- Key: deregistration_delay.timeout_seconds
Value: '30'
rApolloRoute53RecordSet:
Type: AWS::Route53::RecordSetGroup
Properties:
HostedZoneId:
Fn::ImportValue: !Sub route53-zone-id
Comment: Route for internal access the apollo api load balancer
RecordSets:
- Name: !Sub ${pDeploymentEnvironment}.${pApiDomain}
Type: A
AliasTarget:
DNSName: !GetAtt rApolloLoadBalancer.DNSName
HostedZoneId: !GetAtt rApolloLoadBalancer.CanonicalHostedZoneID
rApolloApiDomainName:
Type: AWS::ApiGateway::DomainName
Properties:
DomainName: !Sub ${pDeploymentEnvironment}.${pApiDomain}
RegionalCertificateArn: !Ref pDomainCert
SecurityPolicy: TLS_1_2
EndpointConfiguration:
Types:
- REGIONAL
rApolloBasePathMapping:
DependsOn:
- rApolloStage
- rApolloApiDomainName
Type: AWS::ApiGateway::BasePathMapping
Properties:
DomainName: !Sub ${pDeploymentEnvironment}.${pApiDomain}
RestApiId: !Ref rApolloAPI
Stage: 'ui'
Metadata:
cfn-lint:
config:
ignore_checks:
- W3005
rApolloAPI:
Type: AWS::ApiGateway::RestApi
Properties:
Description: "Apollo Graph QL API"
EndpointConfiguration:
Types:
- PRIVATE
Name:
!Join [
"-",
[
"api",
!Ref pDeploymentName,
!Ref pDeploymentEnvironment,
"apollo",
"m",
],
]
Policy: |-
{
"Version" : "2012-10-17",
"Statement":[
{
"Effect": "Allow",
"Principal": "*",
"Action": "execute-api:Invoke",
"Resource": ["execute-api:/*"]
}
]
}
rApolloApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt rApolloAPI.RootResourceId
PathPart: "graphql"
RestApiId: !Ref rApolloAPI
rReactApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt rApolloAPI.RootResourceId
PathPart: '{proxy+}'
RestApiId: !Ref rApolloAPI
rReactSigninApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt rApolloAPI.RootResourceId
PathPart: 'signin'
RestApiId: !Ref rApolloAPI
rReactSignoutApiResource:
Type: AWS::ApiGateway::Resource
Properties:
ParentId: !GetAtt rApolloAPI.RootResourceId
PathPart: 'signout'
RestApiId: !Ref rApolloAPI
rApolloApiMethod:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: false
AuthorizationType: COGNITO_USER_POOLS
AuthorizerId: !Ref rAPIAuthoriser
HttpMethod: ANY
Integration:
ConnectionType: INTERNET
Credentials: !GetAtt rApolloGatewayIamRole.Arn
IntegrationHttpMethod: POST
PassthroughBehavior: WHEN_NO_MATCH
TimeoutInMillis: 29000
Type: AWS_PROXY
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:lambda:path/2015-03-31/functions/${rLambdaApollo.Arn}/invocations"
OperationName: "lambda"
ResourceId: !Ref rApolloApiResource
RestApiId: !Ref rApolloAPI
rReactApiMethod:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: false
AuthorizationType: NONE
HttpMethod: GET
Integration:
ConnectionType: INTERNET
Credentials: !GetAtt rApolloGatewayIamRole.Arn
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
TimeoutInMillis: 29000
Type: AWS
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:s3:path/${rHostingBucket}/{proxy}"
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: integration.response.header.Content-Type
method.response.header.Content-Disposition: integration.response.header.Content-Disposition
RequestParameters:
integration.request.path.proxy: method.request.path.proxy
integration.request.header.Content-Type: method.request.header.Content-Type
integration.request.header.Content-Disposition: method.request.header.Content-Disposition
MethodResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: true
method.response.header.Content-Disposition: true
ResourceId: !Ref rReactApiResource
RestApiId: !Ref rApolloAPI
RequestParameters:
method.request.path.proxy: true
method.request.header.Content-Type: false
method.request.header.Content-Disposition: false
Metadata:
cfn_nag:
rules_to_suppress:
- id: W59
reason: This is used to serve up the UI, prior to the user being bounced out to cognito for authN.
rBaseReactApiMethod:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: false
AuthorizationType: NONE
HttpMethod: GET
Integration:
ConnectionType: INTERNET
Credentials: !GetAtt rApolloGatewayIamRole.Arn
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
TimeoutInMillis: 29000
Type: AWS
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:s3:path/${rHostingBucket}/index.html"
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: integration.response.header.Content-Type
method.response.header.Content-Disposition: integration.response.header.Content-Disposition
RequestParameters:
integration.request.header.Content-Type: method.request.header.Content-Type
integration.request.header.Content-Disposition: method.request.header.Content-Disposition
MethodResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: true
method.response.header.Content-Disposition: true
ResourceId: !GetAtt rApolloAPI.RootResourceId
RestApiId: !Ref rApolloAPI
RequestParameters:
method.request.header.Content-Type: false
method.request.header.Content-Disposition: false
rReactSigninApiMethod:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: false
AuthorizationType: NONE
HttpMethod: GET
Integration:
ConnectionType: INTERNET
Credentials: !GetAtt rApolloGatewayIamRole.Arn
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
TimeoutInMillis: 29000
Type: AWS
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:s3:path/${rHostingBucket}/index.html"
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: integration.response.header.Content-Type
method.response.header.Content-Disposition: integration.response.header.Content-Disposition
RequestParameters:
integration.request.header.Content-Type: method.request.header.Content-Type
integration.request.header.Content-Disposition: method.request.header.Content-Disposition
MethodResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: true
method.response.header.Content-Disposition: true
ResourceId: !Ref rReactSigninApiResource
RestApiId: !Ref rApolloAPI
RequestParameters:
method.request.header.Content-Type: false
method.request.header.Content-Disposition: false
rReactSignoutApiMethod:
Type: AWS::ApiGateway::Method
Properties:
ApiKeyRequired: false
AuthorizationType: NONE
HttpMethod: GET
Integration:
ConnectionType: INTERNET
Credentials: !GetAtt rApolloGatewayIamRole.Arn
IntegrationHttpMethod: GET
PassthroughBehavior: WHEN_NO_MATCH
TimeoutInMillis: 29000
Type: AWS
Uri: !Sub "arn:aws:apigateway:${AWS::Region}:s3:path/${rHostingBucket}/index.html"
IntegrationResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: integration.response.header.Content-Type
method.response.header.Content-Disposition: integration.response.header.Content-Disposition
RequestParameters:
integration.request.header.Content-Type: method.request.header.Content-Type
integration.request.header.Content-Disposition: method.request.header.Content-Disposition
MethodResponses:
- StatusCode: "200"
ResponseParameters:
method.response.header.Content-Type: true
method.response.header.Content-Disposition: true
ResourceId: !Ref rReactSignoutApiResource
RestApiId: !Ref rApolloAPI
RequestParameters:
method.request.header.Content-Type: false
method.request.header.Content-Disposition: false
# TODO: remove this resource and disable all the debuggin metrics on the rApolloStage resource
rApolloStageCustomLogGroup:
Type: AWS::Logs::LogGroup
Properties:
LogGroupName: !Sub ${pDeploymentName}-${pDeploymentEnvironment}-api-custom-logs
RetentionInDays: 1
rApolloStage:
Type: AWS::ApiGateway::Stage
Properties:
StageName: "ui"
Description: ""
RestApiId: !Ref rApolloAPI
DeploymentId: !Ref rApolloDeployment
MethodSettings:
- DataTraceEnabled: true
HttpMethod: "*"
ResourcePath: "/*"
LoggingLevel: "INFO"
MetricsEnabled: true
AccessLogSetting:
DestinationArn: !GetAtt rApolloStageCustomLogGroup.Arn
Format: '{ "requestId":"$context.requestId", "ip": "$context.identity.sourceIp", "caller":"$context.identity.caller", "user":"$context.identity.user","requestTime":"$context.requestTime", "httpMethod":"$context.httpMethod","resourcePath":"$context.resourcePath", "status":"$context.status","protocol":"$context.protocol", "responseLength":"$context.responseLength" }'
rUsagePlan:
Type: AWS::ApiGateway::UsagePlan
Properties:
ApiStages:
- ApiId: !Ref rApolloAPI
Stage: !Ref rApolloStage
Quota:
Limit: 5000
Period: DAY
Throttle:
BurstLimit: 200
RateLimit: 100
UsagePlanName: Conservative Plan
rApolloDeployment:
Type: AWS::ApiGateway::Deployment
DependsOn:
- rApolloApiMethod
Properties:
RestApiId: !Ref rApolloAPI
rApolloUsagePlan:
Type: AWS::ApiGateway::UsagePlan
Properties:
ApiStages:
- ApiId: !Ref rApolloAPI
Stage: !Ref rApolloStage
Description: Basic usage plan to ensure API has some bounds
Throttle:
BurstLimit: 300
RateLimit: 200
UsagePlanName: Internal Usage palan
rApolloGatewayIamRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: ""
Effect: "Allow"
Principal:
Service:
- "apigateway.amazonaws.com"
Action:
- "sts:AssumeRole"
Path: "/"
Policies:
- PolicyName: LambdaAccess
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: "Allow"
Action: "lambda:InvokeFunction"
Resource:
- !GetAtt rLambdaApollo.Arn
- Effect: Allow
Action:
- "s3:Get*"
- "s3:List*"
Resource:
- !Sub "arn:aws:s3:::${rHostingBucket}"
- !Sub "arn:aws:s3:::${rHostingBucket}/*"
rAPIAuthoriser:
Type: AWS::ApiGateway::Authorizer
Properties:
AuthorizerCredentials: !GetAtt
- rRoleLambdaExecutionApollo
- Arn
AuthorizerResultTtlInSeconds: 300
Type: COGNITO_USER_POOLS
ProviderARNs:
- Fn::ImportValue: !Sub "ui-${pDeploymentEnvironment}-cognito-UserPoolArn"
IdentitySource: method.request.header.Authorization
Name:
!Join [
"-",
[
"api",
!Ref pDeploymentName,
!Ref pDeploymentEnvironment,
"auth",
"apollo",
"m",
],
]
RestApiId: !Ref rApolloAPI
rLambdaPermissionGroupsResource:
Type: AWS::Serverless::Function
Properties:
FunctionName: !Sub lda-${pDeploymentName}-${pDeploymentEnvironment}-permissions-resource-m
Description: Custom resource to initialise application permissions for the client.
Runtime: nodejs12.x
Handler: index.handler
Environment:
Variables:
ACCOUNT_ID: !Ref "AWS::AccountId"
ORG: !Ref pOrganizationName
APP: !Ref pDeploymentName
ENV: !Ref pDeploymentEnvironment
DDB_PERMISSIONS_TABLE_NAME: !Sub ddb-dynamo-${pDeploymentEnvironment}-application-permissions-m
ADFS_ADMIN_GROUP: !Sub ${pDeploymentRolePrefix}_administrator
ADFS_ACCESS_GROUP: !Sub ${pDeploymentRolePrefix}_viewer
InlineCode: |
const AWS = require('aws-sdk');
const docClient = new AWS.DynamoDB.DocumentClient();
const { DDB_PERMISSIONS_TABLE_NAME, ADFS_ADMIN_GROUP, ADFS_ACCESS_GROUP } = process.env;
exports.handler = (event, context) => {
console.log("event: ", event);
const { RequestType } = event;
if (!DDB_PERMISSIONS_TABLE_NAME || !ADFS_ADMIN_GROUP || !ADFS_ACCESS_GROUP) {
throw new Error("DDB_PERMISSIONS_TABLE_NAME, ADFS_ADMIN_GROUP, and ADFS_ACCESS_GROUP must all be provided.");
}
let pMessage;
switch (RequestType) {
case "Create": pMessage = handleCreate(); break;
case "Update": pMessage = handleUpdate(); break;
case "Delete": pMessage = handleDelete(); break;
default: throw new Error(`Unknown request type: ${RequestType}`);
}
pMessage.then(message => {
console.log(message, "Sending SUCCESS response to Cloudformation.");
sendResponse(event, context, "SUCCESS");
}).catch(error => {
console.log(error, "Sending FAILED response to Cloudformation.");
sendResponse(event, context, "FAILED");
});
}
async function handleCreate() {
await Promise.all([
docClient.put({
TableName: DDB_PERMISSIONS_TABLE_NAME,
Item: {
id: ADFS_ADMIN_GROUP,
groupName: ADFS_ADMIN_GROUP,
displayName: "Administrators",
overrideAllAccess: true,
}
}).promise(),
docClient.put({
TableName: DDB_PERMISSIONS_TABLE_NAME,
Item: {
id: ADFS_ACCESS_GROUP,
groupName: ADFS_ACCESS_GROUP,
displayName: "Viewers",
overrideAllAccess: true,
}
}).promise(),
]);
return "Successfully initialised permissions.";
}
async function handleDelete() {
const { Items: permissionRecords } = await docClient.scan({
TableName: DDB_PERMISSIONS_TABLE_NAME
}).promise();
if (!permissionRecords || permissionRecords.length < 1) {
console.log("No permissions to delete.");
return
}
await Promise.all(permissionRecords.map(
({ id }) => docClient.delete({
TableName: DDB_PERMISSIONS_TABLE_NAME,
Key: {
id
}
}).promise()
));
return `Successfully deleted ${permissionRecords.length} permissions.`;
}
async function handleUpdate() {
console.log(await handleDelete());
console.log(await handleCreate());
return "Update complete.";
}
// minified from the cfn-response source: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/cfn-lambda-function-code-cfnresponsemodule.html
function sendResponse(e,t,o,s,n,a){var r=JSON.stringify({Status:o,Reason:"See the details in CloudWatch Log Stream: "+t.logStreamName,PhysicalResourceId:n||t.logStreamName,StackId:e.StackId,RequestId:e.RequestId,LogicalResourceId:e.LogicalResourceId,NoEcho:a||!1,Data:s});console.log("Response body:\n",r);var c=require("https"),d=require("url").parse(e.ResponseURL),u={hostname:d.hostname,port:443,path:d.path,method:"PUT",headers:{"content-type":"","content-length":r.length}},l=c.request(u,function(e){console.log("Status code: "+e.statusCode),console.log("Status message: "+e.statusMessage),t.done()});l.on("error",function(e){console.log("send(..) failed executing https.request(..): "+e),t.done()}),l.write(r),l.end()}
MemorySize: 256
Timeout: 60
Role: !GetAtt rRoleLambdaPermissionGroupsResource.Arn
rRoleLambdaPermissionGroupsResource:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Path: !Sub "/${pDeploymentEnvironment}/${pDeploymentName}/"
Policies:
- PolicyName: ApolloLambdaPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:GetLogEvents
- logs:PutLogEvents
- logs:CreateLogStream
- logs:DescribeLogStreams
- logs:PutRetentionPolicy
- logs:CreateLogGroup
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- "dynamodb:Get*"
- "dynamodb:Query"
- "dynamodb:Scan"
- "dynamodb:Update*"
- "dynamodb:PutItem"
- "dynamodb:DeleteItem"
Resource:
- !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*"
rROCreatePermissions:
Type: AWS::CloudFormation::CustomResource
Properties:
ServiceToken: !GetAtt rLambdaPermissionGroupsResource.Arn
rLambdaApollo:
Type: AWS::Serverless::Function
Properties:
CodeUri:
Bucket: !Ref pArtifactsBucket
Key: !Sub deploy/${pDeploymentName}/server.zip
FunctionName:
!Join [
"-",
[
"lda",
!Ref pDeploymentName,
!Ref pDeploymentEnvironment,
"apollo",
"m",
],
]
Environment:
Variables:
ACCOUNT_ID: !Ref "AWS::AccountId"
ORG: !Ref pOrganizationName
APP: !Ref pDeploymentName
ENV: !Ref pDeploymentEnvironment
ACCESS_CONTROL_ALLOW_ORIGIN: "*"
ADFS_ADMIN_GROUP: !Sub ${pDeploymentRolePrefix}_administrator
ADFS_ACCESS_GROUP: !Sub ${pDeploymentRolePrefix}_viewer
Handler: package/dist/lambda.handler
Runtime: nodejs12.x
Description: "Apollo GraphQl Backend"
MemorySize: 512
Timeout: 300
Role: !GetAtt rRoleLambdaExecutionApollo.Arn
rRoleLambdaExecutionApollo:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: lambda.amazonaws.com
Action: "sts:AssumeRole"
Path: !Sub "/${pDeploymentEnvironment}/${pDeploymentName}/"
Policies:
- PolicyName: ApolloLambdaPolicy
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- logs:GetLogEvents
- logs:PutLogEvents
- logs:CreateLogStream
- logs:DescribeLogStreams
- logs:PutRetentionPolicy
- logs:CreateLogGroup
Resource: arn:aws:logs:*:*:*
- Effect: Allow
Action:
- "dynamodb:Get*"
- "dynamodb:Query"
- "dynamodb:Scan"
- "dynamodb:Update*"
- "dynamodb:PutItem"
Resource:
- !Sub "arn:aws:dynamodb:${AWS::Region}:${AWS::AccountId}:table/*${pDeploymentEnvironment}*"
- Effect: Allow
Action:
- "sqs:List*"
- "sqs:ReceiveMessage"
- "sqs:SendMessage*"
- "sqs:DeleteMessage*"
- "sqs:GetQueue*"
Resource:
- !Sub "arn:aws:sqs:${AWS::Region}:${AWS::AccountId}:*"
rCloudWatchRole:
Type: AWS::IAM::Role
Properties:
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service:
- apigateway.amazonaws.com
Action: 'sts:AssumeRole'
Path: /
ManagedPolicyArns:
- >-
arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs
rAccount:
Type: AWS::ApiGateway::Account
Properties:
CloudWatchRoleArn: !GetAtt
- rCloudWatchRole
- Arn
rHostingBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: !Sub ${pDeploymentName}-${pDeploymentEnvironment}-ui-hosting-${AWS::AccountId}
BucketEncryption:
ServerSideEncryptionConfiguration:
- ServerSideEncryptionByDefault:
SSEAlgorithm: AES256
PublicAccessBlockConfiguration:
BlockPublicAcls: True
BlockPublicPolicy: True
IgnorePublicAcls: True
RestrictPublicBuckets: True
WebsiteConfiguration:
IndexDocument: index.html
ErrorDocument: error.html
rVpcEndpointExecuteAPI:
Type: AWS::EC2::VPCEndpoint
Properties:
ServiceName: !Sub com.amazonaws.${AWS::Region}.execute-api
PrivateDnsEnabled: false
VpcEndpointType: Interface
VpcId:
Fn::ImportValue: !Ref pVPCId
SubnetIds:
- Fn::ImportValue: !Sub subnet-a
- Fn::ImportValue: !Sub subnet-b
- Fn::ImportValue: !Sub subnet-c
SecurityGroupIds:
- !Ref rVpcEndpointSecurityGroup
rVpcEndpointExecuteAPIPrivateIPs:
Type: Custom::rVpcEndpointExecuteAPIPrivateIPs
Properties:
ServiceToken: !GetAtt rVpcEndpointLambdaFunction.Arn
NetworkInterfaceIds: !GetAtt rVpcEndpointExecuteAPI.NetworkInterfaceIds
rVpcEndpointLambdaRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub lambda-${pDeploymentEnvironment}-vpc-endpoints-eni2ip-m-role
AssumeRolePolicyDocument:
Version: '2012-10-17'
Statement:
- Effect: Allow
Principal:
Service:
- lambda.amazonaws.com
Action:
- sts:AssumeRole
Path: '/'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/AWSCloudFormationFullAccess
- arn:aws:iam::aws:policy/AmazonEC2FullAccess
- arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole
- arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole
rVpcEndpointLambdaFunction:
Type: AWS::Lambda::Function
DependsOn: rVpcEndpointLambdaRole
DeletionPolicy: Delete
Properties:
FunctionName: !Sub lambda-${pDeploymentEnvironment}-vpc-endpoints-eni2ip-m
Description: Lambda which returns ips based on enis
Code:
ZipFile: |
import cfnresponse
import json
import boto3
def lambda_handler(event, context):
print('REQUEST RECEIVED:\n' + json.dumps(event))
responseData = {}
if event['RequestType'] == 'Delete':
cfnresponse.send(event, context, cfnresponse.SUCCESS, {})
return
if event['RequestType'] == 'Create':
try:
ec2 = boto3.resource('ec2')
enis = event['ResourceProperties']['NetworkInterfaceIds']
for index, eni in enumerate(enis):
network_interface = ec2.NetworkInterface(eni)
responseData['IP' + str(index)] = network_interface.private_ip_address
print(responseData)
except Exception as e:
responseData = {'error': str(e)}
cfnresponse.send(event, context, cfnresponse.FAILED, responseData)
return
cfnresponse.send(event, context, cfnresponse.SUCCESS, responseData)
Handler: index.lambda_handler
Role: !GetAtt rVpcEndpointLambdaRole.Arn
Runtime: python3.7
Timeout: 10
rVpcEndpointSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupName: !Sub vpc-endpoints-sg
GroupDescription: Allow incoming connections from the private subnets of the VPC
SecurityGroupIngress:
- IpProtocol: '-1'
FromPort: -1
ToPort: -1
CidrIp: !Ref pVPCCidrRange
SecurityGroupEgress:
- IpProtocol: '-1'
FromPort: -1
ToPort: -1
CidrIp: 0.0.0.0/0
VpcId: !Ref pVPCId
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment