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;
1 | apt install terraform |
Create a directory for you project, and add a main.tf configuration file with the following contents;
1 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 31 32 33 34 35 | 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;
1 2 3 4 | 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;
1 2 3 4 5 6 7 8 9 | ┌──(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;
1 | sudo apt install awscli |
Then make sure it’s configured with you access key and secret key:
1 2 3 4 5 | 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;
1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | 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;
1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | # 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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | 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;
1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 | ┌──(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;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | ┌──(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;
1 | sudo apt install trivy |
Run the tool with trivy config main.tf;
1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | ┌──(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;
1 2 3 | 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;
1 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 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 | 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