diff --git a/LICENSE b/LICENSE index d76a8eb..c7679b5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2020 Ɓukasz Tomaszkiewicz +Copyright (c) 2020 Pragmatic Coders Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/alerts/common/main.tf b/alerts/common/main.tf index b5af784..223762c 100644 --- a/alerts/common/main.tf +++ b/alerts/common/main.tf @@ -1,7 +1,7 @@ variable "sns_topic_name" { default = "alerts" } resource "aws_sns_topic" "alerts" { - name = "alerts" + name = var.sns_topic_name } output "sns_topic_arn" { diff --git a/alerts/opsgenie/main.tf b/alerts/opsgenie/main.tf index 988d087..f7f0196 100644 --- a/alerts/opsgenie/main.tf +++ b/alerts/opsgenie/main.tf @@ -4,5 +4,5 @@ resource "aws_sns_topic_subscription" "opsgenie_alarm_notification_subscription" topic_arn = var.sns_topic_arn protocol = "https" endpoint_auto_confirms = true - endpoint = "https://api.%{if var.eu_region}eu.%{endif}opsgenie.com/v1/json/cloudwatch?apiKey=${var.api_key}" + endpoint = "https://api.%{if var.eu_region}eu.%{endif}opsgenie.com/v1/json/cloudwatch%{if var.cloudwatch_event}events%{endif}?apiKey=${var.api_key}" } diff --git a/alerts/opsgenie/variables.tf b/alerts/opsgenie/variables.tf index dc96046..d52722b 100644 --- a/alerts/opsgenie/variables.tf +++ b/alerts/opsgenie/variables.tf @@ -1,3 +1,4 @@ variable "api_key" {} variable "eu_region" {default = false} variable "sns_topic_arn" {} +variable "cloudwatch_event" { default = false } \ No newline at end of file diff --git a/alerts/slack/download.tf b/alerts/slack/download.tf index 6705fd1..2a7b336 100644 --- a/alerts/slack/download.tf +++ b/alerts/slack/download.tf @@ -2,6 +2,6 @@ data "external" "download" { program = [ "/bin/sh", "-c", - "if [[ ! -f /tmp/terraform-artifacts/lambda-slack-alarm-notification.zip ]]; then wget -O /tmp/terraform-artifacts/lambda-slack-alarm-notification.zip https://github.com/tomaszkiewicz/slack-notification-lambda/releases/download/initial/lambda.zip > /dev/null; fi; echo '{}'", + "if [[ ! -f /tmp/terraform-artifacts/lambda-slack-alarm-notification.zip ]]; then wget -O /tmp/terraform-artifacts/lambda-slack-alarm-notification.zip https://github.com/pragmaticcoders/slack-notification-lambda/releases/download/initial/lambda.zip > /dev/null; fi; echo '{}'", ] } diff --git a/api-gateway-v2/alerts/api_alerts.tf b/api-gateway-v2/alerts/api_alerts.tf new file mode 100644 index 0000000..78b9777 --- /dev/null +++ b/api-gateway-v2/alerts/api_alerts.tf @@ -0,0 +1,47 @@ +variable "notifications_sns_topic_arn" { default = "" } +variable "api_name" { default = "" } +variable "api_id" { default = "" } +variable "threshold" {default = 10} +variable "period" {default = 60} +variable "evaluation_periods" {default = 1} +variable "alarm_name" {default = ""} + +resource "aws_cloudwatch_metric_alarm" "api-4xx" { + alarm_name = "api-gateway-4xx-response.${var.alarm_name}" + alarm_description = "This alarm monitors api 4xx response" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.evaluation_periods + metric_name = var.api_id == "" ? "4XXError" : "4xx" + namespace = "AWS/ApiGateway" + period = var.period + statistic = "Sum" + threshold = var.threshold + treat_missing_data = "ignore" + alarm_actions = [var.notifications_sns_topic_arn] + ok_actions = [var.notifications_sns_topic_arn] + + dimensions = merge( + var.api_id != "" ? { ApiId = var.api_id } : {}, + var.api_name != "" ? { ApiName = var.api_name } : {} + ) +} + +resource "aws_cloudwatch_metric_alarm" "api-5xx" { + alarm_name = "api-gateway-5xx-response.${var.alarm_name}" + alarm_description = "This alarm monitors api 5xx response" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.evaluation_periods + metric_name = var.api_id == "" ? "5XXError" : "5xx" + namespace = "AWS/ApiGateway" + period = var.period + statistic = "Sum" + threshold = var.threshold + treat_missing_data = "ignore" + alarm_actions = [var.notifications_sns_topic_arn] + ok_actions = [var.notifications_sns_topic_arn] + + dimensions = merge( + var.api_id != "" ? { ApiId = var.api_id } : {}, + var.api_name != "" ? { ApiName = var.api_name } : {} + ) +} \ No newline at end of file diff --git a/api-gateway-v2/any-route/main.tf b/api-gateway-v2/any-route/main.tf new file mode 100644 index 0000000..96d4a49 --- /dev/null +++ b/api-gateway-v2/any-route/main.tf @@ -0,0 +1,5 @@ +resource "aws_apigatewayv2_route" "public" { + api_id = var.api_id + route_key = "${var.method} ${var.path}" + target = "integrations/${var.integration_id}" +} diff --git a/api-gateway-v2/any-route/variables.tf b/api-gateway-v2/any-route/variables.tf new file mode 100644 index 0000000..d4498fe --- /dev/null +++ b/api-gateway-v2/any-route/variables.tf @@ -0,0 +1,4 @@ +variable "path" {} +variable "method" { default = "ANY" } +variable "api_id" {} +variable "integration_id" {} diff --git a/api-gateway-v2/ecs-service-alb/domain.tf b/api-gateway-v2/ecs-service-alb/domain.tf new file mode 100644 index 0000000..7b8f04f --- /dev/null +++ b/api-gateway-v2/ecs-service-alb/domain.tf @@ -0,0 +1,9 @@ +module "domain" { + source = "../domain" + + api_id = aws_apigatewayv2_api.main.id + domain_name = var.domain_name + dns_zone_id = var.dns_zone_id + certificate_arn = var.certificate_arn + stage = aws_apigatewayv2_stage.main.name +} diff --git a/api-gateway-v2/ecs-service-alb/main.tf b/api-gateway-v2/ecs-service-alb/main.tf new file mode 100644 index 0000000..2e44fd6 --- /dev/null +++ b/api-gateway-v2/ecs-service-alb/main.tf @@ -0,0 +1,62 @@ +resource "aws_apigatewayv2_api" "main" { + name = var.name + protocol_type = "HTTP" + + dynamic "cors_configuration" { + for_each = var.enable_cors ? ["hack"] : [] + + content { + allow_headers = var.cors_allow_headers + allow_methods = var.cors_allow_methods + allow_origins = var.cors_allow_origins + max_age = var.cors_max_age + allow_credentials = var.cors_allow_credentials + expose_headers = var.cors_expose_headers + } + } +} + + + +resource "aws_apigatewayv2_integration" "default" { + api_id = aws_apigatewayv2_api.main.id + integration_type = "HTTP_PROXY" + integration_uri = var.service_arn + integration_method = "ANY" + connection_type = "VPC_LINK" + connection_id = aws_apigatewayv2_vpc_link.service.id + request_parameters = var.integration_request_parameters + + lifecycle { + ignore_changes = [ + passthrough_behavior, + ] + } +} + +resource "aws_apigatewayv2_route" "default" { + count = var.create_default_route ? 1 : 0 + api_id = aws_apigatewayv2_api.main.id + route_key = "$default" + operation_name = "DefaultRoute" + target = "integrations/${aws_apigatewayv2_integration.default.id}" +} + +resource "aws_apigatewayv2_stage" "main" { + api_id = aws_apigatewayv2_api.main.id + name = "live" + auto_deploy = true + + lifecycle { + ignore_changes = [ + access_log_settings, + deployment_id, + ] + } +} + +resource "aws_cloudwatch_log_group" "gw_access" { + count = var.enable_access_log ? 1 : 0 + name = "/api-gateway/${var.name}" + retention_in_days = var.logs_retention_days +} diff --git a/api-gateway-v2/ecs-service-alb/output.tf b/api-gateway-v2/ecs-service-alb/output.tf new file mode 100644 index 0000000..157f25a --- /dev/null +++ b/api-gateway-v2/ecs-service-alb/output.tf @@ -0,0 +1,19 @@ +output "id" { + value = aws_apigatewayv2_api.main.id +} + +output "api_id" { + value = aws_apigatewayv2_api.main.id +} + +output "name" { + value = aws_apigatewayv2_api.main.name +} + +output "default_integration_id" { + value = aws_apigatewayv2_integration.default.id +} + +output "vpc_link_id" { + value = aws_apigatewayv2_vpc_link.service.id +} diff --git a/api-gateway-v2/ecs-service-alb/variables.tf b/api-gateway-v2/ecs-service-alb/variables.tf new file mode 100644 index 0000000..ffa14b6 --- /dev/null +++ b/api-gateway-v2/ecs-service-alb/variables.tf @@ -0,0 +1,72 @@ +variable "name" { default = "api-gateway-ecs-service" } +# variable "route_selection_expression" { default = "$request.body.action" } +variable "domain_name" {} +variable "dns_zone_id" {} +variable "certificate_arn" {} +variable "service_arn" {} +variable "subnet_ids" { + type = list +} +variable "security_group_ids" { + type = list +} +variable "create_default_route" { default = true } + +variable "enable_cors" { default = false } + +variable "cors_allow_headers" { + description = "The set of allowed HTTP headers" + type = list(string) + + default = [ + "Authorization", + "Content-Type", + "X-Amz-Date", + "X-Amz-Security-Token", + "X-Api-Key", + ] +} + +variable "cors_allow_methods" { + description = "The set of allowed HTTP methods" + type = list(string) + + default = [ + "OPTIONS", + "HEAD", + "GET", + "POST", + "PUT", + "PATCH", + "DELETE", + ] +} + +variable "cors_allow_origins" { + description = "The set of allowed origins" + type = list(string) + default = ["*"] +} + +variable "cors_max_age" { + description = "The number of seconds that the browser should cache preflight request results" + type = string + default = "7200" +} + +variable "cors_allow_credentials" { + description = "Whether credentials are included in the CORS request" + default = false +} + +variable "cors_expose_headers" { + description = "The set of exposed HTTP headers" + type = list(string) + default = [] +} +variable "enable_access_log" { default = false } +variable "access_log_format" { default = "{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"httpMethod\":\"$context.httpMethod\",\"status\":\"$context.status\",\"path\": \"$context.path\" }" } +variable "logs_retention_days" { default = 7 } + +variable "integration_request_parameters" { default = {} } + diff --git a/api-gateway-v2/ecs-service-alb/vpc_link.tf b/api-gateway-v2/ecs-service-alb/vpc_link.tf new file mode 100644 index 0000000..d8aabca --- /dev/null +++ b/api-gateway-v2/ecs-service-alb/vpc_link.tf @@ -0,0 +1,5 @@ +resource "aws_apigatewayv2_vpc_link" "service" { + name = "${var.name}-vpc-link" + security_group_ids = var.security_group_ids + subnet_ids = var.subnet_ids +} diff --git a/api-gateway-v2/ecs-service/main.tf b/api-gateway-v2/ecs-service/main.tf index affcad9..e649d77 100644 --- a/api-gateway-v2/ecs-service/main.tf +++ b/api-gateway-v2/ecs-service/main.tf @@ -23,6 +23,7 @@ resource "aws_apigatewayv2_integration" "default" { integration_method = "ANY" connection_type = "VPC_LINK" connection_id = aws_apigatewayv2_vpc_link.service.id + request_parameters = var.integration_request_parameters lifecycle { ignore_changes = [ @@ -44,10 +45,24 @@ resource "aws_apigatewayv2_stage" "main" { name = "live" auto_deploy = true + dynamic "access_log_settings" { + for_each = var.enable_access_log ? ["access_log_enabled"] : [] + + content { + destination_arn = aws_cloudwatch_log_group.gw_access[0].arn + format = var.access_log_format + } + } + lifecycle { ignore_changes = [ - access_log_settings, deployment_id, ] } } + +resource "aws_cloudwatch_log_group" "gw_access" { + count = var.enable_access_log ? 1 : 0 + name = "/api-gateway/${var.name}" + retention_in_days = var.logs_retention_days +} diff --git a/api-gateway-v2/ecs-service/output.tf b/api-gateway-v2/ecs-service/output.tf index 0c94369..157f25a 100644 --- a/api-gateway-v2/ecs-service/output.tf +++ b/api-gateway-v2/ecs-service/output.tf @@ -6,6 +6,10 @@ output "api_id" { value = aws_apigatewayv2_api.main.id } +output "name" { + value = aws_apigatewayv2_api.main.name +} + output "default_integration_id" { value = aws_apigatewayv2_integration.default.id } diff --git a/api-gateway-v2/ecs-service/variables.tf b/api-gateway-v2/ecs-service/variables.tf index b0b26e3..f8a9a4c 100644 --- a/api-gateway-v2/ecs-service/variables.tf +++ b/api-gateway-v2/ecs-service/variables.tf @@ -64,3 +64,9 @@ variable "cors_expose_headers" { type = list(string) default = [] } + +variable "enable_access_log" { default = false } +variable "access_log_format" { default = "{ \"requestId\":\"$context.requestId\", \"ip\": \"$context.identity.sourceIp\", \"httpMethod\":\"$context.httpMethod\",\"status\":\"$context.status\",\"path\": \"$context.path\" }" } +variable "logs_retention_days" { default = 7 } + +variable "integration_request_parameters" { default = {} } diff --git a/api-gateway-v2/jwt-authorized-route/main.tf b/api-gateway-v2/jwt-auth-any-route/main.tf similarity index 100% rename from api-gateway-v2/jwt-authorized-route/main.tf rename to api-gateway-v2/jwt-auth-any-route/main.tf diff --git a/api-gateway-v2/jwt-authorized-route/variables.tf b/api-gateway-v2/jwt-auth-any-route/variables.tf similarity index 100% rename from api-gateway-v2/jwt-authorized-route/variables.tf rename to api-gateway-v2/jwt-auth-any-route/variables.tf diff --git a/api-gateway-v2/jwt-auth-route-with-options/main.tf b/api-gateway-v2/jwt-auth-route-with-options/main.tf new file mode 100644 index 0000000..2685649 --- /dev/null +++ b/api-gateway-v2/jwt-auth-route-with-options/main.tf @@ -0,0 +1,15 @@ +resource "aws_apigatewayv2_route" "auth_on" { + for_each = toset(var.method) + api_id = var.api_id + route_key = "${each.key} ${var.path}" + target = "integrations/${var.integration_id}" + authorization_scopes = var.authorization_scopes + authorizer_id = var.authorizer_id + authorization_type = "JWT" +} + +resource "aws_apigatewayv2_route" "auth_off" { + api_id = var.api_id + route_key = "OPTIONS ${var.path}" + target = "integrations/${var.integration_id}" +} diff --git a/api-gateway-v2/jwt-auth-route-with-options/variables.tf b/api-gateway-v2/jwt-auth-route-with-options/variables.tf new file mode 100644 index 0000000..af8087a --- /dev/null +++ b/api-gateway-v2/jwt-auth-route-with-options/variables.tf @@ -0,0 +1,16 @@ +variable "path" {} +variable "method"{ + type = list + default = [ + "GET","POST","DELETE","HEAD","PATCH","PUT" + ] +} +variable "api_id" {} +variable "integration_id" {} +variable "authorizer_id" {} +variable "authorization_scopes" { + type = list + default = [ + "aws.cognito.signin.user.admin", + ] +} diff --git a/api-gateway-v2/websocket/main.tf b/api-gateway-v2/websocket/main.tf index 8fc9d11..9b58f0e 100644 --- a/api-gateway-v2/websocket/main.tf +++ b/api-gateway-v2/websocket/main.tf @@ -55,6 +55,11 @@ resource "aws_apigatewayv2_stage" "main" { deployment_id = aws_apigatewayv2_deployment.main.id name = "live" + default_route_settings { + throttling_burst_limit = var.throttling_burst_limit + throttling_rate_limit = var.throttling_rate_limit + } + lifecycle { ignore_changes = [ access_log_settings, diff --git a/api-gateway-v2/websocket/variables.tf b/api-gateway-v2/websocket/variables.tf index 9e5d7f1..5f09c60 100644 --- a/api-gateway-v2/websocket/variables.tf +++ b/api-gateway-v2/websocket/variables.tf @@ -6,3 +6,5 @@ variable "disconnect_lambda_invoke_arn" {} variable "domain_name" {} variable "dns_zone_id" {} variable "certificate_arn" {} +variable "throttling_burst_limit" {} +variable "throttling_rate_limit" {} diff --git a/api-gateway/alerts/api_alerts.tf b/api-gateway/alerts/api_alerts.tf new file mode 100644 index 0000000..31ea043 --- /dev/null +++ b/api-gateway/alerts/api_alerts.tf @@ -0,0 +1,44 @@ +variable "notifications_sns_topic_arn" { default = "" } +variable "api_name" { default = "" } +variable "threshold" {default = 10} +variable "period" {default = 60} +variable "evaluation_periods" {default = 1} +variable "alarm_name" {default = ""} + +resource "aws_cloudwatch_metric_alarm" "api-4xx" { + alarm_name = "api-gateway-4xx-response.${var.alarm_name}" + alarm_description = "This alarm monitors api 4xx response" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.evaluation_periods + metric_name = "4XXError" + namespace = "AWS/ApiGateway" + period = var.period + statistic = "Sum" + threshold = var.threshold + treat_missing_data = "ignore" + alarm_actions = [var.notifications_sns_topic_arn] + ok_actions = [var.notifications_sns_topic_arn] + dimensions = { + ApiName = var.api_name + } +} + +resource "aws_cloudwatch_metric_alarm" "api-5xx" { + alarm_name = "api-gateway-5xx-response.${var.alarm_name}" + alarm_description = "This alarm monitors api 5xx response" + comparison_operator = "GreaterThanThreshold" + evaluation_periods = var.evaluation_periods + metric_name = "5XXError" + namespace = "AWS/ApiGateway" + period = var.period + statistic = "Sum" + threshold = var.threshold + treat_missing_data = "ignore" + alarm_actions = [var.notifications_sns_topic_arn] + ok_actions = [var.notifications_sns_topic_arn] + dimensions = { + ApiName = var.api_name + } +} + + diff --git a/api-gateway/base/output.tf b/api-gateway/base/output.tf index c1bdab4..2e4879f 100644 --- a/api-gateway/base/output.tf +++ b/api-gateway/base/output.tf @@ -13,3 +13,7 @@ output "stage_name" { output "invoke_url" { value = aws_api_gateway_deployment.deployment.invoke_url } + +output "api_name" { + value = var.name +} \ No newline at end of file diff --git a/api-gateway/lambda-single/output.tf b/api-gateway/lambda-single/output.tf index c1bdab4..ab91874 100644 --- a/api-gateway/lambda-single/output.tf +++ b/api-gateway/lambda-single/output.tf @@ -6,6 +6,10 @@ output "root_resource_id" { value = aws_api_gateway_rest_api.api.root_resource_id } +output "proxy_resource_id" { + value = aws_api_gateway_resource.lambda.id +} + output "stage_name" { value = var.stage_name } @@ -13,3 +17,7 @@ output "stage_name" { output "invoke_url" { value = aws_api_gateway_deployment.deployment.invoke_url } + +output "api_name" { + value = var.name +} diff --git a/budget/main.tf b/budget/main.tf index 1f62489..3d30dee 100644 --- a/budget/main.tf +++ b/budget/main.tf @@ -8,9 +8,10 @@ variable "alert_mails" { variable "notifications_sns_topic_arn" { default = "" } variable "actual_threshold_percent" { default = 100 } variable "forecast_threshold_percent" { default = 110 } +variable "name" { default = "monthly-budget" } resource "aws_budgets_budget" "monthly" { - name = "monthly-budget" + name = var.name budget_type = "COST" limit_amount = var.monthly_budget + 0.01 // required as tf detects change when integer number used limit_unit = "USD" diff --git a/ci/account/variables.tf b/ci/account/variables.tf index 2fc5fc3..88cc79e 100644 --- a/ci/account/variables.tf +++ b/ci/account/variables.tf @@ -5,9 +5,7 @@ variable "deployer_additional_principals" { } variable "provisioner_additional_principals" { type = list - default = [ - "arn:aws:iam::492614697882:root", - ] + default = [] } variable "deployer_policy" { default = < 0 ? 1 : 0 + zone_id = aws_route53_zone.main.zone_id + name = var.dns_zone + type = "CAA" + ttl = 300 + records = var.caa_records +} diff --git a/dns/variables.tf b/dns/variables.tf index 344cef4..4e86d42 100644 --- a/dns/variables.tf +++ b/dns/variables.tf @@ -3,4 +3,19 @@ variable "dns_zone" {} variable "delegations" { type = map default = {} -} \ No newline at end of file +} + +variable "caa_records" { + type = list + default = [ + "0 issue \"amazon.com\"", + "0 issue \"amazonaws.com\"", + "0 issue \"amazontrust.com\"", + "0 issue \"awstrust.com\"", + "0 issuewild \"amazon.com\"", + "0 issuewild \"amazonaws.com\"", + "0 issuewild \"amazontrust.com\"", + "0 issuewild \"awstrust.com\"", + ] +} + diff --git a/ecs-alb/cluster/data.tf b/ecs-alb/cluster/data.tf new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/ecs-alb/cluster/data.tf @@ -0,0 +1 @@ + diff --git a/ecs-alb/cluster/iam.tf b/ecs-alb/cluster/iam.tf new file mode 100644 index 0000000..e582b48 --- /dev/null +++ b/ecs-alb/cluster/iam.tf @@ -0,0 +1,27 @@ +module "iam_role_execution" { + source = "../../iam/role" + + name = "ecs-execution-${var.cluster_name}" + trusted_aws_services = [ + "ecs-tasks.amazonaws.com", + ] + attach_policies = [ + "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy", + ] + policy = < Assuming role on master" ROLE_ARN="arn:${data.aws_partition.current.partition}:iam::$MASTER_ACCOUNT_ID:role/ci-provisioner" -curl -s -o assume-role.sh https://gitlab.com/luktom/ci/-/raw/master/scripts/assume-role.sh && . assume-role.sh +curl -s -o assume-role.sh https://raw.githubusercontent.com/pragmaticcoders/terraform-modules/master/scripts/assume-role.sh && . assume-role.sh echo "==> Assuming role on $SLAVE_ACCOUNT_NAME" ROLE_ARN="arn:${data.aws_partition.current.partition}:iam::$SLAVE_ACCOUNT_ID:role/OrganizationAccountAccessRole" -curl -s -o assume-role.sh https://gitlab.com/luktom/ci/-/raw/master/scripts/assume-role.sh && . assume-role.sh +curl -s -o assume-role.sh https://raw.githubusercontent.com/pragmaticcoders/terraform-modules/master/scripts/assume-role.sh && . assume-role.sh echo "==> Checking if provisioner role exists" diff --git a/rds/postgres/main.tf b/rds/postgres/main.tf index 3b38b98..2a97cee 100644 --- a/rds/postgres/main.tf +++ b/rds/postgres/main.tf @@ -4,6 +4,8 @@ module "database" { identifier = var.name name = var.name + parameter_group_name = var.name + parameter_group_use_name_prefix = false engine = "postgres" engine_version = "11" diff --git a/s3/encrypted-bucket/main.tf b/s3/encrypted-bucket/main.tf index c8b5651..f8a7de5 100644 --- a/s3/encrypted-bucket/main.tf +++ b/s3/encrypted-bucket/main.tf @@ -67,7 +67,7 @@ resource "aws_s3_bucket" "bucket" { rule { apply_server_side_encryption_by_default { kms_master_key_id = var.kms_key_id - sse_algorithm = "aws:kms" + sse_algorithm = var.sse_algorithm } } } diff --git a/s3/encrypted-bucket/variables.tf b/s3/encrypted-bucket/variables.tf index c07a331..d133dd2 100644 --- a/s3/encrypted-bucket/variables.tf +++ b/s3/encrypted-bucket/variables.tf @@ -9,6 +9,7 @@ variable "days_to_transition" { default = 14 } variable "transition_storage_class" { default = "GLACIER" } variable "enable_expiration" { default = false } variable "days_to_expiration" { default = 90 } +variable "sse_algorithm" { default = "aws:kms" } variable "cors_enabled" { default = false } variable "cors_allowed_headers" { diff --git a/scripts/assume-role-v2.sh b/scripts/assume-role-v2.sh new file mode 100644 index 0000000..01030c2 --- /dev/null +++ b/scripts/assume-role-v2.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +if [[ -z "$ROLE_ARN" ]]; then + echo "==> skipping assume role as no role specified" + return +fi + +echo "==> assuming role $ROLE_ARN" +output=$(aws sts assume-role --role-arn "$ROLE_ARN" --role-session-name "assumed-role") +STATUS=$? +if [ ${STATUS} -ne 0 ]; then + echo "####### Can't perform assume role, check all variables! #######" + exit 1 +fi +echo "==> role $ROLE_ARN assumed" +export AWS_ACCESS_KEY_ID=$(echo $output | jq -c '.Credentials.AccessKeyId' --raw-output) +export AWS_SECRET_ACCESS_KEY=$(echo $output | jq -c '.Credentials.SecretAccessKey' --raw-output) +export AWS_SESSION_TOKEN=$(echo $output | jq -c '.Credentials.SessionToken' --raw-output) \ No newline at end of file diff --git a/scripts/assume-role.sh b/scripts/assume-role.sh new file mode 100644 index 0000000..27305e6 --- /dev/null +++ b/scripts/assume-role.sh @@ -0,0 +1,14 @@ +#!/bin/bash + +if [[ -z "$ROLE_ARN" ]]; then + echo "==> skipping assume role as no role specified" + return +fi + +echo "==> assuming role $ROLE_ARN" +output=$(aws sts assume-role --role-arn "$ROLE_ARN" --role-session-name "assumed-role") + +echo "==> role $ROLE_ARN assumed" +export AWS_ACCESS_KEY_ID=$(echo $output | jq -c '.Credentials.AccessKeyId' | tr -d '"' | tr -d ' ') +export AWS_SECRET_ACCESS_KEY=$(echo $output | jq -c '.Credentials.SecretAccessKey' | tr -d '"' | tr -d ' ') +export AWS_SESSION_TOKEN=$(echo $output | jq -c '.Credentials.SessionToken' | tr -d '"' | tr -d ' ') \ No newline at end of file diff --git a/security/cloudtrail/main.tf b/security/cloudtrail/main.tf index 7bc19dc..feb7914 100644 --- a/security/cloudtrail/main.tf +++ b/security/cloudtrail/main.tf @@ -51,7 +51,7 @@ resource "aws_cloudtrail" "organization" { # sns_topic_name = "${data.terraform_remote_state.master.cloudtrail_events_sns_topic_arn}" enable_log_file_validation = true # kms_key_id = "${data.terraform_remote_state.master.kms_cloudtrail_arn["${var.account_name}"]}" - cloud_watch_logs_group_arn = aws_cloudwatch_log_group.cloudtrail.arn + cloud_watch_logs_group_arn = "${aws_cloudwatch_log_group.cloudtrail.arn}:*" cloud_watch_logs_role_arn = module.role.iam_role_arn depends_on = [ diff --git a/security/vpc-flow-logs/main.tf b/security/vpc-flow-logs/main.tf new file mode 100644 index 0000000..5cc93e7 --- /dev/null +++ b/security/vpc-flow-logs/main.tf @@ -0,0 +1,40 @@ +variable "tenant" {} + +module "bucket" { + source = "../../s3/simple-bucket" + + bucket = "${var.tenant}-vpc-flow-logs" + versioning_enabled = true + + policy = < /dev/null; fi; echo '{}'", + "if [[ ! -f /tmp/terraform-artifacts/lambda-slack-alarm-notification.zip ]]; then wget -O /tmp/terraform-artifacts/lambda-slack-alarm-notification.zip https://github.com/pragmaticcoders/slack-notification-lambda/releases/download/initial/lambda.zip > /dev/null; fi; echo '{}'", ] } \ No newline at end of file diff --git a/slack-alarm-notification-lambda/releases/download/initial/lambda.zip b/slack-alarm-notification-lambda/releases/download/initial/lambda.zip new file mode 100644 index 0000000..c9177c9 Binary files /dev/null and b/slack-alarm-notification-lambda/releases/download/initial/lambda.zip differ diff --git a/sqs/output.tf b/sqs/output.tf index 1df3fe5..8493898 100644 --- a/sqs/output.tf +++ b/sqs/output.tf @@ -6,6 +6,18 @@ output "name" { value = var.name } +output "id" { + value = aws_sqs_queue.queue.id +} + output "dead_letter_arn" { value = aws_sqs_queue.dead_letter.arn } + +output "dead_letter_name" { + value = aws_sqs_queue.dead_letter.name +} + +output "dead_letter_id" { + value = aws_sqs_queue.dead_letter.id +} diff --git a/sso/permission-set/main.tf b/sso/permission-set/main.tf new file mode 100644 index 0000000..5825925 --- /dev/null +++ b/sso/permission-set/main.tf @@ -0,0 +1,41 @@ +data "aws_ssoadmin_instances" "sso" {} + +resource "aws_ssoadmin_permission_set" "sso" { + instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0] + name = var.name + session_duration = var.session_duration +} + +resource "aws_ssoadmin_managed_policy_attachment" "sso" { + for_each = toset(var.managed_policy_arns) + instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0] + managed_policy_arn = each.value + permission_set_arn = aws_ssoadmin_permission_set.sso.arn +} + +resource "aws_ssoadmin_permission_set_inline_policy" "sso" { + count = var.custom_inline_policy_json != "" ? 1 : 0 + inline_policy = var.custom_inline_policy_json + instance_arn = aws_ssoadmin_permission_set.sso.instance_arn + permission_set_arn = aws_ssoadmin_permission_set.sso.arn +} + +data "aws_identitystore_group" "sso" { + identity_store_id = tolist(data.aws_ssoadmin_instances.sso.identity_store_ids)[0] + filter { + attribute_path = "DisplayName" + attribute_value = var.group_name + } +} + +resource "aws_ssoadmin_account_assignment" "sso" { + for_each = toset(var.account_ids) + instance_arn = tolist(data.aws_ssoadmin_instances.sso.arns)[0] + permission_set_arn = aws_ssoadmin_permission_set.sso.arn + + principal_id = data.aws_identitystore_group.sso.group_id + principal_type = "GROUP" + + target_id = each.value + target_type = "AWS_ACCOUNT" +} diff --git a/sso/permission-set/variables.tf b/sso/permission-set/variables.tf new file mode 100644 index 0000000..0ed39c4 --- /dev/null +++ b/sso/permission-set/variables.tf @@ -0,0 +1,25 @@ +variable "name" { + type = string +} + +variable "managed_policy_arns" { + type = list(string) + default = [] +} + +variable "custom_inline_policy_json" { + type = string + default = "" +} + +variable "group_name" { + type = string +} + +variable "account_ids" {} + +variable "session_duration" { + type = string + default = "PT2H" +} + diff --git a/static-website/cloudfront.tf b/static-website/cloudfront.tf index 7d90925..7ca5dd4 100644 --- a/static-website/cloudfront.tf +++ b/static-website/cloudfront.tf @@ -53,7 +53,7 @@ resource "aws_cloudfront_distribution" "website" { viewer_certificate { acm_certificate_arn = var.certificate_arn ssl_support_method = "sni-only" - minimum_protocol_version = "TLSv1" + minimum_protocol_version = "TLSv1.2_2019" } restrictions { diff --git a/static-website/output.tf b/static-website/output.tf index 5a0afb4..a90c8f8 100644 --- a/static-website/output.tf +++ b/static-website/output.tf @@ -16,4 +16,8 @@ output "domain_name" { output "bucket_name" { value = var.bucket_name +} + +output "bucket_arn" { + value = aws_s3_bucket.website.arn } \ No newline at end of file diff --git a/target-group/main.tf b/target-group/main.tf new file mode 100644 index 0000000..61cc55f --- /dev/null +++ b/target-group/main.tf @@ -0,0 +1,32 @@ +resource "aws_lb_target_group" "service-tg" { + name = "${var.service-name}-target-group" + port = var.service_port + protocol = var.target_group_protocol + vpc_id = "${var.vpc_id}" + target_type = "ip" + deregistration_delay = var.deregistration_delay + health_check { + healthy_threshold = var.healthy_threshold + port = var.health_check_port + interval = var.helth_check_interval + path = var.health_check_path + timeout = var.health_check_timeout + unhealthy_threshold = var.unhealthy_threshold + matcher = "200-399" + } +} + +resource "aws_lb_listener_rule" "service-rule" { + listener_arn = var.listener + priority = var.priority + + action { + type = var.listener_action + target_group_arn = aws_lb_target_group.service-tg.arn + } + condition { + host_header { + values = var.host_header + } + } +} diff --git a/target-group/output.tf b/target-group/output.tf new file mode 100644 index 0000000..f78131d --- /dev/null +++ b/target-group/output.tf @@ -0,0 +1,3 @@ +output "alb_target_group" { + value = aws_lb_target_group.service-tg.arn +} \ No newline at end of file diff --git a/target-group/variables.tf b/target-group/variables.tf new file mode 100644 index 0000000..1058aa5 --- /dev/null +++ b/target-group/variables.tf @@ -0,0 +1,16 @@ +variable "service_port" { default = 80 } +variable "vpc_id" {} +variable "health_check_path" {default = "/"} + +variable "health_check_port" {default = 80} +variable "healthy_threshold" {default = "2"} +variable "helth_check_interval" {default = "95"} +variable "deregistration_delay" {default = 2} +variable "health_check_timeout" {default = "94"} +variable "unhealthy_threshold" {default = "2"} +variable "listener_action" {default = "forward"} +variable "target_group_protocol" {default = "HTTP"} +variable "listener" {default = ""} +variable "priority" {default = 50} +variable "host_header" {default = ""} +variable "service-name" {default = "app"} \ No newline at end of file diff --git a/vpc/main.tf b/vpc/main.tf index c3280e9..388ef42 100644 --- a/vpc/main.tf +++ b/vpc/main.tf @@ -26,6 +26,7 @@ module "vpc" { private_subnet_tags = var.eks_cluster_name == "" ? {} : { "kubernetes.io/cluster/${var.eks_cluster_name}" = "shared" + "kubernetes.io/role/internal-elb" = "1" } create_database_subnet_route_table = true @@ -43,4 +44,10 @@ module "vpc" { single_nat_gateway = var.production_mode == false one_nat_gateway_per_az = true enable_dns_hostnames = true + + enable_flow_log = var.enable_flow_log + flow_log_destination_type = var.flow_log_destination_type + flow_log_destination_arn = var.flow_log_destination_arn + flow_log_traffic_type = var.flow_log_traffic_type + } diff --git a/vpc/variables.tf b/vpc/variables.tf index 1ebfabf..b20e311 100644 --- a/vpc/variables.tf +++ b/vpc/variables.tf @@ -5,3 +5,7 @@ variable "production_mode" { default = false } variable "enable_nat_gateway" { default = false } variable "enable_ipv6" { default = false } variable "max_azs" { default = 3 } +variable "enable_flow_log" { default = false } +variable "flow_log_destination_type" { default = "" } +variable "flow_log_destination_arn" { default = "" } +variable "flow_log_traffic_type" { default = "ALL" }