Terraform

Terraform is a Infrastructure as Code (IaC) tool that allows for the provisioning of on premise, or cloud resources. In this article we’re going to be looking at the security considerations when using Terraform.

Deploying Local Docker Images

Installing the Client Software

Terraform is packaged as a single binary, which can be downloaded here. In Kali Linux it can be installed with;

apt install terraform

Create a directory for you project, and add a main.tf configuration file with the following contents;

terraform {
  required_providers {
    docker = {
      source  = "kreuzwerker/docker"
      version = "~> 3.0.1"
    }
  }
}

provider "docker" {}

# Download the latest Kali Linux Docker image
resource "docker_image" "kalilinux" {
  name         = "kalilinux/kali-rolling:latest"
  keep_locally = false
}

# Run a container based on the image
resource "docker_container" "kalilinux" {
  image = docker_image.kalilinux.image_id
  name  = "KaliLinux"

  ports {
    internal = 80
    external = 8000
  }

# The Kali image will exit unless there is a long running command
  command = [
    "tail",
    "-f",
    "/dev/null"
  ]

}

The configuration just creates a local Kali Docker image. By default, Kali instances will terminate after running, so a long running command is required.

The following commands are then used to control the lifecycle of the deployed resources;

sudo terraform init                 # Ensure the provider is setup correctly
sudo terraform plan                 # Determine what changes will be made to the infrastructure
sudo terraform apply                # Deploy resources based on the main.tf configuration
sudo terraform destroy              # Destroy the resources

Run terraform init to ensure the docker provider is configured, then run terraform apply and you should be able to see the running docker instance;

┌──(kali㉿kali)-[~/terraform-docker-deploy]
└─$ sudo docker container ls                          
CONTAINER ID   IMAGE          COMMAND               CREATED          STATUS          PORTS                  NAMES
f2ef98729f47   71579488294a   "tail -f /dev/null"   34 seconds ago   Up 33 seconds   0.0.0.0:8000->80/tcp   KaliLinux
                                                                                                                                                                                                                                                                                            
┌──(kali㉿kali)-[~/terraform-docker-deploy]
└─$ sudo docker container exec -it KaliLinux  /bin/bash
┌──(root㉿f2ef98729f47)-[/]
└─#

Deploying AWS Resources

Now for a slightly more complex example. We’re going to setup an Kali EC2 instance, configure inbound security groups and install some tools.

Start by installing the AWS command line tools;

sudo apt install awscli

Then make sure it’s configured with you access key and secret key:

aws configure
AWS Access Key ID [None]: <AccessKey>
AWS Secret Access Key [None]: <SecretAccessKey>
Default region name [None]: eu-central-1
Default output format [None]:

Next, we need to search for the Kali Linux AMI image ID;

aws ec2 describe-images --filters "Name=name,Values=Kali*"
{
    "Images": [
        {
            "Architecture": "x86_64",
            "CreationDate": "2023-04-17T11:46:47.000Z",
            "ImageId": "ami-049539438c75e8e87",
            "ImageLocation": "aws-marketplace/Kali Linux on AWS-239c5ea9-c6ae-402b-8598-93ef545c46f3",
            "ImageType": "machine",
            "Public": true,
            "OwnerId": "679593333241",
            "PlatformDetails": "Linux/UNIX",
            "UsageOperation": "RunInstances",
            "ProductCodes": [
                {
                    "ProductCodeId": "23wacvuracm6vvc5b5d175uyb",
                    "ProductCodeType": "marketplace"
                }
            ],
            "State": "available",
            "BlockDeviceMappings": [
                {
                    "DeviceName": "/dev/xvda",
                    "Ebs": {
                        "DeleteOnTermination": true,
                        "SnapshotId": "snap-0354909bfbe497ab6",
                        "VolumeSize": 12,
                        "VolumeType": "gp2",
                        "Encrypted": false
                    }
                }
            ],
            "Description": "Kali Linux on AWS",
            "EnaSupport": true,
            "Hypervisor": "xen",
            "ImageOwnerAlias": "aws-marketplace",
            "Name": "Kali Linux on AWS-239c5ea9-c6ae-402b-8598-93ef545c46f3",
            "RootDeviceName": "/dev/xvda",
            "RootDeviceType": "ebs",
            "SriovNetSupport": "simple",
            "VirtualizationType": "hvm",
            "DeprecationTime": "2025-04-17T11:46:47.000Z"
        }
    ]
}

Create the main.tf configuration file;

# Configure the AWS provider
provider "aws" {
  region = "eu-west-3"  # Paris
}

# Create an EC2 instance
resource "aws_instance" "kali" {
  ami           = "ami-08cd986d3d674d547"
  vpc_security_group_ids = [aws_security_group.security_group_1.id] # Our AWS Security group
  instance_type = "t2.micro"
  key_name = "aws_key"

  depends_on = [
  aws_key_pair.devkey
  ]

  user_data = <<-EOL
  #!/bin/bash -xe

  apt update
  apt install nmap --yes
  apt install metasploit-framework --yes
  EOL

}

# Configure the SSH public key
resource "aws_key_pair" "devkey" {
  key_name   = "aws_key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCY5rCpZLH7i9xyUAZM4pIBOEZ+3BuEdNCNr5POA0M6kISsNw7fnHBzaFmyONmeEq5ut2UI3HiSFFv5uQbOQLl1ViYPQT7ES/qmMSCY374lMmQqF7ZGy1LYe8A+7ZMzDx2dcblr2VZ48lq1RV+o8tSQj/P1OwP08XF4nCwIzs6x46ica8NQeTWWAOMFObCfGIkhj+gbmuZWgtdxaosu5YMcjC284tTyUtYLIjQSmkDszWJwX+VhbSZAZPcSJ2QieLmWtByoKIwOQfBuX3zCwn58Ph1pojgzfomUL523pAXo3ZJBcCy9/+8/Mpd2/klZb4cgAFzXWEQA0vIxrGyIWxnEn0Ql9dSnmAqDKNlWJkihvVh8s70oFTB7F3/tH4XyUCMgERMe1aZBXGB1Xi0rGf425TZRXUOZXEEHV0MMGzdfcWk64mV3yMFVBgyPB9+FpczcvWH8kPSE+4AtQuG0X7O1jkz+xVT+b/1udF76kUEC8m330DzLJ/Fk5kQemPYHYvNZyJFsr8QZuUjaqexCyjbj9ulVDEcdgA/TW23wlEARFqzfKexd35I/FVJ+o9/pVuycAkSXNp3FE9XZHfdSQW8NmPt7V68EQCpXliOJHyJbfuwEz3xWdd+jLYOx32I3uzJ4rA/kwOe0dDxFACC30wHd0n5Vrts5hAqq7fqqL5ECzZ= kali@kali"

}

# Below gets the instance ID and public IP address
output "instance_id" {
  description = "ID of the EC2 instance"
  value       = aws_instance.kali.id
}

output "instance_public_ip" {
  description = "Public IP address of the EC2 instance"
  value       = aws_instance.kali.public_ip
}

resource "aws_security_group" "security_group_1" {
  name = "kali-sec-group"
  description = "Allow HTTP/HTTPS/SSH"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

 ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  ingress {
    from_port   = 22
    to_port     = 22
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

Run a initialisation;

terraform init 

Initializing the backend...

Initializing provider plugins...
- Reusing previous version of hashicorp/aws from the dependency lock file
- Finding latest version of hashicorp/external...
- Using previously-installed hashicorp/aws v5.14.0
- Installing hashicorp/external v2.3.1...
- Installed hashicorp/external v2.3.1 (signed by HashiCorp)

Terraform has made some changes to the provider dependency selections recorded
in the .terraform.lock.hcl file. Review those changes and commit them to your
version control system if they represent changes you intended to make.

Terraform has been successfully initialized!

You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.

If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.

Deploy using the apply command;

┌──(kali㉿kali)-[~/terraform-aws-deploy]
└─$ terraform apply  

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # aws_instance.kali will be created
  + resource "aws_instance" "kali" {
      + ami                                  = "ami-08cd986d3d674d547"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = (known after apply)
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_stop                     = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + host_resource_group_arn              = (known after apply)
      + iam_instance_profile                 = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_lifecycle                   = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t2.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = "aws_key"
      + monitoring                           = (known after apply)
      + outpost_arn                          = (known after apply)
      + password_data                        = (known after apply)
      + placement_group                      = (known after apply)
      + placement_partition_number           = (known after apply)
      + primary_network_interface_id         = (known after apply)
      + private_dns                          = (known after apply)
      + private_ip                           = (known after apply)
      + public_dns                           = (known after apply)
      + public_ip                            = (known after apply)
      + secondary_private_ips                = (known after apply)
      + security_groups                      = (known after apply)
      + source_dest_check                    = true
      + spot_instance_request_id             = (known after apply)
      + subnet_id                            = (known after apply)
      + tags_all                             = (known after apply)
      + tenancy                              = (known after apply)
      + user_data                            = "12e9e0c9ccfd56f7bff1dbe2e6f5e77406f42f3e"
      + user_data_base64                     = (known after apply)
      + user_data_replace_on_change          = false
      + vpc_security_group_ids               = (known after apply)
    }

  # aws_key_pair.devkey will be created
  + resource "aws_key_pair" "devkey" {
      + arn             = (known after apply)
      + fingerprint     = (known after apply)
      + id              = (known after apply)
      + key_name        = "aws_key"
      + key_name_prefix = (known after apply)
      + key_pair_id     = (known after apply)
      + key_type        = (known after apply)
      + public_key      = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAACAQCY5rCpZLH7i9xyUAZM4pIBOEZ+3BuEdNCNr5POA0M6kISsNw7fnHBzaFmyONmeEq5ut2UI3HiSFFv5uQbOQLl1ViYPQT7ES/qmMSCY374lMmQqF7ZGy1LYe8A+7ZMzDx2dcblr2VZ48lq1RV+o8tSQj/P1OwP08XF4nCwIzs6x46ica8NQeTWWAOMFObCfGIkhj+gbmuZWgtdxaosu5YMcjC284tTyUtYLIjQSmkDszWJwX+VhbSZAZPcSJ2QieLmWtByoKIwOQfBuX3zCwn58Ph1pojgzfomUL523pAXo3ZJBcCy9/+8/Mpd2/klZb4cgAFzXWEQA0vIxrGyIWxnEn0Ql9dSnmAqDKNlWJkihvVh8s70oFTB7F3/tH4XyUCMgERMe1aZBXGB1Xi0rGf425TZRXUOZXEEHV0MMGzdfcWk64mV3yMFVBgyPB9+FpczcvWH8kPSE+4AtQuG0X7O1jkz+xVT+b/1udF76kUEC8m330DzLJ/Fk5kQemPYHYvNZyJFsr8QZuUjaqexCyjbj9ulVDEcdgA/TW23wlEARFqzfKexd35I/FVJ+o9/pVuycAkSXNp3FE9XZHfdSQW8NmPt7V68EQCpXliOJHyJbfuwEz3xWdd+jLYOx32I3uzJ4rA/kwOe0dDxFACC30wHd0n5Vrts5hAqq7fqqL5ECzZ= kali@kali"
      + tags_all        = (known after apply)
    }

  # aws_security_group.security_group_1 will be created
  + resource "aws_security_group" "security_group_1" {
      + arn                    = (known after apply)
      + description            = "Allow HTTP/HTTPS/SSH"
      + egress                 = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 0
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "-1"
              + security_groups  = []
              + self             = false
              + to_port          = 0
            },
        ]
      + id                     = (known after apply)
      + ingress                = [
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 22
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 22
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 443
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 443
            },
          + {
              + cidr_blocks      = [
                  + "0.0.0.0/0",
                ]
              + description      = ""
              + from_port        = 80
              + ipv6_cidr_blocks = []
              + prefix_list_ids  = []
              + protocol         = "tcp"
              + security_groups  = []
              + self             = false
              + to_port          = 80
            },
        ]
      + name                   = "kali-sec-group"
      + name_prefix            = (known after apply)
      + owner_id               = (known after apply)
      + revoke_rules_on_delete = false
      + tags_all               = (known after apply)
      + vpc_id                 = (known after apply)
    }

Plan: 3 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + instance_id        = (known after apply)
  + instance_public_ip = (known after apply)

Do you want to perform these actions?
  Terraform will perform the actions described above.
  Only 'yes' will be accepted to approve.

  Enter a value: yes

aws_key_pair.devkey: Creating...
aws_security_group.security_group_1: Creating...
aws_key_pair.devkey: Creation complete after 0s [id=aws_key]
aws_security_group.security_group_1: Creation complete after 2s [id=sg-1b5a076c8a16aa718]
aws_instance.kali: Creating...
aws_instance.kali: Still creating... [10s elapsed]
aws_instance.kali: Still creating... [20s elapsed]
aws_instance.kali: Still creating... [30s elapsed]
aws_instance.kali: Still creating... [40s elapsed]
aws_instance.kali: Creation complete after 42s [id=i-05b2576ec68035447]

Apply complete! Resources: 3 added, 0 changed, 0 destroyed.                                                                                                                                                                                                                                 
                                                                                                                                                                                                                                                                                            
Outputs:                                                                                                                                                                                                                                                                                    
                                                                                                                                                                                                                                                                                            
instance_id = "i-05b2576ec68035447"                                                                                                                                                                                                                                                         
instance_public_ip = "15.188.3.246"

You should then be able to SSH into the instance using the “kali” user account;

┌──(kali㉿kali)-[~/terraform-aws-deploy]
└─$ ssh kali@15.188.3.246
Linux kali 6.3.0-kali1-cloud-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.3.7-1kali1 (2023-06-29) x86_64

The programs included with the Kali GNU/Linux system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Kali GNU/Linux comes with ABSOLUTELY NO WARRANTY, to the extent
permitted by applicable law.
┏━(Message from Kali developers)
┃
┃ This is a minimal installation of Kali Linux, you likely
┃ want to install supplementary tools. Learn how:
┃ ⇒ https://www.kali.org/docs/troubleshooting/common-minimum-setup/
┃
┃ This is a cloud installation of Kali Linux. Learn more about
┃ the specificities of the various cloud images:
┃ ⇒ https://www.kali.org/docs/troubleshooting/common-cloud-setup/
┃
┗━(Run: “touch ~/.hushlogin” to hide this message)
┌──(kali㉿kali)-[~]
└─$ uptime
 09:55:36 up 4 min,  3 users,  load average: 0.08, 0.09, 0.05


Terraform Security Configuration Audits

Trivy

Trivy is a tool to perform security audits against numerous cloud services, including docker configuration files. It can be installed in Kali using;

sudo apt install trivy

Run the tool with trivy config main.tf;

┌──(kali㉿kali)-[~/terraform-aws-deploy]
└─$ trivy config main.tf 
2023-09-10T11:05:31.969+0100  INFO     Misconfiguration scanning is enabled
2023-09-10T11:05:32.554+0100    INFO     Detected config files: 2

main.tf (terraform)

Tests: 3 (SUCCESSES: 1, FAILURES: 2, EXCEPTIONS: 0)
Failures: 2 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 2, CRITICAL: 0)

HIGH: Instance does not require IMDS access to require a token
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════

IMDS v2 (Instance Metadata Service) introduced session authentication tokens which improve security when talking to IMDS.
By default <code>aws_instance</code> resource sets IMDS session auth tokens to be optional. 
To fully protect IMDS you need to enable session tokens by using <code>metadata_options</code> block and its <code>http_tokens</code> variable set to <code>required</code>.


See https://avd.aquasec.com/misconfig/avd-aws-0028
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:7-16
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   7 ┌ resource "aws_instance" "kali" {
   8 │   ami           = "ami-08cd986d3d674d547"
   9 │   instance_type = "t2.micro"
  10 │   key_name = "aws_key"
  11 │ 
  12 │   depends_on = [
  13 │   aws_key_pair.devkey
  14 │   ]
  15 │ 
  16 └ }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


HIGH: Root block device is not encrypted.
════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════════
Block devices should be encrypted to ensure sensitive data is held securely at rest.

See https://avd.aquasec.com/misconfig/avd-aws-0131
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
 main.tf:7-16
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
   7 ┌ resource "aws_instance" "kali" {
   8 │   ami           = "ami-08cd986d3d674d547"
   9 │   instance_type = "t2.micro"                                                                                                                                                                                                                                                         
  10 │   key_name = "aws_key"                                                                                                                                                                                                                                                               
  11 │                                                                                                                                                                                                                                                                                      
  12 │   depends_on = [
  13 │   aws_key_pair.devkey
  14 │   ]
  15 │ 
  16 └ }
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────


Terrascan

Tenable also make a tool for scanning Terraform configurations, Terrascan. This can be installed in Kali using;

curl -L "$(curl -s https://api.github.com/repos/tenable/terrascan/releases/latest | grep -o -E "https://.+?_Linux_x86_64.tar.gz")" > terrascan.tar.gz
tar -xf terrascan.tar.gz terrascan && rm terrascan.tar.gz
sudo install terrascan /usr/local/bin && rm terrascan

Then just run the tool against your configuration file;

terrascan scan main.tf 
2023/09/10 13:01:04 [DEBUG] GET https://registry.terraform.io/v1/providers/hashicorp/aws/versions

Violation Details -
    
        Description    :        Security Groups - Unrestricted Specific Ports - (HTTP,80)
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        ./
        Line           :        45
        Severity       :        HIGH
        -----------------------------------------------------------------------
        
        Description    :        EC2 instances should disable IMDS or require IMDSv2 as this can be related to the weaponization phase of kill chain
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        ./
        Line           :        7
        Severity       :        MEDIUM
        -----------------------------------------------------------------------
        
        Description    :        Security Groups - Unrestricted Specific Ports - (SSH,22)
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        ./
        Line           :        45
        Severity       :        HIGH
        -----------------------------------------------------------------------
        
        Description    :        Security Groups - Unrestricted Specific Ports - (HTTPS,443)
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        ./
        Line           :        45
        Severity       :        LOW
        -----------------------------------------------------------------------
        
        Description    :        Ensure that detailed monitoring is enabled for EC2 instances.
        File           :        main.tf
        Module Name    :        root
        Plan Root      :        ./
        Line           :        7
        Severity       :        HIGH
        -----------------------------------------------------------------------
        

Scan Summary -

        File/Folder         :   /home/kali/terraform-aws-deploy
        IaC Type            :   terraform
        Scanned At          :   2023-09-10 12:01:05.795945918 +0000 UTC
        Policies Validated  :       142
        Violated Policies   :   5
        Low                 :      1
        Medium              :      1
        High                :      3

In Conclusion

When auditing Terraform configurations, it’s a good idea to review the following items;

  • Review the configuration files using a combination of automated and manual review
  • Review the resource being deployed. This includes the systems being deployed, and the code being run in the environment
  • Review the security of the cloud provider responsible for deploying the resources