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.
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.
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.
The local-exec
provisioner allow you to run shell commands on your local machine.
provisioner "local-exec" {
command = "something"
}
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"
}
}
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.
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"
]
}
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:
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 :)
[[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
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"
}
The HTTP C2 VM was designed to be an HTTP/S Cobalt Strike server. To do that, we need to:
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}")}"
}
}
}
[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
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.