Terraform - GCP

Getting Started - What we are going to build

The goal of this guide is to use Terraform to deploy Twingate on a Google Cloud Platform (GCP) VPC including all required components (Connector, Remote Network) and Configuration Items (Resource, Group, etc.):

938938

First let's setup a new folder for our Terraform code to reside:

mkdir twingate_gcp_demo
cd twingate_gcp_demo

All commands below will be run from within the folder created, we can now open this folder in our editor of choice.

📘

Storing Terraform Code

For simplicity and readability all the code will be included in a single main.tf file. For more information on Terraform code structure, please see this guide and this guide.

Setting Up the Terraform Providers

📘

On Terraform Providers

A Terraform Provider is a Terraform Plugin that leverages an external API (like the Twingate API) and makes certain functions available right from within Terraform without having to know anything about the external API itself.

First let's setup the provider configuration: create a new file called main.tf (amending each value to match your environment/requirements):

terraform {
  required_providers {
    twingate = {
      source = "twingate/twingate"
      version = "0.1.10"
    }

    }
}

provider "google" {
  project = "twingate-projects"
  region  = "europe-west2"
  zone    = "europe-west2-c"
}

👍

Twingate Terraform Provider

The latest Terraform Provider for Twingate is always available here.

We need 2 providers in this case: One for Twingate (it will allow us to create and configure Twingate specific Configuration Items) and one for GCP (it will allow us to spin up the required infrastructure / VPC).

Once this is in place we can run the following command to download and install those providers locally on your system:

terraform init

You should see the provider plugins being initialized and installed successfully:

Initializing the backend...

Initializing provider plugins...
- Finding twingate/twingate versions matching "0.1.10"...
- Finding latest version of hashicorp/google...
- Installing twingate/twingate v0.1.10...
- Installed twingate/twingate v0.1.10 (self-signed, key ID E8EBBE80BA0C9369)
- Installing hashicorp/google v4.30.0...
- Installed hashicorp/google v4.30.0 (signed by HashiCorp)

Partner and community providers are signed by their developers.
If you'd like to know more about provider signing, you can read about it here:
https://www.terraform.io/docs/cli/plugins/signing.html

Terraform has created a lock file .terraform.lock.hcl to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.

Terraform has been successfully initialized!

🚧

GCP Credentials

This guide assumes you have credentials setup to connect to GCP, if not you can find details of how to do this on the official Terraform page here.

Creating the Twingate infrastructure

To create the Twingate infrastructure we will need a way to authenticate with Twingate. To do this we will create a new API key which we will use with Terraform.

In order to do so: Navigate to Settings -> API then Generate a new Token:

495495

You will need to set your token with Read, Write & Provision permissions, but you may want to restrict the allowed IP range to only where you will run your Terraform commands from.

Click on generate and copy the token.

Terraform vars file

Like all programming languages, terraform can use variables to define values. Let's create a new file called terraform.tfvars, we will use it to define useful variables.

Add the following lines to this file, adding in the value of the API Token into tg_api_key and the name of your Twingate Tenant. (The Tenant is the mycorp part of the URL to your Twingate Console if the full URL was https://mycorp.twingate.com)

tg_api_key="Copied API key"
tg_network="mycorp"

❗️

Using Source Control

If you are using or thinking of using source control, please ensure you exclude this file for security reasons. (You don't want your API tokens visible in clear text on a public repository!)

We can then add these variables to the main.tf file:

variable "tg_api_key" {}
variable "tg_network" {}

And reference these in the provider config (no change needed):

provider "twingate" {
  api_token = var.tg_api_key
  network   = var.tg_network
}

Now we can start creating resources in Twingate, let's first start with the highest level concept: the Twingate Remote Network:

resource "twingate_remote_network" "gcp_demo_network" {
  name = "gcp demo remote network"
}

Some clarification on what is happening here:

  • resource does NOT refer to a Twingate Resource but means a resource in the Terraform sense
  • twingate_remote_network refers to the type of object we are creating as defined by the Twingate Terraform provider
  • gcp_demo_network is a unique ID we are specifying in order to be able to reference this object when doing other things (like creating a Connector attached to this newly created Remote Network)
  • gcp demo remote network is the actual name used to name our newly created Remote Network (and is the name visible in the admin console)

Let's now create the connector:

resource "twingate_connector" "gcp_demo_connector" {
  remote_network_id = twingate_remote_network.gcp_demo_network.id
}

Some clarification here as well:

  • twingate_connector refers to a Connector as per the Terraform provider for Twingate
  • remote_network_id is the only parameter required to create a Connector: this is consistent with creating a connector from the Admin Console: you need to attach it to a remote network.
  • twingate_remote_network.gcp_demo_network.id is read as <Terraform resource type>.<Terraform resource name>.<internal ID of that object>

And finally generating the Tokens we will use when setting up the connector:

resource "twingate_connector_tokens" "twingate_connector_tokens" {
  connector_id = twingate_connector.gcp_demo_connector.id
}

It's a good idea at this point to do a quick check on our Terrform script by running:

terraform plan

📘

On Terraform Plan

Terraform plan is a non destructive command: it runs a simulation of what Terraform needs to do: it is therefore safe to run and is useful towards troubleshooting your code.

You should see a response similar to this:

Terraform will perform the following actions:

  # twingate_connector.gcp_demo_connector will be created
  + resource "twingate_connector" "gcp_demo_connector" {
      + id                = (known after apply)
      + name              = (known after apply)
      + remote_network_id = (known after apply)
    }

  # twingate_connector_tokens.twingate_connector_tokens will be created
  + resource "twingate_connector_tokens" "twingate_connector_tokens" {
      + access_token  = (sensitive value)
      + connector_id  = (known after apply)
      + id            = (known after apply)
      + refresh_token = (sensitive value)
    }

  # twingate_remote_network.gcp_demo_network will be created
  + resource "twingate_remote_network" "gcp_demo_network" {
      + id   = (known after apply)
      + name = "gcp demo remote network"
    }

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

If this is consistent with what you are seeing, we can then move onto doing the same for the GCP infrastructure needed.

Creating the GCP Infrastucture

Networking

First lets create a new VPC network by adding the following to our code:

resource "google_compute_network" "demo_vpc_network" {
  name = "twingate-demo-network"
  auto_create_subnetworks = false

}

Then we can create a subnet within the new VPC:

resource "google_compute_subnetwork" "demo_subnet" {
  name          = "twingate-demo-subnetwork"
  ip_cidr_range = "172.16.0.0/24"
  network       = google_compute_network.demo_vpc_network.id
  
}

Then we can create a firewall to allow traffic on port 80 within this subnet only:

resource "google_compute_firewall" "default" {
  name    = "firewall"
  network = google_compute_network.demo_vpc_network.id



  allow {
    protocol = "tcp"
    ports    = ["80"]
  }

  source_ranges = [google_compute_subnetwork.demo_subnet.ip_cidr_range]
}

Virtual Machines

First we can create the webserver VM, install Nginx and set a nice homepage!

resource "google_compute_instance" "vm_instance_webserver" {
  name         = "twingate-demo-webserver"
  machine_type = "e2-micro"

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-1804-bionic-v20220712"
    }
  }

  network_interface {

    network = google_compute_network.demo_vpc_network.id
    subnetwork = google_compute_subnetwork.demo_subnet.id
    access_config {
    }
  }

  metadata_startup_script = <<EOF
    
    #!/bin/bash

    apt-get -y update
    apt-get -y install nginx

    echo "
    <style>
    h1 {text-align: center;}

    </style>

    <h1>TERRAFROM &#128153; TWINGATE </h1> 

    " > /var/www/html/index.html

    service nginx start

    sudo rm -f index.nginx-debian.html

    EOF

}

Next we can create the Twingate connector VM and install Twingate with connection details created earlier:

We will be using a Terraform template file so we can inject our values created earlier for the Twingate installer.

Create a new folder called template and within this folder create a new file called twingate_client.tftpl.

Within this new file paste the following:

#!/bin/bash

curl "https://binaries.twingate.com/connector/setup.sh" | sudo TWINGATE_ACCESS_TOKEN="${accessToken}" TWINGATE_REFRESH_TOKEN="${refreshToken}" TWINGATE_URL="https://${tgnetwork}.twingate.com" bash

Then back in the main.tf file, we can create the new VM for the Twingate connector:

resource "google_compute_instance" "vm_instance_connector" {
  name         = "twingate-demo-connector"
  machine_type = "e2-micro"

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-1804-bionic-v20220712"
    }
  }

  

  network_interface {

    network = google_compute_network.demo_vpc_network.id
    subnetwork = google_compute_subnetwork.demo_subnet.id
    access_config {

    }
  }
  metadata_startup_script = templatefile("${path.module}/template/twingate_client.tftpl", { accessToken = twingate_connector_tokens.twingate_connector_tokens.access_token, refreshToken = twingate_connector_tokens.twingate_connector_tokens.refresh_token, tgnetwork = var.tg_network })

}

Finally we need to create a new Twingate Group and add in the new resource for the web server we just created:

Create a Twingate Group:

resource "twingate_group" "gcp_demo" {
  name = "gcp demo group"
}

Create a New Twingate resource:

resource "twingate_resource" "resource" {
  name = "gcp demo web sever resource"
  address = google_compute_instance.vm_instance_webserver.network_interface.0.network_ip
  remote_network_id = twingate_remote_network.gcp_demo_network.id
  group_ids = [twingate_group.gcp_demo.id]
  protocols {
    allow_icmp = true
    tcp  {
      policy = "RESTRICTED"
      ports = ["80"]
    }
    udp {
      policy = "ALLOW_ALL"
    }
  }
}

As you can see, this is restricting access to port 80. You will want to alter this depending on the application you are using.

Your final scripts should look like this:

Finished Scripts

terraform {
  required_providers {
    twingate = {
      source = "twingate/twingate"
      version = "0.1.10"
    }

    }
}

variable "tg_api_key" {}
variable "tg_network" {}


    
provider "google" {
  project = "twingate-projects"
  region  = "europe-west2"
  zone    = "europe-west2-c"
}

provider "twingate" {
  api_token = var.tg_api_key
  network   = var.tg_network
}

# Create a new remote network in Twingate
resource "twingate_remote_network" "gcp_demo_network" {
  name = "gcp demo remote network"
}

# Create a new twingate connector
resource "twingate_connector" "gcp_demo_connector" {
  remote_network_id = twingate_remote_network.gcp_demo_network.id
}

# Create the tokens for the new connector
resource "twingate_connector_tokens" "twingate_connector_tokens" {
  connector_id = twingate_connector.gcp_demo_connector.id
}



resource "google_compute_network" "demo_vpc_network" {
  name = "twingate-demo-network"
  auto_create_subnetworks = false

}

resource "google_compute_subnetwork" "demo_subnet" {
  name          = "twingate-demo-subnetwork"
  ip_cidr_range = "172.16.0.0/24"
  network       = google_compute_network.demo_vpc_network.id
  
}

resource "google_compute_firewall" "default" {
  name    = "firewall"
  network = google_compute_network.demo_vpc_network.id



  allow {
    protocol = "tcp"
    ports    = ["80"]
  }

  source_ranges = [google_compute_subnetwork.demo_subnet.ip_cidr_range]
}




resource "google_compute_instance" "vm_instance_webserver" {
  name         = "twingate-demo-webserver"
  machine_type = "e2-micro"

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-1804-bionic-v20220712"
    }
  }

  

  network_interface {
    # A default network is created for all GCP projects
    network = google_compute_network.demo_vpc_network.id
    subnetwork = google_compute_subnetwork.demo_subnet.id
    access_config {
    }
  }

  metadata_startup_script = <<EOF
    
    #!/bin/bash
    
    apt-get -y update
    apt-get -y install nginx
    
    echo "
    <style>
    h1 {text-align: center;}
    
    </style>
    
    <h1>TERRAFROM &#128153; TWINGATE </h1> 
    
    " > /var/www/html/index.html
    
    service nginx start
    
    sudo rm -f index.nginx-debian.html
    
    EOF

}

resource "google_compute_instance" "vm_instance_connector" {
  name         = "twingate-demo-connector"
  machine_type = "e2-micro"

  boot_disk {
    initialize_params {
      image = "ubuntu-os-cloud/ubuntu-1804-bionic-v20220712"
    }
  }

  network_interface {
    # A default network is created for all GCP projects
    network = google_compute_network.demo_vpc_network.id
    subnetwork = google_compute_subnetwork.demo_subnet.id
    access_config {

    }
  }
  metadata_startup_script = templatefile("${path.module}/template/twingate_client.tftpl", { accessToken = twingate_connector_tokens.twingate_connector_tokens.access_token, refreshToken = twingate_connector_tokens.twingate_connector_tokens.refresh_token, tgnetwork = var.tg_network })

}

resource "twingate_group" "gcp_demo" {
  name = "gcp demo group"
}

resource "twingate_resource" "resource" {
  name = "gcp demo web sever resource"
  address = google_compute_instance.vm_instance_webserver.network_interface.0.network_ip
  remote_network_id = twingate_remote_network.gcp_demo_network.id
  group_ids = [twingate_group.gcp_demo.id]
  protocols {
    allow_icmp = true
    tcp  {
      policy = "RESTRICTED"
      ports = ["80"]
    }
    udp {
      policy = "ALLOW_ALL"
    }
  }
}
tg_api_key="YOUR KEY HERE"
tg_network="YOU NETWORK HERE"
#!/bin/bash

curl "https://binaries.twingate.com/connector/setup.sh" | sudo TWINGATE_ACCESS_TOKEN="${accessToken}" TWINGATE_REFRESH_TOKEN="${refreshToken}" TWINGATE_URL="https://${tgnetwork}.twingate.com" bash

Deploying It All

Running our script 🏃‍♂️

The first thing we want to do is to check the script using the terraform plan command:

terraform plan

All being well this will run and show you all the resources which will be added to both Twingate and Terraform.

🚧

On Using terraform apply

As this is a brand new infrastructure you should not see anything being destroyed. Please make sure you double check what is being added and where!

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

If everything looks good we can go ahead and apply the code:

terraform apply

You will need to confirm you are happy for the changes to proceed, again double check what is happening is what you expect to happen.

You should then see all the resources being created:

twingate_group.gcp_demo: Creating...
twingate_remote_network.gcp_demo_network: Creating...
google_compute_network.demo_vpc_network: Creating...
twingate_remote_network.gcp_demo_network: Creation complete after 0s [id=UmVtb3RlTmV0d29yazoxNDg2NQ==]
twingate_connector.gcp_demo_connector: Creating...
twingate_group.gcp_demo: Creation complete after 0s [id=R3JvdXA6NTc1MzE=]
twingate_connector.gcp_demo_connector: Creation complete after 1s [id=Q29ubmVjdG9yOjIyMDU4]
twingate_connector_tokens.twingate_connector_tokens: Creating...
twingate_connector_tokens.twingate_connector_tokens: Creation complete after 0s [id=Q29ubmVjdG9yOjIyMDU4]
google_compute_network.demo_vpc_network: Still creating... [10s elapsed]
google_compute_network.demo_vpc_network: Creation complete after 11s [id=projects/twingate-projects/global/networks/twingate-demo-network]
google_compute_subnetwork.demo_subnet: Creating...
google_compute_subnetwork.demo_subnet: Still creating... [10s elapsed]
google_compute_subnetwork.demo_subnet: Still creating... [20s elapsed]
google_compute_subnetwork.demo_subnet: Creation complete after 23s [id=projects/twingate-projects/regions/europe-west2/subnetworks/twingate-demo-subnetwork]
google_compute_firewall.default: Creating...
google_compute_instance.vm_instance_webserver: Creating...
google_compute_instance.vm_instance_connector: Creating...
google_compute_firewall.default: Still creating... [10s elapsed]
google_compute_instance.vm_instance_webserver: Still creating... [10s elapsed]
google_compute_instance.vm_instance_connector: Still creating... [10s elapsed]
google_compute_firewall.default: Creation complete after 12s [id=projects/twingate-projects/global/firewalls/firewall]
google_compute_instance.vm_instance_webserver: Creation complete after 15s [id=projects/twingate-projects/zones/europe-west2-c/instances/twingate-demo-webserver]
twingate_resource.resource: Creating...
google_compute_instance.vm_instance_connector: Creation complete after 15s [id=projects/twingate-projects/zones/europe-west2-c/instances/twingate-demo-connector]
twingate_resource.resource: Creation complete after 1s [id=UmVzb3VyY2U6MjE4NzU0Ng==]

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

Granting access

This part could be achieved via code, however it's more likely you will be managing this manually from the interface due to other integrations, i.e. Azure AD.

Navigate to Team -> Groups

You will see the newly created group in there:

504504

Click on this group, then users and add your user account.

You should also see the new network:

392392

And resource that has been created:

403403

Testing the connection

Provisioning the virtual machines can take a few minutes, so you may want to pause at this point for 5 minutes to give everything time to spin up.

When the connection has been established it will be visible from the Twingate UI:

11881188

Next we can test the connection the web server over Twingate. To do this close your Twingate client if it is open, then re-open it. You should see the new resource as an option:

864864

Click to open this resource in a web browser, you should now see the test page on the web server!

994994

Did this page help you?