Terraform Fundamentals - Resources
Learn what are resources in Terraform and why they are at the heart of any Terraform configuration.
Up until now, we have set the stage by learning about providers — today, we dive into what truly defines the heart of any Terraform configuration: Resources.
In Terraform, a resource represents a single piece of infrastructure that Terraform will manage. This could be anything — an EC2 instance, an S3 bucket, a DNS record, a GitHub repository, a database, and much more. If you think of Terraform like a LEGO set, resources are the individual bricks you stack together to build your final infrastructure.
Follow my journey of 100 Days of Red Team on WhatsApp, Telegram or Discord.
According to the official Terraform documentation, resources are the most important element in the Terraform language — they describe the objects Terraform will create, update, or destroy.
Let’s break this down.
What Exactly is a Terraform Resource?
In simple words, a resource block tells Terraform:
"Here’s something I want you to manage, and here’s how I want it configured."
When we apply our Terraform code:
If the resource doesn’t exist yet, Terraform creates it.
If the resource exists but doesn’t match our configuration, Terraform updates it.
If we remove the resource from code, Terraform destroys it.
Every resource is linked to a specific provider. For example:
aws_instance
comes from the AWS provider.azurerm_virtual_machine
comes from the Azure provider.github_repository
comes from the GitHub provider.
Thus, when we use resources, we’re instructing providers on what exactly to build.
Common resource types
Now that we know what a resource is, let’s look at some of the most common AWS resources we’ll work with in Terraform. These resources cover the fundamental categories of cloud infrastructure: compute, storage, networking, security, and identity.
Since we have been working with the AWS provider so far, the examples below focus on AWS-specific resources. However, the same concept applies to other providers like Azure, Google Cloud, GitHub, and even SaaS platforms — they all define resources that can be created and managed through Terraform.
🔗 Tip: You can explore all available resources for every provider in the official Terraform Registry > Select a provider > Click on Documentation > in the panel on left click on the arrow to expand the drop-down menu of the respective element. For AWS specifically, check the AWS Provider Documentation to see the full list of resource types you can use.
Here are some essential AWS resource types:
aws_instance
— Launches a virtual machine (EC2 instance) in AWS.
Useful when we want to run servers for websites, redirectors, C2 infrastructure, or any kind of cloud-hosted application.aws_s3_bucket
— Creates a storage bucket in AWS S3.
Commonly used to store files, host static websites, or stage payloads and data for cloud operations.aws_iam_user
— Creates a new IAM user account.
Useful for creating users programmatically for automation purposes, or for short-lived access during red team operations.aws_security_group
— Defines a firewall rule set around your EC2 instances or other resources.
Useful for controlling which ports and IP addresses can access your infrastructure.aws_vpc
— Provisions a Virtual Private Cloud (VPC).
This allows you to isolate your cloud resources into private networks — an important concept for both security and stealth.aws_route53_record
— Manages DNS records within AWS Route 53.
You can create, modify, or delete domain records, which is critical for setting up phishing domains, redirectors, or C2 infrastructure.aws_lambda_function
— Deploys serverless functions in AWS Lambda.
A growing trend in both cloud development and offensive operations is to leverage serverless compute for stealthy, ephemeral tasks.
This is just the beginning. Terraform’s AWS provider supports hundreds of resource types, giving you almost total control over everything in your cloud environment — all through code.
🧠 Visualization Tip:
As you think about Terraform resources, picture yourself:
Signing a lease for a cloud apartment (EC2 instance),
Renting a storage locker for your files (S3 bucket),
Handing someone a security badge and keys (IAM user).
Each action creates a real-world object — and Terraform just automates these actions for you. Keeping this mental model in mind will make writing infrastructure code feel much more intuitive.
Anatomy of a resource block
Here’s the typical structure of a resource block:
resource "<PROVIDER>_<TYPE>" "<NAME>" {
# Arguments (settings) go here
}
For example, a basic EC2 instance might look like this:
resource "aws_instance" "my_web_server" {
ami = "ami-0c55b159cbfafe1f0"
instance_type = "t2.micro"
}
Breaking this down:
aws_instance
is the resource type.my_web_server
is the local name you give to reference it elsewhere.Inside the braces
{}
, we set the configuration arguments specific to that resource.
Terraform tracks this resource in its state file and ensures its real-world counterpart matches the defined configuration.
Following are the resource blocks from the Terraform Hello World project:
resource "aws_instance" "hello" {
ami = var.ami_id
instance_type = var.instance_type
user_data = <<-EOF
#!/bin/bash
echo 'ec2-user:YourSecurePassword123' | chpasswd
sed -i 's/PasswordAuthentication no/PasswordAuthentication yes/' /etc/ssh/sshd_config
systemctl restart sshd
EOF
vpc_security_group_ids = [aws_security_group.allow_ssh.id]
tags = {
Name = "TerraformHelloWorld"
}
}
resource "aws_security_group" "allow_ssh" {
name = "allow_ssh"
description = "Allow SSH from my IP"
vpc_id = data.aws_vpc.default.id
ingress {
description = "SSH"
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = [local.my_ip_cidr]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
Here’s another sample configuration that provisions a static website onto an S3 bucket:
Following permissions need to be added to the
TerraformEC2Access
IAM policy before using the following configuration:
"s3:CreateBucket",
"s3:PutBucketWebsite",
"s3:PutBucketAcl",
"s3:GetBucketAcl",
"s3:PutBucketPolicy",
"s3:GetObjectTagging",
"s3:GetObject",
"s3:PutObject",
"s3:DeleteObject",
"s3:DeleteBucketPolicy",
"s3:GetBucketWebsite",
"s3:GetBucketPublicAccessBlock",
"s3:GetBucketTagging",
"s3:GetBucketLogging",
"s3:GetReplicationConfiguration",
"s3:GetLifecycleConfiguration",
"s3:PutBucketPublicAccessBlock",
"s3:GetAccelerateConfiguration",
"s3:GetBucketRequestPayment",
"s3:GetBucketPolicy",
"s3:PutBucketPolicy",
"s3:ListBucket",
"s3:GetBucketVersioning",
"s3:GetBucketCORS",
"s3:DeleteBucket",
"s3:GetBucketObjectLockConfiguration",
"s3:GetEncryptionConfiguration",
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.0"
}
}
}
provider "aws" {
region = "us-east-1"
}
# Create the S3 bucket with static website hosting enabled
resource "aws_s3_bucket" "website_bucket" {
bucket = "my-terraform-website-12345" # <-- Change this to your unique bucket name
force_destroy = true
website {
index_document = "index.html"
error_document = "error.html"
}
}
resource "aws_s3_bucket_public_access_block" "example" {
bucket = aws_s3_bucket.website_bucket.id
block_public_acls = false
block_public_policy = false
ignore_public_acls = false
restrict_public_buckets = false
}
resource "aws_s3_bucket_policy" "public_policy" {
bucket = aws_s3_bucket.website_bucket.id
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Effect = "Allow"
Action = "s3:GetObject"
Resource = "arn:aws:s3:::${aws_s3_bucket.website_bucket.bucket}/*"
Principal = "*"
}
]
})
}
# Upload the index.html file
resource "aws_s3_object" "index" {
bucket = aws_s3_bucket.website_bucket.id
key = "index.html"
content = "<html><body><h1>My first website using Terraform</h1></body></html>"
content_type = "text/html"
}
# Upload an optional error.html file (optional but nice to have)
resource "aws_s3_object" "error" {
bucket = aws_s3_bucket.website_bucket.id
key = "error.html"
content = "<html><body><h1>404 Not Found</h1></body></html>"
content_type = "text/html"
}
# Output the website URL
output "website_url" {
value = aws_s3_bucket.website_bucket.website_endpoint
}
This Terraform configuration creates an S3 bucket with a static website hosting configuration, uploads a basic index page, and sets up a custom error page. Additionally, it sets up an S3 bucket policy that allows public read access to the contents of the bucket.
You can also find the project version of this configuration in 100 Days of Red Team GitHub repository.
⚠️ Caution: Allowing public access to an S3 bucket can expose sensitive data and introduce security risks. Only store non-sensitive content in public buckets, and always implement proper security controls when handling sensitive information.
TL;DR
- Resources are key building blocks for provisioning cloud infrastructure.
- Each provider makes available several resources that can be managed using a Terraform configuration.
Follow my journey of 100 Days of Red Team on WhatsApp, Telegram or Discord.