diff --git a/air-tech/README.md b/air-tech/README.md new file mode 100644 index 0000000..b1f45ae --- /dev/null +++ b/air-tech/README.md @@ -0,0 +1,19 @@ +# Air-Tech + +## Definition of Done + +- [x] This infrastructure is a production quality infrastructure. +- [x] The coding exercise is to run this environment in AWS. +- [ ] We are looking for an infrastructure-as-code (IaC) solution using Pulumi. +- [x] You are free to architect the infrastructure as you see fit. The final goal is to run both services (Web and API) on AWS. We are leaving all the details to you. +- [x] Be mindful of security best practices. The API is not a public API, and it is only accessed by Web UI. +- [x] Only the web UI should be accessible through the internet (port 80) +- [ ] Tag all the environment resources for auditing purposes. + +## Diagram + +![Infra Diagram](infra_diagram.png) + +## What's missing? + +Tags are missing, the solution does not run to a viable url because of permissions issues between ECS and ECR. When these are tweaked outside of Pulumi, the service works as expected. diff --git a/air-tech/__main__.py b/air-tech/__main__.py index 0460a41..a4e7ec2 100644 --- a/air-tech/__main__.py +++ b/air-tech/__main__.py @@ -1,19 +1,17 @@ import pulumi -from pulumi import Config, Output, export +from pulumi import Config import pulumi_aws as aws -from pulumi_aws import acm, alb, ecs, ec2, lb, iam, ecr +from pulumi_aws import acm, ecs, ec2, lb, iam, ecr import pulumi_awsx as awsx -import json config = Config() -# container_port = config.get_int("containerPort", 80) + cpu = config.get_int("cpu", 512) memory = config.get_int("memory", 128) -# Create a VPC + vpc = ec2.Vpc("production", cidr_block="10.0.0.0/16") -# Create a Gateway ig = ec2.InternetGateway("internet-gateway", vpc_id=vpc.id) cert = acm.Certificate( @@ -21,14 +19,12 @@ cert = acm.Certificate( domain_name="air-tech.ahosking.com", validation_method="DNS", ) -# Public Subnet + public_subnet = ec2.Subnet( "public-subnet", vpc_id=vpc.id, cidr_block="10.0.1.0/26", availability_zone="us-east-2a", - - # Allow the Internet Gateway to handle public traffic map_public_ip_on_launch=True ) public_subnet2 = ec2.Subnet( @@ -36,17 +32,15 @@ public_subnet2 = ec2.Subnet( vpc_id=vpc.id, cidr_block="10.0.2.0/26", availability_zone="us-east-2b", - - # Allow the Internet Gateway to handle public traffic map_public_ip_on_launch=True ) -# Associate the Gateway to a route -route_table = ec2.RouteTable('route-table', vpc_id=vpc.id) -route = ec2.Route('route', route_table_id=route_table.id, - destination_cidr_block='0.0.0.0/0', gateway_id=ig.id) +route_table = ec2.RouteTable("route-table", vpc_id=vpc.id) +route = ec2.Route( + "route", route_table_id=route_table.id, + destination_cidr_block="0.0.0.0/0", gateway_id=ig.id) + -# Private Subnet private_subnet = ec2.Subnet( "private-subnet", vpc_id=vpc.id, @@ -63,37 +57,36 @@ private_subnet2 = ec2.Subnet( ) repo = ecr.Repository("repo") -# Create an IAM role -ecs_task_execution_role = iam.Role("ecsExecutionRole", - path="/", - assume_role_policy=iam.get_policy_document(statements=[{ - "actions": ["sts:AssumeRole"], - "principals": { - "type": "Service", - "identifiers": ["ecs-tasks.amazonaws.com"] - } - } - ]).json, - ) +# ecs_task_execution_role = iam.Role("ecsExecutionRole", +# path="/", +# assume_role_policy=iam.get_policy_document(statements=[{ +# "actions": ["sts:AssumeRole"], +# "principals": { +# "type": "Service", +# "identifiers": ["ecs-tasks.amazonaws.com"] +# } +# } +# ]).json, +# ) -# Attach a policy to the execution role that will allow it to pull images from ECR -iam.RolePolicy("accessECRImages", - role=ecs_task_execution_role.id, - policy={ - "Version": "2012-10-17", - "Statement": [ - { - "Effect": "Allow", - "Action": [ - "ecr:GetDownloadUrlForLayer", - "ecr:BatchGetImage", - "ecr:BatchCheckLayerAvailability", - ], - "Resource": repo.repository_arn - } - ] - } - ) + +# iam.RolePolicy("accessECRImages", +# role=ecs_task_execution_role.id, +# policy={ +# "Version": "2012-10-17", +# "Statement": [ +# { +# "Effect": "Allow", +# "Action": [ +# "ecr:GetDownloadUrlForLayer", +# "ecr:BatchGetImage", +# "ecr:BatchCheckLayerAvailability", +# ], +# "Resource": repo.repository_arn +# } +# ] +# } +# ) image_web = awsx.ecr.Image( @@ -118,7 +111,8 @@ security_group = aws.ec2.SecurityGroup( cidr_blocks=["0.0.0.0/0"], ipv6_cidr_blocks=["::/0"], )]) -# Create an ECS Cluster + + cluster = ecs.Cluster("cluster") pulumi.export("certificateArn", cert.arn) @@ -129,50 +123,52 @@ loadbalancer = lb.LoadBalancer( subnets=[public_subnet.id, public_subnet2.id]) -target_group = lb.TargetGroup('target-group', - port=5000, - protocol="HTTP", - target_type="ip", - vpc_id=vpc.id) +target_group = lb.TargetGroup( + "target-group", + port=5000, + protocol="HTTP", + target_type="ip", + vpc_id=vpc.id) -# Create a listener for the ALB at port 80 -listener = lb.Listener('listener', - load_balancer_arn=loadbalancer.arn, - port=80, - default_actions=[{ - 'type': "forward", - 'target_group_arn': target_group.arn # Forward to our target group - }]) +listener = lb.Listener( + "listener", + load_balancer_arn=loadbalancer.arn, + port=80, + default_actions=[{ + "type": "forward", + "target_group_arn": target_group.arn + }]) -web_sg = ec2.SecurityGroup('web-sg', - vpc_id=vpc.id, - ingress=[ - { - 'protocol': 'tcp', - 'from_port': 80, - 'to_port': 80, - 'cidr_blocks': ['0.0.0.0/0'], - } - ], - egress=[ - { - 'protocol': '-1', - 'from_port': 0, - 'to_port': 0, - 'cidr_blocks': ['0.0.0.0/0'], - } - ] - ) +web_sg = ec2.SecurityGroup( + "web-sg", + vpc_id=vpc.id, + ingress=[ + { + "protocol": "tcp", + "from_port": 80, + "to_port": 80, + "cidr_blocks": ["0.0.0.0/0"], + } + ], + egress=[ + { + "protocol": "-1", + "from_port": 0, + "to_port": 0, + "cidr_blocks": ["0.0.0.0/0"], + } + ] +) api = awsx.ecs.FargateService( "api", cluster=cluster.arn, network_configuration={ - 'assignPublicIp': "false", - 'subnets': [private_subnet.id], - 'securityGroups': [web_sg.id] + "assignPublicIp": "false", + "subnets": [private_subnet.id], + "securityGroups": [web_sg.id] }, task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs( container=awsx.ecs.TaskDefinitionContainerDefinitionArgs( @@ -193,11 +189,10 @@ service = awsx.ecs.FargateService( "web", cluster=cluster.arn, network_configuration={ - 'assignPublicIp': "true", - 'subnets': [public_subnet.id], - 'securityGroups': [web_sg.id] + "assignPublicIp": "true", + "subnets": [public_subnet.id], + "securityGroups": [web_sg.id] }, - # assign_public_ip=True, task_definition_args=awsx.ecs.FargateServiceTaskDefinitionArgs( container=awsx.ecs.TaskDefinitionContainerDefinitionArgs( name="theOne",