opexxx
5/29/2019 - 10:37 AM

Using Terraform and Docker, demoed with CyberChef

Using Terraform and Docker, demoed with CyberChef

Using Terraform and Docker on OSX

Recently I had to learn myself some Terraform for real, and it hit me - Docker (which I have come to use extensively) would be a perfect environment in which to do this.

Before you begin, make sure you have Terraform installed:

$ brew install terraform

Start the Docker TCP listener

Terraform communicates with Docker over a TCP port, enabling it to orchestrate Docker all over the fleet. On OSX, Docker disables this port by default. We will use socat to expose the Docker UNIX domain socket to the network on port 2375.

Install socat if you haven't already:

$ brew install socat

And then fire it up to connect the network socket and UNIX domain socket for Docker:

$ socat TCP-LISTEN:2375,reuseaddr,fork UNIX-CONNECT:/var/run/docker.sock

There, you're done and Docker is now reachable from Terraform over the network.

Set up our Terraform configuration

Terraform creates a way for us to describe the network state we want as a series of configuration options. The ones we will use in this super simple proof of concept:

  • provider - specifying Docker and how to reach our host
  • resource for the Docker image and where to find it in a container registry
  • resource for the container itself and what options we want for it

Put all of this in your file config.tf:

provider "docker" {
    host = "tcp://localhost:2375"
}

resource "docker_image" "cyberchef" {
    name = "remnux/cyberchef"
}

resource "docker_container" "cyberchef-image" {
    name = "cyberchef-image"
    image = "${docker_image.cyberchef.latest}"
    ports {
      internal = 8080
      external = 8080
    }
}

Make surey you've initialized Terraform:

$ terraform init

You should see something in green that looks like Terraform has been successfully initialized!

Once you have that, ask Terraform to come up with a plan to get from your current state to where you want it to be, and store that in config.tfplan:

$ terraform plan -out config.tfplan

You should see output like this (I've already fetched the image, so you might see it fetch the image if you don't have it):

Refreshing Terraform state in-memory prior to plan...
The refreshed state will be used to calculate this plan, but will not be
persisted to local or remote state storage.

docker_image.cyberchef: Refreshing state... (ID: sha256:24a9170bee05c6cac9f61078a241293f...4e3d09650835a0300ba83dremnux/cyberchef)
docker_container.cyberchef-image: Refreshing state... (ID: da4aaf97029f548b7772d724c81d6ec50f293048b898fc515fedde7fb95061d0)

------------------------------------------------------------------------

An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
  + create
  
Terraform will perform the following actions:

  + docker_container.cyberchef-image
      id:               <computed>
      attach:           "false"
      bridge:           <computed>
      container_logs:   <computed>
      exit_code:        <computed>
      gateway:          <computed>
      image:            "sha256:24a9170bee05c6cac9f61078a241293f55ce1a0dc84e3d09650835a0300ba83d"
      ip_address:       <computed>
      ip_prefix_length: <computed>
      log_driver:       "json-file"
      logs:             "false"
      must_run:         "true"
      name:             "cyberchef-image"
      network_data.#:   <computed>
      ports.#:          "1"
      ports.0.external: "8080"
      ports.0.internal: "8080"
      ports.0.ip:       "0.0.0.0"
      ports.0.protocol: "tcp"
      restart:          "no"
      rm:               "false"
      start:            "true"

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

------------------------------------------------------------------------

This plan was saved to: config.tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "config.tfplan"

And that's a promising result - if we ask Terraform to execute this plan, we'll get a Docker container runinng with those options. Let's apply the plan:

$ terraform apply "config.tfplan"

And you should see output like this:

docker_container.cyberchef-image: Creating...
  attach:           "" => "false"
  bridge:           "" => "<computed>"
  container_logs:   "" => "<computed>"
  exit_code:        "" => "<computed>"
  gateway:          "" => "<computed>"
  image:            "" => "sha256:24a9170bee05c6cac9f61078a241293f55ce1a0dc84e3d09650835a0300ba83d"
  ip_address:       "" => "<computed>"
  ip_prefix_length: "" => "<computed>"
  log_driver:       "" => "json-file"
  logs:             "" => "false"
  must_run:         "" => "true"
  name:             "" => "cyberchef-image"
  network_data.#:   "" => "<computed>"
  ports.#:          "" => "1"
  ports.0.external: "" => "8080"
  ports.0.internal: "" => "8080"
  ports.0.ip:       "" => "0.0.0.0"
  ports.0.protocol: "" => "tcp"
  restart:          "" => "no"
  rm:               "" => "false"
  start:            "" => "true"
docker_container.cyberchef-image: Creation complete after 2s (ID: a320838c2841d660d77a85ab6459270e1c65d3f9ad70513017aa3cff2e328341)

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

And voila, you should have a running Docker image, in this case of CyberChef:

$  docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
a320838c2841        24a9170bee05        "grunt dev"         18 seconds ago      Up 15 seconds       0.0.0.0:8080->8080/tcp   cyberchef-image

Verify that it's accessible over the local network on port 8080 and begin using the awesome CyberChef resource for your analysis needs.

$ open http://localhost:8080