diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index f14e4487..26d97d65 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -36,10 +36,16 @@ jobs: aws --profile $AWS_PROFILE configure set aws_access_key_id $AWS_ACCESS_KEY_ID aws --profile $AWS_PROFILE configure set aws_secret_access_key $AWS_SECRET_ACCESS_KEY + - name: Configure Terraform + uses: hashicorp/setup-terraform@v3 + - name: Prepare settings env: - PARAM_JSON: ${{ secrets.PARAM_JSON }} - run: echo "$PARAM_JSON" > sys/cloudformation/parameters.secrets.prod.json + PARAM_TFVARS: ${{ secrets.PARAM_TFVARS }} + run: echo "$PARAM_TFVARS" > sys/terraform/terraform.tfvars + + - name: Terraform init + run: terraform init - name: Deploy env: diff --git a/.gitignore b/.gitignore index 7bc0a98f..af19e36b 100644 --- a/.gitignore +++ b/.gitignore @@ -45,4 +45,7 @@ sys/nginx/certs/ca-key.pem sys/nginx/certs/server.csr sys/nginx/certs/server.key sys/nginx/certs/server.pem +sys/terraform/.terraform +sys/terraform/terraform.tfvars +sys/terraform/terraform.tfstate ###< custom ### diff --git a/Makefile b/Makefile index 1ede5f6f..d2100d0b 100644 --- a/Makefile +++ b/Makefile @@ -80,23 +80,9 @@ PREVIOUS_TAG=$(shell git ls-remote --tags 2>&1 | awk '{print $$2}' | sort -r | h ECR_REGISTRY = $(AWS_ACCOUNT_ID).dkr.ecr.$(AWS_REGION).amazonaws.com deploy_prod: build_prod_images push_prod_images ## deploy to prod - cat sys/cloudformation/parameters.prod.json \ - | sed -e 's/{"ParameterKey": "EcrImageTagNginx", "ParameterValue": ".*"}/{"ParameterKey": "EcrImageTagNginx", "ParameterValue": "'$(VER)'"}/' \ - -e 's/{"ParameterKey": "EcrImageTagPhp", "ParameterValue": ".*"}/{"ParameterKey": "EcrImageTagPhp", "ParameterValue": "'$(VER)'"}/' \ - | tee sys/cloudformation/parameters.prod.json.new; \ - mv sys/cloudformation/parameters.prod.json sys/cloudformation/parameters.prod.json.bak; \ - mv sys/cloudformation/parameters.prod.json.new sys/cloudformation/parameters.prod.json; \ - cat sys/cloudformation/parameters.secrets.prod.json \ - | sed -e 's/{"ParameterKey": "EcrImageTagNginx", "ParameterValue": ".*"}/{"ParameterKey": "EcrImageTagNginx", "ParameterValue": "'$(VER)'"}/' \ - -e 's/{"ParameterKey": "EcrImageTagPhp", "ParameterValue": ".*"}/{"ParameterKey": "EcrImageTagPhp", "ParameterValue": "'$(VER)'"}/' \ - | tee sys/cloudformation/parameters.secrets.prod.json.new; \ - mv sys/cloudformation/parameters.secrets.prod.json sys/cloudformation/parameters.secrets.prod.json.bak; \ - mv sys/cloudformation/parameters.secrets.prod.json.new sys/cloudformation/parameters.secrets.prod.json; \ - aws --profile=$(AWS_PROFILE) cloudformation create-change-set --capabilities CAPABILITY_NAMED_IAM \ - --stack=poser-ecs \ - --change-set-name=poser-ecs-$(VER) \ - --template-body=file://$$PWD/sys/cloudformation/stack.yaml \ - --parameters=file://sys/cloudformation/parameters.secrets.prod.json +# TODO: convert to terraform apply +# TODO: modify IAM policy to allow only the creation of ECS tasks via pipelines + terraform plan -var="ecr_image_tag_nginx=$(VER)" -var="ecr_image_tag_php=$(VER)" build_%: export BADGE_POSER_REGISTRY = $(ECR_REGISTRY)/badge-poser build_%: export DOCKER_BUILDKIT = 1 diff --git a/sys/cloudformation/parameters.prod.json b/sys/cloudformation/parameters.prod.json deleted file mode 100644 index c46ac753..00000000 --- a/sys/cloudformation/parameters.prod.json +++ /dev/null @@ -1,19 +0,0 @@ -[ - {"ParameterKey": "ELBCertificateArn", "ParameterValue": ""}, - {"ParameterKey": "EcrImageTagNginx", "ParameterValue": "1734722017"}, - {"ParameterKey": "EcrImageTagPhp", "ParameterValue": "1734722017"}, - {"ParameterKey": "Environment", "ParameterValue": "prod"}, - {"ParameterKey": "ExecRoleArn", "ParameterValue": ""}, - {"ParameterKey": "Subnets", "ParameterValue": ""}, - {"ParameterKey": "VpcId", "ParameterValue": ""}, - - {"ParameterKey": "EnvAPPENV", "ParameterValue": "prod"}, - {"ParameterKey": "EnvREDISHOST", "ParameterValue": ""}, - {"ParameterKey": "EnvAPPSECRET", "ParameterValue": ""}, - {"ParameterKey": "EnvBITBUCKETSECRET", "ParameterValue": ""}, - {"ParameterKey": "EnvBITBUCKETTOKEN", "ParameterValue": ""}, - {"ParameterKey": "EnvGITLABTOKEN", "ParameterValue": ""}, - {"ParameterKey": "EnvCIRCLECITOKEN", "ParameterValue": ""}, - {"ParameterKey": "EnvGITHUBUSERNAME", "ParameterValue": ""}, - {"ParameterKey": "EnvSENTRYDSN", "ParameterValue": ""} -] diff --git a/sys/cloudformation/stack.png b/sys/cloudformation/stack.png deleted file mode 100644 index db199f9f..00000000 Binary files a/sys/cloudformation/stack.png and /dev/null differ diff --git a/sys/cloudformation/stack.yaml b/sys/cloudformation/stack.yaml index 1647c7c3..4c4ff616 100644 --- a/sys/cloudformation/stack.yaml +++ b/sys/cloudformation/stack.yaml @@ -187,6 +187,7 @@ Resources: # SECURITY GROUP sgelb: + DeletionPolicy: Retain Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub '${ServiceName}-elb' @@ -210,6 +211,7 @@ Resources: - Key: env Value: !Ref ServiceName sgecs: + DeletionPolicy: Retain Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub '${ServiceName}-ecs' @@ -233,6 +235,7 @@ Resources: - Key: env Value: !Ref ServiceName sgredis: + DeletionPolicy: Retain Type: AWS::EC2::SecurityGroup Properties: GroupDescription: !Sub '${ServiceName}-redis' @@ -254,6 +257,7 @@ Resources: # CLOUDWATCH LOGS cloudwatchloggroup: + DeletionPolicy: Retain Type: AWS::Logs::LogGroup Properties: LogGroupName: !Sub '${ServiceName}-logs' @@ -261,6 +265,7 @@ Resources: # ELB elb: + DeletionPolicy: Retain Type: AWS::ElasticLoadBalancingV2::LoadBalancer Properties: Name: !Sub '${ServiceName}-elb' @@ -273,6 +278,7 @@ Resources: # ELB LISTENER elblistener80: + DeletionPolicy: Retain Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: @@ -286,6 +292,7 @@ Resources: Protocol: HTTP elblistener443: + DeletionPolicy: Retain Type: AWS::ElasticLoadBalancingV2::Listener Properties: DefaultActions: @@ -303,6 +310,7 @@ Resources: # ECS CLUSTER ecscluster: + DeletionPolicy: Retain Type: AWS::ECS::Cluster Properties: ClusterName: !Sub '${ServiceName}-cluster-${Environment}' @@ -318,6 +326,7 @@ Resources: # ECS SERVICE ecsservice: + DeletionPolicy: Retain Type: AWS::ECS::Service Properties: Cluster: !Ref ecscluster @@ -342,8 +351,8 @@ Resources: # ECS TASK DEFINITION ecstask: - Type: AWS::ECS::TaskDefinition DeletionPolicy: Retain + Type: AWS::ECS::TaskDefinition UpdateReplacePolicy: Retain Properties: ExecutionRoleArn: !Ref ExecRoleArn @@ -430,6 +439,7 @@ Resources: # AUTO SCALING asscalabletarget: + DeletionPolicy: Retain Type: AWS::ApplicationAutoScaling::ScalableTarget DependsOn: - ecsservice @@ -443,6 +453,7 @@ Resources: # ELB TARGET GROUP elbtargetgroup: + DeletionPolicy: Retain Type: AWS::ElasticLoadBalancingV2::TargetGroup DependsOn: - elb @@ -468,6 +479,7 @@ Resources: # ELB LISTENER RULE elblistenerrule80: + DeletionPolicy: Retain Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: Actions: @@ -485,6 +497,7 @@ Resources: ListenerArn: !Ref elblistener80 Priority: 1 elblistenerrule443: + DeletionPolicy: Retain Type: AWS::ElasticLoadBalancingV2::ListenerRule Properties: Actions: @@ -501,6 +514,7 @@ Resources: # SCHEDULED TASK eventrulecontributorsupdate: + DeletionPolicy: Retain Type: AWS::Events::Rule Properties: Name: 'app-contributors-update' @@ -522,6 +536,7 @@ Resources: # IAM USER iamusergithubactions: + DeletionPolicy: Retain Type: AWS::IAM::User Properties: Policies: @@ -576,6 +591,7 @@ Resources: # IAM ACCESS-KEY iamkey: + DeletionPolicy: Retain Type: AWS::IAM::AccessKey Properties: UserName: !Ref iamusergithubactions diff --git a/sys/terraform/.terraform.lock.hcl b/sys/terraform/.terraform.lock.hcl new file mode 100644 index 00000000..537023c6 --- /dev/null +++ b/sys/terraform/.terraform.lock.hcl @@ -0,0 +1,25 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.82.2" + constraints = "~> 5.0" + hashes = [ + "h1:RuPaHbllUB8a2TGTyc149wJfoh6zhIEjUvFYKR6iP2E=", + "zh:0262fc96012fb7e173e1b7beadd46dfc25b1dc7eaef95b90e936fc454724f1c8", + "zh:397413613d27f4f54d16efcbf4f0a43c059bd8d827fe34287522ae182a992f9b", + "zh:436c0c5d56e1da4f0a4c13129e12a0b519d12ab116aed52029b183f9806866f3", + "zh:4d942d173a2553d8d532a333a0482a090f4e82a2238acf135578f163b6e68470", + "zh:624aebc549bfbce06cc2ecfd8631932eb874ac7c10eb8466ce5b9a2fbdfdc724", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:9e632dee2dfdf01b371cca7854b1ec63ceefa75790e619b0642b34d5514c6733", + "zh:a07567acb115b60a3df8f6048d12735b9b3bcf85ec92a62f77852e13d5a3c096", + "zh:ab7002df1a1be6432ac0eb1b9f6f0dd3db90973cd5b1b0b33d2dae54553dfbd7", + "zh:bc1ff65e2016b018b3e84db7249b2cd0433cb5c81dc81f9f6158f2197d6b9fde", + "zh:bcad84b1d767f87af6e1ba3dc97fdb8f2ad5de9224f192f1412b09aba798c0a8", + "zh:cf917dceaa0f9d55d9ff181b5dcc4d1e10af21b6671811b315ae2a6eda866a2a", + "zh:d8e90ecfb3216f3cc13ccde5a16da64307abb6e22453aed2ac3067bbf689313b", + "zh:d9054e0e40705df729682ad34c20db8695d57f182c65963abd151c6aba1ab0d3", + "zh:ecf3a4f3c57eb7e89f71b8559e2a71e4cdf94eea0118ec4f2cb37e4f4d71a069", + ] +} diff --git a/sys/terraform/cloudwatch.tf b/sys/terraform/cloudwatch.tf new file mode 100644 index 00000000..67ae45ff --- /dev/null +++ b/sys/terraform/cloudwatch.tf @@ -0,0 +1,27 @@ +# TODO: import dashboard + +resource "aws_cloudwatch_event_rule" "eventrulecontributorsupdate" { + name = "app-contributors-update" + schedule_expression = "rate(24 hours)" + state = "DISABLED" + // CF Property(Targets) = [ + // { + // Id = "phpfpm" + // Arn = aws_ecs_cluster.ecscluster.arn + // RoleArn = aws_iam_role.ecs_task_role.arn + // Input = "{"containerOverrides":[{"name":"phpfpm","command":["./bin/console","app:contributors:update"]}]}" + // EcsParameters = { + // TaskDefinitionArn = aws_ecs_task_definition.ecstask.arn + // LaunchType = "FARGATE" + // NetworkConfiguration = { + // AwsVpcConfiguration = { + // SecurityGroups = [ + // aws_security_group.sgecs.arn + // ] + // Subnets = var.subnets + // } + // } + // } + // } + // ] +} diff --git a/sys/terraform/data.tf b/sys/terraform/data.tf new file mode 100644 index 00000000..eb58f218 --- /dev/null +++ b/sys/terraform/data.tf @@ -0,0 +1,3 @@ +data "aws_region" "current" {} + +data "aws_caller_identity" "current" {} diff --git a/sys/terraform/ecs.tf b/sys/terraform/ecs.tf new file mode 100644 index 00000000..eb1dd825 --- /dev/null +++ b/sys/terraform/ecs.tf @@ -0,0 +1,106 @@ +resource "aws_security_group" "sgecs" { + description = "${var.service_name}-ecs" + name = "${var.service_name}-ecs" + egress { + cidr_blocks = ["0.0.0.0/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + } + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "sgecs_ingress_http" { + type = "ingress" + security_group_id = aws_security_group.sgecs.id + cidr_blocks = ["0.0.0.0/0"] + from_port = 80 + protocol = "tcp" + to_port = 80 +} +resource "aws_security_group_rule" "sgecs_ingress_https" { + type = "ingress" + security_group_id = aws_security_group.sgecs.id + cidr_blocks = ["0.0.0.0/0"] + from_port = 443 + protocol = "tcp" + to_port = 443 +} + +resource "aws_cloudwatch_log_group" "cloudwatchloggroup" { + name = "${var.service_name}-logs" + retention_in_days = 14 +} + +resource "aws_ecs_cluster" "ecscluster" { + name = "${var.service_name}-cluster-${var.environment}" + // CF Property(CapacityProviders) = [ + // "FARGATE", + // "FARGATE_SPOT" + // ] + setting { + name = "containerInsights" + value = "disabled" + } +} + +resource "aws_ecs_service" "ecsservice" { + cluster = aws_ecs_cluster.ecscluster.arn + desired_count = 1 + health_check_grace_period_seconds = 15 + + capacity_provider_strategy { + base = 0 + capacity_provider = "FARGATE_SPOT" + weight = 2 + } + capacity_provider_strategy { + base = 1 + capacity_provider = "FARGATE" + weight = 1 + } + + load_balancer { + container_name = "nginx" + container_port = 80 + target_group_arn = aws_lb_target_group.elbtargetgroup.id + } + name = var.service_name + task_definition = "${var.service_name}:${aws_ecs_task_definition.ecstask.revision}" + network_configuration { + assign_public_ip = true + security_groups = [aws_security_group.sgecs.id] + subnets = var.subnets + } +} + +resource "aws_ecs_task_definition" "ecstask" { + execution_role_arn = aws_iam_role.ecs_task_role.arn + container_definitions = templatefile("ecs/task-definition.json", { + account_id = data.aws_caller_identity.current.account_id + aws_region = data.aws_region.current.name + service_name = var.service_name + ecr_image_tag_nginx = var.ecr_image_tag_nginx + ecr_image_tag_php = var.ecr_image_tag_php + cloudwatch_log_group = aws_cloudwatch_log_group.cloudwatchloggroup.name + env_app_debug = var.env_app_debug + env_app_env = var.env_app_env + env_app_xdebug = var.env_app_xdebug + env_app_xdebug_host = var.env_app_xdebug_host + env_bitbucket_auth_method = var.env_bitbucket_auth_method + env_github_auth_method = var.env_github_auth_method + env_phpfpm_host = var.env_phpfpm_host + env_redis_host = aws_elasticache_cluster.rediscluster.cache_nodes[0].address + env_resolver_ip = var.env_resolver_ip + env_sentry_dsn = var.env_sentry_dsn + env_trusted_proxies = var.env_trusted_proxies + }) + memory = "2048" + family = var.service_name + requires_compatibilities = [ + "FARGATE" + ] + network_mode = "awsvpc" + cpu = "1024" + skip_destroy = true +} diff --git a/sys/terraform/ecs/task-definition.json b/sys/terraform/ecs/task-definition.json new file mode 100644 index 00000000..ed57e0d3 --- /dev/null +++ b/sys/terraform/ecs/task-definition.json @@ -0,0 +1,160 @@ +[{ + "name": "phpfpm", + "portMappings": [ + { + "hostPort": 9000, + "protocol": "tcp", + "containerPort": 9000 + } + ], + "command": [], + "credentialSpecs": [], + "dnsSearchDomains": [], + "dnsServers": [], + "dockerLabels": {}, + "dockerSecurityOptions": [], + "entryPoint": [], + "environment": [ + { + "name": "APP_ENV", + "value": "${env_app_env}" + }, + { + "name": "APP_DEBUG", + "value": "${env_app_debug}" + }, + { + "name": "APP_XDEBUG", + "value": "${env_appxdebug}" + }, + { + "name": "APP_XDEBUG_HOST", + "value": "${env_app_xdebug_host}" + }, + { + "name": "REDIS_HOST", + "value": "${env_redis_host}" + }, + { + "name": "GITHUB_AUTH_METHOD", + "value": "${env_github_auth_method}" + }, + { + "name": "SENTRY_DSN", + "value": "${env_sentry_dsn}" + }, + { + "name": "BITBUCKET_AUTH_METHOD", + "value": "${env_bitbucket_auth_method}" + }, + { + "name": "TRUSTED_PROXIES", + "value": "${env_trusted_proxies}" + } + ], + "image": "${account_id}.dkr.ecr.${aws_region}.amazonaws.com/${service_name}:phpfpm-${ecr_image_tag_php}", + "essential": true, + "environmentFiles": [], + "extraHosts": [], + "links": [], + "mountPoints": [], + "secrets": [ + { + "name": "APP_SECRET", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:APP_SECRET::" + }, + { + "name": "GITHUB_USERNAME", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:GITHUB_USERNAME::" + }, + { + "name": "GITHUB_SECRET", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:GITHUB_SECRET::" + }, + { + "name": "CIRCLE_CI_TOKEN", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:CIRCLE_CI_TOKEN::" + }, + { + "name": "BITBUCKET_SECRET", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:BITBUCKET_SECRET::" + }, + { + "name": "BITBUCKET_TOKEN", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:BITBUCKET_TOKEN::" + }, + { + "name": "GITLAB_TOKEN", + "valueFrom": "arn:aws:secretsmanager:${aws_region}:${account_id}:secret:${service_name}:GITLAB_TOKEN::" + } + ], + "systemControls": [], + "ulimits": [], + "volumesFrom": [], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "${cloudwatch_log_group}", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "${service_name}-phpfpm" + }, + "secretOptions": [] + } +}, +{ + "name": "nginx", + "dependsOn": [ + { + "condition": "START", + "containerName": "phpfpm" + } + ], + "command": [], + "credentialSpecs": [], + "dnsSearchDomains": [], + "dnsServers": [], + "dockerLabels": {}, + "dockerSecurityOptions": [], + "entryPoint": [], + "portMappings": [ + { + "hostPort": 80, + "protocol": "tcp", + "containerPort": 80 + } + ], + "environment": [ + { + "name": "PHPFPM_HOST", + "value": "${env_phpfpm_host}" + }, + { + "name": "REDIS_HOST", + "value": "${env_redis_host}" + }, + { + "name": "RESOLVER_IP", + "value": "${env_resolver_ip}" + } + ], + "image": "${account_id}.dkr.ecr.${aws_region}.amazonaws.com/${service_name}:nginx-${ecr_image_tag_nginx}", + "essential": true, + "environmentFiles": [], + "extraHosts": [], + "links": [], + "mountPoints": [], + "secrets": [], + "systemControls": [], + "ulimits": [], + "volumesFrom": [], + "logConfiguration": { + "logDriver": "awslogs", + "options": { + "awslogs-group": "${cloudwatchloggroup}", + "awslogs-region": "${aws_region}", + "awslogs-stream-prefix": "${service_name}-nginx" + }, + "secretOptions": [] + } +} +] diff --git a/sys/terraform/elb.tf b/sys/terraform/elb.tf new file mode 100644 index 00000000..175cc42c --- /dev/null +++ b/sys/terraform/elb.tf @@ -0,0 +1,119 @@ +resource "aws_security_group" "sgelb" { + description = "${var.service_name}-elb" + name = "${var.service_name}-elb" + egress { + cidr_blocks = ["0.0.0.0/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + } + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "sgelb_ingress_http" { + type = "ingress" + security_group_id = aws_security_group.sgelb.id + cidr_blocks = ["0.0.0.0/0"] + from_port = 80 + protocol = "tcp" + to_port = 80 +} +resource "aws_security_group_rule" "sgelb_ingress_https" { + type = "ingress" + security_group_id = aws_security_group.sgelb.id + cidr_blocks = ["0.0.0.0/0"] + from_port = 443 + protocol = "tcp" + to_port = 443 +} + +resource "aws_lb_target_group" "elbtargetgroup" { + name = "badegposer" + port = 80 + protocol = "HTTP" + vpc_id = var.vpc_id + target_type = "ip" +} + +resource "aws_lb" "elb" { + name = "${var.service_name}-elb" + subnets = var.subnets + security_groups = [aws_security_group.sgelb.id] +} + +resource "aws_lb_listener" "elblistener80" { + load_balancer_arn = aws_lb.elb.arn + port = 80 + protocol = "HTTP" + default_action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "AWS is a teapot" + status_code = "418" + } + } +} + +resource "aws_lb_listener" "elblistener443" { + load_balancer_arn = aws_lb.elb.arn + certificate_arn = aws_acm_certificate.cert.arn + port = 443 + default_action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "https teapot" + status_code = "418" + } + } +} + +resource "aws_appautoscaling_target" "asscalabletarget" { + max_capacity = 1 + min_capacity = 1 + resource_id = "service/${var.service_name}-cluster-${var.environment}/${var.service_name}" + role_arn = var.exec_role_arn_autoscale + scalable_dimension = "ecs:service:DesiredCount" + service_namespace = "ecs" +} + +resource "aws_lb_listener_rule" "elblistenerrule80" { + listener_arn = aws_lb_listener.elblistener80.arn + action { + type = "fixed-response" + fixed_response { + content_type = "text/plain" + message_body = "AWS is a teapot" + status_code = "418" + } + } + condition {} +} + +resource "aws_lb_listener_rule" "elblistenerrule443" { + listener_arn = aws_lb_listener.elblistener443.arn + action { + type = "forward" + target_group_arn = aws_lb_target_group.elbtargetgroup.arn + + forward { + target_group { + arn = aws_lb_target_group.elbtargetgroup.arn + weight = 1 + } + } + } + condition { + host_header { + values = [ + "poser.pugx.org", + ] + } + } + condition { + path_pattern { + values = ["/*"] + } + } +} diff --git a/sys/terraform/iam.tf b/sys/terraform/iam.tf new file mode 100644 index 00000000..d2c2619d --- /dev/null +++ b/sys/terraform/iam.tf @@ -0,0 +1,84 @@ +data "aws_iam_policy_document" "iamusergithubactions" { + statement { + sid = "GitHubActionsDeploy" + effect = "Allow" + actions = ["cloudformation:CreateChangeSet", "sts:GetCallerIdentity"] + resources = ["arn:aws:ecr:eu-west-1:*:repository/badge-poser", "arn:aws:cloudformation:eu-west-1:*:stack/poser-ecs/6ad34900-d679-11ea-a884-0a9b71aae734"] + } + statement { + sid = "GitHubActionsDeployECR" + effect = "Allow" + actions = [ + "ecr:BatchCheckLayerAvailability", + "ecr:BatchGetImage", + "ecr:CompleteLayerUpload", + "ecr:DescribeImages", + "ecr:DescribeRepositories", + "ecr:GetDownloadUrlForLayer", + "ecr:InitiateLayerUpload", + "ecr:ListImages", + "ecr:PutImage", + "ecr:UploadLayerPart" + ] + resources = ["arn:aws:ecr:eu-west-1:*:repository/badge-poser"] + } + statement { + sid = "GitHubActionsDeployECRToken" + effect = "Allow" + actions = ["ecr:GetAuthorizationToken"] + resources = ["*"] + } +} + +resource "aws_iam_user_policy" "lb_ro" { + name = "GitHubActionsDeploy" + user = aws_iam_user.iamusergithubactions.name + policy = data.aws_iam_policy_document.iamusergithubactions.json +} + +resource "aws_iam_user" "iamusergithubactions" { + name = "github_action_deploy" +} + +resource "aws_iam_access_key" "iamkey" { + user = aws_iam_user.iamusergithubactions.name +} + +resource "aws_iam_role" "ecs_task_role" { + name = "${var.service_name}-ecs-exec" + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Sid = "" + Principal = { + Service = "ecs-tasks.amazonaws.com" + } + }, + ] + }) +} + +resource "aws_iam_role_policy" "read_secrets_policy" { + name = "read-secrets" + role = aws_iam_role.ecs_task_role.id + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Action = ["secretsmanager:GetSecretValue", "secretsmanager:DescribeSecret"] + Effect = "Allow" + Resource = aws_secretsmanager_secret.poser.arn + }, + ] + }) +} + +resource "aws_iam_role_policy_attachment" "ecs_task_role" { + role = aws_iam_role.ecs_task_role.name + policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" +} diff --git a/sys/terraform/import.tf b/sys/terraform/import.tf new file mode 100644 index 00000000..4f43bae7 --- /dev/null +++ b/sys/terraform/import.tf @@ -0,0 +1,110 @@ +# ECS +import { + to = aws_security_group.sgecs + id = "sg-06c2c1b1e7d48f166" +} +import { + to = aws_security_group_rule.sgecs_ingress_http + id = "sg-06c2c1b1e7d48f166_ingress_tcp_80_80_0.0.0.0/0" +} +import { + to = aws_security_group_rule.sgecs_ingress_https + id = "sg-06c2c1b1e7d48f166_ingress_tcp_443_443_0.0.0.0/0" +} +import { + to = aws_ecs_cluster.ecscluster + id = "badge-poser-cluster-prod" +} +import { + to = aws_ecs_service.ecsservice + id = "badge-poser-cluster-prod/badge-poser" +} +import { + to = aws_ecs_task_definition.ecstask + id = "arn:aws:ecs:eu-west-1:478389220392:task-definition/badge-poser:138" +} + +# ELB +import { + to = aws_security_group.sgelb + id = "sg-039400b411ff60301" +} +import { + to = aws_security_group_rule.sgelb_ingress_http + id = "sg-039400b411ff60301_ingress_tcp_80_80_0.0.0.0/0" +} +import { + to = aws_security_group_rule.sgelb_ingress_https + id = "sg-039400b411ff60301_ingress_tcp_443_443_0.0.0.0/0" +} +import { + to = aws_lb_target_group.elbtargetgroup + id = "arn:aws:elasticloadbalancing:eu-west-1:478389220392:targetgroup/badegposer/d24c3e0c7d0276d3" +} +import { + to = aws_lb.elb + id = "arn:aws:elasticloadbalancing:eu-west-1:478389220392:loadbalancer/app/badge-poser-elb/81d698b74f86c6b9" +} +import { + to = aws_lb_listener.elblistener80 + id = "arn:aws:elasticloadbalancing:eu-west-1:478389220392:listener/app/badge-poser-elb/81d698b74f86c6b9/cff7a4a219047f82" +} +import { + to = aws_lb_listener.elblistener443 + id = "arn:aws:elasticloadbalancing:eu-west-1:478389220392:listener/app/badge-poser-elb/81d698b74f86c6b9/fc943ccbe12b086f" +} +import { + to = aws_lb_listener_rule.elblistenerrule80 + id = "arn:aws:elasticloadbalancing:eu-west-1:478389220392:listener-rule/app/badge-poser-elb/81d698b74f86c6b9/cff7a4a219047f82/23a572ed933cc547" +} +import { + to = aws_lb_listener_rule.elblistenerrule443 + id = "arn:aws:elasticloadbalancing:eu-west-1:478389220392:listener-rule/app/badge-poser-elb/81d698b74f86c6b9/fc943ccbe12b086f/57de0169e306c96e" +} + +# REDIS +import { + to = aws_security_group.sgredis + id = "sg-09ad9402145d8eb17" +} +import { + to = aws_security_group_rule.sgredis_ingress_redis + id = "sg-09ad9402145d8eb17_ingress_tcp_6379_6379_0.0.0.0/0" +} +import { + to = aws_elasticache_cluster.rediscluster + id = "poser-stats" +} +import { + to = aws_elasticache_subnet_group.redissubnet + id = "poser-subnet" +} + +# LOGS +import { + to = aws_cloudwatch_log_group.cloudwatchloggroup + id = "badge-poser-logs" +} + + +# CRONJOBS +import { + to = aws_cloudwatch_event_rule.eventrulecontributorsupdate + id = "default/app-contributors-update" +} + +# IAM +import { + to = aws_iam_user.iamusergithubactions + id = "github_action_deploy" +} +import { + to = aws_iam_user_policy.lb_ro + id = "github_action_deploy:GitHubActionsDeploy" +} + +# ACM +import { + to = aws_acm_certificate.cert + id = "arn:aws:acm:eu-west-1:478389220392:certificate/2225440f-8847-4834-a90b-4b81a0105955" +} diff --git a/sys/terraform/main.tf b/sys/terraform/main.tf new file mode 100644 index 00000000..6964e52e --- /dev/null +++ b/sys/terraform/main.tf @@ -0,0 +1,29 @@ +locals { + mappings = { + } + stack_name = "stack" +} + +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 5.0" + } + } + + # backend "s3" { + # bucket = "mybucket" + # key = "prod.tf" + # region = "eu-west-1" + # } +} + +provider "aws" { + profile = "poser" + default_tags { + tags = { + "env" = var.service_name + } + } +} diff --git a/sys/terraform/redis.tf b/sys/terraform/redis.tf new file mode 100644 index 00000000..e73a9ff4 --- /dev/null +++ b/sys/terraform/redis.tf @@ -0,0 +1,38 @@ +resource "aws_security_group" "sgredis" { + description = "${var.service_name}-redis" + name = "${var.service_name}-redis" + egress { + cidr_blocks = ["0.0.0.0/0"] + protocol = "-1" + from_port = 0 + to_port = 0 + } + vpc_id = var.vpc_id +} + +resource "aws_security_group_rule" "sgredis_ingress_redis" { + type = "ingress" + security_group_id = aws_security_group.sgredis.id + cidr_blocks = ["0.0.0.0/0"] + from_port = 6379 + protocol = "tcp" + to_port = 6379 +} + +resource "aws_elasticache_cluster" "rediscluster" { + cluster_id = "poser-stats" + auto_minor_version_upgrade = true + node_type = "cache.t4g.micro" + subnet_group_name = aws_elasticache_subnet_group.redissubnet.id + engine = "redis" + engine_version = "7.0" + security_group_ids = [aws_security_group.sgredis.id] + snapshot_retention_limit = 1 + transit_encryption_enabled = false +} + +resource "aws_elasticache_subnet_group" "redissubnet" { + name = "poser-subnet" + description = "poser-subnet" + subnet_ids = var.subnets +} diff --git a/sys/terraform/secret.tf b/sys/terraform/secret.tf new file mode 100644 index 00000000..e6c22f0f --- /dev/null +++ b/sys/terraform/secret.tf @@ -0,0 +1,16 @@ +resource "aws_secretsmanager_secret" "poser" { + name = var.service_name +} + +resource "aws_secretsmanager_secret_version" "poser" { + secret_id = aws_secretsmanager_secret.poser.id + secret_string = jsonencode({ + APP_SECRET = var.env_app_secret + GITHUB_SECRET = var.env_github_secret + GITHUB_USERNAME = var.env_github_username + CIRCLECI_TOKEN = var.env_circleci_token + BITBUCKET_SECRET = var.env_bitbucket_secret + BITBUCKET_TOKEN = var.env_bitbucket_token + GITLAB_TOKEN = var.env_gitlab_token + }) +} diff --git a/sys/terraform/variables.tf b/sys/terraform/variables.tf new file mode 100644 index 00000000..fd9c2b99 --- /dev/null +++ b/sys/terraform/variables.tf @@ -0,0 +1,128 @@ +variable "ecr_image_tag_nginx" { + description = "Specifies the ECR Image Tag for nginx container." + type = string +} + +variable "ecr_image_tag_php" { + description = "Specifies the ECR Image Tag for PHP-FPM container." + type = string +} + +variable "environment" { + description = "The Environment" + type = string +} + +variable "exec_role_arn_autoscale" { + description = "Specifies the ARN of the Execution Role for Autoscaling." + type = string +} + +variable "service_name" { + description = "The name of the service being created. It identifies all the resources related to it." + type = string + default = "badge-poser" +} + +variable "subnets" { + description = "Specifies the ID of Subnets belongin to the correct VPC." + type = list(string) +} + +variable "vpc_id" { + description = "Specifies the ID of an existing VPC in which to launch your container instances." + type = string +} + +variable "env_app_debug" { + description = "Environment variable for APP_DEBUG" + type = string + default = "0" +} + +variable "env_app_env" { + description = "Environment variable for APP_ENV" + type = string + default = "prod" +} + +variable "env_app_secret" { + description = "Environment variable for APP_SECRET" + type = string +} + +variable "env_app_xdebug" { + description = "Environment variable for APP_XDEBUG" + type = string + default = "0" +} + +variable "env_app_xdebug_host" { + description = "Environment variable for APP_XDEBUG_HOST" + type = string +} + +variable "env_bitbucket_auth_method" { + description = "Environment variable for BITBUCKET_AUTH_METHOD" + type = string + default = "http_password" +} + +variable "env_bitbucket_secret" { + description = "Environment variable for BITBUCKET_SECRET" + type = string +} + +variable "env_bitbucket_token" { + description = "Environment variable for BITBUCKET_TOKEN" + type = string +} + +variable "env_circleci_token" { + description = "Environment variable for CIRCLE_CI_TOKEN" + type = string +} + +variable "env_github_auth_method" { + description = "Environment variable for GITHUB_AUTH_METHOD" + type = string + default = "access_token_header" +} + +variable "env_github_secret" { + description = "Environment variable for GITHUB_SECRET" + type = string +} + +variable "env_github_username" { + description = "Environment variable for GITHUB_USERNAME" + type = string +} + +variable "env_phpfpm_host" { + description = "Environment variable for PHPFPM_HOST" + type = string + default = "127.0.0.1" +} + +variable "env_resolver_ip" { + description = "Environment variable for RESOLVER_IP" + type = string + default = "169.254.169.253" +} + +variable "env_sentry_dsn" { + description = "Environment variable for SENTRY_DSN" + type = string +} + +variable "env_trusted_proxies" { + description = "Environment variable for TRUSTED_PROXIES" + type = string + default = "REMOTE_ADDR" +} + +variable "env_gitlab_token" { + description = "Environment variable for GITLAB_TOKEN" + type = string +}