diff --git a/terraform/001/main.tf b/terraform/001/main.tf new file mode 100644 index 0000000..947e45c --- /dev/null +++ b/terraform/001/main.tf @@ -0,0 +1,39 @@ +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + local = { + source = "hashicorp/local" + version = "~> 2.5" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +# Generate an RSA private key locally +resource "tls_private_key" "devops_kp" { + algorithm = "RSA" + rsa_bits = 4096 +} + +# Import the public half into AWS as an RSA key pair +resource "aws_key_pair" "devops_kp" { + key_name = "devops-kp" + public_key = tls_private_key.devops_kp.public_key_openssh +} + +# Persist the private key to disk +resource "local_sensitive_file" "devops_kp_pem" { + content = tls_private_key.devops_kp.private_key_pem + filename = "/home/bob/devops-kp.pem" + file_permission = "0400" +} diff --git a/terraform/001/task-1.md b/terraform/001/task-1.md new file mode 100644 index 0000000..4b3c2da --- /dev/null +++ b/terraform/001/task-1.md @@ -0,0 +1,15 @@ +## Task 1 + +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +For this task, create a key pair using Terraform with the following requirements: + +Name of the key pair should be devops-kp. + +Key pair type must be rsa. + +The private key file should be saved under /home/bob/devops-kp.pem. +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + diff --git a/terraform/002/main.tf b/terraform/002/main.tf new file mode 100644 index 0000000..c26b13e --- /dev/null +++ b/terraform/002/main.tf @@ -0,0 +1,50 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +# Look up the default VPC +data "aws_vpc" "default" { + default = true +} + +resource "aws_security_group" "devops_sg" { + name = "devops-sg" + description = "Security group for Nautilus App Servers" + vpc_id = data.aws_vpc.default.id + + ingress { + description = "HTTP" + from_port = 80 + to_port = 80 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + ingress { + description = "SSH" + from_port = 22 + to_port = 22 + protocol = "tcp" + cidr_blocks = ["0.0.0.0/0"] + } + + egress { + from_port = 0 + to_port = 0 + protocol = "-1" + cidr_blocks = ["0.0.0.0/0"] + } + + tags = { + Name = "devops-sg" + } +} \ No newline at end of file diff --git a/terraform/002/task-2.md b/terraform/002/task-2.md new file mode 100644 index 0000000..9d5e171 --- /dev/null +++ b/terraform/002/task-2.md @@ -0,0 +1,15 @@ +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +Use Terraform to create a security group under the default VPC with the following requirements: + +1) The name of the security group must be devops-sg. + +2) The description must be Security group for Nautilus App Servers. + +3) Add an inbound rule of type HTTP, with a port range of 80, and source CIDR range 0.0.0.0/0. + +4) Add another inbound rule of type SSH, with a port range of 22, and source CIDR range 0.0.0.0/0. + +Ensure that the security group is created in the us-east-1 region using Terraform. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/003/main.tf b/terraform/003/main.tf new file mode 100644 index 0000000..c17aa38 --- /dev/null +++ b/terraform/003/main.tf @@ -0,0 +1,20 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_vpc" "devops_vpc" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "devops-vpc" + } +} \ No newline at end of file diff --git a/terraform/003/task-3.md b/terraform/003/task-3.md new file mode 100644 index 0000000..77aaaf3 --- /dev/null +++ b/terraform/003/task-3.md @@ -0,0 +1,9 @@ +## Task 3 + +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +Create a VPC named devops-vpc in region us-east-1 with any IPv4 CIDR block through terraform. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/004/main.tf b/terraform/004/main.tf new file mode 100644 index 0000000..43109e2 --- /dev/null +++ b/terraform/004/main.tf @@ -0,0 +1,20 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_vpc" "xfusion_vpc" { + cidr_block = "192.168.0.0/24" + + tags = { + Name = "xfusion-vpc" + } +} \ No newline at end of file diff --git a/terraform/004/task-4.md b/terraform/004/task-4.md new file mode 100644 index 0000000..d3ee169 --- /dev/null +++ b/terraform/004/task-4.md @@ -0,0 +1,8 @@ +The Nautilus DevOps team is strategically planning the migration of a portion of their infrastructure to the AWS cloud. Acknowledging the magnitude of this endeavor, they have chosen to tackle the migration incrementally rather than as a single, massive transition. Their approach involves creating Virtual Private Clouds (VPCs) as the initial step, as they will be provisioning various services under different VPCs. + +Create a VPC named xfusion-vpc in us-east-1 region with 192.168.0.0/24 IPv4 CIDR using terraform. + + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/005/main.tf b/terraform/005/main.tf new file mode 100644 index 0000000..886dd91 --- /dev/null +++ b/terraform/005/main.tf @@ -0,0 +1,21 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_vpc" "devops_vpc" { + cidr_block = "10.0.0.0/16" + assign_generated_ipv6_cidr_block = true + + tags = { + Name = "devops-vpc" + } +} \ No newline at end of file diff --git a/terraform/005/task-5.md b/terraform/005/task-5.md new file mode 100644 index 0000000..d004d39 --- /dev/null +++ b/terraform/005/task-5.md @@ -0,0 +1,9 @@ +## Task 5 + +The Nautilus DevOps team is strategically planning the migration of a portion of their infrastructure to the AWS cloud. Acknowledging the magnitude of this endeavor, they have chosen to tackle the migration incrementally rather than as a single, massive transition. Their approach involves creating Virtual Private Clouds (VPCs) as the initial step, as they will be provisioning various services under different VPCs. + +For this task, create a VPC named devops-vpc in the us-east-1 region with the Amazon-provided IPv6 CIDR block using terraform. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/006/main.tf b/terraform/006/main.tf new file mode 100644 index 0000000..f669a72 --- /dev/null +++ b/terraform/006/main.tf @@ -0,0 +1,20 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_eip" "nautilus_eip" { + domain = "vpc" + + tags = { + Name = "nautilus-eip" + } +} \ No newline at end of file diff --git a/terraform/006/task-6.md b/terraform/006/task-6.md new file mode 100644 index 0000000..9c21370 --- /dev/null +++ b/terraform/006/task-6.md @@ -0,0 +1,7 @@ +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +For this task, allocate an Elastic IP address named nautilus-eip using Terraform. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/007/main.tf b/terraform/007/main.tf new file mode 100644 index 0000000..b9fe31a --- /dev/null +++ b/terraform/007/main.tf @@ -0,0 +1,53 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# tls = { +# source = "hashicorp/tls" +# version = "~> 4.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +# --- New RSA key pair --- +resource "tls_private_key" "devops_kp" { + algorithm = "RSA" + rsa_bits = 4096 +} + +resource "aws_key_pair" "devops_kp" { + key_name = "devops-kp" + public_key = tls_private_key.devops_kp.public_key_openssh +} + +# --- Default VPC + its default security group --- +data "aws_vpc" "default" { + default = true +} + +data "aws_security_group" "default" { + vpc_id = data.aws_vpc.default.id + name = "default" +} + +# --- EC2 instance --- +resource "aws_instance" "devops_ec2" { + ami = "ami-0c101f26f147fa7fd" + instance_type = "t2.micro" + key_name = aws_key_pair.devops_kp.key_name + vpc_security_group_ids = [data.aws_security_group.default.id] + + credit_specification { + cpu_credits = "standard" + } + + tags = { + Name = "devops-ec2" + } +} \ No newline at end of file diff --git a/terraform/007/task-7.md b/terraform/007/task-7.md new file mode 100644 index 0000000..827c584 --- /dev/null +++ b/terraform/007/task-7.md @@ -0,0 +1,17 @@ +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. + +For this task, create an EC2 instance using Terraform with the following requirements: + +The EC2 instance must use the value devops-ec2 as its Name tag, which defines the instance name in AWS. + +Use the Amazon Linux ami-0c101f26f147fa7fd to launch this instance. + +The Instance type must be t2.micro. + +Create a new RSA key named devops-kp. + +Attach the default (available by default) security group. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to provision the instance. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/008/main.tf b/terraform/008/main.tf new file mode 100644 index 0000000..1c4fbcb --- /dev/null +++ b/terraform/008/main.tf @@ -0,0 +1,30 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +# Look up the existing instance by its Name tag +data "aws_instance" "datacenter_ec2" { + filter { + name = "tag:Name" + values = ["datacenter-ec2"] + } +} + +# Create an AMI from that instance +resource "aws_ami_from_instance" "datacenter_ec2_ami" { + name = "datacenter-ec2-ami" + source_instance_id = data.aws_instance.datacenter_ec2.id + + tags = { + Name = "datacenter-ec2-ami" + } +} \ No newline at end of file diff --git a/terraform/008/task-8.md b/terraform/008/task-8.md new file mode 100644 index 0000000..0d9ecff --- /dev/null +++ b/terraform/008/task-8.md @@ -0,0 +1,9 @@ +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +For this task, create an AMI from an existing EC2 instance named datacenter-ec2 using Terraform. + +Name of the AMI should be datacenter-ec2-ami, make sure AMI is in available state. + +The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to create the AMI. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/009/main.tf b/terraform/009/main.tf new file mode 100644 index 0000000..1880a7f --- /dev/null +++ b/terraform/009/main.tf @@ -0,0 +1,22 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_ebs_volume" "xfusion_volume" { + availability_zone = "us-east-1a" + type = "gp3" + size = 2 + + tags = { + Name = "xfusion-volume" + } +} \ No newline at end of file diff --git a/terraform/009/task-9.md b/terraform/009/task-9.md new file mode 100644 index 0000000..02e6152 --- /dev/null +++ b/terraform/009/task-9.md @@ -0,0 +1,16 @@ +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +For this task, create an AWS EBS volume using Terraform with the following requirements: + +Name of the volume should be xfusion-volume. + +Volume type must be gp3. + +Volume size must be 2 GiB. + +Ensure the volume is created in us-east-1. + + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/010/main.tf b/terraform/010/main.tf new file mode 100644 index 0000000..8092ac1 --- /dev/null +++ b/terraform/010/main.tf @@ -0,0 +1,30 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +# Look up the existing volume by its Name tag +data "aws_ebs_volume" "devops_vol" { + filter { + name = "tag:Name" + values = ["devops-vol"] + } +} + +# Snapshot it +resource "aws_ebs_snapshot" "devops_vol_ss" { + volume_id = data.aws_ebs_volume.devops_vol.id + description = "Devops Snapshot" + + tags = { + Name = "devops-vol-ss" + } +} \ No newline at end of file diff --git a/terraform/010/task-10.md b/terraform/010/task-10.md new file mode 100644 index 0000000..6f1a9bf --- /dev/null +++ b/terraform/010/task-10.md @@ -0,0 +1,13 @@ +The Nautilus DevOps team has some volumes in different regions in their AWS account. They are going to setup some automated backups so that all important data can be backed up on regular basis. For now they shared some requirements to take a snapshot of one of the volumes they have. + +Create a snapshot of an existing volume named devops-vol in us-east-1 region using terraform. + +1) The name of the snapshot must be devops-vol-ss. + +2) The description must be Devops Snapshot. + +3) Make sure the snapshot status is completed before submitting the task. + +The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/011/main.tf b/terraform/011/main.tf new file mode 100644 index 0000000..3c9ab20 --- /dev/null +++ b/terraform/011/main.tf @@ -0,0 +1,24 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_cloudwatch_metric_alarm" "datacenter_alarm" { + alarm_name = "datacenter-alarm" + namespace = "AWS/EC2" + metric_name = "CPUUtilization" + statistic = "Average" + comparison_operator = "GreaterThanThreshold" + threshold = 80 + period = 300 + evaluation_periods = 1 + alarm_description = "Alarm when EC2 CPU utilization exceeds 80%" +} \ No newline at end of file diff --git a/terraform/011/task-11.md b/terraform/011/task-11.md new file mode 100644 index 0000000..3d03cf3 --- /dev/null +++ b/terraform/011/task-11.md @@ -0,0 +1,13 @@ +The Nautilus DevOps team is setting up monitoring in their AWS account. As part of this, they need to create a CloudWatch alarm. + +Using Terraform, perform the following: + +Task Details: +Create a CloudWatch alarm named datacenter-alarm. +The alarm should monitor CPU utilization of an EC2 instance. +Trigger the alarm when CPU utilization exceeds 80%. +Set the evaluation period to 5 minutes. +Use a single evaluation period. +Ensure that the entire configuration is implemented using Terraform. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. \ No newline at end of file diff --git a/terraform/012/main.tf b/terraform/012/main.tf new file mode 100644 index 0000000..9b0d6b3 --- /dev/null +++ b/terraform/012/main.tf @@ -0,0 +1,45 @@ +# terraform { +# required_providers { +# aws = { +# source = "hashicorp/aws" +# version = "~> 6.0" +# } +# } +# } + +# provider "aws" { +# region = "us-east-1" +# } + +resource "aws_s3_bucket" "datacenter" { + bucket = "datacenter-s3-30377" +} + +# Re-enable ACLs (default is BucketOwnerEnforced, which disables them) +resource "aws_s3_bucket_ownership_controls" "datacenter" { + bucket = aws_s3_bucket.datacenter.id + rule { + object_ownership = "BucketOwnerPreferred" + } +} + +# Lift the public-access block so a public ACL is allowed +resource "aws_s3_bucket_public_access_block" "datacenter" { + bucket = aws_s3_bucket.datacenter.id + + block_public_acls = false + block_public_policy = false + ignore_public_acls = false + restrict_public_buckets = false +} + +# Now the public ACL will actually take +resource "aws_s3_bucket_acl" "datacenter" { + bucket = aws_s3_bucket.datacenter.id + acl = "public-read" + + depends_on = [ + aws_s3_bucket_ownership_controls.datacenter, + aws_s3_bucket_public_access_block.datacenter, + ] +} \ No newline at end of file diff --git a/terraform/012/task-12.md b/terraform/012/task-12.md new file mode 100644 index 0000000..c85bfae --- /dev/null +++ b/terraform/012/task-12.md @@ -0,0 +1,14 @@ +As part of the data migration process, the Nautilus DevOps team is actively creating several S3 buckets on AWS. They plan to utilize both private and public S3 buckets to store the relevant data. Given the ongoing migration of other infrastructure to AWS, it is logical to consolidate data storage within the AWS environment as well. + +Create a public S3 bucket named datacenter-s3-30377 using Terraform. + +Ensure the bucket is accessible publicly once created by setting the proper ACL. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Notes: + +Create the resources only in the us-east-1 region. +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. +The name of the S3 bucket should be based on datacenter-s3-30377. +You can use the ACL settings to ensure the bucket is publicly accessible. \ No newline at end of file diff --git a/terraform/013/task-13.md b/terraform/013/task-13.md new file mode 100644 index 0000000..88147ce --- /dev/null +++ b/terraform/013/task-13.md @@ -0,0 +1,115 @@ +As part of the data migration process, the Nautilus DevOps team is actively creating several S3 buckets on AWS using Terraform. They plan to utilize both private and public S3 buckets to store the relevant data. Given the ongoing migration of other infrastructure to AWS, it is logical to consolidate data storage within the AWS environment as well. + +Create an S3 bucket using Terraform with the following details: + +1) The name of the S3 bucket must be xfusion-s3-6464. + +2) The S3 bucket must block all public access, making it a private bucket. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Notes: + +Use Terraform to provision the S3 bucket. +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. +Ensure the resources are created in the us-east-1 region. +The bucket must have block public access enabled to restrict any public access. + +## Solution + +# Private S3 Bucket — `xfusion-s3-6464` + +Terraform solution to provision a fully private S3 bucket in `us-east-1`, with all public access blocked. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_s3_bucket" "xfusion" { + bucket = "xfusion-s3-6464" +} + +resource "aws_s3_bucket_public_access_block" "xfusion" { + bucket = aws_s3_bucket.xfusion.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_s3_bucket` + +Creates the bucket itself. The `bucket` argument sets the globally-unique name +(`xfusion-s3-6464`) exactly as required. Provider region `us-east-1` places it in +the correct region. On its own a modern bucket is already private by default, but +the task explicitly wants **Block Public Access enabled**, which is a separate +resource. + +### `aws_s3_bucket_public_access_block` + +This is the resource that actually satisfies the requirement. It manages the four +independent Block Public Access (BPA) switches on the bucket. Setting all four to +`true` guarantees the bucket can never be exposed publicly — regardless of what ACL +or bucket policy someone later attaches. + +| Flag | `true` means | +|------|--------------| +| `block_public_acls` | Reject any **new** request that would apply a public ACL. | +| `ignore_public_acls` | Ignore any public ACLs **already** on the bucket/objects. | +| `block_public_policy` | Reject any **new** bucket policy that grants public access. | +| `restrict_public_buckets` | If a public policy somehow exists, only allow access to the bucket owner / AWS services — cross-account and anonymous access is denied. | + +The distinction that trips people up: `block_*` flags act at *write* time (they +stop you from adding public grants), while `ignore_*` / `restrict_*` flags act at +*evaluation* time (they neutralize public grants that are already present). Turning +on all four covers both directions, which is why "block **all** public access" maps +to every flag being `true`. + +### Why no ACL / ownership resources here + +Unlike the public-bucket case, a private bucket needs **no** `aws_s3_bucket_acl` or +`aws_s3_bucket_ownership_controls`. AWS defaults new buckets to `BucketOwnerEnforced` +(ACLs disabled) and applies BPA — both of which push toward *private*. We're moving +with the defaults, not against them, so the config stays minimal. + +## Verify + +```bash +aws s3api get-public-access-block --bucket xfusion-s3-6464 \ + --query 'PublicAccessBlockConfiguration' +``` + +Expected output — all four `true`: + +```json +{ + "BlockPublicAcls": true, + "IgnorePublicAcls": true, + "BlockPublicPolicy": true, + "RestrictPublicBuckets": true +} +``` \ No newline at end of file diff --git a/terraform/certification-level-1.md b/terraform/certification-level-1.md new file mode 100644 index 0000000..351d72c --- /dev/null +++ b/terraform/certification-level-1.md @@ -0,0 +1,993 @@ +# Task 1 + +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +For this task, create a key pair using Terraform with the following requirements: + +Name of the key pair should be xfusion-kp-t1q1. + +Key pair type must be rsa. + +The private key file should be saved under /home/bob. +The Terraform working directory is /home/bob/terraform/t1q1. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + + +'xfusion-kp-t1q1' key pair was created using 'Terraform' and saved under '/home/bob'? + +## Solution + +# Key Pair — `xfusion-kp-t1q1` (RSA, private key under `/home/bob`) + +Terraform generates an RSA private key locally, imports its public half into AWS as a +key pair, and writes the `.pem` to `/home/bob`. + +## `main.tf` (in working dir `/home/bob/terraform/t1q1`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + tls = { + source = "hashicorp/tls" + version = "~> 4.0" + } + local = { + source = "hashicorp/local" + version = "~> 2.5" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +# Generate an RSA private key locally +resource "tls_private_key" "xfusion_kp" { + algorithm = "RSA" + rsa_bits = 4096 +} + +# Import the public half into AWS as an RSA key pair +resource "aws_key_pair" "xfusion_kp" { + key_name = "xfusion-kp-t1q1" + public_key = tls_private_key.xfusion_kp.public_key_openssh +} + +# Save the private key to /home/bob +resource "local_sensitive_file" "xfusion_kp_pem" { + content = tls_private_key.xfusion_kp.private_key_pem + filename = "/home/bob/xfusion-kp-t1q1.pem" + file_permission = "0400" +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t1q1 +terraform init +terraform apply -auto-approve +``` + +## How it works + +Creating a downloadable key pair takes three coordinated resources across three +providers — AWS doesn't hand back private key material on its own, so the private key +is generated locally and only the public half is uploaded. + +### `tls_private_key` + +- **`algorithm = "RSA"`** — generates an RSA keypair. This is what makes the resulting + AWS key pair `type = rsa` (the type is derived from the imported key material, not + set as an argument on `aws_key_pair`). +- **`rsa_bits = 4096`** — key strength. 2048 also works; 4096 is a stronger default. + +The private key never leaves the local machine — it's held in Terraform state and +written to disk in the last step. + +### `aws_key_pair` + +- **`key_name = "xfusion-kp-t1q1"`** — the key pair name, exactly as required. +- **`public_key = tls_private_key.xfusion_kp.public_key_openssh`** — imports the + **public** half in OpenSSH format. AWS stores only the public key; because it's an + RSA public key, the key pair registers with `type = rsa`. The attribute reference + also creates an implicit dependency so the TLS key is generated first. + +### `local_sensitive_file` + +- **`content = tls_private_key.xfusion_kp.private_key_pem`** — the PEM-encoded private + key, pulled from the TLS resource. +- **`filename = "/home/bob/xfusion-kp-t1q1.pem"`** — the required location. The task + says save it *under* `/home/bob`, so the file goes directly in that directory named + after the key pair. +- **`file_permission = "0400"`** — read-only for the owner. A private key should never + be world-readable, and strict graders/SSH clients reject loose permissions. +- Using `local_sensitive_file` (rather than `local_file`) keeps the private key out of + Terraform's plan/apply console output. If the lab's `local` provider is very old and + rejects it, swap to `local_file` with the same arguments. + +## Verify + +```bash +ls -l /home/bob/xfusion-kp-t1q1.pem + +aws ec2 describe-key-pairs --key-names xfusion-kp-t1q1 \ + --query 'KeyPairs[0].{Name:KeyName,Type:KeyType}' +``` + +Expected — the `.pem` file present with `0400` (`-r--------`) permissions, and the key +pair reporting `KeyType: rsa`. + +# Task 2 + +The Nautilus DevOps team has been creating a couple of services on AWS cloud. They have been breaking down the migration into smaller tasks, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. Recently they came up with requirements mentioned below. + +There is an instance named xfusion-ec2-t1q3 and an elastic-ip named xfusion-ec2-eip-t1q3 in us-east-1 region. Attach the xfusion-ec2-eip-t1q3 elastic-ip to the xfusion-ec2-t1q3 instance using Terraform only. The Terraform working directory is /home/bob/terraform/t1q3. Update the main.tf file (do not create a separate .tf file) to attach the specified Elastic IP to the instance. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + + +'xfusion-ec2-eip-t1q3' is attached to the 'xfusion-ec2-t1q3' instance using 'terraform'? + +## Solution + +# Attach Elastic IP — `xfusion-ec2-eip-t1q3` → `xfusion-ec2-t1q3` + +Both the instance (`aws_instance.ec2`) and the Elastic IP (`aws_eip.ec2_eip`) are +already managed in this `main.tf`. So no data sources are needed — you reference the +existing resources directly and add a single association resource. + +## `main.tf` (append the association; leave the existing two resources unchanged) + +```hcl +# Provision EC2 instance +resource "aws_instance" "ec2" { + ami = "ami-0c101f26f147fa7fd" + instance_type = "t2.micro" + subnet_id = "subnet-e79b9411489b0eca0" + vpc_security_group_ids = [ + "sg-3ebc829d9883de7da" + ] + + tags = { + Name = "xfusion-ec2-t1q3" + } +} + +# Provision Elastic IP +resource "aws_eip" "ec2_eip" { + tags = { + Name = "xfusion-ec2-eip-t1q3" + } +} + +# Associate the Elastic IP with the instance +resource "aws_eip_association" "ec2_eip_assoc" { + allocation_id = aws_eip.ec2_eip.id + instance_id = aws_instance.ec2.id +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t1q3 +terraform plan # should show only the new aws_eip_association to add +terraform apply -auto-approve +``` + +## How it works + +### Referencing managed resources (not data sources) + +Because both the instance and the EIP already live in this configuration, you point the +association straight at their resource attributes: + +- **`allocation_id = aws_eip.ec2_eip.id`** — a VPC Elastic IP's `id` is its allocation + ID (`eipalloc-...`), which is what an association binds. (Data sources would only be + needed if these were created outside this config — here they aren't.) +- **`instance_id = aws_instance.ec2.id`** — the instance's `i-...` ID. + +Referencing the resource attributes (rather than hardcoding IDs) creates **implicit +dependencies**: Terraform knows the association depends on both the instance and the +EIP, orders creation correctly, and the plan cleanly shows only the new association +being added — the two existing resources are untouched. + +### `aws_eip_association` + +This standalone resource represents the binding between the EIP and the instance. On +apply, Terraform calls `AssociateAddress`, and the instance's public-facing IP becomes +the Elastic IP. Keeping the association as its own resource (rather than folding an +`instance = ...` argument into the `aws_eip` block) is the cleaner, more explicit +pattern and isolates the attach/detach lifecycle — destroying just this resource would +detach the EIP while leaving the instance and EIP intact. + +> The `plan` should report **1 to add, 0 to change, 0 to destroy**. If it wants to +> change or replace the instance or EIP, something in the existing blocks was altered — +> appending only the association keeps them clean. + +## Verify + +```bash +aws ec2 describe-addresses \ + --filters Name=tag:Name,Values=xfusion-ec2-eip-t1q3 \ + --query 'Addresses[0].{EIP:PublicIp,InstanceId:InstanceId,AssocId:AssociationId}' + +aws ec2 describe-instances \ + --filters Name=tag:Name,Values=xfusion-ec2-t1q3 \ + --query 'Reservations[0].Instances[0].PublicIpAddress' +``` + +Expected — the address shows a populated `InstanceId` (the `xfusion-ec2-t1q3` instance) +and an `AssociationId`, and the instance's `PublicIpAddress` equals the EIP's +`PublicIp`. + +# Task 3 + +When establishing infrastructure on the AWS cloud, Identity and Access Management (IAM) is among the first and most critical services to configure. IAM facilitates the creation and management of user accounts, groups, roles, policies, and other access controls. The Nautilus DevOps team is currently in the process of configuring these resources and has outlined the following requirements: + +For this task, create an IAM user named iamuser_yousuf_t2q1 using terraform. The Terraform working directory is /home/bob/terraform/t2q1. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +## Solution + +# IAM User — `iamuser_yousuf_t2q1` + +Terraform solution to create a single IAM user. + +## `main.tf` (in working dir `/home/bob/terraform/t2q1`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_user" "yousuf" { + name = "iamuser_yousuf_t2q1" +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t2q1 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_iam_user` + +One resource is all this task needs. The `name` argument sets the IAM user's +identifier to `iamuser_yousuf_t2q1`, exactly as required. + +- **The `iamuser_` prefix is enforced.** These locked-down sandboxes require IAM user + names to start with `iamuser_`. The supplied name already satisfies that, so the + create stays inside the login user's permission scope and won't be denied. + +- **IAM is global.** The `region` in the provider is needed to initialize and + authenticate, but IAM resources aren't regional — the user is visible account-wide + regardless of region. + +- **No policies, keys, or login profile.** The task asks only for the user to exist, so + nothing is attached. This keeps the config minimal and avoids the custom-policy / + attach restrictions these sandboxes enforce (attaching permissions broader than the + login user has would fail with `AccessDenied`). + +### Why nothing else is needed + +An IAM user with no attached permissions is perfectly valid — it simply can't do +anything yet. Groups, policies, access keys, and login profiles are all separate +resources added later as requirements dictate. For "create a user named X," one +`aws_iam_user` block is the complete answer. + +## Verify + +```bash +aws iam get-user --user-name iamuser_yousuf_t2q1 \ + --query 'User.{Name:UserName,Id:UserId,Arn:Arn}' +``` + +Expected — the user's name, a unique ID, and an ARN of the form +`arn:aws:iam:::user/iamuser_yousuf_t2q1`. + +# Task 4 + +The Nautilus DevOps team has been creating a couple of services on AWS cloud. They have been breaking down the migration into smaller tasks, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. Recently they came up with requirements mentioned below. + +An IAM user named iamuser_yousuf_t2q3 and a policy named iampolicy_yousuf_t2q3 already exists. Use Terraform to attach the IAM policy iampolicy_yousuf_t2q3 to the IAM user iamuser_yousuf_t2q3. The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to attach the specified IAM policy to the IAM user. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +## Solution + +# Attach IAM Policy — `iampolicy_yousuf_t2q3` → `iamuser_yousuf_t2q3` + +The user (`aws_iam_user.user`) and the customer-managed policy (`aws_iam_policy.policy`) +already exist in this `main.tf`. The task is to bind them, which is one appended +`aws_iam_user_policy_attachment` resource referencing both. + +## `main.tf` (append the attachment; leave the existing two resources unchanged) + +```hcl +# Create IAM user +resource "aws_iam_user" "user" { + name = "iamuser_yousuf_t2q3" + + tags = { + Name = "iamuser_yousuf_t2q3" + } +} + +# Create IAM Policy +resource "aws_iam_policy" "policy" { + name = "iampolicy_yousuf_t2q3" + description = "IAM policy allowing EC2 read actions for yousuf" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = ["ec2:Read*"] + Resource = "*" + } + ] + }) +} + +# Attach the policy to the user +resource "aws_iam_user_policy_attachment" "attach" { + user = aws_iam_user.user.name + policy_arn = aws_iam_policy.policy.arn +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform plan # should show only the new attachment to add +terraform apply -auto-approve +``` + +## How it works + +### `aws_iam_user_policy_attachment` + +This resource attaches a **managed** policy (AWS-managed or customer-managed) to an IAM +user. It's the right resource here because `iampolicy_yousuf_t2q3` is a standalone +customer-managed policy with its own ARN — as opposed to an *inline* policy, which +would use `aws_iam_user_policy` and embed the JSON directly in the user. + +- **`user = aws_iam_user.user.name`** — the attachment targets the user by name. +- **`policy_arn = aws_iam_policy.policy.arn`** — managed-policy attachments are made by + ARN. The `aws_iam_policy` resource exposes its generated `arn`, so referencing the + attribute avoids hardcoding the account ID. + +Referencing both resources by attribute creates **implicit dependencies**: Terraform +keeps the user and policy first, then the attachment. The plan shows only the +attachment being added — the existing user and policy are untouched. + +### Managed vs. inline — why this resource + +| Resource | Use when | +|----------|----------| +| `aws_iam_user_policy_attachment` | Attaching an existing **managed** policy (this task). | +| `aws_iam_policy_attachment` | Attaching one policy to many principals at once — **avoid**; it's exclusive and detaches principals it doesn't manage. | +| `aws_iam_user_policy` | Defining an **inline** policy embedded in the user. | + +`aws_iam_user_policy_attachment` is the correct, non-exclusive choice for wiring one +managed policy to one user. + +### Sandbox note + +The policy grants only `ec2:Read*` — read-only actions well within the login user's +permission scope, so the attach won't trip the anti-privilege-escalation lockdown. +Attaching a policy broader than your login user has is what triggers `AccessDenied`; +that isn't the case here. + +## Verify + +```bash +aws iam list-attached-user-policies --user-name iamuser_yousuf_t2q3 \ + --query 'AttachedPolicies[*].{Name:PolicyName,Arn:PolicyArn}' +``` + +Expected — a list containing `iampolicy_yousuf_t2q3` with its ARN. + +# Task 5 + +The Nautilus DevOps team needs to set up an SNS topic for sending notifications. They need to create an SNS topic with the following specifications: + +1) The topic name should be xfusion-notifications-t3q4. + +Use Terraform to create this SNS topic. The Terraform working directory is /home/bob/terraform/t3q4. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + + +SNS topic 'xfusion-notifications-t3q4' was created using 'Terraform'? + +## Solution + +# SNS Topic — `xfusion-notifications-t3q4` + +Terraform solution to create a standard SNS topic. + +## `main.tf` (in working dir `/home/bob/terraform/t3q4`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_sns_topic" "xfusion_notifications" { + name = "xfusion-notifications-t3q4" +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t3q4 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_sns_topic` + +One resource is all this task needs. The `name` argument sets the topic name to +`xfusion-notifications-t3q4`, exactly as required. + +- **Standard topic by default.** SNS has two classes: **Standard** (high throughput, + best-effort ordering, at-least-once delivery) and **FIFO** (strict ordering, + exactly-once, name must end in `.fifo`). With no `.fifo` suffix and `fifo_topic` + unset, this creates a Standard topic — the right choice for general notifications. + +- **A topic is just the pub/sub channel.** Creating it gives you an endpoint publishers + send to. Nothing receives messages until you add **subscriptions** (email, SMS, SQS, + Lambda, HTTP, etc.). The task only asks for the topic, so no + `aws_sns_topic_subscription` resources are included. + +- **No access policy specified.** By default SNS attaches a policy allowing the topic + owner (this account) to publish and manage it — sufficient for the task. + +## Verify + +```bash +TOPIC_ARN=$(aws sns list-topics \ + --query "Topics[?ends_with(TopicArn, ':xfusion-notifications-t3q4')].TopicArn | [0]" \ + --output text) + +echo "[$TOPIC_ARN]" + +aws sns get-topic-attributes --topic-arn "$TOPIC_ARN" \ + --query 'Attributes.{Arn:TopicArn,Owner:Owner}' +``` + +Expected — a topic ARN of the form +`arn:aws:sns:us-east-1::xfusion-notifications-t3q4`. + +# Task 6 + +The Nautilus DevOps team needs to set up a DynamoDB table for storing user data. They need to create a DynamoDB table with the following specifications: + +1) The table name should be xfusion-users-t3q1. + +2) The primary key should be xfusion_id_t3q1 (String). + +3) The table should use PAY_PER_REQUEST billing mode. + +Use Terraform to create this DynamoDB table. The Terraform working directory is /home/bob/terraform/t3q1. Create the main.tf file (do not create a different .tf file) to create the DynamoDB table. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + + +DynamoDB table 'xfusion-users-t3q1' was created using 'Terraform' with 'PAY_PER_REQUEST' billing mode? + +## Solution + +# DynamoDB Table — `xfusion-users-t3q1` + +Terraform solution to create an on-demand DynamoDB table keyed on a string partition +key. + +## `main.tf` (in working dir `/home/bob/terraform/t3q1`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_dynamodb_table" "xfusion_users" { + name = "xfusion-users-t3q1" + billing_mode = "PAY_PER_REQUEST" + hash_key = "xfusion_id_t3q1" + + attribute { + name = "xfusion_id_t3q1" + type = "S" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t3q1 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_dynamodb_table` + +A single resource provisions the whole table. + +- **`name = "xfusion-users-t3q1"`** — the table name, exactly as required. + +- **`hash_key = "xfusion_id_t3q1"`** — sets the table's **partition key** (DynamoDB's + API calls it the "hash key"). This is the primary key the task asks for. No + `range_key` is defined, so `xfusion_id_t3q1` alone uniquely identifies each item — a + simple primary key rather than a composite one. + +- **`attribute` block** — DynamoDB is schemaless for non-key fields, so you only ever + declare attributes that participate in a key. Here `xfusion_id_t3q1` is the partition + key, so it **must** be declared with its type. `type = "S"` marks it as a **String** + (valid types: `S` string, `N` number, `B` binary). Referencing a `hash_key` without + a matching `attribute` block fails validation with "all attributes must be indexed." + +- **`billing_mode = "PAY_PER_REQUEST"`** — puts the table in **on-demand** mode. You + pay per read/write request with no capacity to provision, and DynamoDB auto-scales to + traffic. Because of this mode, `read_capacity` / `write_capacity` are **omitted** — + they're only valid under `PROVISIONED` billing, and setting them here would be a + config error. + +### Why on-demand fits here + +Beyond being the task requirement, `PAY_PER_REQUEST` is the simplest correct choice: no +throughput math, no capacity planning, and it avoids the low provisioned-capacity caps +that constrained sandbox environments impose on `PROVISIONED` tables. The table comes +up ready to take reads and writes immediately. + +## Verify + +```bash +aws dynamodb describe-table --table-name xfusion-users-t3q1 \ + --query 'Table.{Name:TableName,Billing:BillingModeSummary.BillingMode,Key:KeySchema,Attrs:AttributeDefinitions,Status:TableStatus}' +``` + +Expected — `BillingMode: PAY_PER_REQUEST`, key schema with `xfusion_id_t3q1` as `HASH`, +an attribute definition of `xfusion_id_t3q1` type `S`, and `TableStatus: ACTIVE` (a few +seconds after create). + +# Task 7 + +As part of the data migration process, the Nautilus DevOps team is actively creating several S3 buckets on AWS. They plan to utilize both private and public S3 buckets to store the relevant data. Given the ongoing migration of other infrastructure to AWS, it is logical to consolidate data storage within the AWS environment as well. + +Create a public S3 bucket named xfusion-s3-7078-t4q2 using Terraform. + +Ensure the bucket is accessible publicly once created by setting the proper ACL. + +The Terraform working directory is /home/bob/terraform/t4q2. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Notes: + +Create the resources only in the us-east-1 region. +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. +The name of the S3 bucket should be based on xfusion-s3-7078-t4q2. +You can use the ACL settings to ensure the bucket is publicly accessible. + +'xfusion-s3-7078-t4q2' Public S3 bucket was created using 'Terraform'? + +## Solution + +# Public S3 Bucket — `xfusion-s3-7078-t4q2` + +Making a bucket publicly readable via ACL requires undoing two modern AWS defaults +first: ACLs are disabled (`BucketOwnerEnforced`) and public access is blocked. A bare +`acl = "public-read"` errors with `AccessControlListNotSupported` without the enabling +resources, in the right order. + +## `main.tf` (in working dir `/home/bob/terraform/t4q2`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_s3_bucket" "xfusion" { + bucket = "xfusion-s3-7078-t4q2" +} + +# Re-enable ACLs (default is BucketOwnerEnforced, which disables them) +resource "aws_s3_bucket_ownership_controls" "xfusion" { + bucket = aws_s3_bucket.xfusion.id + rule { + object_ownership = "BucketOwnerPreferred" + } +} + +# Lift the public-access block so a public ACL is allowed +resource "aws_s3_bucket_public_access_block" "xfusion" { + bucket = aws_s3_bucket.xfusion.id + + block_public_acls = false + block_public_policy = false + ignore_public_acls = false + restrict_public_buckets = false +} + +# Now the public ACL will actually take +resource "aws_s3_bucket_acl" "xfusion" { + bucket = aws_s3_bucket.xfusion.id + acl = "public-read" + + depends_on = [ + aws_s3_bucket_ownership_controls.xfusion, + aws_s3_bucket_public_access_block.xfusion, + ] +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t4q2 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Order of operations is the whole trick + +Since 2023 AWS creates buckets with ACLs disabled **and** Block Public Access on. To +apply a public-read ACL you must, in this order: + +1. **`aws_s3_bucket_ownership_controls` → `BucketOwnerPreferred`** — re-enables ACLs + (the default `BucketOwnerEnforced` disables them entirely). `ObjectWriter` also + works; `BucketOwnerPreferred` is the safe pick. +2. **`aws_s3_bucket_public_access_block` → all four flags `false`** — lifts the block. + Any one left `true` will silently strip or reject the public ACL. +3. **`aws_s3_bucket_acl` → `public-read`** — the actual public grant. + +The **`depends_on`** on the ACL resource forces steps 1 and 2 to complete before the +ACL is applied. Without it, Terraform may race and hit `AccessControlListNotSupported` +or `AccessDenied`. + +### Why ACL, not a bucket policy + +The task explicitly says to make the bucket public *via ACL*. `acl = "public-read"` +grants the `AllUsers` group READ on the bucket — that's the ACL lever the task asks +for, as opposed to an `aws_s3_bucket_policy` with a public `Principal`. + +## Verify + +```bash +aws s3api get-bucket-acl --bucket xfusion-s3-7078-t4q2 \ + --query 'Grants[?Grantee.URI!=`null`].{Grantee:Grantee.URI,Perm:Permission}' +``` + +Expected — a grant to the `AllUsers` group +(`http://acs.amazonaws.com/groups/global/AllUsers`) with `READ`. That's the public-read +confirmation. + +# Task 8 + +As part of the data migration process, the Nautilus DevOps team is actively creating several S3 buckets on AWS using Terraform. They plan to utilize both private and public S3 buckets to store the relevant data. Given the ongoing migration of other infrastructure to AWS, it is logical to consolidate data storage within the AWS environment as well. + +Create an S3 bucket using Terraform with the following details: + +1) The name of the S3 bucket must be xfusion-s3-7078-t4q1. + +2) The S3 bucket must block all public access, making it a private bucket. + +The Terraform working directory is /home/bob/terraform/t4q1. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Notes: + +Use Terraform to provision the S3 bucket. +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. +Ensure the resources are created in the us-east-1 region. +The bucket must have block public access enabled to restrict any public access. + +'xfusion-s3-7078-t4q1' private S3 bucket was created using 'terraform'? + +## Solution + +# Private S3 Bucket — `xfusion-s3-7078-t4q1` + +Terraform solution for a fully private bucket in `us-east-1`, with all public access +blocked. + +## `main.tf` (in working dir `/home/bob/terraform/t4q1`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_s3_bucket" "xfusion" { + bucket = "xfusion-s3-7078-t4q1" +} + +resource "aws_s3_bucket_public_access_block" "xfusion" { + bucket = aws_s3_bucket.xfusion.id + + block_public_acls = true + block_public_policy = true + ignore_public_acls = true + restrict_public_buckets = true +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t4q1 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_s3_bucket` + +Creates the bucket with the required name. Modern buckets are already private by +default, but the task explicitly wants **Block Public Access enabled**, which is a +separate resource. + +### `aws_s3_bucket_public_access_block` + +This is the resource that satisfies the requirement. It manages the four independent +Block Public Access (BPA) switches; all four `true` guarantees the bucket can never be +exposed publicly, regardless of any ACL or policy attached later. + +| Flag | `true` means | +|------|--------------| +| `block_public_acls` | Reject any **new** request applying a public ACL. | +| `ignore_public_acls` | Ignore any public ACLs **already** present. | +| `block_public_policy` | Reject any **new** public bucket policy. | +| `restrict_public_buckets` | If a public policy exists, restrict access to the owner / AWS services only. | + +The distinction: `block_*` flags act at *write* time (stop you adding public grants), +while `ignore_*` / `restrict_*` act at *evaluation* time (neutralize grants already +there). All four `true` covers both directions — which is what "block **all** public +access" means. + +### Why no ACL / ownership resources here + +Unlike a public bucket, a private one needs **no** `aws_s3_bucket_acl` or +`aws_s3_bucket_ownership_controls`. AWS defaults new buckets to `BucketOwnerEnforced` +(ACLs disabled) and applies BPA — both push toward private. We move with the defaults, +so the config stays minimal. + +## Verify + +```bash +aws s3api get-public-access-block --bucket xfusion-s3-7078-t4q1 \ + --query 'PublicAccessBlockConfiguration' +``` + +Expected — all four `true`: + +```json +{ + "BlockPublicAcls": true, + "IgnorePublicAcls": true, + "BlockPublicPolicy": true, + "RestrictPublicBuckets": true +} +``` + +# Task 9 + +The Nautilus DevOps team is strategically planning the migration of a portion of their infrastructure to the AWS cloud. Acknowledging the magnitude of this endeavor, they have chosen to tackle the migration incrementally rather than as a single, massive transition. Their approach involves creating Virtual Private Clouds (VPCs) as the initial step, as they will be provisioning various services under different VPCs. + +Create a VPC named xfusion-vpc-t5q2 in us-east-1 region with 192.168.0.0/24 IPv4 CIDR using terraform. + + +The Terraform working directory is /home/bob/terraform/t5q2. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + + +'xfusion-vpc-t5q2' was created using 'terraform'? + +## Solution + +# VPC — `xfusion-vpc-t5q2` (CIDR `192.168.0.0/24`) + +Terraform solution to create a VPC with a specific IPv4 CIDR block. + +## `main.tf` (in working dir `/home/bob/terraform/t5q2`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_vpc" "xfusion_vpc" { + cidr_block = "192.168.0.0/24" + + tags = { + Name = "xfusion-vpc-t5q2" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t5q2 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_vpc` + +One resource creates the VPC. + +- **`cidr_block = "192.168.0.0/24"`** — the required IPv4 range. A `/24` gives 256 + addresses (minus the 5 AWS reserves per subnet when you later carve subnets out). +- **`tags = { Name = "xfusion-vpc-t5q2" }`** — a VPC has no native name field, so the + `Name` tag is what the console and graders read as its name. Don't skip it — without + the tag the VPC exists but appears unnamed. +- **Region** comes from the provider (`us-east-1`). + +## Verify + +```bash +aws ec2 describe-vpcs \ + --filters Name=tag:Name,Values=xfusion-vpc-t5q2 \ + --query 'Vpcs[0].{Id:VpcId,Cidr:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}' +``` + +Expected — the VPC ID, `CidrBlock: 192.168.0.0/24`, and `Name: xfusion-vpc-t5q2`. + +# Task 10 + +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. Recognizing the scale of this undertaking, they have opted to approach the migration in incremental steps rather than as a single massive transition. To achieve this, they have segmented large tasks into smaller, more manageable units. This granular approach enables the team to execute the migration in gradual phases, ensuring smoother implementation and minimizing disruption to ongoing operations. By breaking down the migration into smaller tasks, the Nautilus DevOps team can systematically progress through each stage, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. + +Create a VPC named xfusion-vpc-t5q1 in region us-east-1 with any IPv4 CIDR block through terraform. + +The Terraform working directory is /home/bob/terraform/t5q1. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + + +'xfusion-vpc-t5q1' was created using 'terraform'? + +## Solution + +# VPC — `xfusion-vpc-t5q1` (any IPv4 CIDR) + +Terraform solution to create a VPC. The task allows any IPv4 CIDR, so a conventional +private `/16` is used. + +## `main.tf` (in working dir `/home/bob/terraform/t5q1`) + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_vpc" "xfusion_vpc" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "xfusion-vpc-t5q1" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform/t5q1 +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_vpc` + +One resource creates the VPC. + +- **`cidr_block = "10.0.0.0/16"`** — the task allows *any* IPv4 block, so this uses a + standard RFC 1918 private range. A `/16` provides 65,536 addresses — plenty of room + to carve subnets from later. Any valid private CIDR (e.g. `172.16.0.0/16`, + `192.168.0.0/24`) would satisfy the requirement equally; `10.0.0.0/16` is the + conventional default. +- **`tags = { Name = "xfusion-vpc-t5q1" }`** — a VPC has no native name field, so the + `Name` tag is what the console and graders read as its name. +- **Region** comes from the provider (`us-east-1`). + +## Verify + +```bash +aws ec2 describe-vpcs \ + --filters Name=tag:Name,Values=xfusion-vpc-t5q1 \ + --query 'Vpcs[0].{Id:VpcId,Cidr:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}' +``` + +Expected — the VPC ID, `CidrBlock: 10.0.0.0/16`, and `Name: xfusion-vpc-t5q1`. \ No newline at end of file diff --git a/terraform/task-14.md b/terraform/task-14.md new file mode 100644 index 0000000..ed0c86a --- /dev/null +++ b/terraform/task-14.md @@ -0,0 +1,84 @@ +# Assignment + +When establishing infrastructure on the AWS cloud, Identity and Access Management (IAM) is among the first and most critical services to configure. IAM facilitates the creation and management of user accounts, groups, roles, policies, and other access controls. The Nautilus DevOps team is currently in the process of configuring these resources and has outlined the following requirements: + +For this task, create an IAM user named iamuser_james using terraform. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# IAM User — `iamuser_james` + +Terraform solution to create a single IAM user named `iamuser_james`. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_user" "james" { + name = "iamuser_james" +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_iam_user` + +This single resource is all the task needs. The `name` argument sets the IAM +user's login/identifier to `iamuser_james` exactly as required. + +A few things worth knowing about what's happening under the hood: + +- **IAM is global, not regional.** IAM resources live in the `aws` partition, not + in any one region. The `region` in the provider block is still required for the + provider to initialize and authenticate, but it has no bearing on *where* the + user is created — an IAM user is visible account-wide regardless of region. + +- **The `iamuser_` prefix is not cosmetic.** In these locked-down sandbox + environments, IAM user names must begin with `iamuser_`. The task-supplied name + already satisfies that, so the create call stays within the login user's + permission scope and won't get denied. + +- **No path, policy, or login profile is set.** The task asks only for the user to + exist, so we deliberately don't attach policies, create access keys, or set a + console password. That keeps the resource minimal and avoids the inline-policy / + custom-policy restrictions that these sandboxes enforce — attaching a policy the + login user can't grant would fail with `AccessDenied`. + +### Why nothing else is needed + +An IAM user with no attached permissions is perfectly valid — it simply can't do +anything yet. Groups, policies, access keys, and login profiles are all separate +resources you'd add later as requirements dictate. For "create a user named X," +one `aws_iam_user` block is the complete and correct answer. + +## Verify + +```bash +aws iam get-user --user-name iamuser_james \ + --query 'User.{Name:UserName,Id:UserId,Arn:Arn}' +``` + +Expected — the user's name, a unique ID, and an ARN of the form +`arn:aws:iam:::user/iamuser_james`. \ No newline at end of file diff --git a/terraform/task-15.md b/terraform/task-15.md new file mode 100644 index 0000000..d09a451 --- /dev/null +++ b/terraform/task-15.md @@ -0,0 +1,86 @@ +# Assignment + +The ammar DevOps team has been creating a couple of services on AWS cloud. They have been breaking down the migration into smaller tasks, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. Recently they came up with requirements mentioned below. + +Create an IAM group named iamgroup_ammar using terraform. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# IAM Group — `iamgroup_ammar` + +Terraform solution to create a single IAM group named `iamgroup_ammar`. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_group" "ammar" { + name = "iamgroup_ammar" +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_iam_group` + +One resource covers the whole task. The `name` argument sets the group's +identifier to `iamgroup_ammar` exactly as required. + +Key points about what's going on: + +- **IAM is global.** Like all IAM resources, a group is not tied to a region. The + provider still needs a `region` to initialize and authenticate, but the group is + account-wide once created — the region value doesn't affect it. + +- **The `iamgroup_` prefix is enforced.** In these locked-down sandbox + environments, IAM group names must start with `iamgroup_`. The supplied name + already meets that rule, so the create stays inside the login user's permission + scope and won't be denied. + +- **No policies attached, no members added.** The task asks only for the group to + exist. We deliberately skip `aws_iam_group_policy_attachment` and + `aws_iam_group_membership`, which keeps the config minimal and sidesteps the + inline / custom-policy restrictions these sandboxes impose. Attaching a policy the + login user can't grant would fail with `AccessDenied`. + +### Group vs. user — same pattern, different resource + +An IAM group is just a container for users that lets you attach permissions to many +people at once. On its own an empty group does nothing until you (a) attach policies +and (b) add users — both separate resources added later as requirements dictate. For +"create a group named X," a single `aws_iam_group` block is the complete answer, +mirroring the `aws_iam_user` pattern. + +## Verify + +```bash +aws iam get-group --group-name iamgroup_ammar \ + --query 'Group.{Name:GroupName,Id:GroupId,Arn:Arn}' +``` + +Expected — the group's name, a unique ID, and an ARN of the form +`arn:aws:iam:::group/iamgroup_ammar`. \ No newline at end of file diff --git a/terraform/task-16.md b/terraform/task-16.md new file mode 100644 index 0000000..d08606d --- /dev/null +++ b/terraform/task-16.md @@ -0,0 +1,131 @@ +# Assignment + +When establishing infrastructure on the AWS cloud, Identity and Access Management (IAM) is among the first and most critical services to configure. IAM facilitates the creation and management of user accounts, groups, roles, policies, and other access controls. The Nautilus DevOps team is currently in the process of configuring these resources and has outlined the following requirements. + +Create an IAM policy named iampolicy_siva in us-east-1 region using Terraform. It must allow read-only access to the EC2 console, i.e., this policy must allow users to view all instances, AMIs, and snapshots in the Amazon EC2 console. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# IAM Policy — `iampolicy_siva` (read-only EC2 console) + +Terraform solution to create a customer-managed IAM policy that grants read-only +access to view instances, AMIs, and snapshots in the EC2 console. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_policy" "siva" { + name = "iampolicy_siva" + description = "Read-only access to view instances, AMIs, and snapshots in the EC2 console" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Sid = "EC2ReadOnlyConsole" + Effect = "Allow" + Action = [ + "ec2:DescribeInstances", + "ec2:DescribeImages", + "ec2:DescribeTags", + "ec2:DescribeSnapshots" + ] + Resource = "*" + } + ] + }) +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_iam_policy` + +This creates a **customer-managed policy** — a standalone, reusable permission +document that can later be attached to users, groups, or roles. The task requires a +specific name (`iampolicy_siva`), so a managed AWS policy like +`AmazonEC2ReadOnlyAccess` can't be substituted; the policy has to be created +explicitly with that exact name. + +- `name` — the policy's identifier, `iampolicy_siva`, matching the required + `iampolicy_` naming convention these environments enforce. +- `description` — free-text summary; optional but good hygiene. +- `policy` — the JSON policy document, built with `jsonencode()` so it stays + readable HCL instead of a raw heredoc string (and Terraform validates the + structure at plan time). + +### The policy document + +This is AWS's canonical "read-only access to the EC2 console" statement. Each action +is a `Describe*` call — none of them mutate anything, which is what makes the policy +strictly read-only: + +| Action | What it lets the user view | +|--------|----------------------------| +| `ec2:DescribeInstances` | All EC2 instances | +| `ec2:DescribeImages` | All AMIs | +| `ec2:DescribeSnapshots` | All EBS snapshots | +| `ec2:DescribeTags` | Tags on those resources (so the console renders names/labels correctly) | + +`DescribeTags` is included because the EC2 console leans on tag data to display +resource names and metadata — without it the console view is functional but +degraded. This four-action set is exactly what the AWS documentation prescribes for +this scenario. + +- **`Effect = "Allow"`** grants the listed actions. +- **`Resource = "*"`** — EC2 `Describe*` actions don't support resource-level + permissions (they're list/read operations that span the account), so `*` is the + correct and only valid scope here. +- **`Version = "2012-10-17"`** is the current IAM policy language version — always + use this literal date, not today's date. + +### Sandbox / IAM note + +Creating a customer-managed policy uses `iam:CreatePolicy`, which is often +restricted in locked-down lab environments. Because this task *explicitly requires* +a named custom policy, the lab provisions the permission for this specific scenario, +so the create succeeds. The policy is created but **not attached** to any principal +— the task only asks for the policy to exist. Attachment to a user/group/role is a +separate step you'd add when required. + +## Verify + +```bash +POLICY_ARN=$(aws iam list-policies --scope Local \ + --query "Policies[?PolicyName=='iampolicy_siva'].Arn | [0]" --output text) + +echo "[$POLICY_ARN]" + +aws iam get-policy-version \ + --policy-arn "$POLICY_ARN" \ + --version-id v1 \ + --query 'PolicyVersion.Document' +``` + +Expected — the policy document echoing the four `ec2:Describe*` actions with +`Effect: Allow` and `Resource: *`. \ No newline at end of file diff --git a/terraform/task-17.md b/terraform/task-17.md new file mode 100644 index 0000000..246699e --- /dev/null +++ b/terraform/task-17.md @@ -0,0 +1,100 @@ +# Assignment +The Nautilus DevOps team needs to set up a DynamoDB table for storing user data. They need to create a DynamoDB table with the following specifications: + +1) The table name should be nautilus-users. + +2) The primary key should be nautilus_id (String). + +3) The table should use PAY_PER_REQUEST billing mode. + +Use Terraform to create this DynamoDB table. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to create the DynamoDB table. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# DynamoDB Table — `nautilus-users` + +Terraform solution to create an on-demand DynamoDB table keyed on a string +partition key. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_dynamodb_table" "nautilus_users" { + name = "nautilus-users" + billing_mode = "PAY_PER_REQUEST" + hash_key = "nautilus_id" + + attribute { + name = "nautilus_id" + type = "S" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_dynamodb_table` + +A single resource provisions the whole table. + +- **`name = "nautilus-users"`** — the table name, exactly as required. + +- **`hash_key = "nautilus_id"`** — sets the table's **partition key** (DynamoDB's + API calls it the "hash key"). This is the primary key the task asks for. No + `range_key` is defined, so `nautilus_id` alone uniquely identifies each item — a + simple primary key rather than a composite one. + +- **`attribute` block** — this is the part people forget. DynamoDB is schemaless for + non-key fields, so you only ever declare attributes that participate in a key + (partition key, sort key, or an index key). Here `nautilus_id` is the partition + key, so it **must** be declared with its type. `type = "S"` marks it as a + **String** (the valid types are `S` string, `N` number, `B` binary). Referencing a + `hash_key` without a matching `attribute` block fails validation with + "all attributes must be indexed." + +- **`billing_mode = "PAY_PER_REQUEST"`** — puts the table in **on-demand** mode. + You pay per read/write request with no capacity to provision or manage, and + DynamoDB auto-scales to traffic. Because of this mode, the `read_capacity` / + `write_capacity` arguments are **omitted** — they're only valid (and required) + under `PROVISIONED` billing. Setting them here would be a config error. + +### Why on-demand fits here + +Beyond being the task requirement, `PAY_PER_REQUEST` is the simplest correct choice: +no throughput math, no capacity planning, and it avoids the low provisioned-capacity +caps that constrained sandbox environments impose on `PROVISIONED` tables. The table +comes up ready to take reads and writes immediately. + +## Verify + +```bash +aws dynamodb describe-table --table-name nautilus-users \ + --query 'Table.{Name:TableName,Billing:BillingModeSummary.BillingMode,Key:KeySchema,Attrs:AttributeDefinitions,Status:TableStatus}' +``` + +Expected — `BillingMode: PAY_PER_REQUEST`, a key schema with `nautilus_id` as +`HASH`, an attribute definition of `nautilus_id` type `S`, and `TableStatus: ACTIVE` +(the table takes a few seconds to transition from `CREATING` to `ACTIVE`). diff --git a/terraform/task-18.md b/terraform/task-18.md new file mode 100644 index 0000000..d2dd5a1 --- /dev/null +++ b/terraform/task-18.md @@ -0,0 +1,111 @@ +# Assignment + +The Nautilus DevOps team needs to create an AWS Kinesis data stream for real-time data processing. This stream will be used to ingest and process large volumes of streaming data, which will then be consumed by various applications for analytics and real-time decision-making. + +The stream should be named xfusion-stream. + +Use Terraform to create this Kinesis stream. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. +Before submitting the task, ensure that terraform plan returns No changes. Your infrastructure matches the configuration. + +# Solution + +# Kinesis Data Stream — `xfusion-stream` + +Terraform solution to create a provisioned Kinesis data stream with a single shard, +configured so `terraform plan` reports no drift after apply. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_kinesis_stream" "xfusion_stream" { + name = "xfusion-stream" + shard_count = 1 + retention_period = 24 + + stream_mode_details { + stream_mode = "PROVISIONED" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve + +# Idempotency check required by the task: +terraform plan +# -> "No changes. Your infrastructure matches the configuration." +``` + +## How it works + +### `aws_kinesis_stream` + +- **`name = "xfusion-stream"`** — the stream name, exactly as required. + +- **`stream_mode_details { stream_mode = "PROVISIONED" }`** — Kinesis has two + capacity modes: `PROVISIONED` (you manage shards) and `ON_DEMAND` (AWS + auto-scales). This is set to `PROVISIONED` explicitly. The two modes are mutually + exclusive with `shard_count`: `PROVISIONED` **requires** `shard_count`, while + `ON_DEMAND` **forbids** it. Getting this pairing wrong is the usual cause of a + perpetual non-empty plan or an apply error. + +- **`shard_count = 1`** — one shard. A shard is the base throughput unit (1 MB/s or + 1000 records/s in, 2 MB/s out). One shard is plenty for a lab and is the + provisioned-capacity choice that pairs with `PROVISIONED` mode. + +- **`retention_period = 24`** — hours that records stay in the stream before aging + out. `24` is the AWS default and the minimum; setting it explicitly to the default + value keeps the resource stable and readable. + +### Why the plan comes back clean + +The task explicitly requires `terraform plan` to report **no changes** after apply. +Two things guarantee that here: + +1. **Mode and shard count are consistent.** `PROVISIONED` + an explicit + `shard_count` is the stable, non-conflicting combination. Mixing `ON_DEMAND` with + a `shard_count`, or omitting the mode and letting it get inferred, is what + typically produces a drift diff on the next plan. + +2. **Every value set matches what AWS stores.** `retention_period = 24` equals the + service default, and no other arguments (encryption, shard-level metrics) are + toggled, so there's nothing for the provider to reconcile on refresh. + +### Sandbox note + +Constrained lab environments cap Kinesis at **PROVISIONED mode, 1 shard per stream, +≤24h retention, and ≤2 streams per account**. This config sits inside every one of +those limits, so it won't be silently reset or rejected. + +## Verify + +```bash +aws kinesis describe-stream-summary --stream-name xfusion-stream \ + --query 'StreamDescriptionSummary.{Name:StreamName,Status:StreamStatus,Mode:StreamModeDetails.StreamMode,Shards:OpenShardCount,Retention:RetentionPeriodHours}' +``` + +Expected — name `xfusion-stream`, `StreamStatus: ACTIVE` (a few seconds after +create), `StreamMode: PROVISIONED`, `OpenShardCount: 1`, retention `24`. \ No newline at end of file diff --git a/terraform/task-19.md b/terraform/task-19.md new file mode 100644 index 0000000..4c8faea --- /dev/null +++ b/terraform/task-19.md @@ -0,0 +1,92 @@ +# Assignment + +The Nautilus DevOps team needs to set up an SNS topic for sending notifications. They need to create an SNS topic with the following specifications: + +1) The topic name should be nautilus-notifications. + +Use Terraform to create this SNS topic. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# SNS Topic — `nautilus-notifications` + +Terraform solution to create a standard SNS topic. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_sns_topic" "nautilus_notifications" { + name = "nautilus-notifications" +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_sns_topic` + +One resource is all this task needs. The `name` argument sets the topic name to +`nautilus-notifications` exactly as required. + +A few points on what's happening: + +- **Standard topic by default.** SNS has two topic classes: **Standard** (high + throughput, best-effort ordering, at-least-once delivery) and **FIFO** (strict + ordering, exactly-once, name must end in `.fifo`). Since the name has no `.fifo` + suffix and `fifo_topic` isn't set, this creates a Standard topic — the right + choice for general notifications. + +- **A topic is just the pub/sub channel.** Creating the topic gives you an endpoint + that publishers send messages to. Nothing receives those messages until you add + **subscriptions** (email, SMS, SQS, Lambda, HTTP, etc.). The task only asks for + the topic itself, so no `aws_sns_topic_subscription` resources are included — those + would be added later as delivery targets are decided. + +- **No access policy specified.** By default SNS attaches a policy allowing the + topic owner (this account) to publish and manage it. That default is sufficient + for the task; a custom `policy` would only be needed to grant cross-account or + service-specific publish rights. + +### Sandbox note + +Constrained lab environments permit **basic SNS operations** — standard topics like +this one. Nothing advanced (FIFO, complex delivery policies) is in play, so the +create proceeds without restriction. + +## Verify + +```bash +TOPIC_ARN=$(aws sns list-topics \ + --query "Topics[?ends_with(TopicArn, ':nautilus-notifications')].TopicArn | [0]" \ + --output text) + +echo "[$TOPIC_ARN]" + +aws sns get-topic-attributes --topic-arn "$TOPIC_ARN" \ + --query 'Attributes.{Name:DisplayName,Arn:TopicArn,Owner:Owner}' +``` + +Expected — a topic ARN of the form +`arn:aws:sns:us-east-1::nautilus-notifications`. \ No newline at end of file diff --git a/terraform/task-20.md b/terraform/task-20.md new file mode 100644 index 0000000..e8e21a8 --- /dev/null +++ b/terraform/task-20.md @@ -0,0 +1,106 @@ +# Assignment + +The Nautilus DevOps team needs to create an SSM parameter in AWS with the following requirements: + +1) The name of the parameter should be nautilus-ssm-parameter. + +2) Set the parameter type to String. + +3) Set the parameter value to nautilus-value. + +4) The parameter should be created in the us-east-1 region. + +5) Ensure the parameter is successfully created using terraform and can be retrieved when the task is completed. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# SSM Parameter — `nautilus-ssm-parameter` + +Terraform solution to create a plaintext String parameter in AWS Systems Manager +Parameter Store. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_ssm_parameter" "nautilus" { + name = "nautilus-ssm-parameter" + type = "String" + value = "nautilus-value" +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### `aws_ssm_parameter` + +One resource covers the whole task. SSM Parameter Store is a managed key/value store +for configuration data and secrets, and this creates a single entry in it. + +- **`name = "nautilus-ssm-parameter"`** — the parameter's key/path. Names can be + hierarchical (e.g. `/app/prod/db-url`); a flat name like this one is stored at the + root. + +- **`type = "String"`** — the parameter type. The three types are: + - `String` — a single plaintext value (used here). + - `StringList` — a comma-separated list of values. + - `SecureString` — an encrypted value backed by a KMS key. + + The task wants a plain `String`, so no encryption or KMS key is involved. + +- **`value = "nautilus-value"`** — the stored value. Because the type is `String`, + it's held as-is in plaintext and returned verbatim on read. + +- **Region** comes from the provider (`us-east-1`). Unlike IAM, SSM parameters + **are** regional — a parameter created here is only visible in `us-east-1`, which + satisfies requirement #4. + +### Retrieval (requirement #5) + +The task asks that the parameter be retrievable once created. Nothing extra is +needed in the config for that — a `String` parameter is readable immediately via the +SSM API. The verification command below confirms it. + +> Note: if this were a `SecureString`, you'd need `--with-decryption` on read to get +> the plaintext back. For a plain `String` that flag is unnecessary. + +## Verify + +```bash +aws ssm get-parameter --name nautilus-ssm-parameter \ + --query 'Parameter.{Name:Name,Type:Type,Value:Value}' +``` + +Expected: + +```json +{ + "Name": "nautilus-ssm-parameter", + "Type": "String", + "Value": "nautilus-value" +} +``` \ No newline at end of file diff --git a/terraform/task-21.md b/terraform/task-21.md new file mode 100644 index 0000000..6e02fc9 --- /dev/null +++ b/terraform/task-21.md @@ -0,0 +1,103 @@ +# Assignment + +The Nautilus DevOps team needs to set up CloudWatch logging for their application. They need to create a CloudWatch log group and log stream with the following specifications: + +1) The log group name should be xfusion-log-group. + +2) The log stream name should be xfusion-log-stream. + +Use Terraform to create the CloudWatch log group and log stream. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# CloudWatch Logs — `xfusion-log-group` + `xfusion-log-stream` + +Terraform solution to create a CloudWatch log group and a log stream nested inside +it. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_cloudwatch_log_group" "xfusion" { + name = "xfusion-log-group" +} + +resource "aws_cloudwatch_log_stream" "xfusion" { + name = "xfusion-log-stream" + log_group_name = aws_cloudwatch_log_group.xfusion.name +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +CloudWatch Logs has a two-level hierarchy: a **log group** is the container (where +you set retention, encryption, and access policy), and a **log stream** is an +ordered sequence of log events inside a group, typically one per source (an +instance, container, or function). This task creates one of each. + +### `aws_cloudwatch_log_group` + +- **`name = "xfusion-log-group"`** — the group name, exactly as required. +- No `retention_in_days` is set, so logs are retained **indefinitely** (the AWS + default). The task doesn't specify retention, so the default is fine; you'd add + `retention_in_days = N` if a policy required expiring logs. + +### `aws_cloudwatch_log_stream` + +- **`name = "xfusion-log-stream"`** — the stream name, exactly as required. +- **`log_group_name = aws_cloudwatch_log_group.xfusion.name`** — a stream can't + exist on its own; it must live inside a group. Referencing the group resource's + `name` attribute (rather than hardcoding the string) does two things: + 1. It wires the value correctly, and + 2. it creates an **implicit dependency** so Terraform provisions the group + **before** the stream. Without that ordering, the stream create would fail + because its parent group wouldn't exist yet. + + This is why no explicit `depends_on` is needed — the attribute reference expresses + the dependency for you. + +### Ordering matters + +The dependency direction is one-way: group first, then stream. On `destroy`, +Terraform reverses it automatically — stream removed before group — so teardown is +clean too. + +## Verify + +```bash +# Confirm the group exists +aws logs describe-log-groups \ + --log-group-name-prefix xfusion-log-group \ + --query 'logGroups[0].{Name:logGroupName,Retention:retentionInDays}' + +# Confirm the stream exists inside it +aws logs describe-log-streams \ + --log-group-name xfusion-log-group \ + --query 'logStreams[?logStreamName==`xfusion-log-stream`].logStreamName' +``` + +Expected — the group `xfusion-log-group` (retention `null` = never expire), and the +stream query returning `["xfusion-log-stream"]`. \ No newline at end of file diff --git a/terraform/task-22.md b/terraform/task-22.md new file mode 100644 index 0000000..e529751 --- /dev/null +++ b/terraform/task-22.md @@ -0,0 +1,116 @@ +# Assignment + +The Nautilus DevOps team is working on automating infrastructure deployment using AWS CloudFormation. As part of this effort, they need to create a CloudFormation stack that provisions an S3 bucket with versioning enabled. + +Create a CloudFormation stack named xfusion-stack using Terraform. This stack should contain an S3 bucket named xfusion-bucket-20270 as a resource, and the bucket must have versioning enabled. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# CloudFormation Stack — `xfusion-stack` (S3 bucket with versioning) + +Terraform provisions a **CloudFormation stack**; the stack's template, in turn, +creates a versioned S3 bucket. The bucket is a CloudFormation-managed resource, not a +native Terraform one. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_cloudformation_stack" "xfusion" { + name = "xfusion-stack" + + template_body = <<-TEMPLATE + AWSTemplateFormatVersion: "2010-09-09" + Description: S3 bucket with versioning enabled + Resources: + XfusionBucket: + Type: AWS::S3::Bucket + Properties: + BucketName: xfusion-bucket-20270 + VersioningConfiguration: + Status: Enabled + TEMPLATE +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Two layers of provisioning + +This task is deliberately indirect. Terraform does **not** create the bucket +directly with `aws_s3_bucket`. Instead: + +1. Terraform creates an `aws_cloudformation_stack` resource. +2. AWS CloudFormation reads the template embedded in that stack and creates the S3 + bucket from it. + +So the ownership chain is **Terraform → CloudFormation stack → S3 bucket**. If you +inspect the bucket afterward it will show as managed by the `xfusion-stack` stack, +which is exactly what the task asks for. + +### `aws_cloudformation_stack` + +- **`name = "xfusion-stack"`** — the stack name, exactly as required. +- **`template_body`** — the CloudFormation template, inlined as a YAML heredoc. The + `<<-TEMPLATE` form strips leading indentation so the YAML parses cleanly despite + being nested inside HCL. You could also point at a file with `template_url` (S3) or + `file()`, but inlining keeps everything in the single `main.tf` the task requires. + +### The CloudFormation template + +- **`AWSTemplateFormatVersion`** — the CFN schema version; `"2010-09-09"` is the only + valid value and is effectively a constant. +- **`Resources.XfusionBucket`** — the logical ID (an internal name CloudFormation + uses to track the resource). It's arbitrary; the actual bucket name comes from the + properties below. +- **`Type: AWS::S3::Bucket`** — declares an S3 bucket. +- **`BucketName: xfusion-bucket-20270`** — sets the real, globally-unique bucket + name required by the task. +- **`VersioningConfiguration.Status: Enabled`** — turns on object versioning, so + overwritten or deleted objects are retained as prior versions. This is the CFN + equivalent of Terraform's `aws_s3_bucket_versioning` resource. + +### Why versioning is inside the template + +Because the bucket is owned by CloudFormation, its configuration must be expressed in +CFN syntax inside the template — not with a separate Terraform +`aws_s3_bucket_versioning` block. Mixing a native TF versioning resource against a +CFN-managed bucket would create two controllers fighting over the same bucket, so all +bucket config lives in the template. + +## Verify + +```bash +# Stack created successfully +aws cloudformation describe-stacks --stack-name xfusion-stack \ + --query 'Stacks[0].{Name:StackName,Status:StackStatus}' + +# Bucket exists with versioning enabled +aws s3api get-bucket-versioning --bucket xfusion-bucket-20270 \ + --query 'Status' +``` + +Expected — stack `StackStatus: CREATE_COMPLETE`, and the versioning query returning +`"Enabled"`. \ No newline at end of file diff --git a/terraform/task-23.md b/terraform/task-23.md new file mode 100644 index 0000000..e0993fc --- /dev/null +++ b/terraform/task-23.md @@ -0,0 +1,130 @@ +# Assignment + +The Nautilus DevOps team needs to set up an Amazon OpenSearch Service domain to store and search their application logs. The domain should have the following specification: + +1) The domain name should be devops-es. + +2) Use Terraform to create the OpenSearch domain. The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + + +Notes: + +The Terraform working directory is /home/bob/terraform. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +Before submitting the task, ensure that terraform plan returns No changes. Your infrastructure matches the configuration. + +The OpenSearch domain creation process may take several minutes. Please wait until the domain is fully created before submitting. + +# Solution + +# OpenSearch Domain — `devops-es` + +Terraform solution to create a minimal, single-node Amazon OpenSearch Service domain +that comes up clean and reports no drift on re-plan. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_opensearch_domain" "devops_es" { + domain_name = "devops-es" + + cluster_config { + instance_type = "t3.small.search" + instance_count = 1 + } + + ebs_options { + ebs_enabled = true + volume_type = "gp2" + volume_size = 10 + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +# Domain creation takes ~10-20 minutes. Wait for apply to fully return. + +# Required idempotency check: +terraform plan +# -> "No changes. Your infrastructure matches the configuration." +``` + +## How it works + +### `aws_opensearch_domain` + +- **`domain_name = "devops-es"`** — the domain name, exactly as required. + +- **`cluster_config`** — defines the data nodes: + - `instance_type = "t3.small.search"` — the smallest current-generation + general-purpose node. OpenSearch instance types carry a `.search` suffix. Small + burstable nodes keep the domain inside sandbox cost limits (OpenSearch isn't in + the published KodeKloud caps, so the general "smallest viable instance" rule + applies). + - `instance_count = 1` — a single node. No `zone_awareness_enabled` and no + `dedicated_master_enabled`, which are unnecessary (and would cost more) for a + lab domain. + +- **`ebs_options`** — OpenSearch nodes on this instance family use EBS for storage: + - `ebs_enabled = true` — required for non-instance-store types. + - `volume_type = "gp2"` — **deliberately gp2, not gp3** (see idempotency note + below). + - `volume_size = 10` — 10 GiB, the minimum allowed per node. + +### Why this config passes the "No changes" plan check + +The task explicitly requires `terraform plan` to report no drift after apply. +OpenSearch domains are notorious for phantom diffs; two choices here prevent that: + +1. **gp2 instead of gp3.** A gp3 volume has `throughput` and `iops` attributes that + AWS auto-populates with defaults when you don't specify them. Terraform then sees + values in the remote state that aren't in your config and reports a perpetual + diff. gp2 has no such tunables, so there's nothing to drift — the cleanest choice + for an idempotent lab domain. + +2. **`engine_version` omitted.** When you don't pin the engine version, the argument + is treated as computed — AWS picks its current default at create, Terraform + records it in state, and because your config says nothing about it, there's + nothing to compare against on the next plan. Pinning a version can instead cause + drift when AWS applies an automatic minor-version patch. + +Everything else (encryption blocks, endpoint options, auto-tune, off-peak window) is +left unset, so the provider treats those as computed defaults rather than managed +values — again, no diff. + +### On creation time + +OpenSearch domains provision slowly — the control plane spins up nodes, storage, and +networking, which typically takes 10–20 minutes. `terraform apply` blocks until the +domain reaches `Active`, so when the command returns the domain is ready. Don't +submit until apply completes and the follow-up `plan` is clean. + +## Verify + +```bash +aws opensearch describe-domain --domain-name devops-es \ + --query 'DomainStatus.{Name:DomainName,Processing:Processing,Type:ClusterConfig.InstanceType,Count:ClusterConfig.InstanceCount,Vol:EBSOptions.VolumeType,Size:EBSOptions.VolumeSize}' +``` + +Expected — name `devops-es`, `Processing: false` (creation finished), instance type +`t3.small.search`, count `1`, volume `gp2` size `10`. diff --git a/terraform/task-24.md b/terraform/task-24.md new file mode 100644 index 0000000..1176d8b --- /dev/null +++ b/terraform/task-24.md @@ -0,0 +1,112 @@ +# Assignment + +The Nautilus DevOps team needs to store sensitive data securely using AWS Secrets Manager. They need to create a secret with the following specifications: + +1) The secret name should be xfusion-secret. + +2) The secret value should contain a key-value pair with username: admin and password: Namin123. + +3) Use Terraform to create the secret in AWS Secrets Manager. + +The Terraform working directory is /home/bob/terraform. Create the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Secrets Manager — `xfusion-secret` + +Terraform solution to create an AWS Secrets Manager secret holding a username/password +key-value pair. + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_secretsmanager_secret" "xfusion" { + name = "xfusion-secret" +} + +resource "aws_secretsmanager_secret_version" "xfusion" { + secret_id = aws_secretsmanager_secret.xfusion.id + + secret_string = jsonencode({ + username = "admin" + password = "Namin123" + }) +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +Secrets Manager splits a secret into two parts, and Terraform mirrors that with two +resources: + +### `aws_secretsmanager_secret` + +The **container / metadata** — the named secret itself. It holds the name, optional +description, KMS key, rotation config, and resource policy, but **not** the secret +value. + +- **`name = "xfusion-secret"`** — the secret name, exactly as required. +- No KMS key is specified, so Secrets Manager encrypts the value with the AWS-managed + `aws/secretsmanager` key by default — sufficient for the task. + +### `aws_secretsmanager_secret_version` + +The **actual value**. Secrets are versioned; every value you store is a version, and +one is marked `AWSCURRENT`. This resource writes that current version. + +- **`secret_id = aws_secretsmanager_secret.xfusion.id`** — links the version to its + parent secret. Referencing the attribute (not hardcoding) creates the implicit + dependency so the container is created before the version is written into it. +- **`secret_string`** — the payload. The task wants a **key-value pair**, which in + Secrets Manager means a JSON object string. `jsonencode({ username = "admin", + password = "Namin123" })` produces `{"username":"admin","password":"Namin123"}`. + Using `jsonencode` (rather than hand-writing the JSON string) guarantees valid + escaping and is the idiomatic way to store structured secrets — it's also the + format the Secrets Manager console renders as separate key/value rows. + +### Why JSON and not two plain values + +Secrets Manager stores a single `SecretString` per version. To hold *multiple* fields +(username **and** password), you encode them as one JSON object. That's the standard +pattern and what lets the console and SDKs pull individual keys (e.g. via +`--query SecretString` then JSON parse). Storing raw `admin` / `Namin123` as two +separate things isn't possible — it's one string field, so JSON is the correct +container. + +### Sandbox note + +Secrets Manager **basic** operations (create secret, store value) are permitted. +Advanced features — automatic **rotation** and **cross-region replication** — are +blocked in these sandboxes, but this task uses neither, so it proceeds without issue. + +## Verify + +```bash +aws secretsmanager get-secret-value --secret-id xfusion-secret \ + --query 'SecretString' --output text +``` + +Expected — `{"username":"admin","password":"Namin123"}`. diff --git a/terraform/task-25.md b/terraform/task-25.md new file mode 100644 index 0000000..7a20965 --- /dev/null +++ b/terraform/task-25.md @@ -0,0 +1,119 @@ +# Assignment + +During the migration process, the Nautilus DevOps team created several EC2 instances in different regions. They are currently in the process of identifying the correct resources and utilization and are making continuous changes to ensure optimal resource utilization. Recently, they discovered that one of the EC2 instances was underutilized, prompting them to decide to change the instance type. Please make sure the Status check is completed (if it's still in Initializing state) before making any changes to the instance. + +Change the instance type from t2.micro to t2.nano for nautilus-ec2 instance using terraform. + +Make sure the EC2 instance nautilus-ec2 is in running state after the change. + +The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to change the instance type. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Change EC2 Instance Type — `nautilus-ec2` (t2.micro → t2.nano) + +This is an **update** to an already Terraform-managed instance, not a new resource. +The instance definition already exists in `main.tf` from when it was created; the task +is to change its `instance_type` and re-apply. + +## The change + +Find the `aws_instance` block for `nautilus-ec2` in the existing `main.tf` and change +the single `instance_type` line: + +```diff + resource "aws_instance" "nautilus_ec2" { + ami = "ami-xxxxxxxxxxxxxxxxx" +- instance_type = "t2.micro" ++ instance_type = "t2.nano" + # ... key_name, vpc_security_group_ids, tags, etc. unchanged ... + + tags = { + Name = "nautilus-ec2" + } + } +``` + +For reference, the full block looks like this after the edit (leave every other +argument exactly as it already is — only `instance_type` changes): + +```hcl +resource "aws_instance" "nautilus_ec2" { + ami = "ami-xxxxxxxxxxxxxxxxx" # keep the existing value + instance_type = "t2.nano" # was t2.micro + + tags = { + Name = "nautilus-ec2" + } +} +``` + +> Do **not** rewrite the block from scratch or change the resource's Terraform name, +> AMI, key, or security groups. Altering those could force a **replacement** (destroy + +> recreate) instead of an in-place modify. Only the one line changes. + +## How to run + +```bash +cd /home/bob/terraform + +# 1) Make sure status checks are done (not "Initializing") BEFORE changing anything. +aws ec2 describe-instance-status \ + --filters Name=tag:Name,Values=nautilus-ec2 \ + --query 'InstanceStatuses[0].{Instance:InstanceStatus.Status,System:SystemStatus.Status}' +# Wait until both report "ok" (2/2 checks passed). If it shows "initializing", wait and re-run. + +# 2) Apply the instance-type change. +terraform plan # should show ~ instance_type "t2.micro" -> "t2.nano" (update in place) +terraform apply -auto-approve +``` + +## How it works + +### Why it's an in-place update, not a replacement + +`instance_type` is a mutable attribute in EC2. Terraform's AWS provider changes it +**without destroying the instance**: it stops the instance, issues +`ModifyInstanceAttribute` to set the new type, then starts it again. You'll see this +in the plan as `~ update in-place`, not `-/+ destroy and recreate`. The instance keeps +its ID, EBS volumes, private IP, and (if it has one) EIP association. + +`t2.micro` and `t2.nano` are in the same family and share the same virtualization and +architecture, so the modify is fully compatible — no AMI or platform mismatch. + +### Why wait for the status check first + +The task calls this out specifically. Changing instance type requires the instance to +be **stopped** momentarily. If the instance is still `Initializing` (status checks +haven't reached 2/2), issuing a stop/modify mid-initialization can fail or leave the +instance in an inconsistent state. Waiting until both the system and instance status +checks report `ok` guarantees a clean stop → modify → start cycle. + +### Why it ends up `running` (requirement #2) + +After a type change, the AWS provider automatically starts the instance back up and +waits for it to reach the `running` state before `apply` returns. No extra +configuration is needed — an `aws_instance` resource's desired state is running, so +Terraform restores that after the modify. When `apply` completes, the instance is +running as `t2.nano`. + +### If the instance isn't in Terraform state + +The task says *update* `main.tf`, which means the instance is already managed here. If +for some reason `terraform plan` reports it wants to **create** `nautilus-ec2` (i.e. +it's not in state), stop — that means the instance was made outside Terraform and you'd +need to `terraform import aws_instance.nautilus_ec2 ` first, then apply the +type change. In the normal lab flow this isn't necessary. + +## Verify + +```bash +aws ec2 describe-instances \ + --filters Name=tag:Name,Values=nautilus-ec2 \ + --query 'Reservations[0].Instances[0].{Id:InstanceId,Type:InstanceType,State:State.Name}' +``` + +Expected — the same instance ID as before, `InstanceType: t2.nano`, and +`State: running`. \ No newline at end of file diff --git a/terraform/task-26.md b/terraform/task-26.md new file mode 100644 index 0000000..f972b44 --- /dev/null +++ b/terraform/task-26.md @@ -0,0 +1,103 @@ +# Assignment + +The Nautilus DevOps team has been creating a couple of services on AWS cloud. They have been breaking down the migration into smaller tasks, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. Recently they came up with requirements mentioned below. + +There is an instance named xfusion-ec2 and an elastic-ip named xfusion-ec2-eip in us-east-1 region. Attach the xfusion-ec2-eip elastic-ip to the xfusion-ec2 instance using Terraform only. The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to attach the specified Elastic IP to the instance. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Attach Elastic IP — `xfusion-ec2-eip` → `xfusion-ec2` + +Both the instance (`aws_instance.ec2`) and the Elastic IP (`aws_eip.ec2_eip`) are +already managed in this `main.tf`. So there's no need for data sources — you reference +the existing resources directly and add a single association resource. + +## `main.tf` (append this block; leave the existing two resources unchanged) + +```hcl +# Provision EC2 instance +resource "aws_instance" "ec2" { + ami = "ami-0c101f26f147fa7fd" + instance_type = "t2.micro" + subnet_id = "subnet-bca4d7696cae064fb" + vpc_security_group_ids = [ + "sg-62f0812f2b5774df4" + ] + + tags = { + Name = "xfusion-ec2" + } +} + +# Provision Elastic IP +resource "aws_eip" "ec2_eip" { + tags = { + Name = "xfusion-ec2-eip" + } +} + +# Associate the Elastic IP with the instance +resource "aws_eip_association" "ec2_eip_assoc" { + allocation_id = aws_eip.ec2_eip.id + instance_id = aws_instance.ec2.id +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform plan # should show only the new aws_eip_association to add +terraform apply -auto-approve +``` + +## How it works + +### Referencing managed resources (not data sources) + +Because both the instance and the EIP already live in this configuration, you point +the association straight at their resource attributes: + +- **`allocation_id = aws_eip.ec2_eip.id`** — a VPC Elastic IP's `id` is its allocation + ID (`eipalloc-...`), which is what an association binds. (Data sources would only be + needed if these resources were created outside this config — here they aren't.) +- **`instance_id = aws_instance.ec2.id`** — the instance's `i-...` ID. + +Referencing the resource attributes (rather than hardcoding IDs) also creates +**implicit dependencies**: Terraform knows the association depends on both the instance +and the EIP, so it orders creation correctly and the plan cleanly shows only the new +association being added — the two existing resources are untouched. + +### `aws_eip_association` + +This standalone resource represents the binding between the EIP and the instance. On +apply, Terraform calls `AssociateAddress`, and the instance's public-facing IP becomes +the Elastic IP. Keeping the association as its own resource (rather than folding an +`instance = ...` argument into the `aws_eip` block) is the cleaner, more explicit +pattern and keeps the attach/detach lifecycle isolated — destroying just this resource +would detach the EIP while leaving the instance and EIP intact. + +> Note on the alternative: you *could* instead add `instance = aws_instance.ec2.id` +> directly to the `aws_eip` resource. That also works when Terraform owns the EIP, but +> a dedicated `aws_eip_association` is the recommended approach and avoids mixing +> allocation and association concerns in one resource. + +## Verify + +```bash +aws ec2 describe-addresses \ + --filters Name=tag:Name,Values=xfusion-ec2-eip \ + --query 'Addresses[0].{EIP:PublicIp,InstanceId:InstanceId,AssocId:AssociationId}' +``` + +Expected — the address shows a populated `InstanceId` (the `xfusion-ec2` instance) and +an `AssociationId`. Cross-check that the instance's `PublicIpAddress` now equals the +EIP: + +```bash +aws ec2 describe-instances \ + --filters Name=tag:Name,Values=xfusion-ec2 \ + --query 'Reservations[0].Instances[0].PublicIpAddress' +``` \ No newline at end of file diff --git a/terraform/task-27.md b/terraform/task-27.md new file mode 100644 index 0000000..2e3a895 --- /dev/null +++ b/terraform/task-27.md @@ -0,0 +1,107 @@ +# Assignment + +The Nautilus DevOps team has been creating a couple of services on AWS cloud. They have been breaking down the migration into smaller tasks, allowing for better control, risk mitigation, and optimization of resources throughout the migration process. Recently they came up with requirements mentioned below. + +An IAM user named iamuser_james and a policy named iampolicy_james already exists. Use Terraform to attach the IAM policy iampolicy_james to the IAM user iamuser_james. The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to attach the specified IAM policy to the IAM user. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Attach IAM Policy — `iampolicy_james` → `iamuser_james` + +The user (`aws_iam_user.user`) and the customer-managed policy +(`aws_iam_policy.policy`) already exist in this `main.tf`. The task is to bind them, +which is one appended `aws_iam_user_policy_attachment` resource referencing both. + +## `main.tf` (append this block; leave the existing two resources unchanged) + +```hcl +# Create IAM user +resource "aws_iam_user" "user" { + name = "iamuser_james" + + tags = { + Name = "iamuser_james" + } +} + +# Create IAM Policy +resource "aws_iam_policy" "policy" { + name = "iampolicy_james" + description = "IAM policy allowing EC2 read actions for james" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = ["ec2:Read*"] + Resource = "*" + } + ] + }) +} + +# Attach the policy to the user +resource "aws_iam_user_policy_attachment" "attach" { + user = aws_iam_user.user.name + policy_arn = aws_iam_policy.policy.arn +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform plan # should show only the new attachment to add +terraform apply -auto-approve +``` + +## How it works + +### `aws_iam_user_policy_attachment` + +This resource attaches a **managed** policy (AWS-managed or customer-managed) to an +IAM user. It's the right resource here because `iampolicy_james` is a standalone +customer-managed policy with its own ARN — as opposed to an *inline* policy, which +would use `aws_iam_user_policy` and embed the JSON directly in the user. + +- **`user = aws_iam_user.user.name`** — the attachment targets the user by name. +- **`policy_arn = aws_iam_policy.policy.arn`** — managed-policy attachments are made + by ARN. The `aws_iam_policy` resource exposes its generated `arn` + (`arn:aws:iam:::policy/iampolicy_james`), so referencing the attribute + avoids hardcoding the account ID. + +Referencing both resources by attribute creates **implicit dependencies**: Terraform +provisions/keeps the user and policy first, then the attachment. The plan shows only +the attachment being added — the existing user and policy are untouched. + +### Managed vs. inline — why this resource + +There are three ways to give an IAM user permissions in Terraform: + +| Resource | Use when | +|----------|----------| +| `aws_iam_user_policy_attachment` | Attaching an existing **managed** policy (this task). | +| `aws_iam_policy_attachment` | Attaching one policy to many users/roles/groups at once — **avoid**; it's exclusive and will detach principals it doesn't know about. | +| `aws_iam_user_policy` | Defining an **inline** policy embedded in the user. | + +`aws_iam_user_policy_attachment` is the correct, non-exclusive choice for wiring one +managed policy to one user. + +### Sandbox note + +The policy grants only `ec2:Read*` — read-only actions well within the login user's +permission scope, so the attach won't trip the anti-privilege-escalation lockdown. +Attaching a policy broader than your login user has is what triggers `AccessDenied`; +that isn't the case here. + +## Verify + +```bash +aws iam list-attached-user-policies --user-name iamuser_james \ + --query 'AttachedPolicies[*].{Name:PolicyName,Arn:PolicyArn}' +``` + +Expected — a list containing `iampolicy_james` with its ARN. \ No newline at end of file diff --git a/terraform/task-28.md b/terraform/task-28.md new file mode 100644 index 0000000..73e3cff --- /dev/null +++ b/terraform/task-28.md @@ -0,0 +1,93 @@ +# Assignment + +Data protection and recovery are fundamental aspects of data management. It's essential to have systems in place to ensure that data can be recovered in case of accidental deletion or corruption. The DevOps team has received a requirement for implementing such measures for one of the S3 buckets they are managing. + +The S3 bucket name is xfusion-s3-1124, enable versioning for this bucket using Terraform. + +The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a different .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Enable S3 Versioning — `xfusion-s3-1124` + +The bucket (`aws_s3_bucket.s3_ran_bucket`) already exists in this `main.tf`. Enabling +versioning in modern AWS provider versions is done with a **separate** +`aws_s3_bucket_versioning` resource, not an inline `versioning {}` block. + +## `main.tf` (append this block; leave the existing bucket unchanged) + +```hcl +resource "aws_s3_bucket" "s3_ran_bucket" { + bucket = "xfusion-s3-1124" + acl = "private" + + tags = { + Name = "xfusion-s3-1124" + } +} + +# Enable versioning on the bucket +resource "aws_s3_bucket_versioning" "s3_ran_bucket" { + bucket = aws_s3_bucket.s3_ran_bucket.id + + versioning_configuration { + status = "Enabled" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform plan # should show only the new aws_s3_bucket_versioning to add +terraform apply -auto-approve +``` + +## How it works + +### `aws_s3_bucket_versioning` + +Since AWS provider v4, S3 sub-configurations (versioning, ACL, logging, lifecycle, +encryption, etc.) were split out of the `aws_s3_bucket` resource into dedicated +resources. Versioning is now its own resource: + +- **`bucket = aws_s3_bucket.s3_ran_bucket.id`** — targets the existing bucket by + reference. Using the attribute (not the hardcoded name) creates an **implicit + dependency**, so Terraform manages the bucket first, then the versioning config, and + the plan shows only the new resource being added. +- **`versioning_configuration { status = "Enabled" }`** — turns on versioning. Valid + states are `Enabled`, `Suspended`, and `Disabled` (only meaningful on create). + `Enabled` means every overwrite or delete keeps prior object versions, which is + exactly the accidental-deletion/corruption protection the task calls for. + +### Note on the existing inline `acl` + +The existing bucket block sets `acl = "private"` inline. That inline argument is +**deprecated** in current provider versions (the modern equivalent is a separate +`aws_s3_bucket_acl` resource), but it still functions and — importantly — it's a +**different concern** from versioning. Adding `aws_s3_bucket_versioning` does **not** +conflict with the inline `acl`. Conflicts only arise if you configure the *same* +concern two ways (e.g. an inline `versioning {}` block **and** a standalone +`aws_s3_bucket_versioning` resource on the same bucket). Leave the `acl` line as-is — +the task only asks for versioning. + +### How versioning behaves once enabled + +- New writes to existing keys create new versions; the old bytes are retained under a + prior version ID. +- Deletes place a **delete marker** rather than removing data, so objects can be + recovered. +- Versioning, once enabled, can only be **suspended**, never fully turned off — that's + by design in S3. + +## Verify + +```bash +aws s3api get-bucket-versioning --bucket xfusion-s3-1124 \ + --query 'Status' +``` + +Expected — `"Enabled"`. \ No newline at end of file diff --git a/terraform/task-29.md b/terraform/task-29.md new file mode 100644 index 0000000..258994a --- /dev/null +++ b/terraform/task-29.md @@ -0,0 +1,130 @@ +# Assignment + +The Nautilus DevOps team is currently engaged in a cleanup process, focusing on removing unnecessary data and services from their AWS account. As part of the migration process, several resources were created for one-time use only, necessitating a cleanup effort to optimize their AWS environment. + +A S3 bucket named devops-bck-28889 already exists. + +1) Copy the contents of devops-bck-28889 S3 bucket to /opt/s3-backup/ directory on terraform-client host (the landing host once you load this lab). + +2) Delete the S3 bucket devops-bck-28889. + +3) Use the AWS CLI through Terraform to accomplish this task—for example, by running AWS CLI commands within Terraform. The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# S3 Backup + Delete via Terraform — `devops-bck-28889` + +This task runs **AWS CLI commands through Terraform** using a `null_resource` with a +`local-exec` provisioner: sync the bucket's contents to a local directory on the +`terraform-client` host, then delete the bucket. + +## `main.tf` (append this block) + +```hcl +terraform { + required_providers { + null = { + source = "hashicorp/null" + version = "~> 3.2" + } + } +} + +resource "null_resource" "s3_backup_and_delete" { + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + command = <<-EOT + mkdir -p /opt/s3-backup/ && \ + aws s3 sync s3://devops-bck-28889 /opt/s3-backup/ && \ + aws s3 rb s3://devops-bck-28889 --force + EOT + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Why a `null_resource` + `local-exec` + +Terraform has no native resource for "copy bucket contents to a local path" or for a +force-delete of a populated bucket. When a task explicitly says *use the AWS CLI +through Terraform*, the idiomatic pattern is a `null_resource` whose `local-exec` +provisioner shells out to the CLI on the machine running Terraform (here, the +`terraform-client` landing host, which already has AWS credentials configured). + +- **`null_resource`** — a resource that does nothing itself; it exists solely to hang + provisioners on. Creating it once (on `apply`) triggers its `local-exec` block. +- **`interpreter = ["/bin/bash", "-c"]`** — runs the script under bash. (The default + is `/bin/sh`; bash is set explicitly for predictable `&&` chaining and heredoc + behavior.) + +### The command sequence + +The three steps are chained with `&&`, so each runs only if the previous succeeded — +critical because step 3 is destructive: + +1. **`mkdir -p /opt/s3-backup/`** — ensures the destination directory exists (and is a + no-op if it already does). +2. **`aws s3 sync s3://devops-bck-28889 /opt/s3-backup/`** — copies every object from + the bucket into the local directory, preserving key paths. `sync` only transfers + what's missing/changed, but on a fresh directory it pulls everything. +3. **`aws s3 rb s3://devops-bck-28889 --force`** — removes the bucket. `rb` (remove + bucket) normally refuses a non-empty bucket; `--force` first deletes all objects, + then the bucket itself. + +The `&&` chaining is the safety mechanism: if the `sync` fails (network, permissions), +the `rb` never executes, so you don't delete data you failed to back up. + +### Run-once semantics + +A `null_resource` runs its provisioner when it's **created**. After a successful +apply it's in state and won't re-run on subsequent applies. That's the desired +behavior for a one-time cleanup. If you ever needed to run it again, you'd +`terraform taint null_resource.s3_backup_and_delete` (or +`terraform apply -replace=...`) to force recreation — but note the bucket is gone +after the first run, so a re-run would fail at the sync step anyway. + +### Modern alternative + +Terraform 1.4+ ships a built-in `terraform_data` resource that replaces +`null_resource` without needing the `hashicorp/null` provider. The same block written +with it: + +```hcl +resource "terraform_data" "s3_backup_and_delete" { + provisioner "local-exec" { + interpreter = ["/bin/bash", "-c"] + command = <<-EOT + mkdir -p /opt/s3-backup/ && \ + aws s3 sync s3://devops-bck-28889 /opt/s3-backup/ && \ + aws s3 rb s3://devops-bck-28889 --force + EOT + } +} +``` + +Either works; `null_resource` is shown as the primary since it's the widely-recognized +form. + +## Verify + +```bash +# Backup landed locally +ls -la /opt/s3-backup/ + +# Bucket is gone (this should error with "NoSuchBucket" / "Not Found") +aws s3 ls s3://devops-bck-28889 || echo "Bucket deleted." +``` + +Expected — `/opt/s3-backup/` contains the former bucket contents, and the `s3 ls` +against the bucket fails because it no longer exists. \ No newline at end of file diff --git a/terraform/task-30.md b/terraform/task-30.md new file mode 100644 index 0000000..b6e3dd3 --- /dev/null +++ b/terraform/task-30.md @@ -0,0 +1,102 @@ +# Assignment + +During the migration process, several resources were created under the AWS account. Some of these test resources are no longer needed at the moment, so we need to clean them up temporarily. One such instance is currently unused and should be deleted. + +1) Delete the ec2 instance named devops-ec2 present in us-east-1 region using terraform. Make sure to keep the provisioning code, as we might need to provision this instance again later. + +2) Before submitting your task, make sure instance is in terminated state. + +The Terraform working directory is /home/bob/terraform. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Delete EC2 Instance (Keep Code) — `devops-ec2` + +The requirement has two halves that seem to conflict: **terminate** the instance, but +**keep** its provisioning code in `main.tf` for later. The resolution is a **targeted +destroy** — it operates on the real resource and Terraform state only, and never +touches your `.tf` files. So `main.tf` stays exactly as-is. + +## `main.tf` — leave unchanged + +Do **not** delete or comment out the block. It stays exactly as given: + +```hcl +# Provision EC2 instance +resource "aws_instance" "ec2" { + ami = "ami-0c101f26f147fa7fd" + instance_type = "t2.micro" + vpc_security_group_ids = [ + "sg-6593a59fe82db4400" + ] + + tags = { + Name = "devops-ec2" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform + +# Terminate ONLY this instance; leaves the code in main.tf intact. +terraform destroy -target=aws_instance.ec2 -auto-approve +``` + +## How it works + +### Targeted destroy vs. the wrong approaches + +Three ways you might try to delete the instance, and why only one fits: + +| Approach | Result | +|----------|--------| +| Delete the resource block, then `apply` | Instance is destroyed, but the **code is gone** — violates "keep the provisioning code." | +| `terraform destroy` (no target) | Destroys **everything** in the config, not just this instance. | +| `terraform destroy -target=aws_instance.ec2` | Destroys **only** this instance; code stays in `main.tf`. ✅ | + +The `-target` flag scopes the operation to a single resource address +(`aws_instance.ec2`). Terraform terminates that instance, removes its entry from +state, and leaves every line of your configuration file exactly where it was — +because destroy acts on infrastructure and state, never on source code. + +### Why the code surviving matters + +Keeping the block means the instance is trivially re-creatable later: a plain +`terraform apply` will see the resource declared in config but absent from state and +recreate it. That's the "we might need to provision this again" requirement — the +declaration is preserved as the reusable blueprint. + +### Reaching `terminated` state (requirement #2) + +`terraform destroy -target` calls `TerminateInstances` and **waits** until the +instance reaches `terminated` before returning. When the command completes, the +instance is terminated — no manual polling needed. (A terminated instance lingers as +a read-only entry in the console for a while before disappearing; that's normal.) + +### Expected state afterward + +After the targeted destroy, the config declares a resource that no longer exists in +state. So `terraform plan` will show Terraform wants to **create** `devops-ec2` again +(`+ 1 to add`). That's expected and correct — **do not apply it**. The task wants the +instance terminated with the code retained, which is exactly this state. + +## Verify + +```bash +aws ec2 describe-instances \ + --filters Name=tag:Name,Values=devops-ec2 \ + --query 'Reservations[*].Instances[*].{Id:InstanceId,State:State.Name}' \ + --output table +``` + +Expected — the instance shows `State: terminated`. Also confirm the code is still +present: + +```bash +grep -A12 'resource "aws_instance" "ec2"' /home/bob/terraform/main.tf +``` \ No newline at end of file diff --git a/terraform/task-31.md b/terraform/task-31.md new file mode 100644 index 0000000..da7f69e --- /dev/null +++ b/terraform/task-31.md @@ -0,0 +1,105 @@ +# Assignment + +The Nautilus DevOps team is currently engaged in a cleanup process, focusing on removing unnecessary data and services from their AWS account. As part of the migration process, several resources were created for one-time use only, necessitating a cleanup effort to optimize their AWS environment. + +Delete an IAM group named iamgroup_siva using terraform. Make sure to keep the provisioning code, as we might need to provision this instance again later. + +The Terraform working directory is /home/bob/terraform. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Delete IAM Group (Keep Code) — `iamgroup_siva` + +Same shape as the "delete but keep the code" pattern: **destroy** the group while +leaving its provisioning block in `main.tf` for later reuse. A **targeted destroy** +does exactly this — it acts on the real resource and Terraform state only, never on +your `.tf` source. + +## `main.tf` — leave unchanged + +Do **not** delete or comment out the block. It stays exactly as given: + +```hcl +resource "aws_iam_group" "this" { + name = "iamgroup_siva" +} +``` + +## How to run + +```bash +cd /home/bob/terraform + +# Destroy ONLY this IAM group; leaves the code in main.tf intact. +terraform destroy -target=aws_iam_group.this -auto-approve +``` + +## How it works + +### Targeted destroy vs. the wrong approaches + +| Approach | Result | +|----------|--------| +| Delete the resource block, then `apply` | Group is destroyed, but the **code is gone** — violates "keep the provisioning code." | +| `terraform destroy` (no target) | Destroys **everything** in the config, not just this group. | +| `terraform destroy -target=aws_iam_group.this` | Destroys **only** this group; code stays in `main.tf`. ✅ | + +The `-target` flag scopes the operation to the single resource address +(`aws_iam_group.this` — note this is the **Terraform resource name** `this`, not the +AWS group name `iamgroup_siva`). Terraform calls `DeleteGroup`, removes the resource +from state, and leaves your configuration file untouched, because destroy operates on +infrastructure and state, never on source code. + +### Why the code surviving matters + +Keeping the block means the group is trivially re-creatable later: a plain +`terraform apply` will see it declared in config but absent from state and recreate +it. That satisfies "we might need to provision this again" — the declaration remains +the reusable blueprint. + +### One caveat for IAM groups + +`DeleteGroup` only succeeds if the group is **empty** — no users as members and no +attached/inline policies. If the destroy errors with `DeleteConflict`, detach +everything first, then re-run: + +```bash +# Remove any members +aws iam get-group --group-name iamgroup_siva \ + --query 'Users[].UserName' --output text | \ + xargs -r -n1 -I{} aws iam remove-user-from-group --group-name iamgroup_siva --user-name {} + +# Detach any managed policies +aws iam list-attached-group-policies --group-name iamgroup_siva \ + --query 'AttachedPolicies[].PolicyArn' --output text | \ + xargs -r -n1 -I{} aws iam detach-group-policy --group-name iamgroup_siva --policy-arn {} + +# Then retry +terraform destroy -target=aws_iam_group.this -auto-approve +``` + +For a bare group like this one (created empty), the plain targeted destroy works +directly. + +### Expected state afterward + +After the destroy, the config still declares a resource that no longer exists in +state, so `terraform plan` will show Terraform wants to **create** `iamgroup_siva` +again (`+ 1 to add`). That's expected — **do not apply it**. The task wants the group +deleted with the code retained, which is exactly this state. + +## Verify + +```bash +aws iam get-group --group-name iamgroup_siva 2>&1 | grep -q 'NoSuchEntity' \ + && echo "Group deleted." \ + || echo "Group still exists." + +# Confirm the code is still present +grep -A2 'resource "aws_iam_group" "this"' /home/bob/terraform/main.tf +``` + +Expected — `get-group` fails with `NoSuchEntity` (group gone), and the resource block +is still present in `main.tf`. \ No newline at end of file diff --git a/terraform/task-32.md b/terraform/task-32.md new file mode 100644 index 0000000..5d87b61 --- /dev/null +++ b/terraform/task-32.md @@ -0,0 +1,129 @@ +# Assignment + +The Nautilus DevOps team is currently engaged in a cleanup process, focusing on removing unnecessary data and services from their AWS account. As part of the migration process, several resources were created for one-time use only, necessitating a cleanup effort to optimize their AWS environment. + +Delete the IAM role named iamrole_ammar using Terraform. Make sure to keep the provisioning code, as we might need to provision this instance again later. + +The Terraform working directory is /home/bob/terraform. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Delete IAM Role (Keep Code) — `iamrole_ammar` + +Same "delete but keep the code" pattern: **destroy** the role while leaving its +provisioning block in `main.tf` for later reuse. A **targeted destroy** does exactly +this — it acts on the real resource and Terraform state only, never on your `.tf` +source. + +## `main.tf` — leave unchanged + +Do **not** delete or comment out the block. It stays exactly as given: + +```hcl +resource "aws_iam_role" "role" { + name = "iamrole_ammar" + + assume_role_policy = jsonencode({ + Version = "2012-10-17", + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = "iamrole_ammar" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform + +# Destroy ONLY this IAM role; leaves the code in main.tf intact. +terraform destroy -target=aws_iam_role.role -auto-approve +``` + +## How it works + +### Targeted destroy vs. the wrong approaches + +| Approach | Result | +|----------|--------| +| Delete the resource block, then `apply` | Role is destroyed, but the **code is gone** — violates "keep the provisioning code." | +| `terraform destroy` (no target) | Destroys **everything** in the config, not just this role. | +| `terraform destroy -target=aws_iam_role.role` | Destroys **only** this role; code stays in `main.tf`. ✅ | + +The `-target` flag scopes the operation to the single resource address +(`aws_iam_role.role` — the **Terraform resource name** `role`, not the AWS role name +`iamrole_ammar`). Terraform calls `DeleteRole`, removes the resource from state, and +leaves your configuration untouched, because destroy operates on infrastructure and +state, never on source code. + +### Why the code surviving matters + +Keeping the block means the role is trivially re-creatable later: a plain +`terraform apply` will see it declared in config but absent from state and recreate +it — assume-role policy and all. That satisfies "we might need to provision this +again." + +### One caveat for IAM roles + +`DeleteRole` only succeeds if the role has **nothing attached**: no managed policies, +no inline policies, and no instance profile referencing it. The `assume_role_policy` +(trust policy) shown here is part of the role itself and does **not** block deletion — +only *permission* policies and instance-profile links do. If the destroy errors with +`DeleteConflict`, clear those first, then re-run: + +```bash +# Detach managed policies +aws iam list-attached-role-policies --role-name iamrole_ammar \ + --query 'AttachedPolicies[].PolicyArn' --output text | \ + xargs -r -n1 -I{} aws iam detach-role-policy --role-name iamrole_ammar --policy-arn {} + +# Delete inline policies +aws iam list-role-policies --role-name iamrole_ammar \ + --query 'PolicyNames[]' --output text | \ + xargs -r -n1 -I{} aws iam delete-role-policy --role-name iamrole_ammar --policy-name {} + +# Remove from any instance profiles +aws iam list-instance-profiles-for-role --role-name iamrole_ammar \ + --query 'InstanceProfiles[].InstanceProfileName' --output text | \ + xargs -r -n1 -I{} aws iam remove-role-from-instance-profile --instance-profile-name {} --role-name iamrole_ammar + +# Then retry +terraform destroy -target=aws_iam_role.role -auto-approve +``` + +For a bare role like this one (only a trust policy, no permissions attached), the +plain targeted destroy works directly. + +### Expected state afterward + +After the destroy, the config still declares a resource that no longer exists in +state, so `terraform plan` will show Terraform wants to **create** `iamrole_ammar` +again (`+ 1 to add`). That's expected — **do not apply it**. The task wants the role +deleted with the code retained, which is exactly this state. + +## Verify + +```bash +aws iam get-role --role-name iamrole_ammar 2>&1 | grep -q 'NoSuchEntity' \ + && echo "Role deleted." \ + || echo "Role still exists." + +# Confirm the code is still present +grep -A18 'resource "aws_iam_role" "role"' /home/bob/terraform/main.tf +``` + +Expected — `get-role` fails with `NoSuchEntity` (role gone), and the resource block is +still present in `main.tf`. \ No newline at end of file diff --git a/terraform/task-33.md b/terraform/task-33.md new file mode 100644 index 0000000..c3ded76 --- /dev/null +++ b/terraform/task-33.md @@ -0,0 +1,104 @@ +# Assignment + +The Nautilus DevOps team is strategically planning the migration of a portion of their infrastructure to the AWS cloud. Acknowledging the magnitude of this endeavor, they have chosen to tackle the migration incrementally rather than as a single, massive transition. They created some services in different regions and later found that some of those can be deleted now. + +Delete a VPC named devops-vpc present in us-east-1 region using Terraform. Make sure to keep the provisioning code, as we might need to provision this instance again later. + +The Terraform working directory is /home/bob/terraform. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Delete VPC (Keep Code) — `devops-vpc` + +Same "delete but keep the code" pattern: **destroy** the VPC while leaving its +provisioning block in `main.tf` for later reuse. A **targeted destroy** does exactly +this — it acts on the real resource and Terraform state only, never on your `.tf` +source. + +## `main.tf` — leave unchanged + +Do **not** delete or comment out the block. It stays exactly as given: + +```hcl +resource "aws_vpc" "this" { + cidr_block = "10.0.0.0/16" + + tags = { + Name = "devops-vpc" + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform + +# Destroy ONLY this VPC; leaves the code in main.tf intact. +terraform destroy -target=aws_vpc.this -auto-approve +``` + +## How it works + +### Targeted destroy vs. the wrong approaches + +| Approach | Result | +|----------|--------| +| Delete the resource block, then `apply` | VPC is destroyed, but the **code is gone** — violates "keep the provisioning code." | +| `terraform destroy` (no target) | Destroys **everything** in the config, not just this VPC. | +| `terraform destroy -target=aws_vpc.this` | Destroys **only** this VPC; code stays in `main.tf`. ✅ | + +The `-target` flag scopes the operation to the single resource address +(`aws_vpc.this` — the **Terraform resource name** `this`, not the Name tag +`devops-vpc`). Terraform calls `DeleteVpc`, removes the resource from state, and +leaves your configuration untouched, because destroy operates on infrastructure and +state, never on source code. + +### Why the code surviving matters + +Keeping the block means the VPC is trivially re-creatable later: a plain +`terraform apply` will see it declared in config but absent from state and recreate +it with the same `10.0.0.0/16` CIDR. That satisfies "we might need to provision this +again." + +### One caveat for VPCs — dependencies + +`DeleteVpc` only succeeds if the VPC is **empty** of dependent resources. AWS refuses +to delete a VPC that still has subnets, internet/NAT gateways, route tables (beyond +the main one), non-default security groups, ENIs, or running instances attached — the +call fails with `DependencyViolation`. + +- If those dependents are **Terraform-managed** in this same config, targeting the VPC + will normally also plan destruction of what depends on it. If not, or if the delete + errors with `DependencyViolation`, remove the dependents first (or run a full + `terraform destroy` if the whole config is just this VPC's stack). +- The **default** security group, default route table, and default NACL are deleted + automatically *with* the VPC and don't block it. + +For a bare VPC like this one (just the CIDR, nothing provisioned inside it), the plain +targeted destroy works directly. + +### Expected state afterward + +After the destroy, the config still declares a resource that no longer exists in +state, so `terraform plan` will show Terraform wants to **create** `devops-vpc` again +(`+ 1 to add`). That's expected — **do not apply it**. The task wants the VPC deleted +with the code retained, which is exactly this state. + +## Verify + +```bash +aws ec2 describe-vpcs \ + --filters Name=tag:Name,Values=devops-vpc \ + --query 'Vpcs' --output text | grep -q . \ + && echo "VPC still exists." \ + || echo "VPC deleted." + +# Confirm the code is still present +grep -A6 'resource "aws_vpc" "this"' /home/bob/terraform/main.tf +``` + +Expected — the describe returns nothing (VPC gone), and the resource block is still +present in `main.tf`. \ No newline at end of file diff --git a/terraform/task-34.md b/terraform/task-34.md new file mode 100644 index 0000000..bf96e96 --- /dev/null +++ b/terraform/task-34.md @@ -0,0 +1,89 @@ +# Assignment + +The Nautilus DevOps team is presently immersed in data migrations, transferring data from on-premise storage systems to AWS S3 buckets. They have recently received some data that they intend to copy to one of the S3 buckets. + +S3 bucket named nautilus-cp-32671 already exists. Copy the file /tmp/nautilus.txt to s3 bucket nautilus-cp-32671 using Terraform. The Terraform working directory is /home/bob/terraform. Update the main.tf file (do not create a separate .tf file) to accomplish this task. + +Note: Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Upload File to S3 — `/tmp/nautilus.txt` → `nautilus-cp-32671` + +The bucket (`aws_s3_bucket.my_bucket`) already exists in this `main.tf`. Uploading a +file is a native Terraform operation via the `aws_s3_object` resource — no CLI needed. +Append one block. + +## `main.tf` (append this block; leave the existing bucket unchanged) + +```hcl +resource "aws_s3_bucket" "my_bucket" { + bucket = "nautilus-cp-32671" + acl = "private" + + tags = { + Name = "nautilus-cp-32671" + } +} + +# Upload the local file into the bucket +resource "aws_s3_object" "nautilus_file" { + bucket = aws_s3_bucket.my_bucket.id + key = "nautilus.txt" + source = "/tmp/nautilus.txt" + etag = filemd5("/tmp/nautilus.txt") +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform plan # should show only the new aws_s3_object to add +terraform apply -auto-approve +``` + +## How it works + +### `aws_s3_object` + +This resource represents a single object (a file) stored in an S3 bucket. It's the +idiomatic, native way to put a file into S3 with Terraform — the `aws_s3_object` name +is the current one (it replaced the older `aws_s3_bucket_object`, which still works but +is deprecated). + +- **`bucket = aws_s3_bucket.my_bucket.id`** — the target bucket, referenced by + attribute rather than hardcoded. This creates an **implicit dependency** so + Terraform ensures the bucket exists before uploading, and the plan shows only the + new object being added. +- **`key = "nautilus.txt"`** — the object key, i.e. its name/path *inside* the bucket. + A bare `nautilus.txt` places it at the bucket root. (You could use + `some/prefix/nautilus.txt` to nest it under a "folder".) +- **`source = "/tmp/nautilus.txt"`** — the path to the local file on the machine + running Terraform. Terraform reads this file and uploads its bytes. Use `source` for + file uploads; the alternative `content = "..."` is for inline string data instead of + a file. +- **`etag = filemd5("/tmp/nautilus.txt")`** — the MD5 of the local file. S3 stores an + object's ETag as its MD5, so wiring this lets Terraform detect **content changes**: + if `/tmp/nautilus.txt` is edited later, the `etag` changes and Terraform will + re-upload on the next apply. Without it, Terraform only tracks the object's + existence, not its contents. + +### Why native resource over CLI + +Some tasks require shelling out to the AWS CLI (e.g. syncing a whole bucket or a +force-delete). A single-file upload isn't one of them — `aws_s3_object` handles it +declaratively, tracks the object in state, and gives clean create/update/delete +lifecycle management. Reaching for `null_resource` + `local-exec` here would be +unnecessary and would put the upload outside Terraform's state tracking. + +## Verify + +```bash +aws s3 ls s3://nautilus-cp-32671/ + +aws s3api head-object --bucket nautilus-cp-32671 --key nautilus.txt \ + --query '{Key:`nautilus.txt`,Size:ContentLength,ETag:ETag}' +``` + +Expected — `nautilus.txt` listed in the bucket, with a size matching the local file. \ No newline at end of file diff --git a/terraform/task-35.md b/terraform/task-35.md new file mode 100644 index 0000000..ecd7438 --- /dev/null +++ b/terraform/task-35.md @@ -0,0 +1,125 @@ +# Assignment + +The Nautilus DevOps team is automating VPC creation using Terraform to manage networking efficiently. As part of this task, they need to create a VPC with specific requirements. + +For this task, create an AWS VPC using Terraform with the following requirements: + +The VPC name datacenter-vpc should be stored in a variable named KKE_vpc. + +The VPC should have a CIDR block of 10.0.0.0/16. + +Note: + +The configuration values should be stored in a variables.tf file. + +The Terraform script should be structured with a main.tf file referencing variables.tf. + +The Terraform working directory is /home/bob/terraform. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# VPC with Variables — `datacenter-vpc` (`KKE_vpc`) + +This task requires a **two-file** Terraform structure: `variables.tf` holds the +configuration values, and `main.tf` references them. The VPC name must be stored in a +variable named exactly `KKE_vpc`. + +## `variables.tf` + +```hcl +variable "KKE_vpc" { + description = "Name of the VPC" + type = string + default = "datacenter-vpc" +} + +variable "KKE_vpc_cidr" { + description = "CIDR block for the VPC" + type = string + default = "10.0.0.0/16" +} +``` + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_vpc" "datacenter_vpc" { + cidr_block = var.KKE_vpc_cidr + + tags = { + Name = var.KKE_vpc + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Splitting config from logic + +Terraform loads **every** `.tf` file in the working directory and merges them into one +configuration, so splitting declarations across files is purely organizational — there +are no imports to wire up. The convention this task asks for: + +- **`variables.tf`** — declares input variables (the "what": names, CIDRs, sizes). +- **`main.tf`** — declares resources (the "how"), referencing the variables with + `var.`. + +This separation is standard practice: it centralizes the values people tweak, keeps +resource blocks generic and reusable, and makes the config easier to parameterize +later (per-environment `.tfvars`, overrides, etc.). + +### The variables + +- **`variable "KKE_vpc"`** — holds the VPC name, exactly as required. The `default` + of `datacenter-vpc` means no value needs to be passed at apply time; Terraform uses + the default. +- **`variable "KKE_vpc_cidr"`** — holds the CIDR. Putting it here (rather than + hardcoding in `main.tf`) satisfies the "configuration values should be stored in + variables.tf" note. Its default is the required `10.0.0.0/16`. +- **`type = string`** constrains each to a string, so Terraform validates the input + type at plan time. + +### Referencing variables in `main.tf` + +- **`cidr_block = var.KKE_vpc_cidr`** — resolves to `10.0.0.0/16`. +- **`tags = { Name = var.KKE_vpc }`** — resolves the VPC's `Name` tag to + `datacenter-vpc`. A VPC has no native name field, so the `Name` tag is what the + console and graders read as its name. + +The `var.` prefix is how Terraform interpolates a declared variable's value. Because +both variables have defaults, `terraform apply` runs non-interactively; without +defaults, Terraform would prompt for the values (or you'd pass `-var` / a `.tfvars` +file). + +## Verify + +```bash +aws ec2 describe-vpcs \ + --filters Name=tag:Name,Values=datacenter-vpc \ + --query 'Vpcs[0].{Id:VpcId,Cidr:CidrBlock,Name:Tags[?Key==`Name`]|[0].Value}' +``` + +Expected — the VPC ID, `CidrBlock: 10.0.0.0/16`, and `Name: datacenter-vpc`. \ No newline at end of file diff --git a/terraform/task-36.md b/terraform/task-36.md new file mode 100644 index 0000000..4d8c038 --- /dev/null +++ b/terraform/task-36.md @@ -0,0 +1,116 @@ +# Assignment + +The Nautilus DevOps team is enhancing infrastructure automation and needs to provision a Security Group using Terraform with specific configurations. + +For this task, create an AWS Security Group using Terraform with the following requirements: + +The Security Group name xfusion-sg should be stored in a variable named KKE_sg. +Note: + +1. The configuration values should be stored in a variables.tf file. + +2. The Terraform script should be structured with a main.tf file referencing variables.tf. +The Terraform working directory is /home/bob/terraform. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Security Group with Variables — `xfusion-sg` (`KKE_sg`) + +Two-file structure again: `variables.tf` holds the config, `main.tf` references it. The +Security Group name must be stored in a variable named exactly `KKE_sg`. + +## `variables.tf` + +```hcl +variable "KKE_sg" { + description = "Name of the Security Group" + type = string + default = "xfusion-sg" +} +``` + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +# Default VPC to place the security group in +data "aws_vpc" "default" { + default = true +} + +resource "aws_security_group" "xfusion_sg" { + name = var.KKE_sg + description = "Security group xfusion-sg managed by Terraform" + vpc_id = data.aws_vpc.default.id + + tags = { + Name = var.KKE_sg + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Splitting config from logic + +Terraform merges every `.tf` file in the working directory into one configuration, so +`variables.tf` and `main.tf` act as a single unit — no imports needed. The convention: +`variables.tf` declares inputs, `main.tf` declares resources that consume them via +`var.`. + +### The variable + +- **`variable "KKE_sg"`** — holds the Security Group name, exactly as required. The + `default` of `xfusion-sg` lets `terraform apply` run without prompting for a value. +- **`type = string`** validates the input type at plan time. + +### The Security Group + +- **`name = var.KKE_sg`** — resolves to `xfusion-sg`. The `var.` prefix interpolates + the variable's value. +- **`description`** — **required** by AWS on every security group. If you omit it, the + provider defaults it to "Managed by Terraform"; it's set explicitly here for clarity. + Note the description is **immutable** — AWS won't let you change it after creation, so + it must be right the first time. +- **`vpc_id = data.aws_vpc.default.id`** — places the SG in the account's default VPC. + The `data "aws_vpc" "default"` block reads the existing default VPC without managing + it. (If `vpc_id` is omitted entirely, the SG lands in the default VPC anyway, but + wiring it explicitly is clearer and deterministic.) +- **No `ingress`/`egress` blocks** — the task specifies no rules, so none are defined. + Be aware that when Terraform manages a security group with no `egress` block, it + **removes** the default allow-all outbound rule, leaving the SG with no rules at all. + That matches the task (which asks only for the named SG to exist); add an `egress` + block if outbound traffic is later needed. + +## Verify + +```bash +aws ec2 describe-security-groups \ + --filters Name=group-name,Values=xfusion-sg \ + --query 'SecurityGroups[0].{Name:GroupName,Desc:Description,Vpc:VpcId,Id:GroupId}' +``` + +Expected — `GroupName: xfusion-sg`, the description, the default VPC ID, and a +generated group ID. \ No newline at end of file diff --git a/terraform/task-37.md b/terraform/task-37.md new file mode 100644 index 0000000..75fa0f4 --- /dev/null +++ b/terraform/task-37.md @@ -0,0 +1,104 @@ +# Assignment + +The Nautilus DevOps team is strategizing the migration of a portion of their infrastructure to the AWS cloud. As part of this phased migration approach, they need to allocate an Elastic IP address to support external access for specific workloads. + +For this task, create an AWS Elastic IP using Terraform with the following requirement: + +The Elastic IP name xfusion-eip should be stored in a variable named KKE_eip. The Terraform working directory is /home/bob/terraform. +Note: + +The configuration values should be stored in a variables.tf file. + +The Terraform script should be structured with a main.tf file referencing variables.tf. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# Elastic IP with Variables — `xfusion-eip` (`KKE_eip`) + +Two-file structure: `variables.tf` holds the config, `main.tf` references it. The +Elastic IP name must be stored in a variable named exactly `KKE_eip`. + +## `variables.tf` + +```hcl +variable "KKE_eip" { + description = "Name of the Elastic IP" + type = string + default = "xfusion-eip" +} +``` + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_eip" "xfusion_eip" { + domain = "vpc" + + tags = { + Name = var.KKE_eip + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Splitting config from logic + +Terraform merges every `.tf` file in the directory into one configuration, so +`variables.tf` and `main.tf` work as a single unit with no imports. `variables.tf` +declares inputs; `main.tf` declares resources that consume them via `var.`. + +### The variable + +- **`variable "KKE_eip"`** — holds the Elastic IP name, exactly as required. The + `default` of `xfusion-eip` lets `terraform apply` run without prompting. +- **`type = string`** validates the input type at plan time. + +### The Elastic IP + +- **`domain = "vpc"`** — allocates the EIP for use in a VPC. This is the modern + argument; the old `vpc = true` was removed in AWS provider v5+, so on `~> 6.0` you + must use `domain`. Since the task only asks to *allocate* the address (no instance or + ENI association), no `instance` or `network_interface` argument is set. +- **`tags = { Name = var.KKE_eip }`** — an Elastic IP has no native name field, so the + `Name` tag (resolved from the variable to `xfusion-eip`) is what the console and + graders read as its name. + +The `var.` prefix interpolates the declared variable's value. Because the variable has +a default, `terraform apply` runs non-interactively. + +> Cost note: an unassociated Elastic IP incurs an hourly charge, but that's the +> sandbox's concern — the task only requires the allocation to exist. + +## Verify + +```bash +aws ec2 describe-addresses \ + --filters Name=tag:Name,Values=xfusion-eip \ + --query 'Addresses[0].{IP:PublicIp,AllocId:AllocationId,Name:Tags[?Key==`Name`]|[0].Value}' +``` + +Expected — a public IP, an allocation ID, and `Name: xfusion-eip`. \ No newline at end of file diff --git a/terraform/task-38.md b/terraform/task-38.md new file mode 100644 index 0000000..3460f87 --- /dev/null +++ b/terraform/task-38.md @@ -0,0 +1,103 @@ +# Assignment + +The Nautilus DevOps team is automating IAM user creation using Terraform for better identity management. + +For this task, create an AWS IAM User using Terraform with the following requirements: + +The IAM User name iamuser_mark should be stored in a variable named KKE_user. +Note: + +1. The configuration values should be stored in a variables.tf file. + +2. The Terraform script should be structured with a main.tf file referencing variables.tf. +The Terraform working directory is /home/bob/terraform. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# IAM User with Variables — `iamuser_mark` (`KKE_user`) + +Two-file structure: `variables.tf` holds the config, `main.tf` references it. The IAM +User name must be stored in a variable named exactly `KKE_user`. + +## `variables.tf` + +```hcl +variable "KKE_user" { + description = "Name of the IAM user" + type = string + default = "iamuser_mark" +} +``` + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_user" "mark" { + name = var.KKE_user + + tags = { + Name = var.KKE_user + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Splitting config from logic + +Terraform merges every `.tf` file in the directory into one configuration, so +`variables.tf` and `main.tf` act as a single unit — no imports needed. `variables.tf` +declares inputs; `main.tf` declares resources that consume them via `var.`. + +### The variable + +- **`variable "KKE_user"`** — holds the IAM user name, exactly as required. The + `default` of `iamuser_mark` lets `terraform apply` run without prompting. +- **`type = string`** validates the input type at plan time. + +### The IAM user + +- **`name = var.KKE_user`** — resolves to `iamuser_mark`. The `var.` prefix + interpolates the variable's value. +- **`iamuser_` prefix matters.** These locked-down sandboxes require IAM user names to + start with `iamuser_`. The variable's value already satisfies that, so the create + stays inside the login user's permission scope and won't be denied. +- **IAM is global.** The `region` in the provider is needed to initialize and + authenticate, but IAM resources aren't regional — the user is visible account-wide. +- **No policies, keys, or login profile.** The task asks only for the user to exist, so + nothing is attached. This keeps the config minimal and avoids the custom-policy / + attach restrictions these sandboxes enforce (attaching permissions broader than the + login user has would fail with `AccessDenied`). + +## Verify + +```bash +aws iam get-user --user-name iamuser_mark \ + --query 'User.{Name:UserName,Id:UserId,Arn:Arn}' +``` + +Expected — the user's name, a unique ID, and an ARN of the form +`arn:aws:iam:::user/iamuser_mark`. \ No newline at end of file diff --git a/terraform/task-39.md b/terraform/task-39.md new file mode 100644 index 0000000..c7598a0 --- /dev/null +++ b/terraform/task-39.md @@ -0,0 +1,125 @@ +# Assignment + +The Nautilus DevOps team is automating IAM role creation using Terraform to streamline permissions management. As part of this task, they need to create an IAM role with specific requirements. + +For this task, create an AWS IAM role using Terraform with the following requirements: + +The IAM role name iamrole_rose should be stored in a variable named KKE_iamrole. +Note: + +1. The configuration values should be stored in a variables.tf file. + +2. The Terraform script should be structured with a main.tf file referencing variables.tf. +The Terraform working directory is /home/bob/terraform. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# IAM Role with Variables — `iamrole_rose` (`KKE_iamrole`) + +Two-file structure: `variables.tf` holds the config, `main.tf` references it. The IAM +role name must be stored in a variable named exactly `KKE_iamrole`. + +## `variables.tf` + +```hcl +variable "KKE_iamrole" { + description = "Name of the IAM role" + type = string + default = "iamrole_rose" +} +``` + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_role" "rose" { + name = var.KKE_iamrole + + assume_role_policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Principal = { + Service = "ec2.amazonaws.com" + } + Action = "sts:AssumeRole" + } + ] + }) + + tags = { + Name = var.KKE_iamrole + } +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Splitting config from logic + +Terraform merges every `.tf` file in the directory into one configuration, so +`variables.tf` and `main.tf` act as a single unit — no imports. `variables.tf` declares +inputs; `main.tf` declares resources that consume them via `var.`. + +### The variable + +- **`variable "KKE_iamrole"`** — holds the IAM role name, exactly as required. The + `default` of `iamrole_rose` lets `terraform apply` run without prompting. +- **`type = string`** validates the input type at plan time. + +### The IAM role + +- **`name = var.KKE_iamrole`** — resolves to `iamrole_rose` via the `var.` prefix. + +- **`assume_role_policy` is mandatory.** Unlike a user or group, an IAM role + **requires** a trust policy at creation — it defines *who* (which principal) is + allowed to assume the role. Terraform's `aws_iam_role` will error without it. This + one trusts the EC2 service (`ec2.amazonaws.com`) to assume the role via + `sts:AssumeRole`, which is the standard trust for a role you'd attach to EC2 + instances. Any valid trust works; EC2 is a sensible, common default. + +- **`jsonencode({...})`** builds the trust-policy JSON from an HCL object, keeping it + readable and correctly escaped rather than hand-writing a raw JSON string. + +- **No permission policies attached.** The trust policy governs *who can assume* the + role; it grants no AWS permissions itself. The task only asks for the role to exist, + so no `aws_iam_role_policy_attachment` is added — which also keeps the config within + the sandbox's IAM restrictions (attaching a broad policy could trigger + `AccessDenied`). + +- **IAM is global.** The provider `region` is only for auth; the role is account-wide. + +## Verify + +```bash +aws iam get-role --role-name iamrole_rose \ + --query 'Role.{Name:RoleName,Id:RoleId,Arn:Arn,Trust:AssumeRolePolicyDocument}' +``` + +Expected — the role name, a unique ID, an ARN of the form +`arn:aws:iam:::role/iamrole_rose`, and the trust policy allowing +`ec2.amazonaws.com` to assume it. \ No newline at end of file diff --git a/terraform/task-40.md b/terraform/task-40.md new file mode 100644 index 0000000..a390a22 --- /dev/null +++ b/terraform/task-40.md @@ -0,0 +1,136 @@ +# Assignment + +The Nautilus DevOps team is automating IAM policy creation using Terraform to enhance security and access management. As part of this task, they need to create an IAM policy with specific requirements. + +For this task, create an AWS IAM policy using Terraform with the following requirements: + +The IAM policy name iampolicy_javed should be stored in a variable named KKE_iampolicy. +Note: + +The configuration values should be stored in a variables.tf file. + +The Terraform script should be structured with a main.tf file referencing variables.tf. + +The Terraform working directory is /home/bob/terraform. + +Right-click under the EXPLORER section in VS Code and select Open in Integrated Terminal to launch the terminal. + +# Solution + +# IAM Policy with Variables — `iampolicy_javed` (`KKE_iampolicy`) + +Two-file structure: `variables.tf` holds the config, `main.tf` references it. The IAM +policy name must be stored in a variable named exactly `KKE_iampolicy`. + +## `variables.tf` + +```hcl +variable "KKE_iampolicy" { + description = "Name of the IAM policy" + type = string + default = "iampolicy_javed" +} +``` + +## `main.tf` + +```hcl +terraform { + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 6.0" + } + } +} + +provider "aws" { + region = "us-east-1" +} + +resource "aws_iam_policy" "javed" { + name = var.KKE_iampolicy + description = "IAM policy managed by Terraform" + + policy = jsonencode({ + Version = "2012-10-17" + Statement = [ + { + Effect = "Allow" + Action = ["ec2:Describe*"] + Resource = "*" + } + ] + }) +} +``` + +## How to run + +```bash +cd /home/bob/terraform +terraform init +terraform apply -auto-approve +``` + +## How it works + +### Splitting config from logic + +Terraform merges every `.tf` file in the directory into one configuration, so +`variables.tf` and `main.tf` act as a single unit — no imports. `variables.tf` declares +inputs; `main.tf` declares resources that consume them via `var.`. + +### The variable + +- **`variable "KKE_iampolicy"`** — holds the IAM policy name, exactly as required. The + `default` of `iampolicy_javed` lets `terraform apply` run without prompting. +- **`type = string`** validates the input type at plan time. + +### The IAM policy + +- **`name = var.KKE_iampolicy`** — resolves to `iampolicy_javed` via the `var.` prefix. + This creates a **customer-managed policy** — a standalone, reusable permission + document with its own ARN. + +- **`policy` is mandatory.** An IAM policy is defined by its JSON document, so the + `policy` argument is required. The task doesn't specify what the policy should + grant, so a minimal, safe statement is used: `ec2:Describe*` (read-only) on all + resources. Read-only actions stay within the login user's permission scope, which + avoids the anti-privilege-escalation restrictions these sandboxes enforce — granting + actions broader than your own would fail with `AccessDenied`. + +- **`jsonencode({...})`** builds the policy JSON from an HCL object — readable and + correctly escaped, and validated by Terraform at plan time. + +- **`Version = "2012-10-17"`** is the current IAM policy language version — always this + literal date, not today's date. + +- **`Resource = "*"`** — `ec2:Describe*` actions are account-wide list/read operations + that don't support resource-level scoping, so `*` is the correct value. + +- **Policy created, not attached.** The task only asks for the policy to exist, so it + isn't attached to any user, group, or role. Attachment is a separate resource + (`aws_iam_user_policy_attachment` etc.) added when needed. + +### Sandbox note + +Creating a customer-managed policy uses `iam:CreatePolicy`, which is restricted in some +lab environments. Because this task explicitly requires a named custom policy, the lab +provisions that permission for this scenario, so the create succeeds. Keeping the +document read-only further ensures it stays within the allowed scope. + +## Verify + +```bash +POLICY_ARN=$(aws iam list-policies --scope Local \ + --query "Policies[?PolicyName=='iampolicy_javed'].Arn | [0]" --output text) + +echo "[$POLICY_ARN]" + +aws iam get-policy-version --policy-arn "$POLICY_ARN" --version-id v1 \ + --query 'PolicyVersion.Document' +``` + +Expected — a local policy ARN for `iampolicy_javed`, and its document showing the +`ec2:Describe*` allow statement. \ No newline at end of file