From 46d3e245274e6f9475d726c8345f6427a853fb98 Mon Sep 17 00:00:00 2001
From: bertrand <bpinel@ippon.fr>
Date: Thu, 31 Jan 2019 22:50:22 +0100
Subject: [PATCH] Add S3 bucket and CloudFront terraform

---
 cloud/cloudformation/api.yaml | 388 ----------------------------------
 cloud/terraform/README.md     |   6 +-
 cloud/terraform/bucket.tf     |  27 +++
 cloud/terraform/cloudfront.tf | 123 +++++++++++
 cloud/xmind/API Gateway.png   | Bin 0 -> 14817 bytes
 5 files changed, 153 insertions(+), 391 deletions(-)
 delete mode 100644 cloud/cloudformation/api.yaml
 create mode 100644 cloud/terraform/bucket.tf
 create mode 100644 cloud/terraform/cloudfront.tf
 create mode 100644 cloud/xmind/API Gateway.png

diff --git a/cloud/cloudformation/api.yaml b/cloud/cloudformation/api.yaml
deleted file mode 100644
index 03973b9..0000000
--- a/cloud/cloudformation/api.yaml
+++ /dev/null
@@ -1,388 +0,0 @@
-AWSTemplateFormatVersion: '2010-09-09'
-Transform: AWS::Serverless-2016-10-31
-Description: Ember EHipster API
-Parameters:
-  LambdaFunctionName:
-    Type: String
-    Default: "EmberEHipsterFunction"
-    Description: "Name of the lambda function"
-  AppClientName:
-    Type: String
-    Default: "EmberEHipsterWebClient"
-    Description: "Cognito user pools app client name"
-Resources:
-  ApiGatewayApi:
-    Type: AWS::Serverless::Api
-    DependsOn: CognitoUserPool
-    Properties:
-      StageName: Prod
-      DefinitionBody:
-        swagger: "2.0"
-        info:
-          version: "2017-02-24T04:09:00Z"
-          title: "EmberEHipsterAPI"
-        basePath: "/Prod"
-        schemes:
-        - "https"
-        paths:
-          "/{type}":
-            get:
-              consumes:
-              - "application/json"
-              produces:
-              - "application/json"
-              parameters:
-              - name: "user"
-                in: "header"
-                required: false
-                type: "string"
-              - name: "id"
-                in: "query"
-                required: false
-                type: "string"
-              responses:
-                "200":
-                  description: "200 response"
-                  schema:
-                    $ref: "#/definitions/Empty"
-                  headers:
-                    Access-Control-Allow-Origin:
-                      type: "string"
-              security:
-              - EmberEHipster: []
-              x-amazon-apigateway-integration:
-                responses:
-                  default:
-                    statusCode: "200"
-                    responseParameters:
-                      method.response.header.Access-Control-Allow-Origin: "'*'"
-                requestTemplates:
-                  application/json: "##  See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\
-                    ##  This template will pass through all parameters including path, querystring,\
-                    \ header, stage variables, and context through to the integration endpoint\
-                    \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body-json\"\
-                    \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\
-                    \    #set($params = $allParams.get($type))\n\"$type\" : {\n    #foreach($paramName\
-                    \ in $params.keySet())\n    \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\
-                    \n        #if($foreach.hasNext),#end\n    #end\n}\n    #if($foreach.hasNext),#end\n\
-                    #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\
-                    \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n   \
-                    \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n    \"user\"\
-                    \ : \"$context.authorizer.claims.sub\",\n\t\"email\" : \"$context.authorizer.claims.email\"\
-                    ,\n    \"account-id\" : \"$context.identity.accountId\",\n    \"api-id\"\
-                    \ : \"$context.apiId\",\n    \"api-key\" : \"$context.identity.apiKey\"\
-                    ,\n    \"authorizer-principal-id\" : \"$context.authorizer.principalId\"\
-                    ,\n    \"caller\" : \"$context.identity.caller\",\n    \"cognito-authentication-provider\"\
-                    \ : \"$context.identity.cognitoAuthenticationProvider\",\n    \"cognito-authentication-type\"\
-                    \ : \"$context.identity.cognitoAuthenticationType\",\n    \"cognito-identity-id\"\
-                    \ : \"$context.identity.cognitoIdentityId\",\n    \"cognito-identity-pool-id\"\
-                    \ : \"$context.identity.cognitoIdentityPoolId\",\n    \"httpMethod\" :\
-                    \ \"$context.httpMethod\",\n    \"stage\" : \"$context.stage\",\n    \"\
-                    source-ip\" : \"$context.identity.sourceIp\",\n    \"user\" : \"$context.identity.user\"\
-                    ,\n    \"user-agent\" : \"$context.identity.userAgent\",\n    \"user-arn\"\
-                    \ : \"$context.identity.userArn\",\n    \"request-id\" : \"$context.requestId\"\
-                    ,\n    \"resource-id\" : \"$context.resourceId\",\n    \"resource-path\"\
-                    \ : \"$context.resourcePath\"\n    }\n}\n"
-                uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region",  ":lambda:path/2015-03-31/functions/arn:aws:lambda:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":function:", !Ref "LambdaFunctionName", "/invocations" ] ]
-                passthroughBehavior: "when_no_templates"
-                httpMethod: "POST"
-                contentHandling: "CONVERT_TO_TEXT"
-                type: "aws"
-            post:
-              consumes:
-              - "application/json"
-              produces:
-              - "application/json"
-              parameters:
-              - name: "user"
-                in: "header"
-                required: false
-                type: "string"
-              responses:
-                "200":
-                  description: "200 response"
-                  schema:
-                    $ref: "#/definitions/Empty"
-                  headers:
-                    Access-Control-Allow-Origin:
-                      type: "string"
-              security:
-              - EmberEHipster: []
-              x-amazon-apigateway-integration:
-                responses:
-                  default:
-                    statusCode: "200"
-                    responseParameters:
-                      method.response.header.Access-Control-Allow-Origin: "'*'"
-                requestTemplates:
-                  application/json: "##  See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\
-                    ##  This template will pass through all parameters including path, querystring,\
-                    \ header, stage variables, and context through to the integration endpoint\
-                    \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body-json\"\
-                    \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\
-                    \    #set($params = $allParams.get($type))\n\"$type\" : {\n    #foreach($paramName\
-                    \ in $params.keySet())\n    \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\
-                    \n        #if($foreach.hasNext),#end\n    #end\n}\n    #if($foreach.hasNext),#end\n\
-                    #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\
-                    \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n   \
-                    \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n    \"user\"\
-                    \ : \"$context.authorizer.claims.sub\",\n\t\"email\" : \"$context.authorizer.claims.email\"\
-                    ,\n    \"account-id\" : \"$context.identity.accountId\",\n    \"api-id\"\
-                    \ : \"$context.apiId\",\n    \"api-key\" : \"$context.identity.apiKey\"\
-                    ,\n    \"authorizer-principal-id\" : \"$context.authorizer.principalId\"\
-                    ,\n    \"caller\" : \"$context.identity.caller\",\n    \"cognito-authentication-provider\"\
-                    \ : \"$context.identity.cognitoAuthenticationProvider\",\n    \"cognito-authentication-type\"\
-                    \ : \"$context.identity.cognitoAuthenticationType\",\n    \"cognito-identity-id\"\
-                    \ : \"$context.identity.cognitoIdentityId\",\n    \"cognito-identity-pool-id\"\
-                    \ : \"$context.identity.cognitoIdentityPoolId\",\n    \"httpMethod\" :\
-                    \ \"$context.httpMethod\",\n    \"stage\" : \"$context.stage\",\n    \"\
-                    source-ip\" : \"$context.identity.sourceIp\",\n    \"user\" : \"$context.identity.user\"\
-                    ,\n    \"user-agent\" : \"$context.identity.userAgent\",\n    \"user-arn\"\
-                    \ : \"$context.identity.userArn\",\n    \"request-id\" : \"$context.requestId\"\
-                    ,\n    \"resource-id\" : \"$context.resourceId\",\n    \"resource-path\"\
-                    \ : \"$context.resourcePath\"\n    }\n}\n"
-                uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region",  ":lambda:path/2015-03-31/functions/arn:aws:lambda:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":function:", !Ref "LambdaFunctionName", "/invocations" ] ]
-                passthroughBehavior: "when_no_templates"
-                httpMethod: "POST"
-                contentHandling: "CONVERT_TO_TEXT"
-                type: "aws"
-            delete:
-              consumes:
-              - "application/json"
-              produces:
-              - "application/json"
-              parameters:
-              - name: "user"
-                in: "header"
-                required: false
-                type: "string"
-              responses:
-                "200":
-                  description: "200 response"
-                  schema:
-                    $ref: "#/definitions/Empty"
-                  headers:
-                    Access-Control-Allow-Origin:
-                      type: "string"
-              security:
-              - EmberEHipster: []
-              x-amazon-apigateway-integration:
-                responses:
-                  default:
-                    statusCode: "200"
-                    responseParameters:
-                      method.response.header.Access-Control-Allow-Origin: "'*'"
-                requestTemplates:
-                  application/json: "##  See http://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-mapping-template-reference.html\n\
-                    ##  This template will pass through all parameters including path, querystring,\
-                    \ header, stage variables, and context through to the integration endpoint\
-                    \ via the body/payload\n#set($allParams = $input.params())\n{\n\"body-json\"\
-                    \ : $input.json('$'),\n\"params\" : {\n#foreach($type in $allParams.keySet())\n\
-                    \    #set($params = $allParams.get($type))\n\"$type\" : {\n    #foreach($paramName\
-                    \ in $params.keySet())\n    \"$paramName\" : \"$util.escapeJavaScript($params.get($paramName))\"\
-                    \n        #if($foreach.hasNext),#end\n    #end\n}\n    #if($foreach.hasNext),#end\n\
-                    #end\n},\n\"stage-variables\" : {\n#foreach($key in $stageVariables.keySet())\n\
-                    \"$key\" : \"$util.escapeJavaScript($stageVariables.get($key))\"\n   \
-                    \ #if($foreach.hasNext),#end\n#end\n},\n\"context\" : {\n    \"user\"\
-                    \ : \"$context.authorizer.claims.sub\",\n\t\"email\" : \"$context.authorizer.claims.email\"\
-                    ,\n    \"account-id\" : \"$context.identity.accountId\",\n    \"api-id\"\
-                    \ : \"$context.apiId\",\n    \"api-key\" : \"$context.identity.apiKey\"\
-                    ,\n    \"authorizer-principal-id\" : \"$context.authorizer.principalId\"\
-                    ,\n    \"caller\" : \"$context.identity.caller\",\n    \"cognito-authentication-provider\"\
-                    \ : \"$context.identity.cognitoAuthenticationProvider\",\n    \"cognito-authentication-type\"\
-                    \ : \"$context.identity.cognitoAuthenticationType\",\n    \"cognito-identity-id\"\
-                    \ : \"$context.identity.cognitoIdentityId\",\n    \"cognito-identity-pool-id\"\
-                    \ : \"$context.identity.cognitoIdentityPoolId\",\n    \"httpMethod\" :\
-                    \ \"$context.httpMethod\",\n    \"stage\" : \"$context.stage\",\n    \"\
-                    source-ip\" : \"$context.identity.sourceIp\",\n    \"user\" : \"$context.identity.user\"\
-                    ,\n    \"user-agent\" : \"$context.identity.userAgent\",\n    \"user-arn\"\
-                    \ : \"$context.identity.userArn\",\n    \"request-id\" : \"$context.requestId\"\
-                    ,\n    \"resource-id\" : \"$context.resourceId\",\n    \"resource-path\"\
-                    \ : \"$context.resourcePath\"\n    }\n}\n"
-                uri: !Join [ "", [ "arn:aws:apigateway:", !Ref "AWS::Region", ":lambda:path/2015-03-31/functions/arn:aws:lambda:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":function:", !Ref "LambdaFunctionName", "/invocations" ] ] 
-                passthroughBehavior: "when_no_templates"
-                httpMethod: "POST"
-                contentHandling: "CONVERT_TO_TEXT"
-                type: "aws"
-            options:
-              consumes:
-              - "application/json"
-              produces:
-              - "application/json"
-              responses:
-                "200":
-                  description: "200 response"
-                  schema:
-                    $ref: "#/definitions/Empty"
-                  headers:
-                    Access-Control-Allow-Origin:
-                      type: "string"
-                    Access-Control-Allow-Methods:
-                      type: "string"
-                    Access-Control-Allow-Headers:
-                      type: "string"
-              x-amazon-apigateway-integration:
-                responses:
-                  default:
-                    statusCode: "200"
-                    responseParameters:
-                      method.response.header.Access-Control-Allow-Methods: "'GET,OPTIONS,POST,PUT,DELETE'"
-                      method.response.header.Access-Control-Allow-Headers: "'Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token,user'"
-                      method.response.header.Access-Control-Allow-Origin: "'*'"
-                requestTemplates:
-                  application/json: "{\"statusCode\": 200}"
-                passthroughBehavior: "when_no_match"
-                type: "mock"
-        securityDefinitions:
-          EmberEHipster:
-            type: "apiKey"
-            name: "user"
-            in: "header"
-            x-amazon-apigateway-authtype: "cognito_user_pools"
-            x-amazon-apigateway-authorizer:
-              providerARNs:
-              - !GetAtt CognitoUserPool.Arn
-              type: "cognito_user_pools"
-        definitions:
-          Empty:
-            type: "object"
-            title: "Empty Schema"
-  LambdaFunction:
-    Type: AWS::Serverless::Function
-    Properties:
-      FunctionName: !Ref LambdaFunctionName
-      Handler: index.handler
-      Runtime: nodejs4.3
-      Policies: AmazonDynamoDBFullAccess
-      Environment:
-        Variables:
-          TABLE_NAME: !Ref Table
-      Events:
-        GetDoc:
-          Type: Api
-          Properties:
-            RestApiId: !Ref ApiGatewayApi
-            Path: /{type}/{id}
-            Method: GET
-        GetDocs:
-          Type: Api
-          Properties:
-            RestApiId: !Ref ApiGatewayApi
-            Path: /{type}
-            Method: GET
-        NewDoc:
-          Type: Api
-          Properties:
-            RestApiId: !Ref ApiGatewayApi
-            Path: /{type}
-            Method: POST
-        DeleteDoc:
-          Type: Api
-          Properties:
-            RestApiId: !Ref ApiGatewayApi
-            Path: /{type}/{id}
-            Method: DELETE
-  CognitoUserPool:
-    Type: AWS::Cognito::UserPool
-    Properties:
-      UserPoolName: EmberEHipster
-      AutoVerifiedAttributes: 
-        - "email"
-  CognitoUserPoolClient:
-    Type: AWS::Cognito::UserPoolClient
-    DependsOn: CognitoUserPool
-    Properties:
-      ClientName: !Ref AppClientName
-      UserPoolId: !Ref CognitoUserPool
-      GenerateSecret: false
-  CognitoIdentityPool:
-    Type: AWS::Cognito::IdentityPool
-    Properties:
-      AllowUnauthenticatedIdentities: true
-      CognitoIdentityProviders:
-        - ClientId: !Ref CognitoUserPoolClient
-          ProviderName: !GetAtt CognitoUserPool.ProviderName
-  CognitoIdentityPoolRoles:
-    Type: AWS::Cognito::IdentityPoolRoleAttachment
-    DependsOn: CognitoIdentityPool
-    Properties:
-      IdentityPoolId: !Ref CognitoIdentityPool
-      Roles:
-        authenticated: !GetAtt AuthenticatedRole.Arn
-        unauthenticated: !GetAtt UnauthenticatedRole.Arn
-  UnauthenticatedRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Version: '2012-10-17'
-        Statement:
-        - Effect: Allow
-          Principal:
-            Federated: cognito-identity.amazonaws.com
-          Action: sts:AssumeRoleWithWebIdentity
-          Condition:
-            StringEquals:
-              cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool
-            ForAnyValue:StringLike:
-              cognito-identity.amazonaws.com:amr: unauthenticated
-      Policies:
-        -
-          PolicyName: EmberEHipsterUnauthenticatedApi
-          PolicyDocument:
-            Version: '2012-10-17'
-            Statement:
-            - Effect: Allow
-              Action:
-              - mobileanalytics:PutEvents
-              - cognito-sync:*
-              Resource:
-              - "*"
-  AuthenticatedRole:
-    Type: AWS::IAM::Role
-    Properties:
-      AssumeRolePolicyDocument:
-        Version: '2012-10-17'
-        Statement:
-        - Effect: Allow
-          Principal:
-            Federated: cognito-identity.amazonaws.com
-          Action: sts:AssumeRoleWithWebIdentity
-          Condition:
-            StringEquals:
-              cognito-identity.amazonaws.com:aud: !Ref CognitoIdentityPool
-            ForAnyValue:StringLike:
-              cognito-identity.amazonaws.com:amr: authenticated
-      Policies:
-        -
-          PolicyName: EmberEHipsterAuthenticatedApi
-          PolicyDocument:
-            Version: '2012-10-17'
-            Statement:
-            - Effect: Allow
-              Action:
-              - execute-api:Invoke
-              Resource: !Join [ "", [ "arn:aws:execute-api:", !Ref "AWS::Region", ":", !Ref "AWS::AccountId", ":", !Ref ApiGatewayApi, "/*" ] ]
-  Table:
-    Type: AWS::Serverless::SimpleTable
-Outputs:
-  CognitoIdentityPoolId:
-    Description: Cognito Identity Pool ID
-    Value: !Ref CognitoIdentityPool
-  CognitoUserPoolsId:
-    Description: Cognito User Pools ID
-    Value: !Ref CognitoUserPool
-  CognitoUserPoolsClientId:
-    Description: Cognito User Pools App Client ID
-    Value: !Ref CognitoUserPoolClient
-  Api:
-    Description: API Gateway ID
-    Value: !Ref ApiGatewayApi
-  ApiUrl:
-    Description: URL of your API endpoint
-    Value: !Join
-      - ''
-      - - https://
-        - !Ref ApiGatewayApi
-        - '.execute-api.'
-        - !Ref 'AWS::Region'
-        - '.amazonaws.com/Prod'
diff --git a/cloud/terraform/README.md b/cloud/terraform/README.md
index 743f9d8..f965987 100644
--- a/cloud/terraform/README.md
+++ b/cloud/terraform/README.md
@@ -4,7 +4,7 @@ Installing AWS infrastructure through Terraform scripts
 This file explains how to set up the expected infrastructure awaited to implement the JSON API.
 The installation is based on Terraform (https://www.terraform.io/)
 
-It expects that by AWS CLI and Terraform have previously been installed and set up correctly. 
+It expects that both AWS CLI and Terraform have been previously installed and set up correctly. 
 The AWS Credentials should also have been set up 
 
 How it works
@@ -14,7 +14,7 @@ AWS JSON API server relies on 3 main components, plus the needed IAM roles :
 - A lambda function in charge of the translation from JSON API format to dynamoDB objects and managing the relationships and the optional parameters provided in the request
 - An API Gateway configured to received the REST HTTP requests and proxying the Lambda
 
-Three Terraform files are available, each one created the needed component and their relationships with each other.
+Several Terraform files are available, each one creating the needed component and their relationships with each other.
 
 Installation
 ------------------------------------------------------------------------------
@@ -31,6 +31,6 @@ aws s3 cp lambda-jsonapi.zip s3://lambda-jsonapi-code-bucket/v1.0.0/lambda-jsona
 ```
 One this is done, simply run the terraform script :
 ```
-terraform apply
+terraform apply -var bucket_name=<bucket name for static web site>
 ```
 For a strange reason I don't understand, the first time you run the terraform command, the mapping for the integration response fails. You have to run the command again to have your complete setup.
\ No newline at end of file
diff --git a/cloud/terraform/bucket.tf b/cloud/terraform/bucket.tf
new file mode 100644
index 0000000..dfd49d2
--- /dev/null
+++ b/cloud/terraform/bucket.tf
@@ -0,0 +1,27 @@
+variable "bucket_name_production" {
+  type    = "string"
+  default = "ember-aws-ehipster-production"
+}
+variable "bucket_name_staging" {
+  type    = "string"
+  default = "ember-aws-ehipster-staging"
+}
+
+resource "aws_s3_bucket" "production" {
+  bucket = "${var.bucket_name_production}"
+  acl    = "public-read"
+
+  website {
+    index_document = "index.html"
+    error_document = "index.html"
+  }
+}
+resource "aws_s3_bucket" "staging" {
+  bucket = "${var.bucket_name_staging}"
+  acl    = "public-read"
+
+  website {
+    index_document = "index.html"
+    error_document = "index.html"
+  }
+}
diff --git a/cloud/terraform/cloudfront.tf b/cloud/terraform/cloudfront.tf
new file mode 100644
index 0000000..b3378b7
--- /dev/null
+++ b/cloud/terraform/cloudfront.tf
@@ -0,0 +1,123 @@
+
+locals {
+  s3_origin_id = "S3Origin"
+}
+
+data "aws_s3_bucket" "production" {
+  bucket = "${var.bucket_name_production}"
+}
+
+data "aws_s3_bucket" "staging" {
+  bucket = "${var.bucket_name_staging}"
+}
+resource "aws_cloudfront_distribution" "s3_distribution_production" {
+  origin {
+    domain_name = "${element(split("/","${data.aws_s3_bucket.production.website_endpoint}"),2)}"
+    origin_id   = "${local.s3_origin_id}"
+
+    custom_origin_config {
+      http_port = 80
+      https_port = 443
+      origin_protocol_policy = "http-only"
+      origin_ssl_protocols = ["SSLv3", "TLSv1.1", "TLSv1.2"]
+    }
+  }
+
+  enabled             = true
+  http_version        = "http2"
+  is_ipv6_enabled     = true
+  comment             = "Production ehipster ClondFront"
+  default_root_object = "index.html"
+
+  default_cache_behavior {
+    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
+    compress = true
+    cached_methods   = ["GET", "HEAD"]
+    target_origin_id = "${local.s3_origin_id}"
+
+    forwarded_values {
+      query_string = true
+
+      cookies {
+        forward = "none"
+      }
+    }
+
+    viewer_protocol_policy = "redirect-to-https"
+    min_ttl                = 0
+    default_ttl            = 3600
+    max_ttl                = 86400
+  }
+
+  price_class = "PriceClass_All"
+
+  restrictions {
+    geo_restriction {
+      restriction_type = "none"
+    }
+  }
+
+  tags = {
+    Environment = "production"
+  }
+
+  viewer_certificate {
+    cloudfront_default_certificate = true
+  }
+}
+
+resource "aws_cloudfront_distribution" "s3_distribution_staging" {
+  origin {
+    domain_name = "${element(split("/","${data.aws_s3_bucket.staging.website_endpoint}"),2)}"
+    origin_id   = "${local.s3_origin_id}"
+
+    custom_origin_config {
+      http_port = 80
+      https_port = 443
+      origin_protocol_policy = "http-only"
+      origin_ssl_protocols = ["SSLv3", "TLSv1.1", "TLSv1.2"]
+    }
+  }
+
+  enabled             = true
+  http_version        = "http2"
+  is_ipv6_enabled     = true
+  comment             = "Staging ehipster ClondFront"
+  default_root_object = "index.html"
+
+  default_cache_behavior {
+    allowed_methods  = ["DELETE", "GET", "HEAD", "OPTIONS", "PATCH", "POST", "PUT"]
+    compress = true
+    cached_methods   = ["GET", "HEAD"]
+    target_origin_id = "${local.s3_origin_id}"
+
+    forwarded_values {
+      query_string = true
+
+      cookies {
+        forward = "none"
+      }
+    }
+
+    viewer_protocol_policy = "redirect-to-https"
+    min_ttl                = 0
+    default_ttl            = 3600
+    max_ttl                = 86400
+  }
+
+  price_class = "PriceClass_All"
+
+  restrictions {
+    geo_restriction {
+      restriction_type = "none"
+    }
+  }
+
+  tags = {
+    Environment = "staging"
+  }
+
+  viewer_certificate {
+    cloudfront_default_certificate = true
+  }
+}
\ No newline at end of file
diff --git a/cloud/xmind/API Gateway.png b/cloud/xmind/API Gateway.png
new file mode 100644
index 0000000000000000000000000000000000000000..a836be8091d7192d5741fb585df8767ae68e4c61
GIT binary patch
literal 14817
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
DmZRo=

literal 0
HcmV?d00001

-- 
GitLab