docs: add Terraform certification notes and tasks
This commit is contained in:
993
terraform/certification-level-1.md
Normal file
993
terraform/certification-level-1.md
Normal file
@@ -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::<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_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:<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`)
|
||||
|
||||
```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`.
|
||||
Reference in New Issue
Block a user