Introduction
The ability to automate your offensive security infrastructure is an important DevOps skill for any red team operator, so in this post, I am going to go over the steps required to automate your own red team infrastructure using Cobalt Strike, DigitalOcean, and Terraform.

I will be building the red team infrastructure seen in the above image, which will require obtaining an Access Token from each API I will need to access. I am going to assume the reader already knows how to obtain Access Tokens from the respective providers, but in case you don’t, refer to the links below for the following services:
- https://www.digitalocean.com/docs/api/create-personal-access-token/
- https://developers.cloudflare.com/access/service-auth/service-token/
- https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html
The next step would be to ensure all the prerequisites are taken care of so I can start with the build configuration.
Prerequisites
Install Terraform
Before I start with building out the Terraform configuration, I am going to make sure Terraform is installed on my host machine. I am using Arch Linux as my host operating system, so these are the steps I took to set up Terraform:
pacman -S unzip --noconfirm wget https://releases.hashicorp.com/terraform/0.12.18/terraform_0.12.18_linux_amd64.zip unzip terraform_0.12.18_linux_amd64.zip sudo mv terraform /usr/local/bin/ terraform --version
SSH Key Generation
Since the above infrastructure has 4 droplets (or machines), I will be generating 4 different SSH Keys, 1 for each droplet.
ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-teamserver_rsa -q -N "" ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-cf-redirector_rsa -q -N "" ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-dns-redirector_rsa -q -N "" ssh-keygen -b 4096 -t rsa -f /root/.ssh/c2-https-redirector_rsa -q -N ""
File Structure
Now that Terraform is installed, I want to setup the file structure for this project. I used the following commands to set up the file structure I will be using:
cd ~/C2/Automation mkdir -p ./bootstrap/{files,scripts} mkdir ssh-keys cp /opt/cobaltstrike.tar.gz ./bootstrap/files/ touch ./bootstrap/scripts/cf-server-setup.sh touch ./bootstrap/scripts/dns-server-setup.sh touch ./bootstrap/scripts/https-server-setup.sh touch ./bootstrap/scripts/teamserver-setup.sh cp ~/.ssh/c2-teamserver_rsa.pub ./ssh-keys/teamserver.pub cp ~/.ssh/c2-cf-redirector_rsa.pub ./ssh-keys/cf-server.pub cp ~/.ssh/c2-dns-redirector_rsa.pub ./ssh-keys/dns-server.pub cp ~/.ssh/c2-https-redirector_rsa.pub ./ssh-keys/https-server.pub touch {providers.tf,variables.tf,do-resources.tf,terraform.tfvars} tree ./ ./ ├── bootstrap │ ├── files │ │ └── cobaltstrike.tar.gz │ └── scripts │ ├── cf-server-setup.sh │ ├── dns-server-setup.sh │ ├── https-server-setup.sh │ └── teamserver-setup.sh ├── do-resources.tf ├── providers.tf ├── ssh-keys │ ├── cf-server.pub │ ├── dns-server.pub │ ├── https-server.pub │ └── teamserver.pub ├── terraform.tfvars └── variables.tf 4 directories, 13 files
I created 2 root level folders bootstrap and ssh-keys. Within the bootstrap folder, I created 2 more folders files and scripts. The files folder will contain any files I want to move from my host machine over to the DigitalOcean droplet. The scripts folder will contain any scripts I want to run on the droplet after it has been created. Back on the root level, the ssh-keys folder contains 1 ssh public key for each droplet being created, with a total of 4 files.
Droplet Configuration
The next step I am going to take in this process is setting up the droplets within DigitalOcean. Each droplet (or machine) will be created in a different region within the DigitalOcean network to create some network segregation. For this red team infrastructure, I will be creating 4 different droplets using Terraform and the DigitalOcean API.
Droplets
- Team Server – Ubuntu 18.04 LTS – 4GB RAM – 2 vCPUs – 50GB SSD
- DNS Redirector – Ubuntu 16.04 LTS – 1GB RAM – 1 vCPU – 20GB SSD
- HTTP/S Redirector – Ubuntu 16.04 LTS – 1GB RAM – 1 vCPU – 20GB SSD
- CloudFront Redirector – Ubuntu 16.04 LTS – 1GB RAM – 1 vCPU – 20GB SSD
Terraform Files
For the droplets creation, I will be creating the following files to be used with Terraform:
- variables.tf
- providers.tf
- do-firewalls.tf
- do-resources.tf
- terraform.tfvars
I will be adding more content (and more files) as I continue through the build, but for droplet creation we can focus on the files listed above.
variables.tf
The variables.tf file tells Terraform which variables are expected to be set before starting the build.
# variables.tf variable "do-token" { type = string description = "DigitalOcean Access Token" } variable "do-teamserver-pubkey" { type = string description = "Teamserver SSH Public Key" } variable "do-cf-redirector-pubkey" { type = string description = "CloudFront Redirector SSH Public Key" } variable "do-dns-redirector-pubkey" { type = string description = "DNS Redirector SSH Public Key" } variable "do-http-redirector-pubkey" { type = string description = "HTTP/S Redirector SSH Public Key" }
In the file above, I have added a variable for the DigitalOcean Access Token, and a variable to store the ssh public key for each droplet I am going to create.
providers.tf
The providers.tf file tells Terraform which provider is responsible for understanding API interactions and exposing resources.
# providers.tf provider "digitalocean" { version = "~> 1.11" token = var.do-token }
terraform.tfvars
The terraform.tfvars file tells Terraform which variables to set at runtime.
# terraform.tfvars do-token = "<your Access Token here>" do-teamserver-pubkey = "/ssh-keys/teamserver.pub" do-cf-redirector-pubkey = "/ssh-keys/cf-server.pub" do-dns-redirector-pubkey = "/ssh-keys/dns-server.pub" do-http-redirector-pubkey = "/ssh-keys/https-server.pub"
do-firewalls.tf
The do-firewalls.tf file contains the configuration for the 3 firewalls I will be setting up within DigitalOcean.
# do-firewalls.tf resource "digitalocean_firewall" "webfw" { name = "webfw" droplet_ids = [ digitalocean_droplet.cf-redirector.id, digitalocean_droplet.https-redirector.id ] inbound_rule { protocol = "tcp" port_range = "65522" source_addresses = [var.operator_ip] } inbound_rule { protocol = "tcp" port_range = "80" source_addresses = ["0.0.0.0/0"] } inbound_rule { protocol = "tcp" port_range = "443" source_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "tcp" port_range = "1-65535" destination_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "udp" port_range = "1-65535" destination_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "icmp" destination_addresses = ["0.0.0.0/0"] } } resource "digitalocean_firewall" "dnsfw" { name = "dnsfw" droplet_ids = [ digitalocean_droplet.dns-redirector.id ] inbound_rule { protocol = "tcp" port_range = "65522" source_addresses = [var.operator_ip] } inbound_rule { protocol = "udp" port_range = "53" source_addresses = ["0.0.0.0/0"] } inbound_rule { protocol = "tcp" port_range = "53" source_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "tcp" port_range = "1-65535" destination_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "udp" port_range = "1-65535" destination_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "icmp" destination_addresses = ["0.0.0.0/0"] } } resource "digitalocean_firewall" "teamfw" { name = "teamfw" droplet_ids = [ digitalocean_droplet.teamserver.id ] inbound_rule { protocol = "tcp" port_range = "65522" source_addresses = [var.operator_ip] } inbound_rule { protocol = "tcp" port_range = "50050" source_addresses = [var.operator_ip] } inbound_rule { protocol = "udp" port_range = "1-65535" source_addresses = [digitalocean_droplet.dns-redirector.ipv4_address] } inbound_rule { protocol = "tcp" port_range = "53" source_addresses = [digitalocean_droplet.dns-redirector.ipv4_address] } inbound_rule { protocol = "tcp" port_range = "80" source_addresses = [ digitalocean_droplet.cf-redirector.ipv4_address, digitalocean_droplet.https-redirector.ipv4_address ] } outbound_rule { protocol = "tcp" port_range = "1-65535" destination_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "udp" port_range = "1-65535" destination_addresses = ["0.0.0.0/0"] } outbound_rule { protocol = "icmp" destination_addresses = ["0.0.0.0/0"] } }
do-resources.tf
This file will contain all the configuration needed to interact with the DigitalOcean API to create 4 droplets (or machines), group all droplets into 1 project within the DigitalOcean dashboard, setup the SSH keys for each droplet, and then connect to each droplet and run the bootstrap shell script.
# do-resources.tf # DigitalOcean SSH Key Configuration # resource "digitalocean_ssh_key" "teamserver" { name = "C2 Team Server SSH Key" public_key = file("${path.module}${var.do-teamserver-pubkey}") } resource "digitalocean_ssh_key" "cf-redirector" { name = "CloudFront Redirector SSH Key" public_key = file("${path.module}${var.do-cf-redirector-pubkey}") } resource "digitalocean_ssh_key" "https-redirector" { name = "HTTP/S Redirector SSH Key" public_key = file("${path.module}${var.do-http-redirector-pubkey}") } resource "digitalocean_ssh_key" "dns-redirector" { name = "DNS Redirector SSH Key" public_key = file("${path.module}${var.do-dns-redirector-pubkey}") } # DigitalOcean Droplet Creation # resource "digitalocean_droplet" "teamserver" { image = "ubuntu-18-04-x64" name = "teamserver" region = "nyc3" size = "s-2vcpu-4gb" ssh_keys = [digitalocean_ssh_key.teamserver.fingerprint] connection { host = digitalocean_droplet.teamserver.ipv4_address type = "ssh" user = "root" private_key = file("~/.ssh/c2-teamserver_rsa") } provisioner "file" { source = "bootstrap/scripts/teamserver-setup.sh" destination = "/tmp/setup.sh" } provisioner "file" { source = "bootstrap/files/cobaltstrike.tar.gz" destination = "/opt/cobaltstrike.tar.gz" } provisioner "remote-exec" { inline = [ "chmod +x /tmp/setup.sh && bash /tmp/setup.sh" ] } } resource "digitalocean_droplet" "dns-redirector" { image = "ubuntu-16-04-x64" name = "dns-redirector" region = "fra1" size = "s-1vcpu-1gb" ssh_keys = [digitalocean_ssh_key.dns-redirector.fingerprint] depends_on = [digitalocean_droplet.teamserver] connection { host = digitalocean_droplet.dns-redirector.ipv4_address type = "ssh" user = "root" private_key = file("~/.ssh/c2-dns-redirector_rsa") } provisioner "file" { source = "bootstrap/scripts/dns-server-setup.sh" destination = "/tmp/setup.sh" } provisioner "remote-exec" { inline = [ "echo ${digitalocean_droplet.teamserver.ipv4_address} > /root/teamserver_ip.txt", "chmod +x /tmp/setup.sh && bash /tmp/setup.sh" ] } } resource "digitalocean_droplet" "https-redirector" { image = "ubuntu-16-04-x64" name = "https-redirector" region = "lon1" size = "s-1vcpu-1gb" ssh_keys = [digitalocean_ssh_key.https-redirector.fingerprint] depends_on = [digitalocean_droplet.teamserver] connection { host = digitalocean_droplet.https-redirector.ipv4_address type = "ssh" user = "root" private_key = file("~/.ssh/c2-https-redirector_rsa") } provisioner "file" { source = "bootstrap/scripts/https-server-setup.sh" destination = "/tmp/setup.sh" } provisioner "file" { source = "bootstrap/cron/certificate.sh" destination = "/tmp/cert.sh" } provisioner "remote-exec" { inline = [ "echo ${digitalocean_droplet.https-redirector.ipv4_address} > /root/ip_address.txt", "echo ${digitalocean_droplet.teamserver.ipv4_address} > /root/teamserver_ip.txt", "echo ${var.cf-subdomain-api}.${var.cf-domain} > /root/domain.txt", "chmod +x /tmp/setup.sh && bash /tmp/setup.sh", "sleep 3" ] } } resource "digitalocean_droplet" "cf-redirector" { image = "ubuntu-16-04-x64" name = "cf-redirector" region = "tor1" size = "s-1vcpu-1gb" ssh_keys = [digitalocean_ssh_key.cf-redirector.fingerprint] depends_on = [digitalocean_droplet.teamserver] connection { host = digitalocean_droplet.cf-redirector.ipv4_address type = "ssh" user = "root" private_key = file("~/.ssh/c2-cf-redirector_rsa") } provisioner "file" { source = "bootstrap/scripts/cf-server-setup.sh" destination = "/tmp/setup.sh" } provisioner "file" { source = "bootstrap/cron/certificate.sh" destination = "/tmp/cert.sh" } provisioner "remote-exec" { inline = [ "echo ${digitalocean_droplet.cf-redirector.ipv4_address} > /root/ip_address.txt", "echo ${digitalocean_droplet.teamserver.ipv4_address} > /root/teamserver_ip.txt", "echo ${var.cf-subdomain-cf}.${var.cf-domain} > /root/domain.txt", "chmod +x /tmp/setup.sh && bash /tmp/setup.sh", "sleep 3" ] } } # DigitalOcean Project # resource "digitalocean_project" "C2-Automation" { name = "C2 Automation" description = "C2 Infrastructure Automation" purpose = "C2 Infrastructure" environment = "Development" depends_on = [ digitalocean_droplet.teamserver, digitalocean_droplet.cf-redirector, digitalocean_droplet.dns-redirector, digitalocean_droplet.https-redirector ] resources = [ digitalocean_droplet.teamserver.urn, digitalocean_droplet.cf-redirector.urn, digitalocean_droplet.dns-redirector.urn, digitalocean_droplet.https-redirector.urn ] } # Output variables # output "teamserver-ip" { value = digitalocean_droplet.teamserver.ipv4_address } output "cf-redirector-ip" { value = digitalocean_droplet.cf-redirector.ipv4_address } output "dns-redirector-ip" { value = digitalocean_droplet.dns-redirector.ipv4_address } output "https-redirector-ip" { value = digitalocean_droplet.https-redirector.ipv4_address }
Shell Scripts
During the file structure setup done as part of the prerequisites above, I created 4 shell scripts within the bootstrap/scripts
folder to run as each droplet (or machine) had completed its setup.
- teamserver-setup.sh
- cf-server-setup.sh
- dns-server-setup.sh
- https-server-setup.sh
The Terraform resource file above allows for a droplet (or machine) to have its own shell script to run after creation. I am going to create a bootstrap shell script to configure each machine after its creation. This will allow me to fully automate the package installation and configuration of each machine deployed.
teamserver-setup.sh
This file will run once the teamserver droplet has been created. This script will need to automate the installation of the Java JDK, and the CobaltStrike package that will be copied from the host machine as stated in the file provisioner code within do-resources.tf.
#!/usr/bin/env bash export DEBIAN_FRONTEND=noninteractive apt update apt upgrade -yq apt install screen -yq systemctl disable systemd-resolved systemctl stop systemd-resolved rm /etc/resolv.conf echo "nameserver 8.8.8.8" >> /etc/resolv.conf echo "nameserver 8.8.4.4" >> /etc/resolv.conf chattr +i /etc/resolv.conf sed -i 's/#Port/Port/g' /etc/ssh/sshd_config sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config cd /opt && \ wget https://files-cdn.liferay.com/mirrors/download.oracle.com/otn-pub/java/jdk/8u121-b13/jdk-8u121-linux-x64.tar.gz tar -zxvf jdk-8u121-linux-x64.tar.gz echo "export PATH=$PATH:/opt/jdk1.8.0_121/bin" >> /root/.bashrc source /root/.bashrc export PATH=$PATH:/opt/jdk1.8.0_121/bin tar -zxvf /opt/cobaltstrike.tar.gz -C /opt cd /opt/cobaltstrike3.14 && nohup ./teamserver "$(cat /root/ip_address.txt)" "$(cat /root/password.txt)" & systemctl restart ssh sleep 2
cf-server-setup.sh
This file will run once the CloudFront redirector droplet has been created. This script will need to automate the installation of Apache2 and Let’s Encrypt.
#!/usr/bin/env bash source /root/.bashrc export DEBIAN_FRONTEND=noninteractive export PUBLIC_IP="" PUBLIC_IP=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address) apt update && apt full-upgrade -yq rm /etc/resolv.conf echo "nameserver 8.8.8.8" >> /etc/resolv.conf echo "nameserver 8.8.4.4" >> /etc/resolv.conf chattr +i /etc/resolv.conf sed -i 's/#Port/Port/g' /etc/ssh/sshd_config sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config yes | add-apt-repository ppa:certbot/certbot apt update apt install apache2 -yq apt install certbot -yq a2enmod ssl rewrite proxy proxy_http a2ensite default-ssl.conf sed -i ':a;$!{N;ba};s/AllowOverride None/'"AllowOverride All"'/3' /etc/apache2/apache2.conf systemctl stop apache2 systemctl reload ssh
https-server-setup.sh
This file will run once the HTTP/S redirector droplet has been created. This script will need to automate the installation of Apache2 and Let’s Encrypt.
#!/usr/bin/env bash source /root/.bashrc export DEBIAN_FRONTEND=noninteractive export PUBLIC_IP="" PUBLIC_IP=$(curl -s http://169.254.169.254/metadata/v1/interfaces/public/0/ipv4/address) apt update && apt full-upgrade -yq rm /etc/resolv.conf echo "nameserver 8.8.8.8" >> /etc/resolv.conf echo "nameserver 8.8.4.4" >> /etc/resolv.conf chattr +i /etc/resolv.conf sed -i 's/#Port/Port/g' /etc/ssh/sshd_config sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config yes | add-apt-repository ppa:certbot/certbot apt update apt install apache2 -yq apt install certbot -yq a2enmod ssl rewrite proxy proxy_http a2ensite default-ssl.conf sed -i ':a;$!{N;ba};s/AllowOverride None/'"AllowOverride All"'/3' /etc/apache2/apache2.conf systemctl stop apache2 systemctl reload ssh
dns-server-setup.sh
This file will run once the DNS redirector droplet has been created. This script will need to redirect DNS UDP / TCP traffic to the teamserver.
#!/usr/bin/env bash export DEBIAN_FRONTEND=noninteractive TEAMSERVER=$(cat /root/teamserver_ip.txt) apt update apt upgrade -yq rm /etc/resolv.conf echo "nameserver 8.8.8.8" >> /etc/resolv.conf echo "nameserver 8.8.4.4" >> /etc/resolv.conf chattr +i /etc/resolv.conf sed -i 's/#Port/Port/g' /etc/ssh/sshd_config sed -i 's/Port 22/Port 65522/g' /etc/ssh/sshd_config systemctl restart ssh iptables -I INPUT -p udp -m udp --dport 53 -j ACCEPT iptables -t nat -A PREROUTING -p udp --dport 53 -j DNAT --to-destination "${TEAMSERVER}":53 iptables -t nat -A POSTROUTING -j MASQUERADE iptables -I FORWARD -j ACCEPT iptables -P FORWARD ACCEPT sysctl net.ipv4.ip_forward=1
At this point I have the Terraform configuration to spin up 4 droplets (or machines) each in a different region, and to then run the bootstrap shell scripts associated with the specific droplet. However, this is only the start of the infrastructure build, but its a good stopping point until part 2.
Coming Up
In Part 2, I will automate the C2 Cloudflare DNS configuration and add some DNS propagation logic to the bootstrap shell scripts so that I can automate the generation of SSL certificates with Let’s Encrypt.