diff --git a/cloud/cloudformation/api.yaml b/cloud/cloudformation/api.yaml deleted file mode 100644 index 03973b9fe53ecab09681bc5b55f597f1f56e598a..0000000000000000000000000000000000000000 --- 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 743f9d89b7ebc4fc5a58832dfc640f8d62469de9..f965987cfa2fb1fd735c7ea4a93260c1da401778 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 0000000000000000000000000000000000000000..dfd49d25015209c2b6c33ef7ebacc61ea64bd7f8 --- /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 0000000000000000000000000000000000000000..b3378b7014a6f38792ad4f42ce386b2d72b4fb20 --- /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 Binary files /dev/null and b/cloud/xmind/API Gateway.png differ