[{"content":" 🛡️ The Concept: What is \u0026lsquo;Sensitive\u0026rsquo;? Standard resources like local_file can accidentally leak secrets (passwords, keys, tokens) into your terminal logs.\nThe Solution: Use local_sensitive_file to gain two layers of protection:\nConsole Masking: Automatically replaces secret values with (sensitive value) during plan and apply. Disk Security: Forces specific OS-level permissions on the created file. 👥 1. Protecting from the \u0026ldquo;Viewer\u0026rdquo; (Logs/Screen) The primary \u0026ldquo;person\u0026rdquo; this protects is anyone watching your terminal or CI/CD logs.\nThe Problem: If you use a regular local_file, Terraform prints the password in plain text during a terraform apply.\nThe Fix: local_sensitive_file masks that content. If a colleague is screen-sharing with you, they won\u0026rsquo;t see your secrets.\n🔐 2. Protecting from \u0026ldquo;Other Users\u0026rdquo; (Linux Permissions) This protects the file from other users logged into the same server.\nDefault Behavior: Usually, files created by a script might be readable by anyone on the system.\nThe Fix: By setting file_permission = \u0026ldquo;0600\u0026rdquo;, you tell the Operating System: \u0026ldquo;Only the person who ran this Terraform command can touch this file.\u0026rdquo;\n🚫 3. The \u0026ldquo;State\u0026rdquo; Catch (The Security Gap) It is a common misconception that \u0026ldquo;Sensitive\u0026rdquo; means the data is encrypted. It is not.\nThe Risk: Anyone with access to your terraform.tfstate file can see the secret in plain text.\nThe Person: This is why you must protect your State Backend (S3, Terraform Cloud, etc.) from unauthorized people.\n⚖️ Comparison: Standard vs. Sensitive Resources Feature local_file local_sensitive_file Terminal Output Prints full content (Unsafe) Masks content as (sensitive) File Permissions Default (Typically 0644) Restricted (Typically 0600) CI/CD Visibility Secrets appear in logs Secrets are redacted in logs State File Stored in Plain Text Stored in Plain Text ⚠️ Best Use Case Public Configs, Readmes SSH Keys, API Tokens, Passwords ⚠️ Critical Security Note: \u0026gt; While local_sensitive_file protects your terminal screen and local disk, it does NOT encrypt the data in your terraform.tfstate file. Anyone with access to your state file can still see your secrets in plain text.\n🎯 Challenge: Secure the Secret Key Now it\u0026rsquo;s your turn! Your goal is to provision a sensitive file using Terraform while following strict security requirements.\n🚩 The Mission Create a Terraform configuration that meets the following criteria:\nResource Type: Use the local_sensitive_file resource. File Name: The file should be created in your current directory and named customer_api_key.txt. Content: The file must contain the string: SECRET_API_TOKEN_999. Privacy: Ensure the file permissions are set so only the owner can read or write to it (hint: use the octal code for \u0026ldquo;Owner: RW, Group: None, Others: None\u0026rdquo;). Verification: Run terraform apply and verify that the actual API token is not visible in your terminal output. 🛠️ Step-by-Step Instructions Step 1: Create a new file named challenge.tf. Step 2: Define the local_sensitive_file resource block. Step 3: Run terraform init to install the local provider. Step 4: Run terraform apply and observe the \u0026ldquo;Sensitive Value\u0026rdquo; redaction. Step 5: Check the file permissions in your terminal: 1 ls -l customer_api_key.txt ❓ Self-Check Questions Did the terminal show SECRET_API_TOKEN_999 during the apply? (It shouldn\u0026rsquo;t!) What happens if you try to use a standard local_file resource instead? Can you find the secret token inside the terraform.tfstate file? (Open it and search!) 💡 Hint If you get stuck on the permissions, remember the \u0026ldquo;Owner Only\u0026rdquo; rule:\nRead (4) + Write (2) = 6 No Access = 0 Resulting Code: \u0026quot;0600\u0026quot; 💡 Key Learnings ✅ Redaction: Terraform protects your screen/logs, but not your .tfstate file. State files still store this in plain text!\n✅ Pathing: Used ${path.module} to ensure the file is created relative to the configuration folder.\n✅ Automation: Learned how to enforce security compliance directly through Infrastructure as Code (IaC).\n🚀 Resources \u0026amp; Links 📂 View Challenge 01 Files — Explore the HCL configuration and solution ","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/01-local-sensitive-file/","summary":"Mastering file permissions and console redaction in Terraform.","title":"01 | Securing Data with Local Sensitive Files"},{"content":" 🧠 The Concept: Logical Providers Most Terraform providers (like AWS or Azure) manage physical infrastructure. However, Logical Providers like random exist entirely within the Terraform state.\nThey are used to:\nAvoid Name Collisions: Ensuring S3 buckets or VMs have unique IDs. Dynamic Testing: Generating temporary names for lab environments. 📋 The Challenge The objective is to use the random_pet resource to generate a unique, human-readable name.\nRequirements: Provider: Use the random provider. Length: The pet name should consist of exactly 2 words. Separator: Use a hyphen (-) between words (e.g., fancy-cat). 🚀 The Solution You can view the full configuration in my Challenge 02 Repository Folder.\n1 2 3 4 5 # The Random Provider is automatically downloaded during \u0026#39;terraform init\u0026#39; resource \u0026#34;random_pet\u0026#34; \u0026#34;my-pet\u0026#34; { length = 2 separator = \u0026#34;-\u0026#34; } 🔍 How to Verify the Result Since the random_pet resource lives only in the Terraform State, you won\u0026rsquo;t see a new file in your folder. Use these commands to see what Terraform \u0026ldquo;breathed into existence.\u0026rdquo;\n1. Using Terraform Show This is the easiest way to see the attributes of your generated pet:\n1 2 bash terraform show Expected output random_pet.my-pet: 1 2 3 4 5 resource \u0026#34;random_pet\u0026#34; \u0026#34;my-pet\u0026#34; { id = \u0026#34;rugged-python\u0026#34; length = 2 separator = \u0026#34;-\u0026#34; } After running terraform apply in the lesson, you noticed a new file appeared in your directory: terraform.tfstate.\nIn the DevOps world, this file is the Source of Truth. If it isn\u0026rsquo;t in the state file, as far as Terraform is concerned, it doesn\u0026rsquo;t exist. Let\u0026rsquo;s look at the \u0026ldquo;DNA\u0026rdquo; of the random_pet we just birthed.\nThe Raw State File Here is exactly what Terraform recorded when we created our pet:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 { \u0026#34;version\u0026#34;: 4, \u0026#34;terraform_version\u0026#34;: \u0026#34;1.14.5\u0026#34;, \u0026#34;serial\u0026#34;: 1, \u0026#34;lineage\u0026#34;: \u0026#34;ae315c74-4a90-5b13-320b-a7bd70485c8a\u0026#34;, \u0026#34;outputs\u0026#34;: {}, \u0026#34;resources\u0026#34;: [ { \u0026#34;mode\u0026#34;: \u0026#34;managed\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;random_pet\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;my-pet\u0026#34;, \u0026#34;provider\u0026#34;: \u0026#34;provider[\\\u0026#34;registry.terraform.io/hashicorp/random\\\u0026#34;]\u0026#34;, \u0026#34;instances\u0026#34;: [ { \u0026#34;schema_version\u0026#34;: 0, \u0026#34;attributes\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;careful-goat\u0026#34;, \u0026#34;keepers\u0026#34;: null, \u0026#34;length\u0026#34;: 2, \u0026#34;prefix\u0026#34;: null, \u0026#34;separator\u0026#34;: \u0026#34;-\u0026#34; }, \u0026#34;sensitive_attributes\u0026#34;: [], \u0026#34;identity_schema_version\u0026#34;: 0 } ] } ], \u0026#34;check_results\u0026#34;: null } Breaking Down the Metadata 1. The Versioning (serial \u0026amp; lineage) Serial (1): This is the version number of your state. Every time you run apply and something changes, this number increments. It’s a ledger of changes. Lineage: This unique UUID is assigned when the state is first created. Even if you rename your project, this ID stays the same, ensuring Terraform knows it\u0026rsquo;s still looking at the same \u0026ldquo;family tree.\u0026rdquo; 2. The Resource Block This is where Terraform maps your code to reality.\nType (random_pet): Matches the resource block in your .tf file. Name (my-pet): This is the local name we gave it. Attributes: This is the most important part! It contains the id (careful-goat). The \u0026ldquo;Aha!\u0026rdquo; Moment: When you run terraform plan, Terraform isn\u0026rsquo;t checking your cloud provider first. It\u0026rsquo;s checking this JSON file to see what it thinks should be there.\nWhat happens if we change the code? If you go back to your main.tf and change the length from 2 to 3, Terraform will:\nRead this state file. See that the current length is 2. Compare it to your code (3). Realize it needs to Destroy the careful-goat and Create a new 3-word pet. ⚠️ A Word of Warning The state file often contains sensitive information (like passwords or API keys) in plain text. Since we are just using random_pet, it\u0026rsquo;s safe. But as we move to AWS or Azure, never commit this file to GitHub.\n🛠️ The \u0026ldquo;Gotcha\u0026rdquo;: Idempotency \u0026amp; Immutability A core Terraform concept is Idempotency. If you run terraform apply again, the name will NOT change.\nWhy? Because the name is now locked in the terraform.tfstate file. To get a new name, you must force a replacement:\n1 terraform apply -replace=\u0026#34;random_pet.my-pet\u0026#34; 📚 Official Resources \u0026amp; Documentation To deepen your understanding of the concepts used in this lab, I recommend reviewing the following official HashiCorp documentation:\nResource Description Random Provider Official documentation for the Logical Random provider. random_pet (Resource) Detailed syntax and attributes for the pet resource. Terraform State How Terraform tracks the relationship between your code and resources. Command: show Official CLI reference for inspecting state and plan files. ","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/02-random-pet-name/","summary":"\u003ch2 id=\"-the-concept-logical-providers\"\u003e🧠 The Concept: Logical Providers\u003c/h2\u003e\n\u003cp\u003eMost Terraform providers (like AWS or Azure) manage physical infrastructure. However, \u003cstrong\u003eLogical Providers\u003c/strong\u003e like \u003ccode\u003erandom\u003c/code\u003e exist entirely within the Terraform state.\u003c/p\u003e\n\u003cp\u003eThey are used to:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eAvoid Name Collisions:\u003c/strong\u003e Ensuring S3 buckets or VMs have unique IDs.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eDynamic Testing:\u003c/strong\u003e Generating temporary names for lab environments.\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"-the-challenge\"\u003e📋 The Challenge\u003c/h2\u003e\n\u003cp\u003eThe objective is to use the \u003ccode\u003erandom_pet\u003c/code\u003e resource to generate a unique, human-readable name.\u003c/p\u003e\n\u003ch3 id=\"requirements\"\u003eRequirements:\u003c/h3\u003e\n\u003col\u003e\n\u003cli\u003e\u003cstrong\u003eProvider:\u003c/strong\u003e Use the \u003ccode\u003erandom\u003c/code\u003e provider.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eLength:\u003c/strong\u003e The pet name should consist of exactly \u003cstrong\u003e2 words\u003c/strong\u003e.\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eSeparator:\u003c/strong\u003e Use a \u003cstrong\u003ehyphen (-)\u003c/strong\u003e between words (e.g., \u003ccode\u003efancy-cat\u003c/code\u003e).\u003c/li\u003e\n\u003c/ol\u003e\n\u003ch2 id=\"-the-solution\"\u003e🚀 The Solution\u003c/h2\u003e\n\u003cp\u003eYou can view the full configuration in my \u003ca href=\"https://github.com/nl-santhosh-kumar/terraform-associate-labs/tree/main/02-random-pet\"\u003eChallenge 02 Repository Folder\u003c/a\u003e.\u003c/p\u003e","title":"02 | Generating Dynamic Resources with Random Pet"},{"content":" 🧠 The Concept: Input vs. Output In the 004 exam, HashiCorp expects you to know how data flows through a Terraform configuration.\nInput Variables: Like function arguments. They allow users to customize the deployment without touching the main code. Output Values: Like return values. They highlight important information (like an IP address) after an apply. 📋 The Challenge The goal is to refactor our \u0026ldquo;Random Pet\u0026rdquo; lab. Instead of hardcoding the length of the pet name, we will use a variable.\nRequirements: Define a variable for prefix. Define a variable for separator. Output the final generated name to the terminal. 🚀 The Solution View the full code here: Challenge 03 Repo\n1. Defining the Input Instead of hardcoding the prefix \u0026ldquo;Mrs\u0026rdquo;, let\u0026rsquo;s create a variable. This allows other team members to use your code without editing the logic.\n1 2 3 4 5 variable \u0026#34;prefix\u0026#34; { type = string default = \u0026#34;Mrs\u0026#34; description = \u0026#34;Prefix of the pet\u0026#34; } 2. Use the variable 1 2 3 4 5 6 7 8 9 10 11 # main.tf resource \u0026#34;random_pet\u0026#34; \u0026#34;my-random-pet\u0026#34; { prefix = var.prefix separator = \u0026#34;.\u0026#34; } # outputs.tf output \u0026#34;my-random-pet-name\u0026#34; { value = random_pet.my-random-pet description = \u0026#34;The full name of the random generated pet\u0026#34; } 3. The Output Anatomy When you run terraform plan then you will see:\n1 2 3 4 5 6 7 my-random-pet-name = { \u0026#34;id\u0026#34; = \u0026#34;Mrs.awaited.bullfrog\u0026#34; \u0026#34;keepers\u0026#34; = tomap(null) /* of string */ \u0026#34;length\u0026#34; = 2 \u0026#34;prefix\u0026#34; = \u0026#34;Mrs\u0026#34; \u0026#34;separator\u0026#34; = \u0026#34;.\u0026#34; } Why does it look like that? You likely used an output that exported the entire resource object. While useful for debugging, it\u0026rsquo;s \u0026ldquo;noisy.\u0026rdquo; In the output above:\ntomap(null): This is Terraform\u0026rsquo;s way of saying, \u0026ldquo;This attribute is a Map type, but it\u0026rsquo;s currently empty.\u0026rdquo;\n/* of string */: This is a hint that if you did provide values, they would need to be strings.\n4. Refining the Output When you run terraform apply then you will see:\n1 2 3 4 5 6 7 my-random-pet-name = { \u0026#34;id\u0026#34; = \u0026#34;Mrs.awaited.bullfrog\u0026#34; \u0026#34;keepers\u0026#34; = tomap(null) /* of string */ \u0026#34;length\u0026#34; = 2 \u0026#34;prefix\u0026#34; = \u0026#34;Mrs\u0026#34; \u0026#34;separator\u0026#34; = \u0026#34;.\u0026#34; } 🧠 Knowledge Check Test your understanding of Terraform State and Outputs before moving to the next lesson.\n⚠️ Quiz data missing.\n📚 004 Exam: Official Documentation Reference To master the variables and outputs section of the Terraform Associate (004) exam, I utilized the following official resources:\n1. Variables \u0026amp; Types Input Variables Overview: Covers how to define variables, use default values, and set descriptions. Type Constraints: Crucial for 004. Explains the difference between primitive types (string, number, bool) and complex types (list, map, object). 2. The Data Flow Output Values: Documentation on how to expose information about your infrastructure to the command line or other configurations. Variable Precedence: A favorite 004 exam topic. This page explains which value \u0026ldquo;wins\u0026rdquo; when a variable is defined in multiple places (e.g., .tfvars vs environment variables). 3. CLI Interaction Command: output: How to extract specific values from your state file after a successful apply. ","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/03-variables-and-outputs/","summary":"Moving away from hardcoded values to dynamic, variable-driven configurations.","title":"03 | Making Code Reusable with Variables"},{"content":" 🧠 The Concept: Input vs. Output While standard functions like timestamp() change every single time you run terraform plan, time_static captures a moment in time and locks it into your state file until you explicitly tell it to change.\nWhy people use it The most common use case is for tagging or naming resources with a \u0026ldquo;Created At\u0026rdquo; date. Without time_static, if you tagged an AWS instance with timestamp(), Terraform would try to update that tag on every single run because the time is always different.\ntime_static stays put, keeping your plan clean.\n🚀 Key Features Persistence: Once created, the rfc3339, unix, and other attributes remain constant.\nTriggers: You can force the time to \u0026ldquo;refresh\u0026rdquo; by using the triggers argument. If any value in the trigger map changes, the resource is recreated with a new current timestamp.\nAttributes: It provides various formats out of the box:\nrfc3339 (e.g., 2026-03-16T10:26:00Z)\nunix (Epoch seconds)\nIndividual components like year, month, day.\nQuick Example 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 resource \u0026#34;time_static\u0026#34; \u0026#34;ami_update\u0026#34; { # This timestamp only changes if the AMI ID changes triggers = { ami_id = data.aws_ami.example.id } } resource \u0026#34;aws_instance\u0026#34; \u0026#34;server\u0026#34; { ami = data.aws_ami.example.id instance_type = \u0026#34;t3.micro\u0026#34; tags = { # This tag will only update when the time_static resource is triggered LastAmiUpdate = time_static.ami_update.rfc3339 } } 📋 The Challenge In this challenge, you will create a local text file that records its own birth certificate. The goal is to prove that Terraform can \u0026ldquo;remember\u0026rdquo; the past without drifting every time you run a command.\nRequirements: Create a time static Create a static file Name of the file: vault.txt. Content: This file was sealed on: (eg: This file was sealed on: 2026-03-16T09:57:46Z) 🚀 Try it Yourself! Don\u0026rsquo;t want to install Terraform? No problem. Click the button below to launch a free, pre-configured environment in your browser:\n🚀 Launch Lab: Time Static The Solution View the full code here: Challenge 04 Repo\n🧠 Knowledge Check Test your understanding of Terraform Time Static before moving to the next lesson.\n⚠️ Quiz data missing.\n📚 004 Exam: Official Documentation Reference To master the variables and outputs section of the Terraform Associate (004) exam, I utilized the following official resources:\nTime Provider: Documentation on Time Provider ","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/04-static-time/","summary":"Learn how to use the time_static resource to create persistent timestamps and avoid unnecessary resource drift.","title":"04 | Use the time_static resource to create persistent timestamps"},{"content":" 🧠 The Core Concept: The \u0026ldquo;Trigger\u0026rdquo; The primary purpose of time_rotating is to force other resources to recreate themselves. It’s a dependency anchor.\nWhen the rotation period expires (e.g., 30 days), the time_rotating resource is marked as \u0026ldquo;changed\u0026rdquo; in the state file. Any resource that references its ID will also be forced to update or replace.\nA Real-World Implementation Here is how you actually use it to rotate a database password every 30 days:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # 1. Define the schedule resource \u0026#34;time_rotating\u0026#34; \u0026#34;db_rotation_period\u0026#34; { rotation_days = 30 } # 2. Generate a password that \u0026#34;depends\u0026#34; on the timer resource \u0026#34;random_password\u0026#34; \u0026#34;db_pass\u0026#34; { length = 16 special = true # This is the magic line: keepers = { rotation_id = time_rotating.db_rotation_period.id } } # 3. Use the local provider to save the new password resource \u0026#34;local_sensitive_file\u0026#34; \u0026#34;db_config\u0026#34; { content = \u0026#34;DB_PASSWORD=${random_password.db_pass.result}\u0026#34; filename = \u0026#34;${path.module}/.env\u0026#34; } Key Attributes to Know rotation_days / rotation_hours / rotation_minutes: Defines how often the \u0026ldquo;alarm\u0026rdquo; goes off.\nrfc3339: The output timestamp of the last rotation.\nid: A unique value that changes every time a rotation occurs. This is what you usually pass into the keepers block of other resources.\n","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/05-time-rotating/","summary":"Learn how to use the time_rotating to rotate a database password every 30 days","title":"05 | Rotate a database password every 30 days with Time Rotating"},{"content":" Infrastructure Connective Tissue 🧬 In Terraform, expressions are the logic layer that turns static configuration into a dynamic, intelligent infrastructure-as-code machine. Think of them as the \u0026ldquo;connective tissue\u0026rdquo; between your hardcoded values and your final cloud resources.\n🛠️ 1. Types \u0026amp; Literals The building blocks of HCL (Hashicorp Configuration Language) are straightforward but strict.\n🔡 Strings: \u0026quot;hello\u0026quot; (Always use double quotes). 🔢 Numbers: 15 or 3.14. 🔘 Booleans: true or false. 📜 Lists: [\u0026quot;us-east-1a\u0026quot;, \u0026quot;us-east-1b\u0026quot;] (Ordered collection of the same type). 🗺️ Maps: { \u0026quot;env\u0026quot; = \u0026quot;prod\u0026quot;, \u0026quot;dept\u0026quot; = \u0026quot;labs\u0026quot; } (Key-value pairs for lookups). 🔗 2. String Interpolation \u0026amp; Templates This is how you inject variables directly into your text strings or scripts.\n1 2 3 4 5 6 resource \u0026#34;aws_instance\u0026#34; \u0026#34;app\u0026#34; { tags = { # Dynamically naming the server based on a variable Name = \u0026#34;server-${var.environment}\u0026#34; } } Pro Tip: You can also use here docs (\u0026laquo;-EOT) for multi-line strings, which is great for cloud-init scripts.\n⚖️ 3. Conditional Expressions (Ternary) Terraform uses the classic ternary syntax: condition ? true_val : false_val. This is the bread and butter of environment-specific logic.\n1 2 # Logic: If environment is \u0026#39;prod\u0026#39;, use m5.large; otherwise, use t3.micro. instance_type = var.env == \u0026#34;prod\u0026#34; ? \u0026#34;m5.large\u0026#34; : \u0026#34;t3.micro\u0026#34; 🔄 4. for Expressions This is where Terraform gets its power. You can transform one collection (like a list or map) into another.\nTo create a list: [for s in var.list : upper(s)] To create a map: {for k, v in var.map : k =\u0026gt; upper(v)} 🚀 Try it Yourself! To make this practical without requiring a cloud account, we will use a Local File use case. This scenario simulates creating configuration files for different \u0026ldquo;microservices\u0026rdquo; using a Map of Objects. This example perfectly demonstrates the Expression Hierarchy:\nVariables/Locals (The data)\nFor Expression (The transformation)\nMeta-arguments (for_each)\nResource Attributes (The final output)\nHow to run it: Don\u0026rsquo;t want to install Terraform? No problem. Click the button below to launch a free, pre-configured environment in your browser:\n🚀 Launch Lab: Expressions Run terraform init (this downloads the local provider).\nRun terraform apply.\nCheck your folder—you\u0026rsquo;ll see a new /configs directory with three files inside!\n","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/06-expressions/","summary":"Learn What are the Infrastructure Connective Tissue","title":"06 | 🏗️ Master Terraform: The Expression Hierarchy"},{"content":" Conditional Statements In Terraform Conditional statements in Terraform are the \u0026ldquo;if-then-else\u0026rdquo; of your infrastructure. Unlike many programming languages that use if blocks, Terraform primarily uses the ternary operator for simple logic and the count meta-argument for resource-level logic.\n1. The Ternary Operator The most common way to use a conditional is the ternary syntax. It evaluates a boolean expression and returns one of two values.\nSyntax: condition ? value_if_true : value_if_false Example: Suppose you want to set the instance type based on the environment:\n1 2 3 4 5 6 7 8 variable \u0026#34;environment\u0026#34; { type = string default = \u0026#34;dev\u0026#34; } locals { instance_size = var.environment == \u0026#34;prod\u0026#34; ? \u0026#34;t3.large\u0026#34; : \u0026#34;t3.micro\u0026#34; } 2. Conditional Resource Creation (The Count Trick) Terraform doesn\u0026rsquo;t have a native if resource {} block. Instead, we use the count meta-argument. If count is set to 1, the resource is created; if it is 0, it is skipped.\nExample: Only create a backup bucket if the enable_backups variable is true:\n1 2 3 4 5 6 7 8 9 10 11 variable \u0026#34;enable_backups\u0026#34; { type = bool default = true } resource \u0026#34;aws_s3_bucket\u0026#34; \u0026#34;backup\u0026#34; { # If true, count is 1 (create). If false, count is 0 (skip). count = var.enable_backups ? 1 : 0 bucket = \u0026#34;my-app-backup-storage\u0026#34; } 3. Dynamic Block (Conditional Logic) Sometimes you don\u0026rsquo;t want to skip an entire resource, just a specific configuration block inside it (like an extra security rule or a tag).\nYou can combine for_each with a conditional list to achieve this:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 resource \u0026#34;aws_security_group\u0026#34; \u0026#34;example\u0026#34; { name = \u0026#34;example-sg\u0026#34; # Only create this ingress block if \u0026#39;is_public\u0026#39; is true dynamic \u0026#34;ingress\u0026#34; { for_each = var.is_public ? [1] : [] content { from_port = 80 to_port = 80 protocol = \u0026#34;tcp\u0026#34; cidr_blocks = [\u0026#34;0.0.0.0/0\u0026#34;] } } } Note In Terraform, both sides of a ternary operator must be the same type. You cannot return a string if true and a number if false. If you find yourself hitting type errors, you might need to use the tostring() or tonumber() functions to keep things consistent.\nTry it for Yourself! To make this practical without requiring a cloud account, we will use a Local File use case. Imagine you are building a module for a Database Server. Your goal is to make the security group smart enough to handle two different scenarios based on a single variable.\nThe Scenario You have a variable called is_internal_only.\nIf is_internal_only is true: The database should only open Port 5432 (PostgreSQL) for internal traffic. If is_internal_only is false: The database is considered \u0026ldquo;Legacy\u0026rdquo; and needs to open Port 5432 (PostgreSQL) AND Port 3306 (MySQL) for external migrations. Your Goal:\nWrite a dynamic \u0026ldquo;ingress\u0026rdquo; block that uses a ternary operator in the for_each to switch between these two lists of ports.** ","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/07-conditional-statements/","summary":"Learn How to use conditional statements in terraform","title":"07 | Conditional Statements in Terraform"},{"content":" The Professional Way to Host Static Content on AWS using Terraform In an idea world, deploying a static website content into a S3 bucket, clicking \u0026ldquo;Make Public\u0026rdquo; and share the link. In that world, there are no security audit, no latency issues for user across the globe and no such thing as an \u0026ldquo;unsecured connection\u0026rdquo; warning in the browser.\nIn real time environment, \u0026ldquo;Public S3 Buckets\u0026rdquo; are often one way ticket to security meeting you do not want to attend. For professional grade deployment, you need:\nZero Public Access: Keeping our storage origin locked down and private. Global Speed: Serving content from the edge, closer to the user. Enforced Encryption: Redirecting every visitor to https:// automatically. In this post, I’m moving away from basic Terraform syntax and diving into a Real-Time Scenario: architecting a secure, private S3 origin fronted by Amazon CloudFront using Origin Access Control (OAC).\nThe Architecture To solve these real-world requirements, we aren\u0026rsquo;t just deploying one resource. We are building a \u0026ldquo;handshake\u0026rdquo; between:\nThe Vault (S3): Where our files live, completely private. The Gatekeeper (CloudFront OAC): The specialized permission that lets only our CDN inside. The Messenger (CloudFront Distribution): The service that delivers our site via HTTPS. The Implementation Amazon Simple Storage Service (Amazon S3) Amazon Simple Storage Service (Amazon S3) is an object storage service offering industry-leading scalability, data availability, security, and performance. Millions of customers of all sizes and industries store, manage, analyze, and protect any amount of data for virtually any use case, such as data lakes, cloud-native applications, and mobile apps. With cost-effective storage classes and easy-to-use management features, you can optimize costs, organize and analyze data, and configure fine-tuned access controls to meet specific business and compliance requirements. Lets create a S3 bucket in terraform 1 2 3 4 5 6 7 8 9 resource \u0026#34;aws_s3_bucket\u0026#34; \u0026#34;s3_bucket\u0026#34; { bucket = \u0026#34;my-static-bucket\u0026#34; # Name of the bucket. If omitted, Terraform will assign a random, unique name tags = { Name = \u0026#34;My bucket\u0026#34; Environment = \u0026#34;Dev\u0026#34; } } Remember:\nBucket name is unique Bucket name must be lowercase and less than or equal to 63 characters in length. force_destroy - (optional, default = false): Boolean that indicates all object (including any locked objects) should be deleted from the bucket when the bucket is destroyed so that the bucket can be destroyed without error. Now that, we have a S3 bucket, lets understand about the blocking public access\nLocking the Front Door | Make the S3 Bucket Private Making the S3 bucket private is good but to make it impossible to be public makes it awesome. Even if someone tries to change it to public, the resource will override and block it.\n📂 Read More About S3 Public Access Block 📂 Read more about blocking public access to your amazon s3 1 2 3 4 5 6 7 8 9 ## Create a resource to restrict public access to the S3 bucket resource \u0026#34;aws_s3_bucket_public_access_block\u0026#34; \u0026#34;static_site_public_access_block\u0026#34; { bucket = aws_s3_bucket.static_site.id # Reference the S3 bucket created above block_public_acls = true # Block public ACLs (Access Control Lists) to prevent unauthorized access block_public_policy = true # Block public bucket policies to prevent unauthorized access ignore_public_acls = true # Ignore public ACLs to prevent unauthorized access restrict_public_buckets = true # Restrict public bucket policies to prevent unauthorized access } Modern Handshake (CloudFront OAC) Amazon CloudFront is a global content delivery network that securely delivers applications, websites, videos, and APIs to viewers across the globe in milliseconds. Leverage CloudFront’s origin access identity (OAI) to secures S3 origin access to CloudFront only. When using OAC, a typical request and response workflow will be:\nA client sends HTTP or HTTPS requests to CloudFront CloudFront edge locations receive the requests. If the requested object is not already cached, CloudFront signs the requests using OAC signing protocol S3 origins authenticate, authorize, or deny the requests. When configuring OAC, “Do not sign requests”, “Sign requests”, and sign requests. For this case, do not choose, Do not override authorization header. Create a Cloudfront Distribution Below are the details required to create a cloud front distribution\nChoose a plan \u0026amp; Distribution Type Distribution Name, Type and Route 53 Integration Defining the Origin S3 Bucket \u0026amp; Path Settings (Allow Private S3 bucket access to cloudfront) Security Handshake Origin Settings Cache \u0026amp; Behavior Settings Web Application Firewall 1. Choose a plan and Distribution Type in cloud front Let\u0026rsquo;s create a s3 distribution and configure it\nOrigin Details Domain Name: Use the regional domain name of the S3 bucket as the origin Origin Id: S3 Origin ID Origin Access Control ID: Id of the Origin Access Control to set the bucket only accessible via the access control id. Since we need the OAC ID (Origin Access Control), lets create it first.\nCreate OAC 1 2 3 4 5 6 resource \u0026#34;aws_cloudfront_origin_access_control\u0026#34; \u0026#34;default_oac\u0026#34; { name = \u0026#34;default-oac\u0026#34; signing_behavior = \u0026#34;always\u0026#34; signing_protocol = \u0026#34;sigv4\u0026#34; origin_access_control_origin_type = \u0026#34;s3\u0026#34; } Now, lets use this in the S3 Distribution\n1 2 3 4 5 6 7 8 9 10 11 12 resource \u0026#34;aws_cloudfront_distribution\u0026#34; \u0026#34;s3_distribution\u0026#34; { origin { domain_name = aws_s3_bucket.static_site.bucket_regional_domain_name # Use the regional domain name of the S3 bucket as the origin origin_id = local.s3_origin_id # Use a local variable for the origin ID origin_access_control_id = aws_cloudfront_origin_access_control.default_oac.id # Reference the OAC created above to restrict access to the S3 bucket } enabled = true is_ipv6_enabled = true comment = \u0026#34;CloudFront distribution for my static site\u0026#34; default_root_object = \u0026#34;index.html\u0026#34; } Define Cache Behavior As part of defining cache behavior,\nTarget Origin ID : should point to S3 Origin ID Viewer Protocol Policy: Redirect HTTP request to HTTPS request Allowed Methods: Configure which methods are allowed to cache Cache Methods: Configure what requests can be cached Along with this, configure Forwarded Values, such as query string and cookies. Based on the application setup you can determine these values.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 default_cache_behavior { target_origin_id = local.s3_origin_id # Reference the origin ID defined above viewer_protocol_policy = \u0026#34;redirect-to-https\u0026#34; # Redirect HTTP requests to HTTPS allowed_methods = [\u0026#34;GET\u0026#34;, \u0026#34;HEAD\u0026#34;] # Allow only GET and HEAD methods for caching cached_methods = [\u0026#34;GET\u0026#34;, \u0026#34;HEAD\u0026#34;] # Cache only GET and HEAD requests forwarded_values { query_string = false # Do not forward query strings to the origin cookies { forward = \u0026#34;none\u0026#34; # Do not forward cookies to the origin } } } This goes part of the Cloud Front Distribution.\nAdditionally you can define restriction on the distribution such as Geo Restriction,\n1 2 3 4 5 restrictions { geo_restriction { restriction_type = \u0026#34;none\u0026#34; # No geographic restrictions on content access } } A Viewer Certificate is the configuration block in CloudFront that determines how your distribution handles SSL/TLS encryption for your end users. When a user visits your website (e.g., https://www.yourdomain.com), the Viewer Certificate is what provides the digital handshake to ensure the connection is secure and that the user is actually talking to your server, not an imposter. Why is it needed? In the world of modern web hosting, HTTPS is non-negotiable. Browsers will flag your site as \u0026ldquo;Not Secure\u0026rdquo; without it. The viewer_certificate block tells AWS which SSL certificate to use to prove your site\u0026rsquo;s identity.\nOption Use Case Cost CloudFront Default Certificate Used for the default AWS domain (e.g., d1234.cloudfront.net) Free ACM Certificate Used for custom domains (e.g., www.example.com). Managed via AWS Certificate Manager. Free (for public certs) 1 2 3 viewer_certificate { cloudfront_default_certificate = true } Custom Domain Setup with Route 53 When you create a CloudFront distribution, AWS gives you a random, autogenerated domain like d1234567890.cloudfront.net. An Alias allows you to use your own professional domain, such as www.yourcompany.com or assets.example.com, to serve that same content.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 # Create a Route 53 record to point the custom domain to the CloudFront distribution resource \u0026#34;aws_route53_record\u0026#34; \u0026#34;cloudfront\u0026#34; { # Iterates through the aliases you defined in your CloudFront resource for_each = aws_cloudfront_distribution.s3_distribution.aliases zone_id = data.aws_route53_zone.my_domain_zone.zone_id name = each.value # Use \u0026#34;A\u0026#34; for an Alias record pointing to a CloudFront distribution type = \u0026#34;A\u0026#34; alias { name = aws_cloudfront_distribution.s3_distribution.domain_name zone_id = aws_cloudfront_distribution.s3_distribution.hosted_zone_id evaluate_target_health = false } } Configuring OAC when creating a new CloudFront distribution Once the distribution is successfully created, you must update the s3 bucket policy. Before that, lets create OAC with terraform.\n📂 Read More About Cloudfront Distribution 📂 Cloudfront Distribution | Developer Guide Note: CloudFront distributions take about 15 minutes to reach a deployed state after creation or modification. During this time, deletes to resources will be blocked. If you need to delete a distribution that is enabled and you do not want to wait, you need to use the retain_on_delete flag.\n","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/posts/static-site-with-cloud-front/","summary":"Learn to protect S3 with cloud front with the help of terraform","title":"The Professional Way to Host Static Content on AWS using Terraform"},{"content":"☁️ My Infrastructure Journey I am currently deep-diving into the world of Cloud Engineering and Infrastructure as Code (IaC). My primary focus is mastering Terraform to build reliable, scalable, and secure environments.\n🎯 The Goal: Terraform Associate (003) This site serves as my living lab notebook. Every challenge you see here is a step toward my HashiCorp certification. I don\u0026rsquo;t just write code; I document the \u0026ldquo;why\u0026rdquo; behind every resource I provision.\n🛠️ Core Competencies IaC: Terraform (HCL), State Management, Provider Configuration. Providers: Local, Random, AWS (in progress). Tooling: Git/GitHub, Linux CLI, Hugo. 🤝 Let\u0026rsquo;s Connect I\u0026rsquo;m always open to discussing best practices or collaborating on labs! LinkedIn | GitHub\n","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/about/","summary":"The journey of an aspiring DevOps Engineer.","title":"About Me"},{"content":"","permalink":"https://nl-santhosh-kumar.github.io/terraform-associate-labs/quiz/","summary":"","title":"Practice Exam"}]