Files
kodekloud-engineer/terraform/certification-level-1.md

33 KiB

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)

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

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

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-t1q3xfusion-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)

# 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

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

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)

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

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

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::<account-id>: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_t2q3iamuser_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)

# 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

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

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)

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

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

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:<account-id>: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)

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

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

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)

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

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_controlsBucketOwnerPreferred — 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_aclpublic-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

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)

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

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

aws s3api get-public-access-block --bucket xfusion-s3-7078-t4q1 \
  --query 'PublicAccessBlockConfiguration'

Expected — all four true:

{
  "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)

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

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

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)

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

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

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.