diff --git a/plans/DatabasePlan.tfplan.txt b/plans/DatabasePlan.tfplan.txt new file mode 100644 index 0000000..d052739 --- /dev/null +++ b/plans/DatabasePlan.tfplan.txt @@ -0,0 +1,234 @@ +module.db.module.db_instance.data.aws_partition.current: Reading... +module.db.module.db_instance.data.aws_partition.current: Read complete after 0s [id=aws] +module.db.module.db_instance.data.aws_iam_policy_document.enhanced_monitoring: Reading... +module.db.module.db_instance.data.aws_iam_policy_document.enhanced_monitoring: Read complete after 0s [id=76086537] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + +Terraform will perform the following actions: + + # aws_db_subnet_group.default will be created + + resource "aws_db_subnet_group" "default" { + + arn = (known after apply) + + description = "Managed by Terraform" + + id = (known after apply) + + name = "public" + + name_prefix = (known after apply) + + subnet_ids = [ + + "subnet-abc123", + + "subnet-ou812", + ] + + supported_network_types = (known after apply) + + tags_all = (known after apply) + + vpc_id = (known after apply) + } + + # aws_security_group.database_security_group will be created + + resource "aws_security_group" "database_security_group" { + + arn = (known after apply) + + description = "Allow database access" + + egress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "Allow database access" + + from_port = 0 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "-1" + + security_groups = [] + + self = false + + to_port = 0 + }, + ] + + id = (known after apply) + + ingress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "Allow database access" + + from_port = 0 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "-1" + + security_groups = [] + + self = false + + to_port = 0 + }, + ] + + name = "database_security_group" + + name_prefix = (known after apply) + + owner_id = (known after apply) + + revoke_rules_on_delete = false + + tags_all = (known after apply) + + vpc_id = "vpc-9876" + } + + # random_password.db_password will be created + + resource "random_password" "db_password" { + + bcrypt_hash = (sensitive value) + + id = (known after apply) + + length = 16 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + numeric = true + + override_special = "_%@" + + result = (sensitive value) + + special = true + + upper = true + } + + # module.db.random_password.master_password[0] will be created + + resource "random_password" "master_password" { + + bcrypt_hash = (sensitive value) + + id = (known after apply) + + length = 16 + + lower = true + + min_lower = 0 + + min_numeric = 0 + + min_special = 0 + + min_upper = 0 + + number = true + + numeric = true + + result = (sensitive value) + + special = false + + upper = true + } + + # module.db.module.db_instance.aws_db_instance.this[0] will be created + + resource "aws_db_instance" "this" { + + address = (known after apply) + + allocated_storage = 5 + + allow_major_version_upgrade = false + + apply_immediately = false + + arn = (known after apply) + + auto_minor_version_upgrade = true + + availability_zone = (known after apply) + + backup_retention_period = (known after apply) + + backup_window = (known after apply) + + ca_cert_identifier = (known after apply) + + character_set_name = (known after apply) + + copy_tags_to_snapshot = false + + db_name = "gitea" + + db_subnet_group_name = "public" + + delete_automated_backups = true + + deletion_protection = false + + endpoint = (known after apply) + + engine = "mysql" + + engine_version = (known after apply) + + engine_version_actual = (known after apply) + + final_snapshot_identifier = (known after apply) + + hosted_zone_id = (known after apply) + + iam_database_authentication_enabled = false + + id = (known after apply) + + identifier = "gitea" + + identifier_prefix = (known after apply) + + instance_class = "db.t2.micro" + + iops = (known after apply) + + kms_key_id = (known after apply) + + latest_restorable_time = (known after apply) + + license_model = (known after apply) + + listener_endpoint = (known after apply) + + maintenance_window = (known after apply) + + master_user_secret = (known after apply) + + master_user_secret_kms_key_id = (known after apply) + + max_allocated_storage = 0 + + monitoring_interval = 0 + + monitoring_role_arn = (known after apply) + + multi_az = false + + nchar_character_set_name = (known after apply) + + network_type = (known after apply) + + option_group_name = (known after apply) + + parameter_group_name = (known after apply) + + password = (sensitive value) + + performance_insights_enabled = false + + performance_insights_kms_key_id = (known after apply) + + performance_insights_retention_period = (known after apply) + + port = 3306 + + publicly_accessible = true + + replica_mode = (known after apply) + + replicas = (known after apply) + + resource_id = (known after apply) + + skip_final_snapshot = false + + snapshot_identifier = (known after apply) + + status = (known after apply) + + storage_encrypted = false + + storage_throughput = (known after apply) + + storage_type = (known after apply) + + tags_all = (known after apply) + + timezone = (known after apply) + + username = "gitea" + + vpc_security_group_ids = (known after apply) + + + timeouts {} + } + + # module.db.module.db_instance.random_id.snapshot_identifier[0] will be created + + resource "random_id" "snapshot_identifier" { + + b64_std = (known after apply) + + b64_url = (known after apply) + + byte_length = 4 + + dec = (known after apply) + + hex = (known after apply) + + id = (known after apply) + + keepers = { + + "id" = "gitea" + } + } + + # module.db.module.db_option_group.aws_db_option_group.this[0] will be created + + resource "aws_db_option_group" "this" { + + arn = (known after apply) + + engine_name = "mysql" + + id = (known after apply) + + major_engine_version = "8.0" + + name = (known after apply) + + name_prefix = "gitea-" + + option_group_description = "gitea option group" + + tags = { + + "Name" = "gitea" + } + + tags_all = { + + "Name" = "gitea" + } + + + timeouts {} + } + + # module.db.module.db_parameter_group.aws_db_parameter_group.this[0] will be created + + resource "aws_db_parameter_group" "this" { + + arn = (known after apply) + + description = "gitea parameter group" + + family = "mysql8.0" + + id = (known after apply) + + name = (known after apply) + + name_prefix = "gitea-" + + tags = { + + "Name" = "gitea" + } + + tags_all = { + + "Name" = "gitea" + } + } + +Plan: 8 to add, 0 to change, 0 to destroy. + +Changes to Outputs: + + db_host = (known after apply) + + db_name = "gitea" + + db_password = (sensitive value) + + db_port = 3306 + + db_username = (sensitive value) + +───────────────────────────────────────────────────────────────────────────── + +Note: You didn't use the -out option to save this plan, so Terraform can't +guarantee to take exactly these actions if you run "terraform apply" now. diff --git a/plans/Web.tfplan.txt b/plans/Web.tfplan.txt new file mode 100644 index 0000000..ac813e3 --- /dev/null +++ b/plans/Web.tfplan.txt @@ -0,0 +1,785 @@ +data.aws_iam_policy_document.allow_exec_command: Reading... +module.ecs.module.cluster.data.aws_partition.current: Reading... +data.aws_iam_policy_document.allow_exec_command: Read complete after 0s [id=3621793318] +module.ecs.module.cluster.data.aws_partition.current: Read complete after 0s [id=aws] +module.ecs.module.service["primary_service"].data.aws_caller_identity.current: Reading... +module.ecs.module.service["primary_service"].data.aws_partition.current: Reading... +module.ecs.module.service["primary_service"].data.aws_region.current: Reading... +module.ecs.module.service["primary_service"].data.aws_partition.current: Read complete after 0s [id=aws] +module.ecs.module.service["primary_service"].data.aws_region.current: Read complete after 0s [id=us-east-1] +module.ecs.module.service["primary_service"].data.aws_subnet.this[0]: Reading... +module.ecs.module.service["primary_service"].module.container_definition["primary_container"].data.aws_region.current: Reading... +module.ecs.module.service["primary_service"].module.container_definition["primary_container"].data.aws_region.current: Read complete after 0s [id=us-east-1] +module.ecs.module.service["primary_service"].data.aws_iam_policy_document.task_exec_assume[0]: Reading... +module.ecs.module.service["primary_service"].data.aws_iam_policy_document.task_exec[0]: Reading... +module.ecs.module.service["primary_service"].data.aws_iam_policy_document.task_exec_assume[0]: Read complete after 0s [id=2291109037] +module.ecs.module.service["primary_service"].data.aws_iam_policy_document.task_exec[0]: Read complete after 0s [id=1415633931] +module.ecs.module.service["primary_service"].data.aws_caller_identity.current: Read complete after 0s [id=834293215331] +module.ecs.module.service["primary_service"].data.aws_iam_policy_document.tasks_assume[0]: Reading... +module.ecs.module.service["primary_service"].data.aws_iam_policy_document.tasks_assume[0]: Read complete after 0s [id=3132515787] +module.ecs.module.service["primary_service"].data.aws_subnet.this[0]: Read complete after 1s [id=subnet-abc123] +data.aws_route53_zone.certificate: Reading... +data.aws_route53_zone.certificate: Read complete after 0s [id=Z03192028LXE6Q2FKX1H] + +Terraform used the selected providers to generate the following execution +plan. Resource actions are indicated with the following symbols: + + create + <= read (data resources) + +Terraform will perform the following actions: + + # aws_acm_certificate.certificate will be created + + resource "aws_acm_certificate" "certificate" { + + arn = (known after apply) + + domain_name = "awesome-app.super-cool-domain.com" + + domain_validation_options = [ + + { + + domain_name = "awesome-app.super-cool-domain.com" + + resource_record_name = (known after apply) + + resource_record_type = (known after apply) + + resource_record_value = (known after apply) + }, + ] + + id = (known after apply) + + key_algorithm = (known after apply) + + not_after = (known after apply) + + not_before = (known after apply) + + pending_renewal = (known after apply) + + renewal_eligibility = (known after apply) + + renewal_summary = (known after apply) + + status = (known after apply) + + subject_alternative_names = [ + + "awesome-app.super-cool-domain.com", + ] + + tags_all = (known after apply) + + type = (known after apply) + + validation_emails = (known after apply) + + validation_method = "DNS" + } + + # aws_acm_certificate_validation.certificate will be created + + resource "aws_acm_certificate_validation" "certificate" { + + certificate_arn = (known after apply) + + id = (known after apply) + + validation_record_fqdns = (known after apply) + } + + # aws_efs_file_system.data will be created + + resource "aws_efs_file_system" "data" { + + arn = (known after apply) + + availability_zone_id = (known after apply) + + availability_zone_name = (known after apply) + + creation_token = "data" + + dns_name = (known after apply) + + encrypted = false + + id = (known after apply) + + kms_key_id = (known after apply) + + number_of_mount_targets = (known after apply) + + owner_id = (known after apply) + + performance_mode = (known after apply) + + size_in_bytes = (known after apply) + + tags_all = (known after apply) + + throughput_mode = "elastic" + } + + # aws_efs_mount_target.mount_target["subnet-abc123"] will be created + + resource "aws_efs_mount_target" "mount_target" { + + availability_zone_id = (known after apply) + + availability_zone_name = (known after apply) + + dns_name = (known after apply) + + file_system_arn = (known after apply) + + file_system_id = (known after apply) + + id = (known after apply) + + ip_address = (known after apply) + + mount_target_dns_name = (known after apply) + + network_interface_id = (known after apply) + + owner_id = (known after apply) + + security_groups = (known after apply) + + subnet_id = "subnet-abc123" + } + + # aws_efs_mount_target.mount_target["subnet-ou812"] will be created + + resource "aws_efs_mount_target" "mount_target" { + + availability_zone_id = (known after apply) + + availability_zone_name = (known after apply) + + dns_name = (known after apply) + + file_system_arn = (known after apply) + + file_system_id = (known after apply) + + id = (known after apply) + + ip_address = (known after apply) + + mount_target_dns_name = (known after apply) + + network_interface_id = (known after apply) + + owner_id = (known after apply) + + security_groups = (known after apply) + + subnet_id = "subnet-ou812" + } + + # aws_iam_policy.allow_exec_command will be created + + resource "aws_iam_policy" "allow_exec_command" { + + arn = (known after apply) + + id = (known after apply) + + name = "allow_exec_command" + + name_prefix = (known after apply) + + path = "/exercises/securing-app-exercise/web/" + + policy = jsonencode( + { + + Statement = [ + + { + + Action = [ + + "ssmmessages:OpenDataChannel", + + "ssmmessages:OpenControlChannel", + + "ssmmessages:CreateDataChannel", + + "ssmmessages:CreateControlChannel", + ] + + Effect = "Allow" + + Resource = "*" + }, + ] + + Version = "2012-10-17" + } + ) + + policy_id = (known after apply) + + tags_all = (known after apply) + } + + # aws_lb.loadbalancer will be created + + resource "aws_lb" "loadbalancer" { + + arn = (known after apply) + + arn_suffix = (known after apply) + + desync_mitigation_mode = "defensive" + + dns_name = (known after apply) + + drop_invalid_header_fields = false + + enable_deletion_protection = false + + enable_http2 = true + + enable_tls_version_and_cipher_suite_headers = false + + enable_waf_fail_open = false + + enable_xff_client_port = false + + id = (known after apply) + + idle_timeout = 60 + + internal = false + + ip_address_type = (known after apply) + + load_balancer_type = "application" + + name = "sec-test-lb" + + preserve_host_header = false + + security_groups = (known after apply) + + subnets = [ + + "subnet-abc123", + + "subnet-ou812", + ] + + tags_all = (known after apply) + + vpc_id = (known after apply) + + xff_header_processing_mode = "append" + + zone_id = (known after apply) + } + + # aws_lb_listener.http will be created + + resource "aws_lb_listener" "http" { + + arn = (known after apply) + + id = (known after apply) + + load_balancer_arn = (known after apply) + + port = 80 + + protocol = "HTTP" + + ssl_policy = (known after apply) + + tags_all = (known after apply) + + + default_action { + + order = (known after apply) + + type = "redirect" + + + redirect { + + host = "#{host}" + + path = "/#{path}" + + port = "443" + + protocol = "HTTPS" + + query = "#{query}" + + status_code = "HTTP_301" + } + } + } + + # aws_lb_listener.https will be created + + resource "aws_lb_listener" "https" { + + arn = (known after apply) + + certificate_arn = (known after apply) + + id = (known after apply) + + load_balancer_arn = (known after apply) + + port = 443 + + protocol = "HTTPS" + + ssl_policy = "ELBSecurityPolicy-TLS-1-0-2015-04" + + tags_all = (known after apply) + + + default_action { + + order = (known after apply) + + target_group_arn = (known after apply) + + type = "forward" + } + } + + # aws_lb_target_group.targetgroup will be created + + resource "aws_lb_target_group" "targetgroup" { + + arn = (known after apply) + + arn_suffix = (known after apply) + + connection_termination = false + + deregistration_delay = "300" + + id = (known after apply) + + ip_address_type = (known after apply) + + lambda_multi_value_headers_enabled = false + + load_balancing_algorithm_type = (known after apply) + + load_balancing_cross_zone_enabled = (known after apply) + + name = "awesome-app" + + port = 80 + + preserve_client_ip = (known after apply) + + protocol = "HTTP" + + protocol_version = (known after apply) + + proxy_protocol_v2 = false + + slow_start = 0 + + tags_all = (known after apply) + + target_type = "ip" + + vpc_id = "vpc-98765" + + + health_check { + + enabled = true + + healthy_threshold = 2 + + interval = 30 + + matcher = (known after apply) + + path = "/api/healthz" + + port = "traffic-port" + + protocol = "HTTP" + + timeout = 3 + + unhealthy_threshold = 2 + } + } + + # aws_route53_record.certificate["awesome-app.super-cool-domain.com"] will be created + + resource "aws_route53_record" "certificate" { + + allow_overwrite = true + + fqdn = (known after apply) + + id = (known after apply) + + name = (known after apply) + + records = (known after apply) + + ttl = 60 + + type = (known after apply) + + zone_id = "Z03192028LXE6Q2FKX1H" + } + + # aws_route53_record.loadbalancer_A will be created + + resource "aws_route53_record" "loadbalancer_A" { + + allow_overwrite = (known after apply) + + fqdn = (known after apply) + + id = (known after apply) + + name = "awesome-app" + + type = "A" + + zone_id = "Z03192028LXE6Q2FKX1H" + + + alias { + + evaluate_target_health = true + + name = (known after apply) + + zone_id = (known after apply) + } + } + + # aws_security_group.open_access will be created + + resource "aws_security_group" "open_access" { + + arn = (known after apply) + + description = "Allow all inbound traffic" + + egress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "" + + from_port = 0 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "-1" + + security_groups = [] + + self = false + + to_port = 0 + }, + ] + + id = (known after apply) + + ingress = [ + + { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + description = "" + + from_port = 0 + + ipv6_cidr_blocks = [] + + prefix_list_ids = [] + + protocol = "-1" + + security_groups = [] + + self = false + + to_port = 0 + }, + ] + + name = "sec-test-open-access" + + name_prefix = (known after apply) + + owner_id = (known after apply) + + revoke_rules_on_delete = false + + tags_all = (known after apply) + + vpc_id = "vpc-98765" + } + + # module.ecs.module.cluster.aws_cloudwatch_log_group.this[0] will be created + + resource "aws_cloudwatch_log_group" "this" { + + arn = (known after apply) + + id = (known after apply) + + name = "/aws/ecs/sec-challenge" + + name_prefix = (known after apply) + + retention_in_days = 90 + + skip_destroy = false + + tags_all = (known after apply) + } + + # module.ecs.module.cluster.aws_ecs_cluster.this[0] will be created + + resource "aws_ecs_cluster" "this" { + + arn = (known after apply) + + id = (known after apply) + + name = "sec-challenge" + + tags_all = (known after apply) + + + configuration { + + execute_command_configuration { + + logging = "DEFAULT" + } + } + + + setting { + + name = "containerInsights" + + value = "enabled" + } + } + + # module.ecs.module.cluster.aws_ecs_cluster_capacity_providers.this[0] will be created + + resource "aws_ecs_cluster_capacity_providers" "this" { + + capacity_providers = [ + + "FARGATE_SPOT", + ] + + cluster_name = "sec-challenge" + + id = (known after apply) + + + default_capacity_provider_strategy { + + base = 0 + + capacity_provider = "FARGATE_SPOT" + + weight = 100 + } + } + + # module.ecs.module.service["primary_service"].data.aws_ecs_task_definition.this[0] will be read during apply + # (depends on a resource or a module with changes pending) + <= data "aws_ecs_task_definition" "this" { + + arn = (known after apply) + + arn_without_revision = (known after apply) + + execution_role_arn = (known after apply) + + family = (known after apply) + + id = (known after apply) + + network_mode = (known after apply) + + revision = (known after apply) + + status = (known after apply) + + task_definition = "primary_service" + + task_role_arn = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_appautoscaling_policy.this["cpu"] will be created + + resource "aws_appautoscaling_policy" "this" { + + alarm_arns = (known after apply) + + arn = (known after apply) + + id = (known after apply) + + name = "cpu" + + policy_type = "TargetTrackingScaling" + + resource_id = (known after apply) + + scalable_dimension = "ecs:service:DesiredCount" + + service_namespace = "ecs" + + + target_tracking_scaling_policy_configuration { + + disable_scale_in = false + + scale_in_cooldown = 300 + + scale_out_cooldown = 60 + + target_value = 75 + + + predefined_metric_specification { + + predefined_metric_type = "ECSServiceAverageCPUUtilization" + } + } + } + + # module.ecs.module.service["primary_service"].aws_appautoscaling_policy.this["memory"] will be created + + resource "aws_appautoscaling_policy" "this" { + + alarm_arns = (known after apply) + + arn = (known after apply) + + id = (known after apply) + + name = "memory" + + policy_type = "TargetTrackingScaling" + + resource_id = (known after apply) + + scalable_dimension = "ecs:service:DesiredCount" + + service_namespace = "ecs" + + + target_tracking_scaling_policy_configuration { + + disable_scale_in = false + + scale_in_cooldown = 300 + + scale_out_cooldown = 60 + + target_value = 75 + + + predefined_metric_specification { + + predefined_metric_type = "ECSServiceAverageMemoryUtilization" + } + } + } + + # module.ecs.module.service["primary_service"].aws_appautoscaling_target.this[0] will be created + + resource "aws_appautoscaling_target" "this" { + + arn = (known after apply) + + id = (known after apply) + + max_capacity = 10 + + min_capacity = 1 + + resource_id = (known after apply) + + role_arn = (known after apply) + + scalable_dimension = "ecs:service:DesiredCount" + + service_namespace = "ecs" + + tags_all = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_ecs_service.this[0] will be created + + resource "aws_ecs_service" "this" { + + cluster = (known after apply) + + deployment_maximum_percent = 200 + + deployment_minimum_healthy_percent = 66 + + desired_count = 1 + + enable_ecs_managed_tags = true + + enable_execute_command = true + + force_new_deployment = true + + iam_role = (known after apply) + + id = (known after apply) + + launch_type = "FARGATE" + + name = "primary_service" + + platform_version = (known after apply) + + scheduling_strategy = "REPLICA" + + tags_all = (known after apply) + + task_definition = (known after apply) + + triggers = (known after apply) + + wait_for_steady_state = false + + + load_balancer { + + container_name = "gitea" + + container_port = 3000 + + target_group_arn = (known after apply) + } + + + network_configuration { + + assign_public_ip = true + + security_groups = (known after apply) + + subnets = [ + + "subnet-abc123", + + "subnet-ou812", + ] + } + + + timeouts {} + } + + # module.ecs.module.service["primary_service"].aws_ecs_task_definition.this[0] will be created + + resource "aws_ecs_task_definition" "this" { + + arn = (known after apply) + + arn_without_revision = (known after apply) + + container_definitions = jsonencode( + [ + + { + + environment = [ + + { + + name = "GITEA__database__DB_TYPE" + + value = "mysql" + }, + + { + + name = "GITEA__database__HOST" + + value = "gitea.cxzynmcy4x5v.us-east-1.rds.amazonaws.com:3306" + }, + + { + + name = "GITEA__database__NAME" + + value = "gitea" + }, + + { + + name = "GITEA__database__PASSWD" + + value = "DL2ryuEAjVjME3li" + }, + + { + + name = "GITEA__database__USER" + + value = "gitea" + }, + + { + + name = "GITEA__server__DOMAIN" + + value = "awesome-app.super-cool-domain.com" + }, + + { + + name = "GITEA__server__HTTP_PORT" + + value = "3000" + }, + + { + + name = "GITEA__server__PROTOCOL" + + value = "http" + }, + + { + + name = "GITEA__server__ROOT_URL" + + value = "https://awesome-app.super-cool-domain.com/" + }, + ] + + essential = true + + healthCheck = { + + command = [ + + "CMD-SHELL", + + "curl -fSs 127.0.0.1:3000/api/healthz || exit 1", + ] + + interval = 30 + + retries = 3 + + timeout = 5 + } + + image = "gitea/gitea:1.19.3" + + interactive = false + + logConfiguration = { + + logDriver = "awslogs" + + options = { + + awslogs-group = "/aws/ecs/primary_service/gitea" + + awslogs-region = "us-east-1" + + awslogs-stream-prefix = "ecs" + } + } + + mountPoints = [ + + { + + containerPath = "/data" + + readOnly = false + + sourceVolume = "data" + }, + ] + + name = "gitea" + + portMappings = [ + + { + + containerPort = 3000 + + hostPort = 3000 + }, + ] + + privileged = false + + pseudoTerminal = false + + readonlyRootFilesystem = false + + startTimeout = 30 + + stopTimeout = 120 + }, + ] + ) + + cpu = "256" + + execution_role_arn = (known after apply) + + family = "primary_service" + + id = (known after apply) + + memory = "512" + + network_mode = "awsvpc" + + requires_compatibilities = [ + + "FARGATE", + ] + + revision = (known after apply) + + skip_destroy = false + + tags_all = (known after apply) + + task_role_arn = (known after apply) + + + runtime_platform { + + cpu_architecture = "X86_64" + + operating_system_family = "LINUX" + } + } + + # module.ecs.module.service["primary_service"].aws_iam_policy.task_exec[0] will be created + + resource "aws_iam_policy" "task_exec" { + + arn = (known after apply) + + description = "Task execution role IAM policy" + + id = (known after apply) + + name = (known after apply) + + name_prefix = "primary_service-" + + path = "/" + + policy = jsonencode( + { + + Statement = [ + + { + + Action = [ + + "logs:PutLogEvents", + + "logs:CreateLogStream", + ] + + Effect = "Allow" + + Resource = "*" + + Sid = "Logs" + }, + + { + + Action = [ + + "ecr:GetDownloadUrlForLayer", + + "ecr:GetAuthorizationToken", + + "ecr:BatchGetImage", + + "ecr:BatchCheckLayerAvailability", + ] + + Effect = "Allow" + + Resource = "*" + + Sid = "ECR" + }, + + { + + Action = "ssm:GetParameters" + + Effect = "Allow" + + Resource = "arn:aws:ssm:*:*:parameter/*" + + Sid = "GetSSMParams" + }, + + { + + Action = "secretsmanager:GetSecretValue" + + Effect = "Allow" + + Resource = "arn:aws:secretsmanager:*:*:secret:*" + + Sid = "GetSecrets" + }, + ] + + Version = "2012-10-17" + } + ) + + policy_id = (known after apply) + + tags_all = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_iam_role.task_exec[0] will be created + + resource "aws_iam_role" "task_exec" { + + arn = (known after apply) + + assume_role_policy = jsonencode( + { + + Statement = [ + + { + + Action = "sts:AssumeRole" + + Effect = "Allow" + + Principal = { + + Service = "ecs-tasks.amazonaws.com" + } + + Sid = "ECSTaskExecutionAssumeRole" + }, + ] + + Version = "2012-10-17" + } + ) + + create_date = (known after apply) + + description = "Task execution role for primary_service" + + force_detach_policies = true + + id = (known after apply) + + managed_policy_arns = (known after apply) + + max_session_duration = 3600 + + name = (known after apply) + + name_prefix = "primary_service-" + + path = "/" + + tags_all = (known after apply) + + unique_id = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_iam_role.tasks[0] will be created + + resource "aws_iam_role" "tasks" { + + arn = (known after apply) + + assume_role_policy = jsonencode( + { + + Statement = [ + + { + + Action = "sts:AssumeRole" + + Condition = { + + ArnLike = { + + "aws:SourceArn" = "arn:aws:ecs:us-east-1:834293215331:*" + } + + StringEquals = { + + "aws:SourceAccount" = "834293215331" + } + } + + Effect = "Allow" + + Principal = { + + Service = "ecs-tasks.amazonaws.com" + } + + Sid = "ECSTasksAssumeRole" + }, + ] + + Version = "2012-10-17" + } + ) + + create_date = (known after apply) + + force_detach_policies = true + + id = (known after apply) + + managed_policy_arns = (known after apply) + + max_session_duration = 3600 + + name = (known after apply) + + name_prefix = "primary_service-" + + path = "/" + + tags_all = (known after apply) + + unique_id = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_iam_role_policy_attachment.task_exec[0] will be created + + resource "aws_iam_role_policy_attachment" "task_exec" { + + id = (known after apply) + + policy_arn = (known after apply) + + role = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_iam_role_policy_attachment.tasks["allow_exec_command"] will be created + + resource "aws_iam_role_policy_attachment" "tasks" { + + id = (known after apply) + + policy_arn = (known after apply) + + role = (known after apply) + } + + # module.ecs.module.service["primary_service"].aws_security_group.this[0] will be created + + resource "aws_security_group" "this" { + + arn = (known after apply) + + description = "Managed by Terraform" + + egress = (known after apply) + + id = (known after apply) + + ingress = (known after apply) + + name = (known after apply) + + name_prefix = "primary_service-" + + owner_id = (known after apply) + + revoke_rules_on_delete = false + + tags_all = (known after apply) + + vpc_id = "vpc-98765" + } + + # module.ecs.module.service["primary_service"].aws_security_group_rule.this["egress_all"] will be created + + resource "aws_security_group_rule" "this" { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + from_port = 0 + + id = (known after apply) + + protocol = "-1" + + security_group_id = (known after apply) + + security_group_rule_id = (known after apply) + + self = false + + source_security_group_id = (known after apply) + + to_port = 0 + + type = "egress" + } + + # module.ecs.module.service["primary_service"].aws_security_group_rule.this["ingress_all"] will be created + + resource "aws_security_group_rule" "this" { + + cidr_blocks = [ + + "0.0.0.0/0", + ] + + from_port = 0 + + id = (known after apply) + + protocol = "-1" + + security_group_id = (known after apply) + + security_group_rule_id = (known after apply) + + self = false + + source_security_group_id = (known after apply) + + to_port = 0 + + type = "ingress" + } + + # module.ecs.module.service["primary_service"].module.container_definition["primary_container"].aws_cloudwatch_log_group.this[0] will be created + + resource "aws_cloudwatch_log_group" "this" { + + arn = (known after apply) + + id = (known after apply) + + name = "/aws/ecs/primary_service/gitea" + + name_prefix = (known after apply) + + retention_in_days = 14 + + skip_destroy = false + + tags_all = (known after apply) + } + +Plan: 30 to add, 0 to change, 0 to destroy. + +───────────────────────────────────────────────────────────────────────────── + +Note: You didn't use the -out option to save this plan, so Terraform can't +guarantee to take exactly these actions if you run "terraform apply" now. diff --git a/src/exercises/ecs-application/database/.terraform.lock.hcl b/src/exercises/ecs-application/database/.terraform.lock.hcl new file mode 100644 index 0000000..be6e966 --- /dev/null +++ b/src/exercises/ecs-application/database/.terraform.lock.hcl @@ -0,0 +1,45 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "5.1.0" + constraints = ">= 4.65.0" + hashes = [ + "h1:cD/xic4fsOb2yPp4UARXqzjN8frK9QSxLM4cPfYz8Hg=", + "zh:0c48f157b804c1f392adb5c14b81e756c652755e358096300ea8dd1283021129", + "zh:1a50495a6c0e5665e51df57dac6e781ec71439b11ebf05f971b6f3a3eb4eb7b2", + "zh:2959ff472c05e56d59e012118dd8d55022f005534c0ae961ce81136de9f66a4d", + "zh:2dfda9133581b99ed6e709e89a453fd2974ce88c703d3e073ec31bf99d7508ce", + "zh:2f3d92cc7a6624da42cee2202f8fb23e6d38f156ab7851884d637282cb0dc709", + "zh:3bc2a34d09cbaf439a1815846904f070c782cd8dfd60b5e0116827cda25f7549", + "zh:4ef43f1a247aa8de8690ac3bbc2b00ebaf6b2872fc8d0f5130e4a8130c874b87", + "zh:5477cb272dcaeb0030091bcf23a9f0f33b5410e44e317e9d3d49446f545dbaa4", + "zh:734c8fb4c0b79c82dd757566761dda5b91ee1ef9a2b848a748ade11e0e1cc69f", + "zh:80346c051b677f4f018da7fe06318b87c5bd0f1ec67ce78ab33baed3bb8b031a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a865b2f88dfee13df14116c5cf53d033d2c15855f4b59b9c65337309a928df2c", + "zh:c0345f266eedaece5612c1000722b302f895d1bc5af1d5a4265f0e7000ca48bb", + "zh:d59703c8e6a9d8b4fbd3b4583b945dfff9cb2844c762c0b3990e1cef18282279", + "zh:d8d04a6a6cd2dfcb23b57e551db7b15e647f6166310fb7d883d8ec67bdc9bdc8", + ] +} + +provider "registry.terraform.io/hashicorp/random" { + version = "3.5.1" + constraints = ">= 3.1.0" + hashes = [ + "h1:VSnd9ZIPyfKHOObuQCaKfnjIHRtR7qTw19Rz8tJxm+k=", + "zh:04e3fbd610cb52c1017d282531364b9c53ef72b6bc533acb2a90671957324a64", + "zh:119197103301ebaf7efb91df8f0b6e0dd31e6ff943d231af35ee1831c599188d", + "zh:4d2b219d09abf3b1bb4df93d399ed156cadd61f44ad3baf5cf2954df2fba0831", + "zh:6130bdde527587bbe2dcaa7150363e96dbc5250ea20154176d82bc69df5d4ce3", + "zh:6cc326cd4000f724d3086ee05587e7710f032f94fc9af35e96a386a1c6f2214f", + "zh:78d5eefdd9e494defcb3c68d282b8f96630502cac21d1ea161f53cfe9bb483b3", + "zh:b6d88e1d28cf2dfa24e9fdcc3efc77adcdc1c3c3b5c7ce503a423efbdd6de57b", + "zh:ba74c592622ecbcef9dc2a4d81ed321c4e44cddf7da799faa324da9bf52a22b2", + "zh:c7c5cde98fe4ef1143bd1b3ec5dc04baf0d4cc3ca2c5c7d40d17c0e9b2076865", + "zh:dac4bad52c940cd0dfc27893507c1e92393846b024c5a9db159a93c534a3da03", + "zh:de8febe2a2acd9ac454b844a4106ed295ae9520ef54dc8ed2faf29f12716b602", + "zh:eab0d0495e7e711cca367f7d4df6e322e6c562fc52151ec931176115b83ed014", + ] +} diff --git a/src/exercises/ecs-application/database/main.tf b/src/exercises/ecs-application/database/main.tf new file mode 100644 index 0000000..c7706df --- /dev/null +++ b/src/exercises/ecs-application/database/main.tf @@ -0,0 +1,79 @@ +resource "random_password" "db_password" { + length = 16 + special = true + override_special = "_%@" +} + +resource "aws_security_group" "database_security_group" { + name = "database_security_group" + description = "Allow database access" + vpc_id = var.vpc_id + + ingress { + description = "Allow database access" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + description = "Allow database access" + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_db_subnet_group" "default" { + name = "public" + subnet_ids = var.db_subnets +} + +module "db" { + source = "terraform-aws-modules/rds/aws" + + identifier = "gitea" + engine = "mysql" + major_engine_version = "8.0" + family = "mysql8.0" + instance_class = "db.t2.micro" + storage_encrypted = false + allocated_storage = 5 + publicly_accessible = true + + db_name = "gitea" + username = "gitea" + port = 3306 + password = random_password.db_password.result + + subnet_ids = var.db_subnets + db_subnet_group_name = aws_db_subnet_group.default.name + vpc_security_group_ids = [ + aws_security_group.database_security_group.id + ] + +} + +output "db_host" { + value = module.db.db_instance_address +} + +output "db_name" { + value = module.db.db_instance_name +} + +output "db_username" { + value = module.db.db_instance_username + sensitive = true +} + +output "db_port" { + value = module.db.db_instance_port +} + +output "db_password" { + value = module.db.db_instance_password + sensitive = true +} diff --git a/src/exercises/ecs-application/database/terragrunt.hcl b/src/exercises/ecs-application/database/terragrunt.hcl new file mode 100644 index 0000000..1985c11 --- /dev/null +++ b/src/exercises/ecs-application/database/terragrunt.hcl @@ -0,0 +1,25 @@ +include { + path = find_in_parent_folders() +} + +include "env" { + path = "${get_terragrunt_dir()}/../../env.hcl" + expose = true + } + +dependency "vpc" { + config_path = "${get_terragrunt_dir()}/../../../exercises//vpc" + + mock_outputs = { + vpc_id = "vpc-test" + private_subnets_ids = [ "subnet-private-a", "subnet-private-b" ] + public_subnets_ids = [ "subnet-public-a", "subnet-public-b" ] + private_subnets_cidr_blocks = [ "10.1.2.0/24", "10.1.4.0/24" ] + public_subnets_cidr_blocks = [ "10.1.1.0/24", "10.1.3.0/24" ] + } +} + +inputs = { + vpc_id = dependency.vpc.outputs.vpc_id + db_subnets = dependency.vpc.outputs.public_subnets_ids +} \ No newline at end of file diff --git a/src/exercises/ecs-application/database/variables.tf b/src/exercises/ecs-application/database/variables.tf new file mode 100644 index 0000000..b3f77e3 --- /dev/null +++ b/src/exercises/ecs-application/database/variables.tf @@ -0,0 +1,9 @@ +variable "vpc_id" { + type = string + description = "The id of the vpc." +} +variable "db_subnets" { + type = set(string) + default = null + description = "The subnets for the database" +} diff --git a/src/exercises/ecs-application/web/.terraform.lock.hcl b/src/exercises/ecs-application/web/.terraform.lock.hcl new file mode 100644 index 0000000..496c5b9 --- /dev/null +++ b/src/exercises/ecs-application/web/.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.1.0" + constraints = ">= 4.55.0" + hashes = [ + "h1:cD/xic4fsOb2yPp4UARXqzjN8frK9QSxLM4cPfYz8Hg=", + "zh:0c48f157b804c1f392adb5c14b81e756c652755e358096300ea8dd1283021129", + "zh:1a50495a6c0e5665e51df57dac6e781ec71439b11ebf05f971b6f3a3eb4eb7b2", + "zh:2959ff472c05e56d59e012118dd8d55022f005534c0ae961ce81136de9f66a4d", + "zh:2dfda9133581b99ed6e709e89a453fd2974ce88c703d3e073ec31bf99d7508ce", + "zh:2f3d92cc7a6624da42cee2202f8fb23e6d38f156ab7851884d637282cb0dc709", + "zh:3bc2a34d09cbaf439a1815846904f070c782cd8dfd60b5e0116827cda25f7549", + "zh:4ef43f1a247aa8de8690ac3bbc2b00ebaf6b2872fc8d0f5130e4a8130c874b87", + "zh:5477cb272dcaeb0030091bcf23a9f0f33b5410e44e317e9d3d49446f545dbaa4", + "zh:734c8fb4c0b79c82dd757566761dda5b91ee1ef9a2b848a748ade11e0e1cc69f", + "zh:80346c051b677f4f018da7fe06318b87c5bd0f1ec67ce78ab33baed3bb8b031a", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a865b2f88dfee13df14116c5cf53d033d2c15855f4b59b9c65337309a928df2c", + "zh:c0345f266eedaece5612c1000722b302f895d1bc5af1d5a4265f0e7000ca48bb", + "zh:d59703c8e6a9d8b4fbd3b4583b945dfff9cb2844c762c0b3990e1cef18282279", + "zh:d8d04a6a6cd2dfcb23b57e551db7b15e647f6166310fb7d883d8ec67bdc9bdc8", + ] +} diff --git a/src/exercises/ecs-application/web/dns.tf b/src/exercises/ecs-application/web/dns.tf new file mode 100644 index 0000000..35d90e1 --- /dev/null +++ b/src/exercises/ecs-application/web/dns.tf @@ -0,0 +1,50 @@ +data "aws_route53_zone" "certificate" { + provider = aws.public_infrastructure + name = "${var.default_domain_name}." +} + +resource "aws_acm_certificate" "certificate" { + domain_name = "${var.default_subdomain}.${var.default_domain_name}" + validation_method = "DNS" + + lifecycle { + create_before_destroy = true + } +} + +resource "aws_route53_record" "certificate" { + provider = aws.public_infrastructure + for_each = { + for dvo in aws_acm_certificate.certificate.domain_validation_options : dvo.domain_name => { + name = dvo.resource_record_name + record = dvo.resource_record_value + type = dvo.resource_record_type + } + } + + allow_overwrite = true + name = each.value.name + records = [each.value.record] + ttl = 60 + type = each.value.type + zone_id = data.aws_route53_zone.certificate.zone_id +} + +resource "aws_acm_certificate_validation" "certificate" { + certificate_arn = aws_acm_certificate.certificate.arn + validation_record_fqdns = [for record in aws_route53_record.certificate : record.fqdn] +} + + +resource "aws_route53_record" "loadbalancer_A" { + provider = aws.public_infrastructure + zone_id = data.aws_route53_zone.certificate.zone_id + name = var.default_subdomain + type = "A" + + alias { + name = aws_lb.loadbalancer.dns_name + zone_id = aws_lb.loadbalancer.zone_id + evaluate_target_health = true + } +} diff --git a/src/exercises/ecs-application/web/efs.tf b/src/exercises/ecs-application/web/efs.tf new file mode 100644 index 0000000..9e876e2 --- /dev/null +++ b/src/exercises/ecs-application/web/efs.tf @@ -0,0 +1,12 @@ +resource "aws_efs_file_system" "data" { + creation_token = "data" + encrypted = false + throughput_mode = "elastic" +} + +resource "aws_efs_mount_target" "mount_target" { + for_each = var.public_subnets + file_system_id = aws_efs_file_system.data.id + subnet_id = each.value + security_groups = [aws_security_group.open_access.id] +} diff --git a/src/exercises/ecs-application/web/lb.tf b/src/exercises/ecs-application/web/lb.tf new file mode 100644 index 0000000..09082d9 --- /dev/null +++ b/src/exercises/ecs-application/web/lb.tf @@ -0,0 +1,75 @@ + +resource "aws_security_group" "open_access" { + name = "sec-test-open-access" + description = "Allow all inbound traffic" + vpc_id = var.vpc_id + + ingress { + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = -1 + cidr_blocks = ["0.0.0.0/0"] + } +} + +resource "aws_lb" "loadbalancer" { + name = "sec-test-lb" + internal = false + load_balancer_type = "application" + security_groups = [aws_security_group.open_access.id] + subnets = var.public_subnets +} + +resource "aws_lb_target_group" "targetgroup" { + name = var.default_subdomain + port = 80 + protocol = "HTTP" + vpc_id = var.vpc_id + target_type = "ip" + + health_check { + healthy_threshold = 2 + unhealthy_threshold = 2 + timeout = 3 + interval = 30 + path = "/api/healthz" + port = "traffic-port" + } + +} + +resource "aws_lb_listener" "https" { + load_balancer_arn = aws_lb.loadbalancer.arn + port = "443" + protocol = "HTTPS" + ssl_policy = "ELBSecurityPolicy-TLS-1-0-2015-04" + certificate_arn = aws_acm_certificate.certificate.arn + + default_action { + target_group_arn = aws_lb_target_group.targetgroup.arn + type = "forward" + } +} + +resource "aws_lb_listener" "http" { + load_balancer_arn = aws_lb.loadbalancer.arn + port = "80" + protocol = "HTTP" + + default_action { + type = "redirect" + + redirect { + port = "443" + protocol = "HTTPS" + status_code = "HTTP_301" + } + } +} diff --git a/src/exercises/ecs-application/web/main.tf b/src/exercises/ecs-application/web/main.tf new file mode 100644 index 0000000..a311544 --- /dev/null +++ b/src/exercises/ecs-application/web/main.tf @@ -0,0 +1,159 @@ +locals { + http_port = 3000 + primary_container_name = "gitea" +} + + + +module "ecs" { + source = "terraform-aws-modules/ecs/aws" + + cluster_name = "sec-challenge" + + fargate_capacity_providers = { + FARGATE_SPOT = { + default_capacity_provider_strategy = { + weight = 100 + } + } + } + + services = { + primary_service = { + cpu = 256 + memory = 512 + subnet_ids = var.public_subnets + assign_public_ip = true + enable_execute_command = true + + container_definitions = { + primary_container = { + name = local.primary_container_name + image = "gitea/gitea:1.19.3" + essential = true + readonly_root_filesystem = false + + port_mappings = [ + { + hostPort = local.http_port + containerPort = local.http_port + }] + + health_check = { + command = ["CMD-SHELL", "curl -fSs 127.0.0.1:3000/api/healthz || exit 1"] + interval = 30 + timeout = 5 + start_period = 60 + retries = 3 + } + + mount_points = [ + { + sourceVolume = "data" + containerPath = "/data" + readOnly = false + } + ] + + environment = [ + { + name = "GITEA__database__DB_TYPE" + value = "mysql" + }, + { + name = "GITEA__database__HOST" + value = "${var.db_host}:${var.db_port}" + }, + { + name = "GITEA__database__NAME" + value = var.db_name + }, + { + name = "GITEA__database__USER" + value = var.db_username + }, + { + name = "GITEA__database__PASSWD" + value = var.db_password + }, + { + name = "GITEA__server__ROOT_URL" + value = "https://${var.default_subdomain}.${var.default_domain_name}/" + }, + { + name = "GITEA__server__HTTP_PORT" + value = local.http_port + }, + { + name = "GITEA__server__PROTOCOL" + value = "http" + }, + { + name = "GITEA__server__DOMAIN" + value = "${var.default_subdomain}.${var.default_domain_name}" + } + ] + } + } + + security_group_rules = { + ingress_all = { + type = "ingress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + egress_all = { + type = "egress" + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + } + + volume = { + data = { + efs_volume_configuration = { + file_system_id = aws_efs_file_system.data.id + transit_encryption = "DISABLED" + } + } + } + + load_balancer = { + primary_container = { + container_name = local.primary_container_name + container_port = "${local.http_port}" + target_group_arn = aws_lb_target_group.targetgroup.arn + } + } + + tasks_iam_role_policies = { + allow_exec_command = aws_iam_policy.allow_exec_command.arn + } + + } + } +} + +data "aws_iam_policy_document" "allow_exec_command" { + statement { + actions = [ + "ssmmessages:CreateControlChannel", + "ssmmessages:CreateDataChannel", + "ssmmessages:OpenControlChannel", + "ssmmessages:OpenDataChannel" + ] + + resources = ["*"] + } +} + +resource "aws_iam_policy" "allow_exec_command" { + name = "allow_exec_command" + path = "/exercises/securing-app-exercise/web/" + policy = data.aws_iam_policy_document.allow_exec_command.json +} diff --git a/src/exercises/ecs-application/web/terragrunt.hcl b/src/exercises/ecs-application/web/terragrunt.hcl new file mode 100644 index 0000000..86b8104 --- /dev/null +++ b/src/exercises/ecs-application/web/terragrunt.hcl @@ -0,0 +1,44 @@ +include { + path = find_in_parent_folders() +} + +include "env" { + path = "${get_terragrunt_dir()}/../../env.hcl" + expose = true + } + +dependency "vpc" { + config_path = "${get_terragrunt_dir()}/../..//vpc" + + mock_outputs = { + vpc_id = "vpc-test" + private_subnets_ids = [ "subnet-private-a", "subnet-private-b" ] + public_subnets_ids = [ "subnet-public-a", "subnet-public-b" ] + private_subnets_cidr_blocks = [ "10.1.2.0/24", "10.1.4.0/24" ] + public_subnets_cidr_blocks = [ "10.1.1.0/24", "10.1.3.0/24" ] + } +} + +dependency "rds" { + config_path = "${get_terragrunt_dir()}/..//database" + + mock_outputs = { + db_host = "db-host" + db_name = "db-name" + db_username = "db-username" + db_password = "db-password" + } +} + +inputs = { + vpc_id = dependency.vpc.outputs.vpc_id + public_subnets = dependency.vpc.outputs.public_subnets_ids + default_domain_name = "super-cool-domain.com" + default_subdomain = "awesome-app" + db_host = dependency.rds.outputs.db_host + db_port = dependency.rds.outputs.db_port + db_name = dependency.rds.outputs.db_name + db_username = dependency.rds.outputs.db_username + db_password = dependency.rds.outputs.db_password + +} \ No newline at end of file diff --git a/src/exercises/ecs-application/web/variables.tf b/src/exercises/ecs-application/web/variables.tf new file mode 100644 index 0000000..5a2726b --- /dev/null +++ b/src/exercises/ecs-application/web/variables.tf @@ -0,0 +1,52 @@ +variable "vpc_id" { + type = string + description = "The id of the vpc." +} + +variable "public_subnets" { + type = set(string) + default = null + description = "The subnets for the loadbalancer" +} + +variable "default_domain_name" { + description = "Domain for the certificate and DNS entry. Do not include subdomain (eg, www.)" + type = string +} + +variable "default_subdomain" { + description = "Subdomain for the certificate and DNS entry." + type = string + default = null +} + + +variable "db_host" { + description = "The hostname of the database" + type = string + default = null +} + +variable "db_name" { + description = "The name of the database" + type = string + default = null +} + +variable "db_username" { + description = "The username of the database" + type = string + default = null +} + +variable "db_port" { + description = "The port of the database" + type = number + default = null +} + +variable "db_password" { + description = "The password of the database" + type = string + default = null +} diff --git a/src/exercises/env.hcl b/src/exercises/env.hcl new file mode 100644 index 0000000..62e160d --- /dev/null +++ b/src/exercises/env.hcl @@ -0,0 +1,21 @@ + +locals { + deployment_role = "exercises" + vpc_cidr = "10.45.0.0/16" + + allow_remote_access = false + remote_access_cidrs = [ + ] + + terragrunt_tags = { + created_by = "terraform" + terraform_path = "/${lower(path_relative_to_include())}" + } + + tags = { + ce_resource_path = "/HIRING/EXERCISES" + created_by = "terraform", + project = "exercises" + cost_center = "hiring" + } +} \ No newline at end of file diff --git a/src/exercises/vpc/.terraform.lock.hcl b/src/exercises/vpc/.terraform.lock.hcl new file mode 100644 index 0000000..e1688e6 --- /dev/null +++ b/src/exercises/vpc/.terraform.lock.hcl @@ -0,0 +1,23 @@ +# This file is maintained automatically by "terraform init". +# Manual edits may be lost in future updates. + +provider "registry.terraform.io/hashicorp/aws" { + version = "4.27.0" + constraints = ">= 2.70.0" + hashes = [ + "h1:PiYNXbUXMPECu/BARC94IRy6TyVnfHGok9VnTWin/gs=", + "h1:w3j7YomUQ9IfRp3MUuY0+hFX1T1cawZoj0Xsc1a46bU=", + "zh:0f5ade3801fec487641e4f7d81e28075b716c787772f9709cc2378d20f325791", + "zh:19ffa83be6b6765a4f821a17b8d260dd0f192a6c40765fa53ac65fd042cb1f65", + "zh:3ac89d33ff8ca75bdc42f31c63ce0018ffc66aa69917c18713e824e381950e4e", + "zh:81a199724e74992c8a029a968d211cb45277d95a2e88d0f07ec85127b6c6849b", + "zh:9b12af85486a96aedd8d7984b0ff811a4b42e3d88dad1a3fb4c0b580d04fa425", + "zh:a2e2c851a37ef97bbccccd2e686b4d016abe207a7f56bff70b10bfdf8ed1cbfd", + "zh:baf844def338d77f8a3106b1411a1fe22e93a82e3dc51e5d33b766f741c4a6a3", + "zh:bc33137fae808f91da0a9de7031cbea77d0ee4eefb4d2ad6ab7f58cc2111a7ff", + "zh:c960ae2b33c8d3327f67a3db5ce1952315146d69dfc3f1b0922242e2b218eec8", + "zh:f3ea1a25797c79c035463a1188a6a42e131f391f3cb714975ce49ccd301cda07", + "zh:f7e77c871d38236e5fedee0086ff77ff396e88964348c794cf38e578fcc00293", + "zh:fb338d5dfafab907b8608bd66cad8ca9ae4679f8c62c2435c2056a38b719baa2", + ] +} diff --git a/src/exercises/vpc/terragrunt.hcl b/src/exercises/vpc/terragrunt.hcl new file mode 100644 index 0000000..08f9283 --- /dev/null +++ b/src/exercises/vpc/terragrunt.hcl @@ -0,0 +1,34 @@ +include { + path = find_in_parent_folders() +} + +include "account" { + path = "${get_terragrunt_dir()}/../../account.hcl" + expose = true + } + +include "env" { + path = "${get_terragrunt_dir()}/../env.hcl" + expose = true + } + +dependency "infrastructure" { + config_path = "${get_terragrunt_dir()}/some/dep/in/larger/project" +} + +terraform { + source = "${get_terragrunt_dir()}/../modules/vpc/" +} + +inputs = { + deployment_long_name = include.account.locals.deployment_long_name + deployment_short_name = include.account.locals.deployment_short_name + deployment_role = include.env.locals.deployment_role + + vpc_cidr = include.env.locals.vpc_cidr + infrastructure_dns_domain_name = dependency.infrastructure.outputs.dns_domain_name + infrastructure_dns_server_ips = dependency.infrastructure.outputs.dns_server_ips + infrastructure_ntp_server_ips = dependency.infrastructure.outputs.ntp_server_ips + infrastructure_netbios_nameserver_ips = dependency.infrastructure.outputs.netbios_name_server_ips + infrastructure_netbios_node_type = dependency.infrastructure.outputs.netbios_node_type +} \ No newline at end of file diff --git a/src/modules/vpc/flowlogs.tf b/src/modules/vpc/flowlogs.tf new file mode 100644 index 0000000..4b38295 --- /dev/null +++ b/src/modules/vpc/flowlogs.tf @@ -0,0 +1,82 @@ +resource "aws_s3_bucket" "flowlogs_bucket" { + bucket = "${var.deployment_short_name}-${var.deployment_role}-flowlogs" + force_destroy = true +} + +resource "aws_s3_bucket_public_access_block" "flowlogs_bucket" { + bucket = aws_s3_bucket.flowlogs_bucket.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} + +resource "aws_s3_bucket_acl" "flowlogs_bucket" { + bucket = aws_s3_bucket.flowlogs_bucket.id + acl = "private" +} + +resource "aws_s3_bucket_server_side_encryption_configuration" "flowlogs_bucket" { + bucket = aws_s3_bucket.flowlogs_bucket.id + rule { + apply_server_side_encryption_by_default { + sse_algorithm = "AES256" + } + } +} + +resource "aws_iam_role" "flowlogs_cloudwatch" { + name = "FlowLogsCloudWatch${title(var.deployment_role)}" + assume_role_policy = jsonencode( + { + Statement = [ + { + Action = "sts:AssumeRole" + Effect = "Allow" + Principal = { + Service = "vpc-flow-logs.amazonaws.com" + } + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_iam_role_policy" "flowlogs_cloudwatch" { + name = "VPCFlowLogs" + role = aws_iam_role.flowlogs_cloudwatch.name + policy = jsonencode( + { + Statement = [ + { + Action = [ + "logs:PutLogEvents", + "logs:DescribeLogStreams", + "logs:DescribeLogGroups", + "logs:CreateLogStream", + "logs:CreateLogGroup", + ] + Effect = "Allow" + Resource = "*" + }, + ] + Version = "2012-10-17" + } + ) +} + +resource "aws_flow_log" "all" { + log_destination = aws_s3_bucket.flowlogs_bucket.arn + log_destination_type = "s3" + traffic_type = "ALL" + vpc_id = module.vpc.vpc_id +} + +resource "aws_flow_log" "all_cloudwatch" { + iam_role_arn = aws_iam_role.flowlogs_cloudwatch.arn + log_destination = "arn:aws:logs:${data.aws_region.current.name}:${data.aws_caller_identity.current.account_id}:log-group:FlowLogs" + traffic_type = "ALL" + vpc_id = module.vpc.vpc_id +} \ No newline at end of file diff --git a/src/modules/vpc/main.tf b/src/modules/vpc/main.tf new file mode 100644 index 0000000..d54be1b --- /dev/null +++ b/src/modules/vpc/main.tf @@ -0,0 +1,70 @@ +data "aws_availability_zones" "available" { + state = "available" +} + +data "aws_caller_identity" "current" {} +data "aws_region" "current" {} + +module "vpc" { + source = "terraform-aws-modules/vpc/aws" + version = "~> 2.0" + + name = var.deployment_short_name + cidr = var.vpc_cidr + + azs = [ + data.aws_availability_zones.available.names[0], + data.aws_availability_zones.available.names[1] + ] + + public_subnets = [ + cidrsubnet(var.vpc_cidr, 8, 1), + cidrsubnet(var.vpc_cidr, 8, 3), + ] + + private_subnets = [ + cidrsubnet(var.vpc_cidr, 8, 2), + cidrsubnet(var.vpc_cidr, 8, 4) + ] + + enable_nat_gateway = true + enable_vpn_gateway = true + enable_s3_endpoint = true + enable_dhcp_options = true + + enable_dns_hostnames = true + enable_dns_support = true + + + public_subnet_suffix = "public" + public_subnet_tags = { + "Name" = "${var.deployment_long_name} public (${var.deployment_role})" + "Purpose" = var.deployment_role + } + public_route_table_tags = { + "Name" = "${var.deployment_long_name} public (${var.deployment_role})" + "Purpose" = var.deployment_role + } + + private_subnet_suffix = "private" + private_subnet_tags = { + "Name" = "${var.deployment_long_name} private (${var.deployment_role})" + "Purpose" = var.deployment_role + } + private_route_table_tags = { + "Name" = "${var.deployment_long_name} private (${var.deployment_role})" + "Purpose" = var.deployment_role + } + + manage_default_security_group = true + default_security_group_name = "default" + default_security_group_ingress = [] + default_security_group_egress = [] + default_security_group_tags = { "Name" = "default (No Access)" } + + tags = { + "Name" = "${var.deployment_long_name} (${var.deployment_role})" + "Purpose" = var.deployment_role + } +} + diff --git a/src/modules/vpc/outputs.tf b/src/modules/vpc/outputs.tf new file mode 100644 index 0000000..e965c89 --- /dev/null +++ b/src/modules/vpc/outputs.tf @@ -0,0 +1,31 @@ +output "vpc_id" { + value = module.vpc.vpc_id +} + +output "vpc_cidr" { + value = module.vpc.vpc_cidr_block +} + +output "public_subnets_ids" { + value = module.vpc.public_subnets +} + +output "private_subnets_ids" { + value = module.vpc.private_subnets +} + +output "public_subnets_cidr_blocks" { + value = module.vpc.public_subnets_cidr_blocks +} + +output "private_subnets_cidr_blocks" { + value = module.vpc.private_subnets_cidr_blocks +} + +output "public_route_table_ids" { + value = module.vpc.public_route_table_ids +} + +output "private_route_table_ids" { + value = module.vpc.private_route_table_ids +} \ No newline at end of file diff --git a/src/modules/vpc/variables.tf b/src/modules/vpc/variables.tf new file mode 100644 index 0000000..29e3bde --- /dev/null +++ b/src/modules/vpc/variables.tf @@ -0,0 +1,44 @@ +variable "deployment_long_name" { + description = "The long name for the deployment (wheeling, tenantgroup, etc...)." + type = string +} + +variable "deployment_short_name" { + description = "The short name for the deployment (wh, tg, etc...)." + type = string +} + +variable "deployment_role" { + description = "The role for the deployment (qa, prod, staging, etc...)." + type = string +} + +variable "vpc_cidr" { + description = "The cidr for the VPC." + type = string +} + +variable "infrastructure_dns_domain_name" { + description = "The dns domain name for internal hosts." + type = string +} + +variable "infrastructure_dns_server_ips" { + description = "The list of ad DNS servers for the domain." + type = list(string) +} + +variable "infrastructure_ntp_server_ips" { + description = "The list of NTP servers for the vpc." + type = list(string) +} + +variable "infrastructure_netbios_nameserver_ips" { + description = "The list of netbios servers for the domain." + type = list(string) +} + +variable "infrastructure_netbios_node_type" { + description = "The node type of the netbios servers." + type = string +} \ No newline at end of file