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-py/main.tf b/alerts/slack-py/main.tf new file mode 100644 index 0000000..ae9a12d --- /dev/null +++ b/alerts/slack-py/main.tf @@ -0,0 +1,26 @@ +module "lambda" { + source = "../../lambda" + + name = var.name + source_dir = var.source_dir + runtime = "python3.8" + handler = "sns_slack.lambda_handler" + memory_size = 128 + timeout = 30 + + environment = { + "SLACK_URL" = var.slack_url + "SLACK_EMOJI" = ":warning:" + "SLACK_USER" = var.slack_user + } + + invoke_allow_principals = [ + "sns.amazonaws.com", + ] +} + +resource "aws_sns_topic_subscription" "subscription" { + topic_arn = var.sns_topic_arn + protocol = "lambda" + endpoint = module.lambda.arn +} diff --git a/alerts/slack-py/output.tf b/alerts/slack-py/output.tf new file mode 100644 index 0000000..055e417 --- /dev/null +++ b/alerts/slack-py/output.tf @@ -0,0 +1,7 @@ +output "function_name" { + value = module.lambda.name +} + +output "function_arn" { + value = module.lambda.arn +} diff --git a/alerts/slack-py/variables.tf b/alerts/slack-py/variables.tf new file mode 100644 index 0000000..84a8c27 --- /dev/null +++ b/alerts/slack-py/variables.tf @@ -0,0 +1,5 @@ +variable "slack_url" {} +variable "slack_user" {} +variable "sns_topic_arn" {} +variable "source_dir" {} +variable "name" {} \ 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/cors/main.tf b/api-gateway/cors/main.tf index ad0e874..9306d18 100644 --- a/api-gateway/cors/main.tf +++ b/api-gateway/cors/main.tf @@ -1,6 +1,6 @@ module "cors" { source = "squidfunk/api-gateway-enable-cors/aws" - version = "0.3.1" + version = "0.3.3" allow_headers = var.allow_headers allow_methods = var.allow_methods 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/cert/main.tf b/cert/main.tf index f7bfb01..415cfa9 100644 --- a/cert/main.tf +++ b/cert/main.tf @@ -15,3 +15,11 @@ module "cert" { zone_id = var.zone_id tags = var.tags } + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + } + } +} diff --git a/ci/account/iam_role_deployer.tf b/ci/account/iam_role_deployer.tf index 5924664..65bd5ce 100644 --- a/ci/account/iam_role_deployer.tf +++ b/ci/account/iam_role_deployer.tf @@ -1,6 +1,6 @@ locals { deployer_principals = concat( - list("arn:${data.aws_partition.current.partition}:iam::${var.master_aws_account_id}:user/ci-deployer"), + tolist(["arn:${data.aws_partition.current.partition}:iam::${var.master_aws_account_id}:user/ci-deployer"]), var.deployer_additional_principals, ) } @@ -33,4 +33,4 @@ resource "aws_iam_role_policy" "additional" { role = module.ci_deployer.name policy = var.deployer_additional_policy -} \ No newline at end of file +} diff --git a/ci/account/iam_role_provisioner.tf b/ci/account/iam_role_provisioner.tf index 6803f84..d5bd7a3 100644 --- a/ci/account/iam_role_provisioner.tf +++ b/ci/account/iam_role_provisioner.tf @@ -1,6 +1,6 @@ locals { provisioner_principals = concat( - list("arn:${data.aws_partition.current.partition}:iam::${var.master_aws_account_id}:user/ci-provisioner"), + tolist(["arn:${data.aws_partition.current.partition}:iam::${var.master_aws_account_id}:user/ci-provisioner"]), var.provisioner_additional_principals, ) sso_trust_policy = < 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..8e7263f 100644 --- a/dns/variables.tf +++ b/dns/variables.tf @@ -3,4 +3,21 @@ 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\"", + "0 issuewild \"letsencrypt.org\"", + "0 issue \"letsencrypt.org\"", + ] +} + diff --git a/ecs-alb/cluster/data.tf b/ecs-alb/cluster/data.tf new file mode 100644 index 0000000..a5b9b0f --- /dev/null +++ b/ecs-alb/cluster/data.tf @@ -0,0 +1,11 @@ +data "aws_iam_policy_document" "main" { + statement { + effect = "Allow" + actions = [ + "ssm:GetParameters" + ] + resources = [ + "arn:aws:ssm:*:*:parameter/ecs/${var.cluster_name}/*" + ] + } +} \ No newline at end of file diff --git a/ecs-alb/cluster/iam.tf b/ecs-alb/cluster/iam.tf new file mode 100644 index 0000000..4ac52fa --- /dev/null +++ b/ecs-alb/cluster/iam.tf @@ -0,0 +1,12 @@ +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 = local.policy +} diff --git a/ecs-alb/cluster/main.tf b/ecs-alb/cluster/main.tf new file mode 100644 index 0000000..406580d --- /dev/null +++ b/ecs-alb/cluster/main.tf @@ -0,0 +1,18 @@ +module "ecs-alb" { + source = "terraform-aws-modules/ecs/aws" + version = "3.5.0" + + name = var.cluster_name + + capacity_providers = ["FARGATE", "FARGATE_SPOT"] + default_capacity_provider_strategy = [ + { + capacity_provider = "FARGATE_SPOT" + weight = 1 + } + ] + + tags = { + Name = var.cluster_name + } +} diff --git a/ecs-alb/cluster/output.tf b/ecs-alb/cluster/output.tf new file mode 100644 index 0000000..e332cc7 --- /dev/null +++ b/ecs-alb/cluster/output.tf @@ -0,0 +1,39 @@ +output "cluster_id" { + value = module.ecs-alb.ecs_cluster_id +} + +output "id" { + value = module.ecs-alb.ecs_cluster_id +} + +output "cluster_arn" { + value = module.ecs-alb.ecs_cluster_id +} + +output "arn" { + value = module.ecs-alb.ecs_cluster_id +} + +output "cluster_name" { + value = var.cluster_name +} + +output "execution_role_arn" { + value = module.iam_role_execution.arn +} + +output "vpc_id" { + value = var.vpc_id +} + +output "namespace_id" { + value = join("", aws_service_discovery_public_dns_namespace.ecs.*.id, aws_service_discovery_private_dns_namespace.ecs.*.id) +} + +output "aws_iam_policy_document_main" { + value = data.aws_iam_policy_document.main +} + +output "iam_role_name" { + value = module.iam_role_execution.iam_role_name +} \ No newline at end of file diff --git a/ecs-alb/cluster/service_discovery.tf b/ecs-alb/cluster/service_discovery.tf new file mode 100644 index 0000000..3bcfa91 --- /dev/null +++ b/ecs-alb/cluster/service_discovery.tf @@ -0,0 +1,12 @@ +resource "aws_service_discovery_private_dns_namespace" "ecs" { + count = var.service_discovery_namespace_type == "private" ? 1 : 0 + + name = var.service_discovery_domain == "" ? "${var.cluster_name}.ecs.local" : var.service_discovery_domain + vpc = var.vpc_id +} + +resource "aws_service_discovery_public_dns_namespace" "ecs" { + count = var.service_discovery_namespace_type == "public" ? 1 : 0 + + name = var.service_discovery_domain +} diff --git a/ecs-alb/cluster/variables.tf b/ecs-alb/cluster/variables.tf new file mode 100644 index 0000000..3fdea99 --- /dev/null +++ b/ecs-alb/cluster/variables.tf @@ -0,0 +1,14 @@ +variable "cluster_name" { default = "ecs" } +variable "vpc_id" {} +variable "service_discovery_domain" { default = "" } +variable "service_discovery_namespace_type" { + description = "none, public or private" + default = "none " +} +variable "policy" { + default = "" +} + +locals { + policy = var.policy == "" ? data.aws_iam_policy_document.main.json : var.policy +} \ No newline at end of file diff --git a/ecs-alb/service-cm/cloudwatch.tf b/ecs-alb/service-cm/cloudwatch.tf new file mode 100644 index 0000000..4a9c682 --- /dev/null +++ b/ecs-alb/service-cm/cloudwatch.tf @@ -0,0 +1,4 @@ +resource "aws_cloudwatch_log_group" "service" { + name = "/ecs/service/${var.name}" + retention_in_days = var.logs_retention_days +} diff --git a/ecs-alb/service-cm/data.tf b/ecs-alb/service-cm/data.tf new file mode 100644 index 0000000..b4c27da --- /dev/null +++ b/ecs-alb/service-cm/data.tf @@ -0,0 +1 @@ +data "aws_region" "current" {} \ No newline at end of file diff --git a/ecs-alb/service-cm/main.tf b/ecs-alb/service-cm/main.tf new file mode 100644 index 0000000..982ffa0 --- /dev/null +++ b/ecs-alb/service-cm/main.tf @@ -0,0 +1,143 @@ +resource "aws_ecs_service" "service" { + name = var.name + cluster = var.cluster_id + task_definition = aws_ecs_task_definition.task.arn + desired_count = var.initial_desired_count + platform_version = "1.4.0" + + network_configuration { + assign_public_ip = var.assign_public_ip + security_groups = [ + module.sg.id, + ] + subnets = var.subnet_ids + } + + service_registries { + container_name = var.service_discovery_container_name + registry_arn = aws_service_discovery_service.service.arn + container_port = var.service_port + } + + capacity_provider_strategy { + base = 0 + capacity_provider = "FARGATE_SPOT" + weight = 1 + } + + # lifecycle { + # ignore_changes = [ + # desired_count, + # ] + # } +} + +module "sg" { + source = "github.com/tomaszkiewicz/terraform-modules/sg" + + name = "ecs-service-${var.name}" + vpc_id = var.vpc_id + ports = [ + var.service_port,var.health_check_port,var.nfs + ] +} + +resource "aws_service_discovery_service" "service" { + name = var.name + + dns_config { + namespace_id = var.service_discovery_namespace_id + + dns_records { + ttl = 1 + type = "A" + } + + dns_records { + ttl = 1 + type = "SRV" + } + + routing_policy = "MULTIVALUE" + } + + # health_check_config { + # type = "HTTP" + # } + + # health_check_custom_config { + # failure_threshold = 1 + # } +} + +data "aws_ecs_container_definition" "existing" { + count = var.container_image_tag == "" ? 1 : 0 + + task_definition = var.name + container_name = var.service_discovery_container_name +} + +resource "aws_ecs_task_definition" "task" { + family = var.name + network_mode = "awsvpc" + cpu = var.cpu + memory = var.memory + task_role_arn = var.task_role_arn + execution_role_arn = var.execution_role_arn + requires_compatibilities = [ + "FARGATE", + ] + dynamic "volume" { + for_each = var.efs_id == "" ? [] :[var.efs_id] + content{ + name = "service-storage" + efs_volume_configuration { + root_directory = "/" + file_system_id = var.efs_id + } + } + } + container_definitions = jsonencode(concat([ + merge( + { + name : var.service_discovery_container_name + image : "${var.container_image}:${var.container_image_tag == "" ? data.aws_ecs_container_definition.existing[0].image_digest : var.container_image_tag}" + essential : true + portMappings : [ + { + hostPort : var.service_port + protocol : "tcp" + containerPort : var.service_port + }, + ] + environment : [ + for k, v in var.environment : { + name : k + value : v + } + ] + secrets : [ + for k, v in var.secrets : { + name : k + valueFrom : v + } + ] + logConfiguration : { + logDriver : "awslogs" + options : { + awslogs-group : aws_cloudwatch_log_group.service.name + awslogs-region : data.aws_region.current.name + awslogs-stream-prefix : var.service_discovery_container_name + } + }, + }, + length(var.entryPoint) == 0 ? {} : { + entryPoint : var.entryPoint + }, + length(var.command) == 0 ? {} : { + command : var.command + }, + ), + ], var.sidecar_definitions)) + +} diff --git a/ecs-alb/service-cm/output.tf b/ecs-alb/service-cm/output.tf new file mode 100644 index 0000000..cab4ecb --- /dev/null +++ b/ecs-alb/service-cm/output.tf @@ -0,0 +1,7 @@ +output "security_group_id" { + value = module.sg.id +} + +output "cloudmap_service_arn" { + value = aws_service_discovery_service.service.arn +} diff --git a/ecs-alb/service-cm/variables.tf b/ecs-alb/service-cm/variables.tf new file mode 100644 index 0000000..502bb00 --- /dev/null +++ b/ecs-alb/service-cm/variables.tf @@ -0,0 +1,42 @@ +variable "cluster_id" {} +variable "subnet_ids" {} +variable "name" {} +variable "vpc_id" {} +variable "container_image" {} +variable "container_image_tag" { default = "" } +variable "service_discovery_namespace_id" {} +variable "service_discovery_container_name" { default = "app" } +variable "cpu" { default = 256 } +variable "memory" { default = 512 } +variable "service_port" { default = 80 } +variable "assign_public_ip" { default = false } +variable "initial_desired_count" { default = 1 } +variable "task_role_arn" { default = "" } +variable "execution_role_arn" { default = "" } +variable "logs_retention_days" { default = 7 } +variable "environment" { + type = map + default = {} +} +variable "secrets" { + type = map + default = {} +} +variable "health_check_path" { default = "/health" } +variable "health_check_port" { default = "" } // uses service_port when not set +variable "health_check_retries" { default = 3 } +variable "health_check_timeout" { default = 5 } +variable "health_check_interval" { default = 5 } +variable "health_check_start_period" { default = 30 } +variable "entryPoint" { + type = list + default = [] +} +variable "command" { + type = list + default = [] +} + +variable "efs_id" {default = ""} +variable "sidecar_definitions" {default = []} +variable "nfs" {default = 2049} diff --git a/ecs-alb/service/cloudwatch.tf b/ecs-alb/service/cloudwatch.tf new file mode 100644 index 0000000..4a9c682 --- /dev/null +++ b/ecs-alb/service/cloudwatch.tf @@ -0,0 +1,4 @@ +resource "aws_cloudwatch_log_group" "service" { + name = "/ecs/service/${var.name}" + retention_in_days = var.logs_retention_days +} diff --git a/ecs-alb/service/data.tf b/ecs-alb/service/data.tf new file mode 100644 index 0000000..b4c27da --- /dev/null +++ b/ecs-alb/service/data.tf @@ -0,0 +1 @@ +data "aws_region" "current" {} \ No newline at end of file diff --git a/ecs-alb/service/locals.tf b/ecs-alb/service/locals.tf new file mode 100644 index 0000000..7f35a9e --- /dev/null +++ b/ecs-alb/service/locals.tf @@ -0,0 +1,3 @@ +locals { + sg_ports = concat([var.service_port,var.health_check_port,var.nfs], var.sg_ports) +} diff --git a/ecs-alb/service/main.tf b/ecs-alb/service/main.tf new file mode 100644 index 0000000..b1304fd --- /dev/null +++ b/ecs-alb/service/main.tf @@ -0,0 +1,136 @@ +resource "aws_ecs_service" "service" { + name = var.name + cluster = var.cluster_id + task_definition = aws_ecs_task_definition.task.arn + desired_count = var.initial_desired_count + enable_execute_command = var.enable_execute_command + platform_version = "1.4.0" + + network_configuration { + assign_public_ip = var.assign_public_ip + security_groups = [module.sg.id] // SG allow from ALB + subnets = var.subnet_ids //private + } + + load_balancer { + target_group_arn = var.alb_target_group //balancer + container_name = var.image_name + container_port = var.service_port + } + + dynamic "load_balancer" { + for_each = var.load_balancer == "" ? {} : { for k, v in var.load_balancer : k => v } + content { + target_group_arn = load_balancer.value["target_group_arn"] + container_name = load_balancer.key + container_port = load_balancer.value["container_port"] + } + } + + capacity_provider_strategy { + base = 0 + capacity_provider = "FARGATE_SPOT" + weight = 1 + } + + # lifecycle { + # ignore_changes = [ + # desired_count, + # ] + # } + +} +data "aws_ecs_container_definition" "existing" { + count = var.container_image_tag == "" ? 1 : 0 + + task_definition = var.name + container_name = var.image_name +} + + + +module "sg" { + source = "github.com/pragmaticcoders/terraform-modules/sg" + + name = "ecs-service-${var.image_name}" + vpc_id = var.vpc_id + ports = local.sg_ports +} + + +resource "aws_ecs_task_definition" "task" { + family = var.name + network_mode = "awsvpc" + cpu = var.cpu + memory = var.memory + task_role_arn = var.task_role_arn + execution_role_arn = var.execution_role_arn + requires_compatibilities = [ + "FARGATE", + ] + dynamic "volume" { + for_each = var.efs_mount == "" ? {} : { for k, v in var.efs_mount : k => v } + content { + name = volume.key + efs_volume_configuration { + root_directory = volume.value["root_directory"] + file_system_id = volume.value["file_system_id"] + transit_encryption = "ENABLED" + transit_encryption_port = volume.value["transit_encryption_port"] == "" ? 2999 : volume.value["transit_encryption_port"] + authorization_config { + access_point_id = volume.value["access_point_id"] + iam = "ENABLED" + } + } + } + } + + container_definitions = jsonencode(concat([ + merge( + { + name : var.image_name + image : "${var.container_image}:${var.container_image_tag == "" ? data.aws_ecs_container_definition.existing[0].image_digest : var.container_image_tag}" + essential : true + stopTimeout: 10 + portMappings : var.portMappings != [] ? var.portMappings : [{ + hostPort : var.service_port + protocol : "tcp" + containerPort : var.service_port + }] + environment : [ + for k, v in var.environment : { + name : k + value : v + } + ] + secrets : [ + for k, v in var.secrets : { + name : k + valueFrom : v + } + ] + mountPoints : [ + for k, v in var.mount_points : { + sourceVolume : v["sourceVolume"] + containerPath : v["containerPath"] + readOnly : v["readOnly"] + } + ] + logConfiguration : { + logDriver : "awslogs" + options : { + awslogs-group : aws_cloudwatch_log_group.service.name + awslogs-region : data.aws_region.current.name + awslogs-stream-prefix : var.container_image + } + }, + }, + length(var.entryPoint) == 0 ? {} : { + entryPoint : var.entryPoint + }, + length(var.command) == 0 ? {} : { + command : var.command + }, + ) + ], var.sidecar_definitions)) +} diff --git a/ecs-alb/service/output.tf b/ecs-alb/service/output.tf new file mode 100644 index 0000000..e11593e --- /dev/null +++ b/ecs-alb/service/output.tf @@ -0,0 +1,5 @@ +output "security_group_id" { + value = module.sg.id +} + + diff --git a/ecs-alb/service/variables.tf b/ecs-alb/service/variables.tf new file mode 100644 index 0000000..d40771b --- /dev/null +++ b/ecs-alb/service/variables.tf @@ -0,0 +1,62 @@ +variable "cluster_id" {} +variable "image_name" {default = "app"} +variable "subnet_ids" {} +variable "name" {} +variable "vpc_id" {} +variable "container_image" {} +variable "container_image_tag" { default = "" } +variable "cpu" { default = 256 } +variable "memory" { default = 512 } +variable "service_port" { default = 80 } +variable "assign_public_ip" { default = false } +variable "initial_desired_count" { default = 1 } +variable "task_role_arn" { default = "" } +variable "execution_role_arn" { default = "" } +variable "logs_retention_days" { default = 7 } +variable "environment" { + type = map + default = {} +} +variable "secrets" { + type = map + default = {} +} +variable "portMappings" { + default = [] + description = "Containers ports" +} +variable "health_check_path" { default = "/health" } +variable "health_check_port" { default = "" } // uses service_port when not set +variable "health_check_retries" { default = 3 } +variable "health_check_timeout" { default = 5 } +variable "health_check_interval" { default = 5 } +variable "health_check_start_period" { default = 30 } +variable "entryPoint" { + type = list + default = [] +} +variable "command" { + type = list + default = [] +} +variable "alb_target_group" {default = ""} +variable "efs_id" {default = ""} +variable "sidecar_definitions" {default = []} +variable "nfs" {default = 2049} +variable "enable_execute_command" { default = false } +variable "efs_mount" { + default = {} + description = "The EFS volumes mount to container directory" +} +variable "mount_points" { + default = {} + description = "Definition of mounting points in a container" +} +variable "load_balancer" { + default = {} + description = "Definition of load balancer for other containers started within one ECS task" +} +variable "sg_ports" { + default = [] + description = "List for Security Group ports" +} diff --git a/ecs/cluster/main.tf b/ecs/cluster/main.tf index 5441915..62537c6 100644 --- a/ecs/cluster/main.tf +++ b/ecs/cluster/main.tf @@ -1,5 +1,6 @@ module "ecs" { source = "terraform-aws-modules/ecs/aws" + version = "3.5.0" name = var.cluster_name diff --git a/ecs/cluster/output.tf b/ecs/cluster/output.tf index ed1c12d..6044bcc 100644 --- a/ecs/cluster/output.tf +++ b/ecs/cluster/output.tf @@ -1,17 +1,17 @@ output "cluster_id" { - value = module.ecs.this_ecs_cluster_id + value = module.ecs.ecs_cluster_id } output "id" { - value = module.ecs.this_ecs_cluster_id + value = module.ecs.ecs_cluster_id } output "cluster_arn" { - value = module.ecs.this_ecs_cluster_id + value = module.ecs.ecs_cluster_id } output "arn" { - value = module.ecs.this_ecs_cluster_id + value = module.ecs.ecs_cluster_id } output "cluster_name" { diff --git a/ecs/fargate-ssh-server/main.tf b/ecs/fargate-ssh-server/main.tf index 2e89f7b..c45f8e3 100644 --- a/ecs/fargate-ssh-server/main.tf +++ b/ecs/fargate-ssh-server/main.tf @@ -58,13 +58,16 @@ resource "aws_service_discovery_service" "service" { } module "sg" { - source = "github.com/tomaszkiewicz/terraform-modules/sg" + source = "github.com/pragmaticcoders/terraform-modules/sg" name = "ecs-service-${var.name}" vpc_id = var.vpc_id ports = [ var.service_port, ] + + cidr_blocks = var.cidr_blocks + } resource "aws_cloudwatch_log_group" "service" { diff --git a/ecs/fargate-ssh-server/variables.tf b/ecs/fargate-ssh-server/variables.tf index 5308047..04fbdf7 100644 --- a/ecs/fargate-ssh-server/variables.tf +++ b/ecs/fargate-ssh-server/variables.tf @@ -10,28 +10,33 @@ variable "cpu" { default = 256 } variable "memory" { default = 512 } variable "execution_role_arn" { default = "" } -variable "container_image" { default = "luktom/ws" } +variable "container_image" { default = "pragmaticcoders/ws" } variable "container_image_tag" { default = "latest" } variable "ssh_public_keys" { - type = list + type = list(any) default = [] } variable "tunnel_only_ssh_public_keys" { - type = list + type = list(any) default = [] } variable "logs_retention_days" { default = 7 } variable "environment" { - type = map + type = map(any) default = {} } variable "secrets" { - type = map + type = map(any) default = {} } variable "efs_filesystem_id" { default = "" } variable "notifications_sns_topic_arn" { default = "" } variable "service_discovery_namespace_id" { default = "" } + +variable "cidr_blocks" { + type = list(string) + default = ["0.0.0.0/0"] +} \ No newline at end of file diff --git a/ecs/service/main.tf b/ecs/service/main.tf index 28b9a3a..1d57d29 100644 --- a/ecs/service/main.tf +++ b/ecs/service/main.tf @@ -33,7 +33,7 @@ resource "aws_ecs_service" "service" { } module "sg" { - source = "github.com/tomaszkiewicz/terraform-modules/sg" + source = "github.com/pragmaticcoders/terraform-modules/sg" name = "ecs-service-${var.name}" vpc_id = var.vpc_id @@ -131,7 +131,7 @@ resource "aws_ecs_task_definition" "task" { ), { name : "healthcheck" - image : "luktom/ws" + image : "pragmaticcoders/ws" essential : true healthCheck : { command : [ diff --git a/eks-fargate/alb.tf b/eks-fargate/alb.tf new file mode 100644 index 0000000..a9dbba7 --- /dev/null +++ b/eks-fargate/alb.tf @@ -0,0 +1,254 @@ +resource "aws_iam_policy" "ALBIngressControllerIAMPolicy" { + name = "ALBIngressControllerIAMPolicy" + policy = < Install docker" apk add --update docker -echo "==> Install docker-machine" -base=https://github.com/docker/machine/releases/download/v0.16.0 && -curl -L $base/docker-machine-$(uname -s)-$(uname -m) >/tmp/docker-machine && -sudo mv /tmp/docker-machine /usr/local/bin/docker-machine && +echo "==> Install docker+machine" +curl -O "https://gitlab-docker-machine-downloads.s3.amazonaws.com/v0.16.2-gitlab.12/docker-machine-Linux-x86_64" +cp docker-machine-Linux-x86_64 /usr/local/bin/docker-machine chmod +x /usr/local/bin/docker-machine echo "==> Symlink configs for docker-machine" @@ -33,6 +32,9 @@ chmod +x /usr/local/bin/gitlab-runner echo "==> Generate template" mkdir -p /data cat << TEMPLATE > /data/template.toml +concurrent = 1 +check_interval = 0 + [[runners]] [runners.docker] image = "alpine" @@ -48,9 +50,9 @@ cat << TEMPLATE > /data/template.toml MachineOptions = [ "amazonec2-use-private-address=true", "amazonec2-tags=runner-manager-name,gitlab-aws-autoscaler,gitlab,true,gitlab-runner-autoscale,true", + "amazonec2-ami=${var.runner_ami}", "amazonec2-request-spot-instance=true", "amazonec2-spot-price=${var.max_spot_price}", -# "amazonec2-block-duration-minutes=60" ] TEMPLATE @@ -65,6 +67,10 @@ if ! grep -q "$REGISTRATION_TOKEN" /data/token; then echo "==> Removing configuration" rm -fv /data/config.toml fi + +echo "==> make sure that config is up to date" +echo "" > /data/config.toml +rm -f /data/config.toml echo "==> Register the runner if needed" if [ ! -f /data/config.toml ]; then @@ -74,12 +80,16 @@ if [ ! -f /data/config.toml ]; then --template-config /data/template.toml \ --config /data/config.toml \ --executor docker+machine \ - --docker-image luktom/ws \ + --docker-image pragmaticcoders/ws \ --tag-list "${join(",", var.gitlab_runner_tags)}" \ --run-untagged=true \ --locked=false fi +echo "==> Fix register concurrent value" +sed -i "s/concurrent.*/concurrent = $CONCURRENT/" /data/config.toml + + echo "==> Start runner" gitlab-runner run -c /data/config.toml EOF @@ -121,7 +131,7 @@ resource "aws_ecs_task_definition" "gitlab_runner_manager" { container_definitions = jsonencode([ { name : "app", - image : "luktom/ws", + image : "pragmaticcoders/ws", essential : true, user : "root", entryPoint : [ @@ -164,6 +174,10 @@ resource "aws_ecs_task_definition" "gitlab_runner_manager" { name : "REGISTER_NON_INTERACTIVE", value : "true", }, + { + name : "CONCURRENT", + value : var.concurrent_value, + }, ], mountPoints : [ { diff --git a/gitlab-runner-environment/efs.tf b/gitlab-runner-environment/efs.tf index bb40c70..caca8b1 100644 --- a/gitlab-runner-environment/efs.tf +++ b/gitlab-runner-environment/efs.tf @@ -1,5 +1,5 @@ module "efs" { - source = "github.com/tomaszkiewicz/terraform-modules/efs" + source = "github.com/pragmaticcoders/terraform-modules/efs" subnet_ids = module.vpc.public_subnet_ids security_group_ids = [ @@ -9,7 +9,7 @@ module "efs" { } module "efs_sg" { - source = "github.com/tomaszkiewicz/terraform-modules/sg" + source = "github.com/pragmaticcoders/terraform-modules/sg" name = "efs" vpc_id = module.vpc.id diff --git a/gitlab-tf-cloud-tenant/variables.tf b/gitlab-tf-cloud-tenant/variables.tf index 7a95f7c..aa3b4d0 100644 --- a/gitlab-tf-cloud-tenant/variables.tf +++ b/gitlab-tf-cloud-tenant/variables.tf @@ -1,5 +1,5 @@ variable "tenant" {} -variable "mail_domain" { default = "luktom.net" } +variable "mail_domain" { default = "pragmaticcoders.com" } variable "group_name" { default = "" } variable "group_path" { default = "" } variable "envs" { diff --git a/iam/role/main.tf b/iam/role/main.tf index 2e8fa72..6b5d84a 100644 --- a/iam/role/main.tf +++ b/iam/role/main.tf @@ -38,3 +38,8 @@ resource "aws_iam_role_policy_attachment" "attachment" { role = aws_iam_role.role.name policy_arn = each.value } + +resource "aws_iam_role_policy_attachment" "lambda_vpc_execution_role" { + role = aws_iam_role.role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole" +} \ No newline at end of file diff --git a/lambda/alerts/main.tf b/lambda/alerts/main.tf index 391f771..b467354 100644 --- a/lambda/alerts/main.tf +++ b/lambda/alerts/main.tf @@ -1,7 +1,7 @@ resource "aws_cloudwatch_metric_alarm" "lambda_concurrent_executions" { count = var.notifications_sns_topic_arn != "" ? 1 : 0 - alarm_name = "lambda-concurrent-executions" + alarm_name = var.product_prefix != "" ? "${var.product_prefix}-lambda-concurrent-executions" : "lambda-concurrent-executions" alarm_description = "This alarm monitors concurrent executions sum across all lambdas" comparison_operator = "GreaterThanThreshold" evaluation_periods = "1" @@ -19,7 +19,7 @@ resource "aws_cloudwatch_metric_alarm" "lambda_concurrent_executions" { resource "aws_cloudwatch_metric_alarm" "lambda_invocations" { count = var.notifications_sns_topic_arn != "" ? 1 : 0 - alarm_name = "lambda-invocations" + alarm_name = var.product_prefix != "" ? "${var.product_prefix}-lambda-invocations" : "lambda-invocations" alarm_description = "This alarm monitors invocations sum across all lambdas" comparison_operator = "GreaterThanThreshold" evaluation_periods = "1" @@ -37,7 +37,7 @@ resource "aws_cloudwatch_metric_alarm" "lambda_invocations" { resource "aws_cloudwatch_metric_alarm" "lambda_throttles" { count = var.notifications_sns_topic_arn != "" ? 1 : 0 - alarm_name = "lambda-throttles" + alarm_name = var.product_prefix != "" ? "${var.product_prefix}-lambda-throttles" : "lambda-throttles" alarm_description = "This alarm monitors throttles sum across all lambdas" comparison_operator = "GreaterThanThreshold" evaluation_periods = "1" diff --git a/lambda/alerts/variable.tf b/lambda/alerts/variable.tf index b8ea86b..f7a68be 100644 --- a/lambda/alerts/variable.tf +++ b/lambda/alerts/variable.tf @@ -1,3 +1,4 @@ variable "concurrent_executions_threshold" { default = 800 } variable "invocations_threshold" { default = 3600 } variable "notifications_sns_topic_arn" { default = "" } +variable "product_prefix" { default = "" } \ No newline at end of file diff --git a/lambda/cloudwatch.tf b/lambda/cloudwatch.tf index c1e3438..98ffa31 100644 --- a/lambda/cloudwatch.tf +++ b/lambda/cloudwatch.tf @@ -10,7 +10,7 @@ resource "aws_cloudwatch_metric_alarm" "lambda_errors" { period = "1440" statistic = "Sum" threshold = 0 - treat_missing_data = "missing" + treat_missing_data = "ignore" alarm_actions = [var.notifications_sns_topic_arn] ok_actions = [var.notifications_sns_topic_arn] insufficient_data_actions = [var.notifications_sns_topic_arn] @@ -29,13 +29,12 @@ resource "aws_cloudwatch_metric_alarm" "lambda_memory_utilization" { evaluation_periods = "1" metric_name = "memory_utilization" namespace = "LambdaInsights" - period = "60" + period = "300" statistic = "Maximum" threshold = var.lambda_insights_memory_utilization_threshold - treat_missing_data = "missing" + treat_missing_data = "ignore" alarm_actions = [var.notifications_sns_topic_arn] ok_actions = [var.notifications_sns_topic_arn] - insufficient_data_actions = [var.notifications_sns_topic_arn] dimensions = { function_name = var.name diff --git a/lambda/lambda_layer_rds_ca.tf b/lambda/lambda_layer_rds_ca.tf new file mode 100644 index 0000000..98b751e --- /dev/null +++ b/lambda/lambda_layer_rds_ca.tf @@ -0,0 +1,5 @@ +module "lambda_layer_rds_ca" { + count = var.enable_lambda_layers_rds_ca != "" ? 1 : 0 + source = "./layers/rds-ca" + layer_name = var.lambda_layer_rds_ca_name +} \ No newline at end of file diff --git a/lambda/layers/rds-ca/main.tf b/lambda/layers/rds-ca/main.tf index 3983d46..4e19844 100644 --- a/lambda/layers/rds-ca/main.tf +++ b/lambda/layers/rds-ca/main.tf @@ -1,6 +1,6 @@ resource "aws_lambda_layer_version" "rds_ca" { filename = "/tmp/terraform-artifacts/lambda-layer-rds-ca.zip" - layer_name = "rds-ca" + layer_name = var.layer_name compatible_runtimes = [ "nodejs10.x", diff --git a/lambda/layers/rds-ca/variables.tf b/lambda/layers/rds-ca/variables.tf new file mode 100644 index 0000000..3f2a9ec --- /dev/null +++ b/lambda/layers/rds-ca/variables.tf @@ -0,0 +1,5 @@ +variable "layer_name" { + type = string + description = "Layer name" + default = "rds-ca" +} \ No newline at end of file diff --git a/lambda/locals.tf b/lambda/locals.tf index 975eb88..1bebe89 100644 --- a/lambda/locals.tf +++ b/lambda/locals.tf @@ -3,6 +3,7 @@ locals { var.layers, var.enable_lambda_insights ? [ "arn:aws:lambda:${data.aws_region.current.name}:580247275435:layer:LambdaInsightsExtension:${var.lambda_insights_version}", - ] : [] + ] : [], + var.enable_lambda_layers_rds_ca ? [ module.lambda_layer_rds_ca[0].arn ] : [] ) } diff --git a/lambda/main.tf b/lambda/main.tf index 9231a08..708a394 100644 --- a/lambda/main.tf +++ b/lambda/main.tf @@ -95,6 +95,7 @@ resource "aws_lambda_function" "lambda_external" { filename, source_code_hash, last_modified, + layers, ] } } diff --git a/lambda/variables.tf b/lambda/variables.tf index 38a12c7..9b4fcc4 100644 --- a/lambda/variables.tf +++ b/lambda/variables.tf @@ -1,4 +1,5 @@ variable "name" {} +variable "lambda_layer_rds_ca_name" { default = "lambda_layer_rds_ca" } variable "runtime" { default = "nodejs12.x" } variable "handler" { default = "index.handler" } variable "source_dir" { default = "" } @@ -20,6 +21,7 @@ variable "schedule_expression" { default = "" } variable "notifications_sns_topic_arn" { default = "" } variable "enable_lambda_insights" { default = false } +variable "enable_lambda_layers_rds_ca" { default = true } variable "lambda_insights_version" { default = 14 } variable "lambda_insights_memory_utilization_threshold" { default = 90 } diff --git a/loadbalancer/main.tf b/loadbalancer/main.tf new file mode 100644 index 0000000..1aeabaf --- /dev/null +++ b/loadbalancer/main.tf @@ -0,0 +1,64 @@ +resource "aws_lb" "app_loadbalancer" { + name = var.alb_name + internal = var.alb_internal + load_balancer_type = "application" + security_groups = var.alb_sg + subnets = var.alb_subnets + + enable_deletion_protection = false +} + +resource "aws_lb_target_group" "default" { + name = "${var.alb_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" "http" { + count = var.create_http_listener ? 1 : 0 + load_balancer_arn = aws_lb.app_loadbalancer.arn + port = var.listener_port + protocol = var.protocol + + default_action { + type = "fixed-response" + + fixed_response { + content_type = "text/plain" + message_body = "You shall not pass" + status_code = "503" + } + } +} + +resource "aws_lb_listener" "https" { + count = var.create_https_listener ? 1 : 0 + + load_balancer_arn = aws_lb.app_loadbalancer.arn + port = 443 + protocol = "HTTPS" + certificate_arn = var.certificate + ssl_policy = "ELBSecurityPolicy-2016-08" + + default_action { + type = "fixed-response" + + fixed_response { + content_type = "text/plain" + message_body = "You shall not pass" + status_code = "503" + } + } +} diff --git a/loadbalancer/output.tf b/loadbalancer/output.tf new file mode 100644 index 0000000..cba57be --- /dev/null +++ b/loadbalancer/output.tf @@ -0,0 +1,13 @@ +output "alb_listener_http_arn" { + value = var.create_http_listener ? aws_lb_listener.http[0].arn : null +} +output "alb_listener_https_arn" { + value = var.create_https_listener ? aws_lb_listener.https[0].arn : null +} +output "alb_target_group" { + value = aws_lb_target_group.default.arn +} + +output "alb_dns_name" { + value = aws_lb.app_loadbalancer.dns_name +} diff --git a/loadbalancer/variables.tf b/loadbalancer/variables.tf new file mode 100644 index 0000000..a9b1609 --- /dev/null +++ b/loadbalancer/variables.tf @@ -0,0 +1,27 @@ +variable "service_port" { default = 80 } +variable "vpc_id" {} +variable "health_check_path" {default = "/"} + +variable "alb_name" {default = "alb"} +variable "alb_sg" {} +variable "alb_subnets" {} +variable "alb_internal" {default = true} +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_port" {default = 80} +variable "protocol" {default = "HTTP"} +variable "listener_action" {default = "forward"} +variable "target_group_protocol" {default = "HTTP"} +variable "certificate" {default = ""} +variable "create_https_listener" { + description = "Running the listener for the HTTPS port" + default = false +} +variable "create_http_listener" { + description = "Running the listener for the HTTP port" + default = false +} diff --git a/organizations/main.tf b/organizations/main.tf index ea0b710..4bff225 100644 --- a/organizations/main.tf +++ b/organizations/main.tf @@ -12,6 +12,7 @@ resource "aws_organizations_organization" "main" { "sso.amazonaws.com", "cloudtrail.amazonaws.com", "config.amazonaws.com", + "macie.amazonaws.com", ] } diff --git a/organizations/provisioner_role.tf b/organizations/provisioner_role.tf index 025632d..86197bb 100644 --- a/organizations/provisioner_role.tf +++ b/organizations/provisioner_role.tf @@ -7,12 +7,12 @@ echo "Creating provisioner role for arn:${data.aws_partition.current.partition}: echo "==> 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..8be25fc 100644 --- a/rds/postgres/main.tf +++ b/rds/postgres/main.tf @@ -4,17 +4,20 @@ module "database" { identifier = var.name name = var.name + parameter_group_name = var.name + parameter_group_use_name_prefix = false - engine = "postgres" - engine_version = "11" - family = "postgres11" - major_engine_version = "11" + engine = var.engine + engine_version = var.engine_version + family = var.family + major_engine_version = var.major_engine_version apply_immediately = var.apply_immediately snapshot_identifier = var.snapshot_identifier - instance_class = var.instance_class - allocated_storage = var.allocated_storage - parameters = var.parameters + instance_class = var.instance_class + allocated_storage = var.allocated_storage + max_allocated_storage = var.max_allocated_storage + parameters = var.parameters performance_insights_enabled = var.performance_insights_enabled @@ -29,13 +32,14 @@ module "database" { port = var.port maintenance_window = "Sat:00:00-Sat:03:00" - backup_window = "03:00-06:00" + backup_window = var.backup_window backup_retention_period = var.backup_retention_period enabled_cloudwatch_logs_exports = var.cloudwatch_logs_exports subnet_ids = var.subnet_ids final_snapshot_identifier = var.name deletion_protection = var.deletion_protection publicly_accessible = var.publicly_accessible + multi_az = var.multi_az } locals { diff --git a/rds/postgres/variables.tf b/rds/postgres/variables.tf index 476b7ba..b01c111 100644 --- a/rds/postgres/variables.tf +++ b/rds/postgres/variables.tf @@ -26,3 +26,11 @@ variable "parameters" { } variable "performance_insights_enabled" { default = true } variable "snapshot_identifier" { default = "" } +variable "backup_window" { default = "03:00-06:00" } +variable "multi_az" { default = false } +variable "engine" { default = "postgres" } +variable "engine_version" { default = 11 } +variable "family" { default = "postgres11" } +variable "major_engine_version" { default = 11 } +variable "max_allocated_storage" { default = 0 } + 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/output.tf b/s3/encrypted-bucket/output.tf index 11983a0..cdcc446 100644 --- a/s3/encrypted-bucket/output.tf +++ b/s3/encrypted-bucket/output.tf @@ -1,7 +1,7 @@ output "bucket" { - value = var.bucket + value = aws_s3_bucket.bucket.id } output "arn" { value = aws_s3_bucket.bucket.arn -} \ No newline at end of file +} 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/scripts/check-efs/check-efs-live.sh b/scripts/check-efs/check-efs-live.sh new file mode 100644 index 0000000..8dc2037 --- /dev/null +++ b/scripts/check-efs/check-efs-live.sh @@ -0,0 +1,30 @@ +#!/bin/bash +echo "check efs/shared-live" + +FILE=/efs/shared/check +if [ -f "$FILE" ]; then + echo "file exists - OK" +else + curl -X POST -H 'Content-type: application/json' --data '{"text":"EFS-shared-live-TIMEOUT!!!"}' https://hooks.slack.com/services/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 + echo "file send - OK" +fi + +echo "check efs/qodex-timescaledb-live" + +FILE=/efs/timescaledb/check +if [ -f "$FILE" ]; then + echo "file exists - OK" +else + curl -X POST -H 'Content-type: application/json' --data '{"text":"EFS-qodex-timescaledb-live-TIMEOUT!!!"}' https://hooks.slack.com/services/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 + echo "file send - OK" +fi + +echo "check efs/qodex-metricsdb-live" + +FILE=/efs/metricsdb/check +if [ -f "$FILE" ]; then + echo "file exists - OK" +else + curl -X POST -H 'Content-type: application/json' --data '{"text":"EFS-qodex-metricsdb-live-TIMEOUT!!!"}' https://hooks.slack.com/services/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 + echo "file send - OK" +fi \ No newline at end of file diff --git a/scripts/ecs-deploy.sh b/scripts/ecs-deploy.sh new file mode 100644 index 0000000..31117d8 --- /dev/null +++ b/scripts/ecs-deploy.sh @@ -0,0 +1,807 @@ +#!/usr/bin/env bash + +# Setup default values for variables +VERSION="3.10.0" +CLUSTER=false +SERVICE=false +TASK_DEFINITION=false +TASK_DEFINITION_FILE=false +MAX_DEFINITIONS=0 +AWS_ASSUME_ROLE=false +IMAGE=false +MIN=false +MAX=false +TIMEOUT=90 +VERBOSE=false +TAGVAR=false +TAGONLY="" +ENABLE_ROLLBACK=false +USE_MOST_RECENT_TASK_DEFINITION=false +AWS_CLI=$(which aws) +AWS_ECS="$AWS_CLI --output json ecs" +FORCE_NEW_DEPLOYMENT=false +SKIP_DEPLOYMENTS_CHECK=false +RUN_TASK=false +RUN_TASK_LAUNCH_TYPE=false +RUN_TASK_PLATFORM_VERSION=false +RUN_TASK_NETWORK_CONFIGURATION=false +RUN_TASK_WAIT_FOR_SUCCESS=false +TASK_DEFINITION_TAGS=false +COPY_TASK_DEFINITION_TAGS=false + +function usage() { + cat < /dev/null 2>&1 || { + echo "Some of the required software is not installed:" + echo " please install $1" >&2; + exit 4; + } +} + +function assumeRole() { + + temp_role=$(aws sts assume-role \ + --role-arn "${AWS_ASSUME_ROLE}" \ + --role-session-name "$(date +"%s")") + + export AWS_ACCESS_KEY_ID=$(echo $temp_role | jq .Credentials.AccessKeyId | xargs) + export AWS_SECRET_ACCESS_KEY=$(echo $temp_role | jq .Credentials.SecretAccessKey | xargs) + export AWS_SESSION_TOKEN=$(echo $temp_role | jq .Credentials.SessionToken | xargs) +} + + +function assumeRoleClean() { + unset AWS_ACCESS_KEY_ID + unset AWS_SECRET_ACCESS_KEY + unset AWS_SESSION_TOKEN +} + + +# Check that all required variables/combinations are set +function assertRequiredArgumentsSet() { + + # AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION and AWS_PROFILE can be set as environment variables + if [ -z ${AWS_ACCESS_KEY_ID+x} ]; then unset AWS_ACCESS_KEY_ID; fi + if [ -z ${AWS_SECRET_ACCESS_KEY+x} ]; then unset AWS_SECRET_ACCESS_KEY; fi + if [ -z ${AWS_DEFAULT_REGION+x} ]; + then unset AWS_DEFAULT_REGION + else + AWS_ECS="$AWS_ECS --region $AWS_DEFAULT_REGION" + fi + if [ -z ${AWS_PROFILE+x} ]; + then unset AWS_PROFILE + else + AWS_ECS="$AWS_ECS --profile $AWS_PROFILE" + fi + + if [ $SERVICE == false ] && [ $TASK_DEFINITION == false ]; then + echo "One of SERVICE or TASK DEFINITION is required. You can pass the value using -n / --service-name for a service, or -d / --task-definition for a task" + exit 5 + fi + if [ $SERVICE != false ] && [ $TASK_DEFINITION != false ]; then + echo "Only one of SERVICE or TASK DEFINITION may be specified, but you supplied both" + exit 6 + fi + if [ $SERVICE != false ] && [ $CLUSTER == false ]; then + echo "CLUSTER is required. You can pass the value using -c or --cluster" + exit 7 + fi + if [ $IMAGE == false ] && [ $FORCE_NEW_DEPLOYMENT == false ]; then + echo "IMAGE is required. You can pass the value using -i or --image" + exit 8 + fi + if ! [[ $MAX_DEFINITIONS =~ ^-?[0-9]+$ ]]; then + echo "MAX_DEFINITIONS must be numeric, or not defined." + exit 9 + fi + + if [ $RUN_TASK == false ] && [ $RUN_TASK_LAUNCH_TYPE != false ]; then + echo 'LAUNCH TYPE requires setting RUN TASK argument. You can set it using --run-task flag.' + exit 10 + fi + + if [ $RUN_TASK == false ] && [ $RUN_TASK_NETWORK_CONFIGURATION != false ]; then + echo 'NETWORK CONFIGURATION requires setting RUN TASK argument. You can set it using --run-task flag.' + exit 11 + fi + + if [ $RUN_TASK == false ] && [ $RUN_TASK_WAIT_FOR_SUCCESS != false ]; then + echo 'WAIT FOR SUCCESS requires setting RUN TASK argument. You can set it using --run-task flag.' + exit 11 + fi + + if [ $RUN_TASK == false ] && [ $RUN_TASK_PLATFORM_VERSION != false ]; then + echo 'PLATFORM VERSION requires setting RUN TASK argument. You can set it using --run-task flag.' + exit 12 + fi + +} + +function parseImageName() { + + # Define regex for image name + # This regex will create groups for: + # - domain + # - port + # - repo + # - image + # - tag + # If a group is missing it will be an empty string + if [[ "x$TAGONLY" == "x" ]]; then + imageRegex="^([a-zA-Z0-9\.\-]+):?([0-9]+)?/([a-zA-Z0-9\._\-]+)(/[\/a-zA-Z0-9\._\-]+)?:?([a-zA-Z0-9\._\-]+)?$" + else + imageRegex="^:?([a-zA-Z0-9\._-]+)?$" + fi + + if [[ $IMAGE =~ $imageRegex ]]; then + # Define variables from matching groups + if [[ "x$TAGONLY" == "x" ]]; then + domain=${BASH_REMATCH[1]} + port=${BASH_REMATCH[2]} + repo=${BASH_REMATCH[3]} + img=${BASH_REMATCH[4]/#\//} + tag=${BASH_REMATCH[5]} + + # Validate what we received to make sure we have the pieces needed + if [[ "x$domain" == "x" ]]; then + echo "Image name does not contain a domain or repo as expected. See usage for supported formats." + exit 10; + fi + if [[ "x$repo" == "x" ]]; then + echo "Image name is missing the actual image name. See usage for supported formats." + exit 11; + fi + + # When a match for image is not found, the image name was picked up by the repo group, so reset variables + if [[ "x$img" == "x" ]]; then + img=$repo + repo="" + fi + else + tag=${BASH_REMATCH[1]} + domain="" + port="" + repo="" + img="" + fi + else + # check if using root level repo with format like mariadb or mariadb:latest + rootRepoRegex="^([a-zA-Z0-9\-]+):?([a-zA-Z0-9\.\-]+)?$" + if [[ $IMAGE =~ $rootRepoRegex ]]; then + img=${BASH_REMATCH[1]} + if [[ "x$img" == "x" ]]; then + echo "Invalid image name. See usage for supported formats." + exit 12 + fi + tag=${BASH_REMATCH[2]} + + # for root level repo, initialize unused variables for checks when rebuilding image below + domain="" + port="" + repo="" + + else + echo "Unable to parse image name: $IMAGE, check the format and try again" + exit 13 + fi + fi + + # If tag is missing make sure we can get it from env var, or use latest as default + if [[ "x$tag" == "x" ]]; then + if [[ $TAGVAR == false ]]; then + tag="latest" + else + tag=${!TAGVAR} + if [[ "x$tag" == "x" ]]; then + tag="latest" + fi + fi + fi + + # Reassemble image name + useImage="" + if [[ "x$TAGONLY" == "x" ]]; then + + if [[ ! -z "$domain" ]]; then + useImage="$domain" + fi + if [[ ! -z "$port" ]]; then + useImage="$useImage:$port" + fi + if [[ ! -z "$repo" ]]; then + useImage="$useImage/$repo" + fi + if [[ ! -z "$img" ]]; then + if [[ -z "$useImage" ]]; then + useImage="$img" + else + useImage="$useImage/$img" + fi + fi + imageWithoutTag="$useImage" + if [[ ! -z "$tag" ]]; then + useImage="$useImage:$tag" + fi + + else + useImage="$TAGONLY" + fi + + # If in test mode output $useImage + if [ "$BASH_SOURCE" != "$0" ]; then + echo $useImage + fi +} + +function getCurrentTaskDefinition() { + if [ $SERVICE != false ]; then + # Get current task definition arn from service + TASK_DEFINITION_ARN=`$AWS_ECS describe-services --services $SERVICE --cluster $CLUSTER | jq -r .services[0].taskDefinition` + TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN` + + # For rollbacks + LAST_USED_TASK_DEFINITION_ARN=$TASK_DEFINITION_ARN + + if [ $USE_MOST_RECENT_TASK_DEFINITION != false ]; then + # Use the most recently created TD of the family; rather than the most recently used. + TASK_DEFINITION_FAMILY=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN | jq -r .taskDefinition.family` + TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_FAMILY` + TASK_DEFINITION_ARN=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_FAMILY | jq -r .taskDefinition.taskDefinitionArn` + fi + elif [ $TASK_DEFINITION != false ]; then + # Get current task definition arn from family[:revision] (or arn) + TASK_DEFINITION_ARN=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION | jq -r .taskDefinition.taskDefinitionArn` + fi + + # Get task definition using current task definition arn + # If we're copying task definition tags to the new revision, also get current task definition tags + if [[ "$COPY_TASK_DEFINITION_TAGS" == true ]]; then + TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN --include TAGS` + TASK_DEFINITION_TAGS=$( echo "$TASK_DEFINITION" | jq ".tags" ) + else + TASK_DEFINITION=`$AWS_ECS describe-task-definition --task-def $TASK_DEFINITION_ARN` + fi +} + +function createNewTaskDefJson() { + + if [ $TASK_DEFINITION_FILE == false ]; then + taskDefinition="$TASK_DEFINITION" + else + taskDefinition="$(cat $TASK_DEFINITION_FILE)" + fi + + # Get a JSON representation of the current task definition + # + Update definition to use new image name + # + Filter the def + if [[ "x$TAGONLY" == "x" ]]; then + DEF=$( echo "$taskDefinition" \ + | sed -e 's~"image":.*'"${imageWithoutTag}"'.*,~"image": "'"${useImage}"'",~g' \ + | jq '.taskDefinition' ) + else + DEF=$( echo "$taskDefinition" \ + | sed -e "s|\(\"image\": *\".*:\)\(.*\)\"|\1${useImage}\"|g" \ + | jq '.taskDefinition' ) + fi + + # Default JQ filter for new task definition + NEW_DEF_JQ_FILTER="family: .family, volumes: .volumes, containerDefinitions: .containerDefinitions, placementConstraints: .placementConstraints" + + # Some options in task definition should only be included in new definition if present in + # current definition. If found in current definition, append to JQ filter. + CONDITIONAL_OPTIONS=(networkMode taskRoleArn placementConstraints executionRoleArn) + for i in "${CONDITIONAL_OPTIONS[@]}"; do + re=".*${i}.*" + if [[ "$DEF" =~ $re ]]; then + NEW_DEF_JQ_FILTER="${NEW_DEF_JQ_FILTER}, ${i}: .${i}" + fi + done + + # Updated jq filters for AWS Fargate + REQUIRES_COMPATIBILITIES=$(echo "${DEF}" | jq -r '. | select(.requiresCompatibilities != null) | .requiresCompatibilities[]') + if `echo ${REQUIRES_COMPATIBILITIES[@]} | grep -q "FARGATE"`; then + FARGATE_JQ_FILTER='requiresCompatibilities: .requiresCompatibilities, cpu: .cpu, memory: .memory' + + if [[ ! "$NEW_DEF_JQ_FILTER" =~ ".*executionRoleArn.*" ]]; then + FARGATE_JQ_FILTER="${FARGATE_JQ_FILTER}, executionRoleArn: .executionRoleArn" + fi + NEW_DEF_JQ_FILTER="${NEW_DEF_JQ_FILTER}, ${FARGATE_JQ_FILTER}" + fi + + # Build new DEF with jq filter + NEW_DEF=$(echo "$DEF" | jq "{${NEW_DEF_JQ_FILTER}}") + + # If in test mode output $NEW_DEF + if [ "$BASH_SOURCE" != "$0" ]; then + echo "$NEW_DEF" + fi +} + +function registerNewTaskDefinition() { + # Register the new task definition, and store its ARN + if [[ "$COPY_TASK_DEFINITION_TAGS" == true && "$TASK_DEFINITION_TAGS" != false ]]; then + NEW_TASKDEF=`$AWS_ECS register-task-definition --cli-input-json "$NEW_DEF" --tags "$TASK_DEFINITION_TAGS" | jq -r .taskDefinition.taskDefinitionArn` + else + NEW_TASKDEF=`$AWS_ECS register-task-definition --cli-input-json "$NEW_DEF" | jq -r .taskDefinition.taskDefinitionArn` + fi +} + +function rollback() { + echo "Rolling back to ${LAST_USED_TASK_DEFINITION_ARN}" + $AWS_ECS update-service --cluster $CLUSTER --service $SERVICE --task-definition $LAST_USED_TASK_DEFINITION_ARN > /dev/null +} + +function updateServiceForceNewDeployment() { + echo 'Force a new deployment of the service' + $AWS_ECS update-service --cluster $CLUSTER --service $SERVICE --force-new-deployment > /dev/null +} + +function updateService() { + if [[ $(echo ${NEW_DEF} | jq ".containerDefinitions[0].healthCheck != null") == true ]]; then + checkFieldName="healthStatus" + checkFieldValue="HEALTHY" + else + checkFieldName="lastStatus" + checkFieldValue="RUNNING" + fi + + UPDATE_SERVICE_SUCCESS="false" + DEPLOYMENT_CONFIG="" + if [ $MAX != false ]; then + DEPLOYMENT_CONFIG=",maximumPercent=$MAX" + fi + if [ $MIN != false ]; then + DEPLOYMENT_CONFIG="$DEPLOYMENT_CONFIG,minimumHealthyPercent=$MIN" + fi + if [ ! -z "$DEPLOYMENT_CONFIG" ]; then + DEPLOYMENT_CONFIG="--deployment-configuration ${DEPLOYMENT_CONFIG:1}" + fi + + DESIRED_COUNT="" + if [ ! -z ${DESIRED+undefined-guard} ]; then + DESIRED_COUNT="--desired-count $DESIRED" + fi + + # Update the service + UPDATE=`$AWS_ECS update-service --cluster $CLUSTER --service $SERVICE $DESIRED_COUNT --task-definition $NEW_TASKDEF $DEPLOYMENT_CONFIG` + + # Only excepts RUNNING state from services whose desired-count > 0 + SERVICE_DESIREDCOUNT=`$AWS_ECS describe-services --cluster $CLUSTER --service $SERVICE | jq '.services[]|.desiredCount'` + if [ $SERVICE_DESIREDCOUNT -gt 0 ]; then + # See if the service is able to come up again + every=10 + i=0 + while [ $i -lt $TIMEOUT ] + do + # Scan the list of running tasks for that service, and see if one of them is the + # new version of the task definition + + RUNNING_TASKS=$($AWS_ECS list-tasks --cluster "$CLUSTER" --service-name "$SERVICE" --desired-status RUNNING \ + | jq -r '.taskArns[]') + + if [[ ! -z $RUNNING_TASKS ]] ; then + RUNNING=$($AWS_ECS describe-tasks --cluster "$CLUSTER" --tasks $RUNNING_TASKS \ + | jq ".tasks[]| if .taskDefinitionArn == \"$NEW_TASKDEF\" then . else empty end|.${checkFieldName}" \ + | grep -e "${checkFieldValue}") || : + + if [ "$RUNNING" ]; then + echo "Service updated successfully, new task definition running."; + + if [[ $MAX_DEFINITIONS -gt 0 ]]; then + FAMILY_PREFIX=${TASK_DEFINITION_ARN##*:task-definition/} + FAMILY_PREFIX=${FAMILY_PREFIX%*:[0-9]*} + TASK_REVISIONS=`$AWS_ECS list-task-definitions --family-prefix $FAMILY_PREFIX --status ACTIVE --sort ASC` + NUM_ACTIVE_REVISIONS=$(echo "$TASK_REVISIONS" | jq ".taskDefinitionArns|length") + if [[ $NUM_ACTIVE_REVISIONS -gt $MAX_DEFINITIONS ]]; then + LAST_OUTDATED_INDEX=$(($NUM_ACTIVE_REVISIONS - $MAX_DEFINITIONS - 1)) + for i in $(seq 0 $LAST_OUTDATED_INDEX); do + OUTDATED_REVISION_ARN=$(echo "$TASK_REVISIONS" | jq -r ".taskDefinitionArns[$i]") + + echo "Deregistering outdated task revision: $OUTDATED_REVISION_ARN" + + $AWS_ECS deregister-task-definition --task-definition "$OUTDATED_REVISION_ARN" > /dev/null + done + fi + + fi + UPDATE_SERVICE_SUCCESS="true" + break + fi + fi + + sleep $every + i=$(( $i + $every )) + done + + if [[ "${UPDATE_SERVICE_SUCCESS}" != "true" ]]; then + # Timeout + echo "ERROR: New task definition not running within $TIMEOUT seconds" + if [[ "${ENABLE_ROLLBACK}" != "false" ]]; then + rollback + fi + exit 1 + fi + else + echo "Skipping check for running task definition, as desired-count <= 0" + fi +} + +function waitForGreenDeployment { + DEPLOYMENT_SUCCESS="false" + every=2 + i=0 + echo "Waiting for service deployment to complete..." + while [ $i -lt $TIMEOUT ] + do + NUM_DEPLOYMENTS=$($AWS_ECS describe-services --services $SERVICE --cluster $CLUSTER | jq "[.services[].deployments[]] | length") + + # Wait to see if more than 1 deployment stays running + # If the wait time has passed, we need to roll back + if [ $NUM_DEPLOYMENTS -eq 1 ]; then + echo "Service deployment successful." + DEPLOYMENT_SUCCESS="true" + # Exit the loop. + i=$TIMEOUT + else + sleep $every + i=$(( $i + $every )) + fi + done + + if [[ "${DEPLOYMENT_SUCCESS}" != "true" ]]; then + if [[ "${ENABLE_ROLLBACK}" != "false" ]]; then + rollback + fi + exit 1 + fi +} + +function runTask { + echo "Run task: $NEW_TASKDEF"; + AWS_ECS_RUN_TASK="$AWS_ECS run-task --cluster $CLUSTER --task-definition $NEW_TASKDEF" + if [ $RUN_TASK_LAUNCH_TYPE != false ]; then + AWS_ECS_RUN_TASK="$AWS_ECS_RUN_TASK --launch-type $RUN_TASK_LAUNCH_TYPE" + fi + + if [ $RUN_TASK_PLATFORM_VERSION != false ]; then + AWS_ECS_RUN_TASK="$AWS_ECS_RUN_TASK --platform-version $RUN_TASK_PLATFORM_VERSION" + fi + + if [ $RUN_TASK_NETWORK_CONFIGURATION != false ]; then + AWS_ECS_RUN_TASK="$AWS_ECS_RUN_TASK --network-configuration \"$RUN_TASK_NETWORK_CONFIGURATION\"" + fi + + TASK_ARN=$(eval $AWS_ECS_RUN_TASK | jq -r '.tasks[0].taskArn') + echo "Executed task: $TASK_ARN" + + if [ $RUN_TASK_WAIT_FOR_SUCCESS == true ]; then + RUN_TASK_SUCCESS=false + every=10 + i=0 + while [ $i -lt $TIMEOUT ] + do + + TASK_JSON=$($AWS_ECS describe-tasks --cluster "$CLUSTER" --tasks "$TASK_ARN") + + TASK_STATUS=$(echo $TASK_JSON | jq -r '.tasks[0].lastStatus') + TASK_EXIT_CODE=$(echo $TASK_JSON | jq -r '.tasks[0].containers[0].exitCode') + + if [ $TASK_STATUS == "STOPPED" ]; then + echo "Task finished with status: $TASK_STATUS" + if [ $TASK_EXIT_CODE != 0 ]; then + echo "Task execution failed with exit code: $TASK_EXIT_CODE" + exit 1 + fi + RUN_TASK_SUCCESS=true + break; + fi + + echo "Checking task status every $every seconds. Status: $TASK_STATUS" + + sleep $every + i=$(( $i + $every )) + done + + if [ $RUN_TASK_SUCCESS == false ]; then + echo "ERROR: New task run took longer than $TIMEOUT seconds" + exit 1 + fi + fi + + + + echo "Task $TASK_ARN executed successfully!" + exit 0 + +} + +###################################################### +# When not being tested, run application as expected # +###################################################### +if [ "$BASH_SOURCE" == "$0" ]; then + set -o errexit + set -o pipefail + set -u + set -e + # If no args are provided, display usage information + if [ $# == 0 ]; then usage; fi + + # Check for AWS, AWS Command Line Interface + require aws + # Check for jq, Command-line JSON processor + require jq + + # Loop through arguments, two at a time for key and value + while [[ $# -gt 0 ]] + do + key="$1" + + case $key in + -k|--aws-access-key) + AWS_ACCESS_KEY_ID="$2" + shift # past argument + ;; + -s|--aws-secret-key) + AWS_SECRET_ACCESS_KEY="$2" + shift # past argument + ;; + -r|--region) + AWS_DEFAULT_REGION="$2" + shift # past argument + ;; + -p|--profile) + AWS_PROFILE="$2" + shift # past argument + ;; + --aws-instance-profile) + echo "--aws-instance-profile is not yet in use" + AWS_IAM_ROLE=true + ;; + -a|--aws-assume-role) + AWS_ASSUME_ROLE="$2" + shift + ;; + -c|--cluster) + CLUSTER="$2" + shift # past argument + ;; + -n|--service-name) + SERVICE="$2" + shift # past argument + ;; + -d|--task-definition) + TASK_DEFINITION="$2" + shift + ;; + -i|--image) + IMAGE="$2" + shift + ;; + -t|--timeout) + TIMEOUT="$2" + shift + ;; + -m|--min) + MIN="$2" + shift + ;; + -M|--max) + MAX="$2" + shift + ;; + -D|--desired-count) + DESIRED="$2" + shift + ;; + -e|--tag-env-var) + TAGVAR="$2" + shift + ;; + -to|--tag-only) + TAGONLY="$2" + shift + ;; + --max-definitions) + MAX_DEFINITIONS="$2" + shift + ;; + --task-definition-file) + TASK_DEFINITION_FILE="$2" + shift + ;; + --enable-rollback) + ENABLE_ROLLBACK=true + ;; + --use-latest-task-def) + USE_MOST_RECENT_TASK_DEFINITION=true + ;; + --force-new-deployment) + FORCE_NEW_DEPLOYMENT=true + ;; + --skip-deployments-check) + SKIP_DEPLOYMENTS_CHECK=true + ;; + --run-task) + RUN_TASK=true + ;; + --launch-type) + RUN_TASK_LAUNCH_TYPE="$2" + shift + ;; + --platform-version) + RUN_TASK_PLATFORM_VERSION="$2" + shift + ;; + --wait-for-success) + RUN_TASK_WAIT_FOR_SUCCESS=true + ;; + --network-configuration) + RUN_TASK_NETWORK_CONFIGURATION="$2" + shift + ;; + --copy-task-definition-tags) + COPY_TASK_DEFINITION_TAGS=true + ;; + -v|--verbose) + VERBOSE=true + ;; + --version) + echo ${VERSION} + exit 0 + ;; + *) + #If another key was given that is not empty display usage. + if [[ ! -z "$key" ]]; then + usage + exit 2 + fi + ;; + esac + shift # past argument or value + done + + if [ $VERBOSE == true ]; then + set -x + fi + + # Check that required arguments are provided + assertRequiredArgumentsSet + + if [[ "$AWS_ASSUME_ROLE" != false ]]; then + assumeRole + fi + + # Not required creation of new a task definition + if [ $FORCE_NEW_DEPLOYMENT == true ]; then + updateServiceForceNewDeployment + if [[ $SKIP_DEPLOYMENTS_CHECK != true ]]; then + waitForGreenDeployment + fi + exit 0 + fi + + # Determine image name + parseImageName + echo "Using image name: $useImage" + + # Get current task definition + getCurrentTaskDefinition + echo "Current task definition: $TASK_DEFINITION_ARN"; + + # create new task definition json + createNewTaskDefJson + + # register new task definition + registerNewTaskDefinition + echo "New task definition: $NEW_TASKDEF"; + + # update service if needed + if [ $SERVICE == false ]; then + if [ $RUN_TASK == true ]; then + runTask + fi + echo "Task definition updated successfully" + else + updateService + + if [[ $SKIP_DEPLOYMENTS_CHECK != true ]]; then + waitForGreenDeployment + fi + fi + + if [[ "$AWS_ASSUME_ROLE" != false ]]; then + assumeRoleClean + fi + + exit 0 + +fi +############################# +# End application run logic # +############################# 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 = < 0 && var.notifications_sns_topic_arn != "" ? 1 : 0 - alarm_name = "ses-daily-sent-limit" + alarm_name = var.product_prefix != "" ? "${var.product_prefix}-ses-daily-sent-limit" : "ses-daily-sent-limit" alarm_description = "This alert monitors the number of mails sent during last day" comparison_operator = "GreaterThanThreshold" evaluation_periods = "1" diff --git a/ses/configuration_set.tf b/ses/configuration_set.tf new file mode 100644 index 0000000..11edb32 --- /dev/null +++ b/ses/configuration_set.tf @@ -0,0 +1,28 @@ +resource "aws_ses_configuration_set" "configuration_set" { + name = var.product_prefix != "" ? "${var.product_prefix}-complaint-set" : "complaint-set" + reputation_metrics_enabled = true +} + +resource "aws_ses_event_destination" "cloudwatch" { + name = var.product_prefix != "" ? "${var.product_prefix}-event-complaint-destination-cloudwatch" : "event-complaint-destination-cloudwatch" + configuration_set_name = aws_ses_configuration_set.configuration_set.name + enabled = true + matching_types = ["complaint", "bounce", "renderingFailure"] + + cloudwatch_destination { + default_value = "reputation" + dimension_name = "Email" + value_source = "messageTag" + } +} + +resource "aws_ses_event_destination" "sns" { + name = var.product_prefix != "" ? "${var.product_prefix}-event-complaint-destination-sns" : "event-complaint-destination-sns" + configuration_set_name = aws_ses_configuration_set.configuration_set.name + enabled = true + matching_types = ["complaint", "bounce", "renderingFailure"] + + sns_destination { + topic_arn = aws_sns_topic.bounce_notification.arn + } +} \ No newline at end of file diff --git a/ses/domains.tf b/ses/domains.tf index 83a4b27..960f871 100644 --- a/ses/domains.tf +++ b/ses/domains.tf @@ -33,3 +33,11 @@ resource "aws_route53_record" "amazonses_verification_record" { aws_ses_domain_identity.domain[each.key].verification_token ] } + +module dmarc { + source = "../ses-dmarc" + for_each = var.configure_dmarc ? var.domains : {} + dns_zone_id = each.value + domain = each.key +} + diff --git a/ses/variables.tf b/ses/variables.tf index 3c58cde..61d1319 100644 --- a/ses/variables.tf +++ b/ses/variables.tf @@ -2,6 +2,7 @@ variable "bounce_notification_lambda_arn" {} variable "bounce_notification_lambda_name" {} variable "daily_sent_limit" { default = 0 } variable "notifications_sns_topic_arn" { default = "" } +variable "product_prefix" { default = "" } variable "domains" { description = "A map of domains with their zone id's" @@ -15,3 +16,4 @@ variable "mails" { } variable "create_user" { default = false } +variable "configure_dmarc" { default = false } diff --git a/sg/main.tf b/sg/main.tf index 7099c4d..fbae807 100644 --- a/sg/main.tf +++ b/sg/main.tf @@ -6,5 +6,9 @@ resource "aws_security_group" "main" { tags = { Name = var.name } + + lifecycle { + create_before_destroy = true + } } diff --git a/slack-alarm-notification-lambda/download.tf b/slack-alarm-notification-lambda/download.tf index 8f8d6c7..a157e83 100644 --- a/slack-alarm-notification-lambda/download.tf +++ b/slack-alarm-notification-lambda/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 '{}'", ] } \ 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/ssm/main.tf b/ssm/main.tf new file mode 100644 index 0000000..c334b2c --- /dev/null +++ b/ssm/main.tf @@ -0,0 +1,14 @@ +resource "aws_ssm_parameter" "ssm_parameter" { + count = length(var.keys) + + name = "/${var.tenant}/${var.product}/${var.keys[count.index]}" + description = "Paremeter created via terraform" + type = var.type + value = "default" + key_id = var.key_id + lifecycle { + ignore_changes = [ + value, + ] + } +} diff --git a/ssm/outputs.tf b/ssm/outputs.tf new file mode 100644 index 0000000..f5cf179 --- /dev/null +++ b/ssm/outputs.tf @@ -0,0 +1,3 @@ +output "arn" { + value = { for key in var.keys : key => aws_ssm_parameter.ssm_parameter[index(var.keys,key)].arn } +} diff --git a/ssm/variables.tf b/ssm/variables.tf new file mode 100644 index 0000000..35f3511 --- /dev/null +++ b/ssm/variables.tf @@ -0,0 +1,16 @@ +variable "type"{ + default = "SecureString" + type = string +} +variable "tenant"{ + type = string +} +variable "product"{ + type = string +} +variable "keys"{ + type = list(string) +} +variable "key_id"{ + type = string +} \ No newline at end of file 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..15b27c5 100644 --- a/static-website/cloudfront.tf +++ b/static-website/cloudfront.tf @@ -12,7 +12,32 @@ resource "aws_cloudfront_distribution" "website" { } } - aliases = [for a in [ + dynamic "origin" { + for_each = var.cloudfront_origin_custom + content { + domain_name = origin.value["domain_name"] + origin_id = origin.key + custom_origin_config { + http_port = origin.value["http_port"] + https_port = origin.value["https_port"] + origin_protocol_policy = origin.value["origin_protocol_policy"] + origin_ssl_protocols = origin.value["origin_ssl_protocols"] + } + } + } + + dynamic "origin" { + for_each = var.cloudfront_origin_s3 + content { + domain_name = origin.value["domain_name"] + origin_id = origin.key + s3_origin_config { + origin_access_identity = aws_cloudfront_origin_access_identity.website.cloudfront_access_identity_path + } + } + } + + aliases = var.additional_alias !="" ? flatten([var.domain_name, var.additional_alias]) : [for a in [ var.domain_name, var.skip_www ? "" : format("%s%s", "www.", var.domain_name) ] : a if a != ""] @@ -50,10 +75,28 @@ resource "aws_cloudfront_distribution" "website" { } } + dynamic "ordered_cache_behavior" { + for_each = var.cloudfront_ordered_cache_behavior + content { + allowed_methods = ordered_cache_behavior.value["allowed_methods"] + cached_methods = ordered_cache_behavior.value["cached_methods"] + path_pattern = ordered_cache_behavior.key + target_origin_id = ordered_cache_behavior.value["target_origin_id"] + viewer_protocol_policy = ordered_cache_behavior.value["viewer_protocol_policy"] + forwarded_values { + query_string = ordered_cache_behavior.value["forwarded_values_query_string"] + cookies { + forward = ordered_cache_behavior.value["forwarded_values_cookies_forward"] + } + headers = ordered_cache_behavior.value["forwarded_values_headers"] + } + } + } + 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..4368d3e 100644 --- a/static-website/output.tf +++ b/static-website/output.tf @@ -16,4 +16,16 @@ output "domain_name" { output "bucket_name" { value = var.bucket_name -} \ No newline at end of file +} + +output "bucket_arn" { + value = aws_s3_bucket.website.arn +} + +output "bucket_id" { + value = aws_s3_bucket.website.id +} + +output "cloudfront_origin_access_identity_arn" { + value = aws_cloudfront_origin_access_identity.website.iam_arn +} diff --git a/static-website/variables.tf b/static-website/variables.tf index 12887bd..99b8617 100644 --- a/static-website/variables.tf +++ b/static-website/variables.tf @@ -1,4 +1,5 @@ variable "domain_name" {} +variable "additional_alias" {default = ""} variable "dns_zone_id" { default = "" } variable "certificate_arn" {} variable "versioning" { default = false } @@ -21,3 +22,15 @@ variable "lambda_viewer_request" { default = "" } locals { bucket_name = var.bucket_name == "" ? var.domain_name : var.bucket_name } +variable "cloudfront_ordered_cache_behavior" { + default = {} + description = "Ordered cache behavior dynamic pool" +} +variable "cloudfront_origin_custom" { + default = {} + description = "Definition of origins with custom origin config" +} +variable "cloudfront_origin_s3" { + default = {} + description = "Definition of origins based on s3 buckets" +} diff --git a/target-group/main.tf b/target-group/main.tf new file mode 100644 index 0000000..5f41abc --- /dev/null +++ b/target-group/main.tf @@ -0,0 +1,47 @@ +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 = var.health_check_matcher + } + lifecycle { + create_before_destroy = true + } +} + +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 + } + + dynamic "condition" { + for_each = var.host_header != "" ? [var.host_header] : [] + content { + host_header { + values = condition.value + } + } + } + dynamic "condition" { + for_each = var.path_pattern != "" ? [var.path_pattern] : [] + content { + path_pattern { + values = condition.value + } + } + } +} 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..df356a2 --- /dev/null +++ b/target-group/variables.tf @@ -0,0 +1,18 @@ +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 "path_pattern" {default = ""} +variable "service-name" {default = "app"} +variable "health_check_matcher" {default = "200-399"} \ 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/output.tf b/vpc/output.tf index 6955fa7..9da0936 100644 --- a/vpc/output.tf +++ b/vpc/output.tf @@ -33,3 +33,7 @@ output "public_subnets_ids_az" { output "private_subnets_ids_az" { value = zipmap(local.sliced_azs, module.vpc.private_subnets) } + +output "nat_gateway_public_ips" { + value = module.vpc.nat_public_ips +} 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" }