Introduction

In Part 1, we looked at using Terraform to deploy cloud infrastructure (VMs, firewalls, DNS records etc). In this post, we’ll look at some examples for automating the installation of tools/software on the VM resources.

Provisioners

Terraform has a number of provisioners that can be used for this purpose - we’ll explore file, local-exec, remote-exec and some of the cool things you can do with them.

Connection

Some of the provisioners require remote access to the VM resources, which, for Linux, can be done via SSH. You will see a connection {} block in some of the examples below. SSH connections support both password and publickey authentication methods.

Local Exec

The local-exec provisioner allow you to run shell commands on your local machine.

provisioner "local-exec" {
    command = "something"
}

Example

This is quite useful for writing information about your infrastructure to file. It also has a method for running a different command when an instance is destroyed.

resource "digitalocean_droplet" "paydel" {
    image = "ubuntu-14-04-x64"
    name = "payload-delivery"
    region = "lon1"
    size = "512mb"
    ssh_keys = ["${digitalocean_ssh_key.rasta.id}"]

    provisioner "local-exec" {
        command = "echo ${digitalocean_droplet.paydel.ipv4_address}>>paydel-ip.txt"
    }

    provisioner "local-exec" {
        when = "destroy"
        command = "del paydel-ip.txt"
    }
}

Verification

digitalocean_droplet.paydel: Creation complete (ID: 63586802)

Apply complete! Resources: 7 added, 0 changed, 0 destroyed.

Outputs:

paydel-ip = 138.68.180.21

PS C:\Users\Rasta\Desktop\terraform> gc .\paydel-ip.txt
138.68.180.21
Destroy complete! Resources: 7 destroyed.

PS C:\Users\Rasta\Desktop\terraform> gc .\paydel-ip.txt
gc : Cannot find path 'C:\Users\Rasta\Desktop\terraform\paydel-ip.txt' because it does not exist.

Remote Exec

The remote-exec provisioner allows you to run shell commands on the VM, but be aware that some environment variables may not be available.

provisioner "remote-exec" {
    inline = [
        "touch /root/test.txt"
    ]
}

Example

The DNS redirector VM was designed to receive incoming DNS requests and redirect them to the DNS C2 server. To do that, we need to:

  • Install Socat
  • Configure the VM to start socat on boot
  • Reboot the VM
resource "aws_instance" "dns-rdir" {
    ami = "ami-489f8e2c" # Amazon Linux AMI 2017.03.1
    instance_type = "t2.micro"
    key_name = "${aws_key_pair.rasta.key_name}"
    vpc_security_group_ids = ["${aws_security_group.dns-rdir.id}"]
    subnet_id = "${aws_subnet.default.id}"
    associate_public_ip_address = true

    provisioner "remote-exec" {
        inline = [
            "sudo yum -y update",
            "sudo yum -y install socat",
            "echo \"socat udp4-listen:53,reuseaddr,fork udp:${digitalocean_droplet.dns-c2.ipv4_address}; echo -ne \\n &\" | sudo tee -a /etc/rc.local",
            "sudo reboot"
        ]

        connection {
            type = "ssh"
            user = "ec2-user"
            private_key = "${file("${var.private-key}")}"
        }
    }
}

Because these are relatively simple tasks - we can just do them on the command line. The other advantage you can see, is that we don’t need to know what the IP address of the DNS C2 server will be up front - we can just reference it through Terraform and it will take care of the rest :)

Verification

[[email protected] ~]# cat /etc/rc.local
#!/bin/sh
#
# This script will be executed *after* all the other init scripts.
# You can put your own initialization stuff in here if you don't
# want to do the full Sys V style init stuff.

touch /var/lock/subsys/local
socat udp4-listen:53,reuseaddr,fork udp:178.62.42.201; echo -ne \n &
[[email protected] ~]# ps aux | grep [s]ocat
root      2527  0.0  0.3  42408  3972 ?        S    Sep23   0:00 socat udp4-listen:53,reuseaddr,fork udp:178.62.42.201

File

The file provisioner is simply used to copy files or directories from your local machine to the VM.

provisioner "file" {
    source      = "test.txt"
    destination = "/root/test.txt"
}

Example

The HTTP C2 VM was designed to be an HTTP/S Cobalt Strike server. To do that, we need to:

  • Install Oracle Java
  • Install git
  • Download & extract Cobalt Strike
  • Clone Raphael’s Malleable C2 repo

This is a little more complicated, so we could write a bash script to do all the work:

#!/bin/bash

apt -y update
apt -y install git

mkdir /usr/local/java
curl -s -j -L -H "Cookie: oraclelicense=accept-securebackup-cookie" http://download.oracle.com/otn-pub/java/jdk/8u144-b01/090f390dda5b47b9b721c7dfaa008135/jdk-8u144-linux-x64.tar.gz -o /usr/local/java/jdk-8u144-linux-x64.tar.gz
cd /usr/local/java
tar zxf jdk-8u144-linux-x64.tar.gz
echo "JAVA_HOME=/usr/local/java/jdk1.8.0_144" >> /etc/profile
echo "JRE_HOME=/usr/local/java/jdk1.8.0_144/jre" >> /etc/profile
echo "PATH=$PATH:/usr/local/java/jdk1.8.0_144/bin:/usr/local/java/jdk1.8.0_144/jre/bin" >> /etc/profile
echo "export JAVA_HOME" >> /etc/profile
echo "export JRE_HOME" >> /etc/profile
echo "export PATH" >> /etc/profile
update-alternatives --install "/usr/bin/java" "java" "/usr/local/java/jdk1.8.0_144/bin/java" 1
update-alternatives --install "/usr/bin/javaws" "javaws" "/usr/local/java/jdk1.8.0_144/bin/javaws" 1
update-alternatives --set java /usr/local/java/jdk1.8.0_144/bin/java
update-alternatives --set javaws /usr/local/java/jdk1.8.0_144/bin/javaws
rm /usr/local/java/jdk-8u144-linux-x64.tar.gz

key='xxxx-xxxx-xxxx-xxxx'
token=`curl -s https://www.cobaltstrike.com/download -d "dlkey=${key}" | grep 'href="/downloads/' | cut -d '/' -f3`
curl -s https://www.cobaltstrike.com/downloads/${token}/cobaltstrike-trial.tgz -o /tmp/cobaltstrike.tgz
mkdir /opt/cobaltstrike
tar zxf /tmp/cobaltstrike.tgz -C /opt
echo ${key} > /root/.cobaltstrike.license
rm /tmp/cobaltstrike.tgz

git clone https://github.com/rsmudge/Malleable-C2-Profiles.git /opt/cobaltstrike/c2

We can then combine provisioners, i.e. use file to upload the script and remote-exec to execute it. They are executed in the order placed (from top to bottom).

resource "digitalocean_droplet" "http-c2" {
    image = "ubuntu-14-04-x64"
    name = "http-c2"
    region = "lon1"
    size = "2gb"
    ssh_keys = ["${digitalocean_ssh_key.rasta.id}"]

    provisioner "file" {
        source = "scripts\\install-c2.txt"
        destination = "/tmp/install-c2.sh"

        connection {
            type = "ssh"
            user = "ec2-user"
            private_key = "${file("${var.private-key}")}"
        }
    }

    provisioner "remote-exec" {
        inline = [
            "chmod +x /tmp/install-c2.sh",
            "/tmp/install-c2.sh",
            "rm -rf /tmp/*"
        ]

        connection {
            type = "ssh"
            user = "ec2-user"
            private_key = "${file("${var.private-key}")}"
        }
    }
}

Verification

[email protected]:~# java -version
java version "1.8.0_144"
Java(TM) SE Runtime Environment (build 1.8.0_144-b01)
Java HotSpot(TM) 64-Bit Server VM (build 25.144-b01, mixed mode)
[email protected]:~# ll /opt/cobaltstrike/
total 21748
drwxr-xr-x 4  501 dialout     4096 Sep 24 10:22 ./
drwxr-xr-x 3 root root        4096 Sep 24 10:22 ../
-rwxr-xr-x 1  501 dialout      126 Sep 21 02:49 agscript*
drwxr-xr-x 6 root root        4096 Sep 24 10:22 c2/
-rwxr-xr-x 1  501 dialout      144 Sep 21 02:49 c2lint*
-rwxr-xr-x 1  501 dialout       93 Sep 21 02:49 cobaltstrike*
-rw-r--r-- 1  501 dialout 21638284 Sep 21 02:49 cobaltstrike.jar
-rw-r--r-- 1  501 dialout    96104 Sep 21 02:49 icon.jpg
-rw-r--r-- 1  501 dialout    87101 Sep 21 02:49 license.pdf
-rw-r--r-- 1  501 dialout    25388 Sep 21 02:49 readme.txt
-rw-r--r-- 1  501 dialout   103682 Sep 21 02:49 releasenotes.txt
-rwxr-xr-x 1  501 dialout     1865 Sep 21 02:49 teamserver*
drwxr-xr-x 2  501 dialout     4096 Sep 21 02:49 third-party/
-rwxr-xr-x 1  501 dialout       87 Sep 21 02:49 update*
-rw-r--r-- 1  501 dialout   266484 Sep 21 02:49 update.jar

Closing

These were just 3 examples of ways to use Terraform Provisioners to execute post-build tasks - hopefully they will give you ideas about how you can automate your own complete builds.