Cloud Pentesting Lab

Posted on Mar 13, 2022

How To Create a Kali & Parrot Pentesting Lab in AWS Using Docker and Terraform

In this post, I’ll quickly run through how to set up an AWS EC2 machine and install pre-configured kali and parrot containers, all provisioned automatically with terraform.


Sign up for a free AWS account. You just need a valid credit card.

Install AWS CLI tool.

Install Terraform CLI tool.

Pull the lab repo from my github that contains everything you need to follow this guide, and cd into it:

git pull && cd ./lab

What’s in the repo?

You’ll find a folder for the kali/ and parrot/ docker images. In each, there’s the dockerfile that specifies the docker build. I have added a few tools to the kali machine, whereas the parrot container remains vanilla. You can leave these as-is for the purposes of this guide.

The terraform/ folder contains the deployment manifest, so let’s have a look:

cd terraform/

Terraform is an infrastructure-as-code tool that lets you manage cloud assets extremely quickly and easily. If you inspect the file, you’ll see it contains all the info needed to deploy an EC2 instance.

For example:

provider "aws" {
  profile = "default"
  region  = "eu-south-1"

sets the AWS zone,

resource "aws_instance" "pt_lab" {
  ami           = "ami-0f8ce9c417115413d"
  instance_type = "t3.micro"
  associate_public_ip_address = true
  key_name         = "ssh-key"
  count = var.instance_count

  tags = {
    Name = "PT Lab Instance ${count.index + 1}"

Creates a new aws_instance called pt_lab, specifies a t3.micro machine, associates a public IP address, associates an SSH key for access and gives it a count which we will see shortly.

root_block_device {
  volume_type = "gp2"
  volume_size = 30

This block assigns a 30GB hard drive.

The following block runs a script that configures the Docker containers:

user_data = <<-EOF
    # clone lab repo and setup filesystem
    sudo su
    git clone /home/ubuntu/lab
    cd /home/ubuntu/lab
    cp kali
    cp parrot
    rm -r terraform/
    # install docker
    apt-get -y update
    apt-get -y upgrade
    apt-get -y install
    # build containers
    cd kali
    chmod +x *.sh
    cd ../parrot
    chmod +x *.sh
    # set aliases
    cd /home/ubuntu
    echo "alias kali=\"sh /home/ubuntu/lab/kali/\"" >> .bashrc
    echo "alias parrot=\"sh /home/ubuntu/lab/parrot/\"" >> .bashrc
    echo "alias stop=\"sh /home/ubuntu/lab/parrot/\"" >> .bashrc

There’s one more important that we will see shortly.

Configure AWS CLI

Create a new AWS Access Key.


aws configure

and follow the prompts to input your AWS Access Key ID and Secret Access Key.

Create an SSH key & add to the manifest

Follow this guide to create an ed25519 SSH key in a location of your choice.

Add your public_key to the manifest in place of mine in this block:

resource "aws_key_pair" "ssh-key" {
  key_name   = "ssh-key"
  public_key = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl9VKriojo6Q60esciAHQxKh8g6Tb00eYIHdAm8RuqPry5xTFP+gh0GS4ub0hZBnOyvC6sa4B/1MTNjL+Ex4rkUfM0gHH0PSSImJPBzKtQQ+sCxk4TsKTjfqxUHQQeaQgqufpy9uiXLRKdX0mZJLWv/Q2o6Dbcj7XhZqdrSzLKHqm7cCjv5b7AzVCRXlENA7fFSDXuupaNyWwfubn55qVielLQOJOTnNuuS2RQrNKDVWSMqAVds+E0SeFVikRAqhbd0YNWYqs139Z/bu9R9vMhLs1HElRimCwx1itjn+GHCLQ25chJHu1+snJmWakjxuLQz0Y8qoo+xrFluzQXQWo1"

This will associate your EC2 instance with your SSH key, granting you remote SSH access.

Save the file.


From within the terraform/ folder, run:

terraform init

to initialise terraform.

Validate the manifest file with:

terraform validate

You should see Success! The configuration is valid. If not, fix any errors.

Then run:

terraform apply

and you should see a confirmation message showing all the changes terraform is about to make. It should look something like this:

$ 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.pt_lab[0] will be created
  + resource "aws_instance" "pt_lab" {
      + ami                                  = "ami-0f8ce9c417115413d"
      + arn                                  = (known after apply)
      + associate_public_ip_address          = true
      + availability_zone                    = (known after apply)
      + cpu_core_count                       = (known after apply)
      + cpu_threads_per_core                 = (known after apply)
      + disable_api_termination              = (known after apply)
      + ebs_optimized                        = (known after apply)
      + get_password_data                    = false
      + host_id                              = (known after apply)
      + id                                   = (known after apply)
      + instance_initiated_shutdown_behavior = (known after apply)
      + instance_state                       = (known after apply)
      + instance_type                        = "t3.micro"
      + ipv6_address_count                   = (known after apply)
      + ipv6_addresses                       = (known after apply)
      + key_name                             = "ssh-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
      + subnet_id                            = (known after apply)
      + tags                                 = {
          + "Name" = "PT Lab Instance 1"
      + tags_all                             = {
          + "Name" = "PT Lab Instance 1"
      + tenancy                              = (known after apply)
      + user_data                            = "b6598edad7c973b8fe63d4a74baf28256a6e3541"
      + user_data_base64                     = (known after apply)
      + vpc_security_group_ids               = (known after apply)

      + capacity_reservation_specification {
          + capacity_reservation_preference = (known after apply)

          + capacity_reservation_target {
              + capacity_reservation_id = (known after apply)

      + ebs_block_device {
          + delete_on_termination = (known after apply)
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + snapshot_id           = (known after apply)
          + tags                  = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = (known after apply)
          + volume_type           = (known after apply)

      + enclave_options {
          + enabled = (known after apply)

      + ephemeral_block_device {
          + device_name  = (known after apply)
          + no_device    = (known after apply)
          + virtual_name = (known after apply)

      + metadata_options {
          + http_endpoint               = (known after apply)
          + http_put_response_hop_limit = (known after apply)
          + http_tokens                 = (known after apply)
          + instance_metadata_tags      = (known after apply)

      + network_interface {
          + delete_on_termination = (known after apply)
          + device_index          = (known after apply)
          + network_interface_id  = (known after apply)

      + root_block_device {
          + delete_on_termination = true
          + device_name           = (known after apply)
          + encrypted             = (known after apply)
          + iops                  = (known after apply)
          + kms_key_id            = (known after apply)
          + throughput            = (known after apply)
          + volume_id             = (known after apply)
          + volume_size           = 30
          + volume_type           = "gp2"

  # aws_key_pair.ssh-key will be created
  + resource "aws_key_pair" "ssh-key" {
      + arn             = (known after apply)
      + fingerprint     = (known after apply)
      + id              = (known after apply)
      + key_name        = "ssh-key"
      + key_name_prefix = (known after apply)
      + key_pair_id     = (known after apply)
      + public_key      = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCl9VKriojo6Q60esciAHQxKh8g6Tb00eYIHdAm8RuqPry5xTFP+gh0GS4ub0hZBnOyvC6sa4B/1MTNjL+Ex4rkUfM0gHH0PSSImJPBzKtQQ+sCxk4TsKTjfqxUHQQeaQgqufpy9uiXLRKdX0mZJLWv/Q2o6Dbcj7XhZqdrSzLKHqm7cCjv5b7AzVCRXlENA7fFSDXuupaNyWwfubn55qVielLQOJOTnNuuS2RQrNKDVWSMqAVds+E0SeFVikRAqhbd0YNWYqs139Z/bu9R9vMhLs1HElRimCwx1itjn+GHCLQ25chJHu1+snJmWakjxuLQz0Y8qoo+xrFluzQXQWo1"
      + tags_all        = (known after apply)

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

Changes to Outputs:
  + instance_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:

Type yes and press enter.

Your cloud pentesting box is being provisioned! Terraform will print out the public IP address and you can access your box with:

ssh -i YOURKEY ubuntu@

where YOURKEY is your private SSH key and is your EC2’s public IP address.

The EC2 machine will be ready almost instantly, but it will take about 20 minutes for the kali and parrot containers to download and install.

Using the lab

Once connected, there are three commands you need to know:

  • kali starts a kali container
  • parrot starts a parrot container
  • stop stops all running containers

To exit a container, just run exit.

Any files that you want to persist from the containers, you can save to /root/client/ in the container which maps to /home/ubuntu/lab/client on the EC2 machine. Remember, any time you stop a container, all its files will be lost.

Customising & destroying the lab

You can make changes to the terraform manifest and run terraform apply again and terraform will automatically make the changes and deploy as needed.

You can alter the dockerfile manifests in the kali/ and parrot/ folders to add your favourite tools.

If you want to destroy the instance, run terraform destroy from within the terraform/ folder of the repo and your instance will be destroyed.


That’s it! I hope you enjoyed setting up the lab. If you had any problems following this guide, drop me a line.