Skip to content

Commit

Permalink
Merge branch 'release/0.1.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Aaron Carlucci committed Nov 24, 2023
2 parents 9581b9d + 96f0947 commit a1ff4b9
Show file tree
Hide file tree
Showing 13 changed files with 585 additions and 0 deletions.
57 changes: 57 additions & 0 deletions .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
on:
workflow_call:
inputs:
aws_region:
description: The AWS region target for deployment
required: true
type: string
aws_replication_region:
description: The AWS replication region target for deployment
required: true
type: string
aws_s3_terraform_state_object_key:
description: The key of the Terraform .tfstate file in AWS S3
required: true
type: string
environment_name:
description: The name of the environment configured in Github repository settings
required: true
type: string
secrets:
aws_assume_role_arn:
description: The AWS IAM role assumed by Github Actions
aws_s3_terraform_state_bucket_name:
description: The AWS S3 bucket name containing Terraform backends, configured in Github repository settings
required: true

jobs:
deploy:
name: Deploy
runs-on: ubuntu-latest
steps:
- name: Clone the Git repository
uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/[email protected]
with:
role-to-assume: ${{ secrets.aws_assume_role_arn }}
aws-region: ${{ inputs.aws_region }}
- name: Setup Terraform
uses: hashicorp/setup-terraform@v2
- name: Terraform Format
run: terraform fmt -check
working-directory: ./terraform
- name: Terraform Init
run: |
terraform init \
-backend-config="bucket=${{ secrets.aws_s3_terraform_state_bucket_name }}" \
-backend-config="key=${{ inputs.aws_s3_terraform_state_object_key }}" \
-backend-config="region=${{ inputs.aws_region }}"
working-directory: ./terraform
- name: Terraform Apply
run: |
terraform apply -auto-approve \
-var="aws_region=${{ inputs.aws_region }}" \
-var="aws_replication_region=${{ inputs.aws_replication_region }}" \
-var="environment=${{ inputs.environment_name }}"
working-directory: ./terraform
33 changes: 33 additions & 0 deletions .github/workflows/development.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Deploy development

on:
push:
branches:
- develop
- feature/*

permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout

jobs:
deploy:
name: Deploy to development
uses: ./.github/workflows/deploy.yml
# Originally the workflow implementation was setup to use environment
# variables configured in the Github repository settings. However,
# after moving to a reusable action, it became ugly to pass those values
# into the called action due to this bug:
#
# https://github.com/orgs/community/discussions/26671#discussioncomment-4295807
#
# So now we're hardcoding the values here and using it as a manifest. Please see
# commit 1ec7a0346abc04b73c03e35c0e228e9dba14300c for the previous implementation.
with:
aws_region: us-east-1
aws_replication_region: us-west-2
aws_s3_terraform_state_object_key: development.tfstate
environment_name: dev
secrets:
aws_assume_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
aws_s3_terraform_state_bucket_name: ${{ secrets.AWS_S3_TERRAFORM_STATE_BUCKET_NAME }}
32 changes: 32 additions & 0 deletions .github/workflows/production.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Deploy production

on:
push:
branches:
- main

permissions:
id-token: write # This is required for requesting the JWT
contents: read # This is required for actions/checkout

jobs:
deploy:
name: Deploy to production
uses: ./.github/workflows/deploy.yml
# Originally the workflow implementation was setup to use environment
# variables configured in the Github repository settings. However,
# after moving to a reusable action, it became ugly to pass those values
# into the called action due to this bug:
#
# https://github.com/orgs/community/discussions/26671#discussioncomment-4295807
#
# So now we're hardcoding the values here and using it as a manifest. Please see
# commit 1ec7a0346abc04b73c03e35c0e228e9dba14300c for the previous implementation.
with:
aws_region: us-east-1
aws_replication_region: us-west-2
aws_s3_terraform_state_object_key: production.tfstate
environment_name: prod
secrets:
aws_assume_role_arn: ${{ secrets.AWS_ASSUME_ROLE_ARN }}
aws_s3_terraform_state_bucket_name: ${{ secrets.AWS_S3_TERRAFORM_STATE_BUCKET_NAME }}
1 change: 1 addition & 0 deletions .terraform-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
1.6.4
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Unreleased

# 0.1.0

* CloudWatch log group encryption using KMS CMK
* Build improvements to leverage callable deployment workflow and explicity manifests for development and production environments
* ECS cluster, service, task and ALB running vanilla nginx image
* KMS customer managed key provisioning with multi-region replication
* VPC network provisioning
* Github Actions Terraform workflow definition and integration
1 change: 1 addition & 0 deletions terraform/data.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
data "aws_caller_identity" "current" {}
65 changes: 65 additions & 0 deletions terraform/ec2.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Security group for the ALB accepting HTTP connections on port 80
#
# TODO: implement SSL encrypted traffic and redirect HTTP to HTTPS
resource "aws_security_group" "alb" {
name = "${local.namespace}-alb"
vpc_id = aws_vpc.vpc.id
}

resource "aws_security_group_rule" "alb_ingress_http" {
cidr_blocks = ["0.0.0.0/0"]
description = "Allow public HTTP traffic"
from_port = 80
ipv6_cidr_blocks = ["::/0"]
protocol = "tcp"
security_group_id = aws_security_group.alb.id
to_port = 80
type = "ingress"
}

resource "aws_security_group_rule" "alb_egress_all" {
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
from_port = 0
ipv6_cidr_blocks = ["::/0"]
protocol = -1
security_group_id = aws_security_group.alb.id
to_port = 0
type = "egress"
}

resource "aws_lb_target_group" "alb" {
name = local.namespace
port = 80
protocol = "HTTP"
target_type = "ip"

health_check {
# TODO: review health check
enabled = true
path = "/"
port = 80
protocol = "HTTP"
}

vpc_id = aws_vpc.vpc.id
}

resource "aws_lb" "alb" {
name = local.namespace
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.alb.id]
subnets = local.public_subnet_ids
}

resource "aws_lb_listener" "alb" {
load_balancer_arn = aws_lb.alb.id
port = 80
protocol = "HTTP"

default_action {
target_group_arn = aws_lb_target_group.alb.id
type = "forward"
}
}
128 changes: 128 additions & 0 deletions terraform/ecs.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
# Encrypt log data with KMS CMK: https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html
resource "aws_cloudwatch_log_group" "hello_world" {
kms_key_id = aws_kms_key.primary.arn
name = "/ecs/${local.namespace}/hello-world"
retention_in_days = 30
}

resource "aws_ecs_cluster" "cluster" {
name = local.namespace
}

# Task execution assumed role
data "aws_iam_policy_document" "ecs_task_assume_role" {
statement {
actions = ["sts:AssumeRole"]
effect = "Allow"
principals {
identifiers = ["ecs-tasks.amazonaws.com"]
type = "Service"
}
}
}

resource "aws_iam_role" "ecs_task_execution" {
name = "${local.namespace}_ecs_task_execution"
assume_role_policy = data.aws_iam_policy_document.ecs_task_assume_role.json
}

# Use the AWS-provided managed role for basic logging and ECR repository permissions
resource "aws_iam_role_policy_attachment" "legacy_listener_aws_task_execution_role_policy" {
role = aws_iam_role.ecs_task_execution.name
policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

# Task definition featuring
# * CloudWatch logs integration
resource "aws_ecs_task_definition" "hello_world" {
container_definitions = jsonencode([
{
# TODO: parameterize cpu, or remove this value because it is not required
# for Fargate containers when assigned at the task level and we only have one task
cpu = 256
# TODO: specify image tag and eventually parameterize
image = "nginx"
logConfiguration = {
logDriver = "awslogs"
options = {
"awslogs-group" : aws_cloudwatch_log_group.hello_world.name
"awslogs-region" : var.aws_region
"awslogs-stream-prefix" : local.namespace
}
},
# TODO: parameterize memory, or remove this value because it is not required
# for Fargate containers when assigned at the task level and we only have one task
memory = 512
name = "hello-world"
networkMode = "FARGATE"
portMappings = [
{
hostPort = 80,
containerPort = 80,
protocol = "tcp"
}
]
}
])

# TODO: parameterize cpu
cpu = 256
execution_role_arn = aws_iam_role.ecs_task_execution.arn
family = "${local.namespace}-hello-world"
# TODO: parameterize memory
memory = 512
network_mode = "awsvpc"
requires_compatibilities = ["FARGATE"]
}

# Security group for the hello-world ECS service accepts HTTP
# connections from the ALB security group
resource "aws_security_group" "app" {
name = "${local.namespace}-app"
vpc_id = aws_vpc.vpc.id
}

resource "aws_security_group_rule" "app_ingress_http" {
description = "Allow HTTP from ALB"
from_port = 80
protocol = "tcp"
security_group_id = aws_security_group.app.id
source_security_group_id = aws_security_group.alb.id
to_port = 80
type = "ingress"
}

resource "aws_security_group_rule" "app_egress_all" {
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
from_port = 0
ipv6_cidr_blocks = ["::/0"]
protocol = -1
security_group_id = aws_security_group.app.id
to_port = 0
type = "egress"
}

# Hello World ECS service
resource "aws_ecs_service" "hello_world" {
name = "${local.namespace}-hello-world"

cluster = aws_ecs_cluster.cluster.id
desired_count = 1
launch_type = "FARGATE"

# TODO: consider service encrypted internal traffic between
# ALB and ECS container on 443 - requires self-signed cert
load_balancer {
target_group_arn = aws_lb_target_group.alb.arn
container_name = "hello-world"
container_port = 80
}

network_configuration {
security_groups = [aws_security_group.app.id]
subnets = local.private_subnet_ids
}

task_definition = aws_ecs_task_definition.hello_world.arn
}
Loading

0 comments on commit a1ff4b9

Please sign in to comment.