Procházet zdrojové kódy

Example works with VPC

Fred Damstra (Macbook 2015) před 2 roky
revize
c9106fb2ac

+ 10 - 0
.gitignore

@@ -0,0 +1,10 @@
+*.bak
+*.swp*
+
+tmp*
+
+terraform/lambda_scripts/*.zip
+.terraform/
+
+# Stupidest thing they ever did
+.terraform.lock.hcl

+ 17 - 0
README.md

@@ -0,0 +1,17 @@
+# Game Server
+
+An API to play games. Used for dumb clients.
+
+Game Ideas:
+
+* 3D tic tac toe
+* euchre
+* ...?
+
+## Architecture
+
+* Cognito for user pool
+* API Gateway for web front end
+* Lambda for serverless
+* memcached for active games
+* dynamodb for persistent data

+ 25 - 0
login.sh

@@ -0,0 +1,25 @@
+#! /bin/bash
+
+pushd terraform
+CLIENT_ID=$( terraform output --raw cognito_client_id )
+ENDPOINT=$( terraform output --raw test_api_endpoint )
+
+RAW=$( curl --location --request POST 'https://cognito-idp.us-east-2.amazonaws.com' \
+            --header 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
+            --header 'Content-Type: application/x-amz-json-1.1' \
+            --data-raw '{
+               "AuthParameters" : {
+                  "USERNAME" : "fdamstra",
+                  "PASSWORD" : "Bluem00n"
+               },
+               "AuthFlow" : "USER_PASSWORD_AUTH",
+               "ClientId" : "'${CLIENT_ID}'"
+            }'
+     )
+ACCESS_TOKEN=$( echo "$RAW" | jq '.AuthenticationResult.AccessToken' -r )
+
+echo RUN:
+echo \ \ \ \ ACCESS_TOKEN="${ACCESS_TOKEN}"
+echo \ \ \ \ curl --request GET \'"${ENDPOINT}"/example\' --header \"Authorization: Bearer \${ACCESS_TOKEN}\"
+
+popd

+ 27 - 0
terraform/.pre-commit-config.yaml

@@ -0,0 +1,27 @@
+repos:
+- repo: https://github.com/gruntwork-io/pre-commit
+  rev: v0.1.17 # Get the latest from: https://github.com/gruntwork-io/pre-commit/releases
+  hooks:
+    - id: tflint
+      args:
+        - "--init"
+        - "--config=.tflint.hcl"
+    - id: tflint
+      args:
+        #        - "--module"
+        - "--config=.tflint.hcl"
+    - id: terraform-validate
+    - id: terraform-fmt
+- repo: https://github.com/antonbabenko/pre-commit-terraform
+  rev: v1.76.0 # Get the latest from: https://github.com/antonbabenko/pre-commit-terraform/releases
+  hooks:
+    - id: terraform_tfsec
+      args:
+        - --args=--config-file .tfsec.yaml
+    - id: terraform_docs
+# checkov is good, but too thorough for our needs
+#    - id: terraform_checkov
+#      args:
+#        - --args=--quiet
+#        - --args=--skip-check CKV_AWS_144 # we don't cross-region replicate our s3
+#- "--skip-check", "CKV_AWS_150", # We do not enable deletion protection for LBs

+ 14 - 0
terraform/.tflint.hcl

@@ -0,0 +1,14 @@
+# This should be enabled automatically, but enabling it manually breaks it.
+#plugin "aws" {
+#  enabled = true
+#  deep_check = false # deep checking makes api calls to verify select things
+#}
+
+# Custom rules go here
+# This also breaks
+#rule "aws_resource_missing_tags" {
+#  enabled = true
+#  tags = [
+#    "tf_module"
+#  ]
+#}

+ 3 - 0
terraform/.tfsec.yaml

@@ -0,0 +1,3 @@
+---
+exclude:
+  - aws-dynamodb-table-customer-key # We don't care about default keys, encryption is fine

+ 21 - 0
terraform/README.md

@@ -0,0 +1,21 @@
+# Terraform Skeleton
+
+A skeleton for fred's terraform projects.
+
+
+
+## Table of Contents
+
+1. [Usage](#usage)
+1. [Requirements](#requirements)
+1. [Providers](#Providers)
+1. [Inputs](#inputs)
+1. [Outputs](#outputs)
+
+## Usage
+
+Fork, edit this readme. Run `git init` then `pre-commit install`
+
+<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
+
+<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

+ 46 - 0
terraform/api.tf

@@ -0,0 +1,46 @@
+resource "aws_apigatewayv2_api" "gateway" {
+  name          = "game_server_api"
+  protocol_type = "HTTP"
+
+  tags = local.tags
+}
+
+resource "aws_apigatewayv2_authorizer" "auth" {
+  api_id           = aws_apigatewayv2_api.gateway.id
+  authorizer_type  = "JWT"
+  identity_sources = ["$request.header.Authorization"]
+  name             = "cognito-authorizer"
+
+  jwt_configuration {
+    audience = [aws_cognito_user_pool_client.client.id]
+    issuer   = "https://${aws_cognito_user_pool.pool.endpoint}"
+  }
+}
+
+resource "aws_apigatewayv2_stage" "test" {
+  api_id      = aws_apigatewayv2_api.gateway.id
+  name        = "test"
+  auto_deploy = true
+
+  default_route_settings {
+    #logging_level            = "INFO"
+    detailed_metrics_enabled = true
+    throttling_burst_limit   = 100
+    throttling_rate_limit    = 100
+  }
+
+  access_log_settings {
+    destination_arn = aws_cloudwatch_log_group.test.arn
+    format          = "{ \"requestId\":\"$context.requestId\", \"extendedRequestId\":\"$context.extendedRequestId\", \"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\" }"
+  }
+}
+
+resource "aws_api_gateway_account" "logging" {
+  cloudwatch_role_arn = aws_iam_role.cloudwatch.arn
+}
+
+resource "aws_cloudwatch_log_group" "test" {
+  name = "/aws/apigateway/game_server/test"
+
+  tags = local.tags
+}

+ 10 - 0
terraform/backend.tf

@@ -0,0 +1,10 @@
+terraform {
+  backend "s3" {
+    bucket = "terraform-remote-state-20221017144428493300000001"
+    # Key must be unique amongst all projects that use this backend
+    key     = "game_server"
+    region  = "us-east-2"
+    encrypt = true
+    profile = "default"
+  }
+}

+ 32 - 0
terraform/cognito.tf

@@ -0,0 +1,32 @@
+resource "aws_cognito_user_pool" "pool" {
+  name = "game_server_pool"
+
+  password_policy {
+    minimum_length                   = 8
+    require_lowercase                = false
+    require_numbers                  = false
+    require_symbols                  = false
+    require_uppercase                = false
+    temporary_password_validity_days = 14
+  }
+
+  schema {
+    name                     = "terraform"
+    attribute_data_type      = "Boolean"
+    mutable                  = false
+    required                 = false
+    developer_only_attribute = false
+  }
+
+  tags = local.tags
+}
+
+resource "aws_cognito_user_pool_client" "client" {
+  name         = "game_server_api"
+  user_pool_id = aws_cognito_user_pool.pool.id
+  explicit_auth_flows = [
+    "ALLOW_USER_PASSWORD_AUTH",
+    "ALLOW_USER_SRP_AUTH",
+    "ALLOW_REFRESH_TOKEN_AUTH"
+  ]
+}

+ 18 - 0
terraform/config.tf

@@ -0,0 +1,18 @@
+locals {
+  # I like unique id to match the terraform backend storage, and I use it for various names and prefixes..
+  unique_id = "game_server"
+
+  # Everything here should be self-explanatory
+  profile = "default"
+  region  = "us-east-2"
+  tags = {
+    "id" : local.unique_id,
+    "tf_module" : basename(path.root),
+    "project" : "game_server",
+  }
+}
+
+# Uncomment if needed
+data "aws_caller_identity" "current" {}
+#data "aws_partition" "current" {}
+data "aws_region" "current" {}

+ 19 - 0
terraform/elasticache.tf

@@ -0,0 +1,19 @@
+resource "aws_elasticache_cluster" "memcache" {
+  cluster_id = "game-server"
+  engine     = "memcached"
+
+  #node_type            = "cache.m6g.large"
+  node_type = "cache.t4g.micro"
+
+  num_cache_nodes      = 1
+  parameter_group_name = "default.memcached1.6"
+  apply_immediately    = true
+  port                 = 11211
+  tags                 = local.tags
+}
+
+resource "aws_elasticache_subnet_group" "memcache" {
+  name       = "game-server"
+  subnet_ids = module.vpc.private_subnets
+  tags       = local.tags
+}

+ 96 - 0
terraform/iam.tf

@@ -0,0 +1,96 @@
+data "aws_iam_policy_document" "lambda_policy" {
+  statement {
+    sid       = "Logs"
+    effect    = "Allow"
+    resources = ["arn:aws:logs:*:*:*"]
+
+    actions = [
+      "logs:CreateLogGroup",
+      "logs:CreateLogStream",
+      "logs:PutLogEvents",
+    ]
+  }
+}
+
+resource "aws_iam_policy" "lambda_policy" {
+  name        = "game_server_lambda"
+  path        = "/game_server/"
+  description = "AWS IAM Policy for Game Server Lambdas"
+  policy      = data.aws_iam_policy_document.lambda_policy.json
+  tags        = local.tags
+}
+
+
+data "aws_iam_policy_document" "lambda_trust" {
+  statement {
+    sid     = ""
+    effect  = "Allow"
+    actions = ["sts:AssumeRole"]
+
+    principals {
+      type        = "Service"
+      identifiers = ["lambda.amazonaws.com"]
+    }
+  }
+}
+
+
+resource "aws_iam_role" "lambda_role" {
+  name = "game_server_lambda"
+  path = "/game_server/"
+
+  assume_role_policy = data.aws_iam_policy_document.lambda_trust.json
+
+  tags = local.tags
+}
+
+resource "aws_iam_role_policy_attachment" "attach_iam_policy_to_iam_role" {
+  role       = aws_iam_role.lambda_role.name
+  policy_arn = aws_iam_policy.lambda_policy.arn
+}
+
+resource "aws_iam_role" "cloudwatch" {
+  name = "api_gateway_cloudwatch_global"
+
+  assume_role_policy = <<EOF
+{
+  "Version": "2012-10-17",
+  "Statement": [
+    {
+      "Sid": "",
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "apigateway.amazonaws.com"
+      },
+      "Action": "sts:AssumeRole"
+    }
+  ]
+}
+EOF
+}
+
+resource "aws_iam_role_policy" "cloudwatch" {
+  name = "default"
+  role = aws_iam_role.cloudwatch.id
+
+  policy = <<EOF
+{
+    "Version": "2012-10-17",
+    "Statement": [
+        {
+            "Effect": "Allow",
+            "Action": [
+                "logs:CreateLogGroup",
+                "logs:CreateLogStream",
+                "logs:DescribeLogGroups",
+                "logs:DescribeLogStreams",
+                "logs:PutLogEvents",
+                "logs:GetLogEvents",
+                "logs:FilterLogEvents"
+            ],
+            "Resource": "*"
+        }
+    ]
+}
+EOF
+}

+ 58 - 0
terraform/lambda_example.tf

@@ -0,0 +1,58 @@
+data "archive_file" "lambda_example" {
+  type        = "zip"
+  source_file = "${path.module}/lambda_scripts/example.py"
+  output_path = "${path.module}/lambda_scripts/example.zip"
+}
+
+resource "aws_lambda_function" "lambda_example" {
+  # If the file is not in the current working directory you will need to include a
+  # path.module in the filename.
+  filename      = data.archive_file.lambda_example.output_path
+  function_name = "game_server_example"
+  role          = aws_iam_role.lambda_role.arn
+  handler       = "example.lambda_handler"
+
+  # The filebase64sha256() function is available in Terraform 0.11.12 and later
+  # For Terraform 0.11.11 and earlier, use the base64sha256() function and the file() function:
+  # source_code_hash = "${base64sha256(file("lambda_function_payload.zip"))}"
+  source_code_hash = data.archive_file.lambda_example.output_base64sha256
+
+  runtime = "python3.9"
+
+  environment {
+    variables = {
+      foo = "bar"
+    }
+  }
+  tags = local.tags
+}
+
+resource "aws_lambda_permission" "api_lambda_example" {
+  statement_id  = "AllowExecutionFromAPI"
+  action        = "lambda:InvokeFunction"
+  function_name = aws_lambda_function.lambda_example.function_name
+  principal     = "apigateway.amazonaws.com"
+
+  # The /*/*/* part allows invocation from any stage, method and resource path
+  # within API Gateway REST API.
+  #source_arn = "${aws_apigatewayv2_stage.test.execution_arn}/*/*/*"
+  source_arn = "${aws_apigatewayv2_api.gateway.execution_arn}/*/*/*"
+  #qualifier     = aws_lambda_alias.test_alias.name
+}
+
+# The API Gateway Route
+resource "aws_apigatewayv2_integration" "lambda_example" {
+  api_id             = aws_apigatewayv2_api.gateway.id
+  integration_type   = "AWS_PROXY"
+  connection_type    = "INTERNET"
+  integration_method = "POST"
+  integration_uri    = "arn:aws:apigateway:${data.aws_region.current.name}:lambda:path/2015-03-31/functions/arn:aws:lambda:${data.aws_region.current.name}:${data.aws_caller_identity.current.id}:function:${aws_lambda_function.lambda_example.function_name}/invocations"
+}
+
+resource "aws_apigatewayv2_route" "lambda_example" {
+  api_id             = aws_apigatewayv2_api.gateway.id
+  route_key          = "GET /example"
+  target             = "integrations/${aws_apigatewayv2_integration.lambda_example.id}"
+  authorization_type = "JWT"
+  authorizer_id      = aws_apigatewayv2_authorizer.auth.id
+}

+ 16 - 0
terraform/lambda_scripts/example.py

@@ -0,0 +1,16 @@
+#! /usr/bin/env python3
+
+import json
+
+
+def lambda_handler(event, context):
+    return {"statusCode": 200, "isBase64Encoded": False, "body": json.dumps({"a": "b"})}
+
+
+def main():
+    print("Returned:")
+    print(json.dumps(lambda_handler(event={}, context={}), default=str, indent=2))
+
+
+if __name__ == "__main__":
+    main()

+ 18 - 0
terraform/output.tf

@@ -0,0 +1,18 @@
+locals {
+}
+
+output "cognito_user_pool" {
+  value = aws_cognito_user_pool.pool.id
+}
+
+output "cognito_client_id" {
+  value = aws_cognito_user_pool_client.client.id
+}
+
+output "api_endpoint" {
+  value = aws_apigatewayv2_api.gateway.api_endpoint
+}
+
+output "test_api_endpoint" {
+  value = aws_apigatewayv2_stage.test.invoke_url
+}

+ 13 - 0
terraform/provider.tf

@@ -0,0 +1,13 @@
+# Configure the AWS Provider
+provider "aws" {
+  region  = local.region
+  profile = local.profile
+
+  # I'm hoping this might be useful for adding a 'last_applied_by' tag
+  #ignore_tags {
+  #  # specific tag
+  #  keys = ["ChangedAt"]
+  #  # or by prefix to ignore ChangedBy too
+  #  key_prefixes = ["Changed"]
+  #}
+}

+ 14 - 0
terraform/required_providers.tf

@@ -0,0 +1,14 @@
+terraform {
+  required_version = ">= 1.0"
+  required_providers {
+    aws = {
+      source  = "hashicorp/aws"
+      version = "~> 4.0"
+    }
+
+    archive = {
+      source  = "hashicorp/archive"
+      version = "> 1.0"
+    }
+  }
+}

+ 10 - 0
terraform/users.tf

@@ -0,0 +1,10 @@
+resource "aws_cognito_user" "fdamstra" {
+  user_pool_id = aws_cognito_user_pool.pool.id
+  username     = "fdamstra"
+  password     = "Bluem00n"
+  attributes = {
+    terraform      = true
+    email          = "fred.damstra@gmail.com"
+    email_verified = true
+  }
+}

+ 59 - 0
terraform/vpc.tf

@@ -0,0 +1,59 @@
+module "vpc" {
+  source = "terraform-aws-modules/vpc/aws"
+
+  name = "game-server"
+  cidr = "10.42.28.0/22"
+
+  azs             = ["us-east-2a", "us-east-2b"]
+  private_subnets = ["10.42.28.0/24", "10.42.29.0/24"]
+  public_subnets  = ["10.42.30.0/24", "10.42.31.0/24"]
+
+  enable_nat_gateway = false
+  enable_vpn_gateway = false
+
+  enable_dns_support   = true
+  enable_dns_hostnames = true
+
+  # IPv6 might be cool
+  #enable_ipv6                                    = true
+  #assign_ipv6_address_on_creation                = true
+  #private_subnet_assign_ipv6_address_on_creation = false
+  #public_subnet_ipv6_prefixes  = [0, 1]
+  #private_subnet_ipv6_prefixes = [2, 3]
+
+  # KISS
+  #enable_dhcp_options = true
+  #dhcp_options_domain_name = "internal.games.monkeybox.org"
+
+  tags = local.tags
+}
+
+module "vpc_endpoints" {
+  source = "terraform-aws-modules/vpc/aws//modules/vpc-endpoints"
+
+  vpc_id = module.vpc.vpc_id
+  #security_group_ids = [data.aws_security_group.default.id]
+
+  endpoints = {
+    #s3 = {
+    #  service = "s3"
+    #  tags    = merge(local.tags, { Name = "s3-vpc-endpoint" })
+    #},
+    #dynamodb = {
+    #  service         = "dynamodb"
+    #  service_type    = "Gateway"
+    #  route_table_ids = flatten([module.vpc.private_route_table_ids, module.vpc.public_route_table_ids])
+    #  tags            = merge(local.tags, { Name = "dynamodb-vpc-endpoint" })
+    #},
+    #lambda = {
+    #  service             = "lambda"
+    #  private_dns_enabled = true
+    #  subnet_ids          = module.vpc.private_subnets
+    #},
+    #elasticache = {
+    ##  service = "elasticache"
+    #  private_dns_enabled = true
+    #  subnet_ids = [ module.vpc.private_subnets ]
+    #}
+  }
+}