# Run the Teleport Terraform Provider in CI or Cloud

This guide covers how to run the Teleport Terraform Provider from:

- your CI/CD pipelines on:

  - GitHub Actions (we'll use the `github` join method)
  - GitlabCI (we'll use the `gitlab` join method)
  - CircleCI (we'll use the `circleci` join method)

- a cloud VM on:

  - AWS (we'll use the `aws` join method)
  - GCP (we'll use the `gcp` join method)

---

NOTE

Running the Terraform provider with native Machine & Workload Identity is supported on Azure, inside a Kubernetes pod, and on servers with Trusted Platform Module (TPM). While those setups are not described in detail in this guide, you can follow their regular Machine & Workload Identity guides and replace the "Configure `tbot`" step by passing the join method and token to the provider.

- [Azure Machine & Workload Identity guide](https://goteleport.com/docs/machine-workload-identity/deployment/azure.md)
- [Kubernetes Machine & Workload Identity guide](https://goteleport.com/docs/machine-workload-identity/deployment/kubernetes.md)
- [TPM Machine & Workload Identity guide](https://goteleport.com/docs/machine-workload-identity/deployment/linux-tpm.md)

HCP Terraform (Terraform Cloud) and self-hosted Terraform Enterprise are supported but require special configuration, so refer to our [dedicated guide](https://goteleport.com/docs/zero-trust-access/infrastructure-as-code/terraform-provider/terraform-cloud.md).

---

This guide does not cover running Teleport locally, on a dedicated server or on certain platforms. See the following more specific guides for those cases:

- [Run the Terraform Provider locally](https://goteleport.com/docs/zero-trust-access/infrastructure-as-code/terraform-provider/local.md)
- [Run the Teleport Terraform Provider on a server](https://goteleport.com/docs/zero-trust-access/infrastructure-as-code/terraform-provider/dedicated-server.md)
- [Run the Teleport Terraform Provider on Spacelift](https://goteleport.com/docs/zero-trust-access/infrastructure-as-code/terraform-provider/spacelift.md)

## How it works

This setup asks the runtime (the CI/CD system, cloud provider, container engine, ...) for an identity proof. This proof is then used directly by the Terraform provider to connect to Teleport and obtain credentials. In this setup, there is no `tbot` daemon involved as the Terraform provider can natively obtain the identity proof and join the Teleport cluster.

The setup only works for select runtimes which Teleport has [a delegated join method](https://goteleport.com/docs/reference/deployment/join-methods.md#delegated-join-methods) for (e.g. GitHub Actions, GitLab CI, ...)

## Prerequisites

You need either:

- A GCP or AWS VM with terraform installed
- A git repo able to run GitHub Actions, GitLab CI or CircleCI jobs

You also need:

- A running Teleport cluster. If you want to get started with Teleport, [sign up](https://goteleport.com/signup) for a free trial or [set up a demo environment](https://goteleport.com/docs/get-started/deploy-community.md).

- The `tctl` and `tsh` clients.

  Installing `tctl` and `tsh` clients

  1. Determine the version of your Teleport cluster. The `tctl` and `tsh` clients must be at most one major version behind your Teleport cluster version. Send a GET request to the Proxy Service at `/v1/webapi/find` and use a JSON query tool to obtain your cluster version. Replace teleport.example.com:443 with the web address of your Teleport Proxy Service:

     ```
     $ TELEPORT_DOMAIN=teleport.example.com:443
     $ TELEPORT_VERSION="$(curl -s https://$TELEPORT_DOMAIN/v1/webapi/find | jq -r '.server_version')"
     ```

  2. Follow the instructions for your platform to install `tctl` and `tsh` clients:

     **Mac**

     Download the signed macOS .pkg installer for Teleport, which includes the `tctl` and `tsh` clients:

     ```
     $ curl -O https://cdn.teleport.dev/teleport-${TELEPORT_VERSION?}.pkg
     ```

     In Finder double-click the `pkg` file to begin installation.

     ---

     DANGER

     Using Homebrew to install Teleport is not supported. The Teleport package in Homebrew is not maintained by Teleport and we can't guarantee its reliability or security.

     ---

     **Windows - Powershell**

     ```
     $ curl.exe -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-windows-amd64-bin.zip
     Unzip the archive and move the `tctl` and `tsh` clients to your %PATH%
     NOTE: Do not place the `tctl` and `tsh` clients in the System32 directory, as this can cause issues when using WinSCP.
     Use %SystemRoot% (C:\Windows) or %USERPROFILE% (C:\Users\<username>) instead.
     ```

     **Linux**

     All of the Teleport binaries in Linux installations include the `tctl` and `tsh` clients. For more options (including RPM/DEB packages and downloads for i386/ARM/ARM64) see our [installation page](https://goteleport.com/docs/installation.md).

     ```
     $ curl -O https://cdn.teleport.dev/teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ tar -xzf teleport-v${TELEPORT_VERSION?}-linux-amd64-bin.tar.gz
     $ cd teleport
     $ sudo ./install
     Teleport binaries have been copied to /usr/local/bin
     ```

## Step 1/4. Create the Terraform provider bot

In this step you will create [a bot](https://goteleport.com/docs/reference/architecture/machine-id-architecture.md#what-is-a-bot) named `terraform`.

Create a file named `terraform-bot.yaml`:

```
kind: bot
version: v1
metadata:
  name: terraform
spec:
  # The terraform-provider is a default role shipped in Teleport granting access
  # to every resource supported by the terraform provider.
  roles: ["terraform-provider"]

```

Then apply it with `tctl`:

```
$ tctl create terraform-bot.yaml

This is an admin-level action and requires MFA to complete
Tap any security key
Detected security key tap
bot "terraform" has been created
```

At this point, you should see your new bot:

```
$ tctl bots ls

Bot       User          Roles
--------- ------------- ------------------
terraform bot-terraform terraform-provider
```

## Step 2/4. Create the bot join token

In this step you will create a token allowing a process to connect to Teleport as the `terraform` bot you created earlier.

The token type and configuration depends on where the Terraform provider is running. See [the joining reference](https://goteleport.com/docs/reference/deployment/join-methods.md) for more details about the joining process, the different join methods, types of tokens, and the fields they support.

**GitHub Actions**

To allow the Terraform Provider to join from GitHub Actions workflows in organization/repository, create the following `terraform-bot-token.yaml`:

```
kind: token
version: v2
metadata:
  name: terraform-bot
spec:
  roles: [Bot]
  join_method: github
  bot_name: terraform
  github:
    # allow specifies rules that control which GitHub Actions runs will be
    # granted access. Those not matching any allow rule will be denied.
    allow:
    - repository: organization/repository

```

**GitLab CI**

To allow the Terraform Provider to join from GitLab CI pipelines in group/project on the gitlab.example.com GitLab instance, create the following `terraform-bot-token.yaml`:

```
kind: token
version: v2
metadata:
  name: terraform-bot
spec:
  roles: [Bot]
  join_method: gitlab
  bot_name: terraform
  gitlab:
    # domain should be the domain of your GitLab instance. If you are using
    # GitLab's cloud hosted offering, omit this field entirely.
    domain: gitlab.example.com
    # allow specifies rules that control which GitLab tokens will be accepted
    # by Teleport. Tokens not matching any allow rule will be denied.
    allow:
      - project_path: group/project

```

**CircleCI**

In order to configure the rules for which CircleCI workflows will be allowed to connect to your Teleport cluster, you must determine the ID of your CircleCI organization and create a CircleCI context.

### Find your organization ID

Open CircleCI and navigate to "Organization settings" from the navbar. You should be presented with an interface titled "Overview" with a section called "Organization ID". Note this value down and substitute organization-id in configuration examples with this.

### Create a context

CircleCI has an organization-level concept called **contexts**, which allow you to configure a series of secrets that should be exposed to a workflow job. You can configure CircleCI to control which actors are allowed to trigger jobs associated with a context.

The contexts that a workflow job has been assigned are also encoded in the identity token that CircleCI creates for the job. This makes them an ideal way for Teleport to determine which CircleCI jobs should be granted access to the Teleport cluster.

In this example, you will create a CircleCI context named `teleport-access`. You will then grant this context access to your Teleport cluster.

To create the CircleCI context, open up "Organization settings" in CircleCI and navigate to "Contexts". Click "Create Context" and provide teleport-access as the name of the context you wish to create. You may substitute this value for a string that makes more sense to your organization, but ensure in future steps of this guide that you replace teleport-access with your value.

Select the context you have just created. You will now be on a page that allows you to configure the context. To determine the ID of the context to use when configuring Teleport, locate the URL of the context settings page, which should have a format similar to the following:

```
https://app.circleci.com/settings/organization/github/gravitational/contexts/00000000-0000-0000-0000-000000000000

```

In this case, the context ID is: `00000000-0000-0000-0000-000000000000`.

Note this value down and substitute context-id in configuration examples with it.

Then, create the following `terraform-bot-token.yaml`, replacing context-id with your context ID:

```
kind: token
version: v2
metadata:
  name: terraform-bot
spec:
  roles: [Bot]
  join_method: circleci
  bot_name: terraform
  circleci:
    organization_id: organization-id
    # allow specifies the rules by which the Auth Service determines if `tbot`
    # should be allowed to join.
    allow:
    - context_id: context-id

```

**AWS**

Make sure you have:

- An AWS IAM role that you wish to grant access to your Teleport cluster. This role must be granted `sts:GetCallerIdentity`. In this guide, this role will be named instance-iam-role.
- An AWS EC2 virtual machine configured with the IAM role attached where you want to run the Terraform provider.
- The AWS account ID: 111111111111

Then, create the following `terraform-bot-token.yaml`:

```
kind: token
version: v2
metadata:
  name: terraform-bot
spec:
  roles: [Bot]
  bot_name: terraform
  join_method: iam
  allow:
    - aws_account: "111111111111"
      aws_arn: "arn:aws:sts::organization-id:assumed-role/instance-iam-role/i-*"

```

**GCP**

Make sure you have:

- A GCP Service Account (SA) attached to the VM you want to run the provider on. This cannot be the default compute SA. In this guide, this SA will be named my-service-account.
- The GCP project ID: my-project-123456

Then, create the following `terraform-bot-token.yaml`:

```
kind: token
version: v2
metadata:
  name: terraform-bot
spec:
  roles: [Bot]
  bot_name: terraform
  join_method: gcp
  gcp:
    allow:
    - project_ids:
        - "my-project-123456"
      service_accounts:
        - "my-service-account@my-project-123456.iam.gserviceaccount.com"

```

Create the bot token described by the `terraform-bot-token.yaml` manifest:

```
$ tctl create -f terraform-bot-token.yaml

This is an admin-level action and requires MFA to complete
Tap any security key
Detected security key tap
provision_token "terraform-bot" has been created
```

## Step 3/4. Configure your Terraform provider

In this step you will write a minimal Terraform code that configures the provider to connect to your Teleport cluster and join with the token you previously created.

To do this you need:

- your Teleport cluster domain including the port: teleport.example.com:443. You can find this in the URL when accessing the Web UI.
- the join method of the token you've created. This is the `spec.join_method` field in the `terraform-bot-token.yaml`: token-join-method.

Create this minimal `main.tf` file:

```
terraform {
  required_providers {
    teleport = {
      source  = "terraform.releases.teleport.dev/gravitational/teleport"
      version = "~> 19.0"
    }
  }
}

provider "teleport" {
  addr        = "teleport.example.com:443"
  join_method = "token-join-method"
  join_token  = "terraform-bot"
}

# We must create a test role, if we don't declare resources, Terraform won't try to
# connect to Teleport and we won't be able to validate the setup.
resource "teleport_role" "test" {
  version = "v7"
  metadata = {
    name        = "test"
    description = "Dummy role to validate Terraform Provider setup"
    labels = {
      test = "yes"
    }
  }

  spec = {}
}

```

**GitHub Actions**

Copy the `main.tf` file in the GitHub repo that runs GitHub Actions pipelines.

**GitLab CI**

Copy the `main.tf` file in the GitLab repo that runs GitLab CI pipelines.

**CircleCI**

Copy the `main.tf` file in the Git repo that runs CircleCI pipelines.

**AWS**

Copy the `main.tf` file on the AWS VM you will run Terraform from.

**GCP**

Copy the `main.tf` file on the GCP VM you will run Terraform from.

## Step 4/4. Run Terraform

This step shows minimal examples on how to run Terraform based on your environment. This code uses the default local backend which is not fit for production purposes. Especially in CI, you must use [non-local Terraform backends](https://developer.hashicorp.com/terraform/language/settings/backends/configuration) so the Terraform state is persisted across CI pipelines.

**GitHub Actions**

In the repo containing your Terraform code, create a `.github/workflows/teleport-terraform.yaml` file with the following content:

```
name: Teleport Terraform Demo
# This is a basic workflow to help you get started.
# It will take the following action whenever a push is made to the "main" branch.
on:
  push:
    branches:
    - main
jobs:
  demo:
    permissions:
      # The "id-token: write" permission is required or Machine & Workload
      # Identity will not be able to authenticate with the cluster.
      id-token: write
      contents: read
    name: terraform-plan
    runs-on: ubuntu-latest

    # You can find more advanced TF workflows at https://github.com/hashicorp/setup-terraform
    steps:
    - uses: actions/checkout@v4
    - uses: hashicorp/setup-terraform@v3
    - name: Terraform Init
      id: init
      run: terraform init
    - name: Terraform Plan
      id: plan
      run: terraform plan -no-color

```

Commit the changes and push to the `main` branch to trigger the GitHub Actions workflow. You should see a successful Terraform plan in the workflow logs.

**GitLab CI**

Recover your cluster name:

```
$ tctl status

Cluster       teleport.example.com
Version       16.2.0
...
```

In the repo containing your Terraform code, create the `.gitlab-ci.yaml` file if it does exist, and add the following content:

```
stages:
  - plan
image:
  name: hashicorp/terraform:1.9
  entrypoint: [""]
terraform-job:
  stage: plan
  # id_tokens configures ID Tokens that GitLab will automatically inject into
  # the environment of your GitLab run.
  #
  # See https://docs.gitlab.com/ee/ci/secrets/id_token_authentication.html
  # for further explanation of the id_tokens configuration in GitLab.
  id_tokens:
    TBOT_GITLAB_JWT:
      # aud for TBOT_GITLAB_JWT must be configured with the name of your
      # Teleport cluster. This is not necessarily the address of your Teleport
      # cluster and will not include a port or scheme (http/https)
      #
      # This helps the Teleport Auth Service know that the token is intended for
      # it, and not a different service or Teleport cluster.
      aud: "teleport.example.com"
  script:
    - terraform init
    - terraform plan

```

Commit the changes and push to any branch to trigger a Gitlab CI pipeline. You should see a successful Terraform plan in the pipeline logs.

**CircleCI**

In the repo containing your Terraform code add the following CircleCI config in `.circleci/config.yml`:

```
version: '2.1'
orbs:
  terraform: circleci/terraform@3.1
workflows:
  deploy_infrastructure:
    jobs:
      - terraform/init:
          tag: 1.9.5
          checkout: true
          context: teleport-access
      - terraform/plan:
          tag: 1.9.5
          context: teleport-access
          requires:
            - terraform/init

```

Commit the changes and push to any branch to trigger a CircleCI pipeline. You should see a successful Terraform plan in the pipeline logs.

**AWS**

Run the Terraform commands on the EC2 instance:

```
$ terraform init
Initializing the backend...

Initializing provider plugins...
- Finding terraform.releases.teleport.dev/gravitational/teleport versions matching ...

$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # teleport_role.test will be created
  + resource "teleport_role" "test" {
      + id       = (known after apply)
      + kind     = (known after apply)
      + metadata = {
          + name      = "test"
          + namespace = (known after apply)
        }
      + spec     = {}
      + version  = "v7"
    }

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

**GCP**

Run the Terraform commands on the GCP VM:

```
$ terraform init
Initializing the backend...

Initializing provider plugins...
- Finding terraform.releases.teleport.dev/gravitational/teleport versions matching ...

$ terraform plan
Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  + create

Terraform will perform the following actions:

  # teleport_role.test will be created
  + resource "teleport_role" "test" {
      + id       = (known after apply)
      + kind     = (known after apply)
      + metadata = {
          + name      = "test"
          + namespace = (known after apply)
        }
      + spec     = {}
      + version  = "v7"
    }

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