Skip to main content

The pieces

Each environment (dev, QA, and production) has its own tenant in the Authsignal Portal. All of the Authsignal configuration lives in one shared module, and every environment uses the same module. Each environment has a small folder that applies the module to its tenant. A few values differ per tenant, such as the domain passkeys belong to: dev.example.com in dev and example.com in production. These are variables, and each environment sets them in its terraform.tfvars file. Each environment also has its own state, which is Terraform’s record of the tenant resources it manages.

Repository layout

The module can be generated from the dev tenant or written by hand. Paths in these guides are relative to the repository root. Commands that start with cd assume you’re at the root.

Environment files

These are the files in envs/dev. Every environment has the same main.tf and variables.tf, apart from the backend settings described in Where state is stored. Only the values in terraform.tfvars differ.
version = "~> 3.12" allows any 3.x release from 3.12 on. terraform init records the exact version in .terraform.lock.hcl. Commit that file in every environment folder, so dev, QA, and production all run the same provider version. To upgrade, change version if needed, run terraform init -upgrade in envs/dev, and copy the updated lock file into the other environment folders. The module declares the same variables again in its own variables.tf, because a module only sees the values passed into it.

Where state is stored

Terraform saves state every time you apply. Without a backend, it writes state to a terraform.tfstate file in the environment’s folder. That’s fine for trying the workflow out, but the file exists only on your machine. If it’s lost, Terraform no longer knows which resources it manages. Never commit state files. State can hold sensitive values, and a committed copy goes out of date as soon as anyone applies. The .gitignore in What not to commit excludes them. For real use, add a backend to each environment’s main.tf so state is kept in shared storage outside the repository. For example, with an S3 bucket that already exists:
Give each environment a different key, so each tenant has its own state. Run terraform init again after adding or changing a backend. HashiCorp’s backend documentation covers S3, HCP Terraform, and the other options. Turn on versioning for the bucket, so you can restore an earlier copy of the state if it’s lost or corrupted. Keep the bucket’s public access blocked. Developers only need the dev state. Give only the pipeline access to the QA and production state, including their .tflock lock files. With S3, that means only the pipeline’s IAM role can read and write them. Keeping QA and production state in a separate bucket from dev makes this easier than setting permissions on individual files. HCP Terraform is another option. It keeps state and also runs the plans. Use a cloud block in place of backend:
Give each environment its own workspace. In the dev workspace, set the execution mode to Local, so plans in envs/dev run on your machine with the environment variables from Connecting to a tenant. With the default, Remote, plans run in HCP Terraform and don’t see them. QA and production stay on Remote, as in Running QA and production.

Connecting to a tenant

The provider reads three environment variables. On your machine, set them for the dev tenant only. They last until you close the terminal.
  • AUTHSIGNAL_HOST is the Management API URL for your tenant’s region. It must end in /v1/management.
  • The tenant ID and Management API secret key are on the API keys page in the Authsignal Portal, under Settings → API keys.
  • read -rs waits for you to paste the secret key and press Enter. Nothing shows as you paste it, and the key stays out of your shell history. Keep read and export on one line. On separate lines, pasting the whole block can make read take the export line as the key.
  • Never commit the secret.
The secret key decides which tenant Terraform talks to, whichever folder you’re in. On your machine, only run Terraform in envs/dev.

Running QA and production

Run Terraform for QA and production only from a CI pipeline, such as GitHub Actions. That keeps the QA and production secret keys off developer machines. Anyone who has them can change those tenants directly. The pipeline runs these commands in the environment’s folder:
However you build the pipeline, it needs to:
  • Keep each environment’s host, tenant ID, and secret key in your CI system’s secrets.
  • Make the QA and production secrets available only to runs from your main branch, and protect main so every change needs an approved pull request. In GitHub Actions, put the secrets in a qa or production environment that only allows deployments from main.
  • Never run a QA or production plan on a pull request or any other branch.
  • Pause after the plan, and apply only once someone else has read the plan and approved it.
  • Reach the state backend.
Terraform code can run other programs during a plan, and those programs can read the secret key. A plan of code nobody has reviewed can send the key anywhere, so approving only the apply doesn’t protect it. Anyone who can change the pipeline, or run their own code with a QA or production key in any other way, can change that tenant. A change to envs/production, the module, or the pipeline’s own files changes production once it’s applied, so ask for review from the people who own production. HashiCorp’s Running Terraform in automation covers how to run Terraform this way. For secrets, approvals, and branch rules, see your CI system’s documentation, such as GitHub Actions environments. If you can, run QA and production as two stages of one pipeline, with production waiting for its own approval. Production then gets the same commit that was applied to QA. Separate pipelines each apply whatever is on main when they run, which can include changes QA hasn’t had yet. If the plan and the apply run as separate jobs, the pipeline passes tfplan between them. It holds the same values as state, so keep the artifact for as short a time as your CI system allows and limit who can download it. If you use HCP Terraform, the workspace replaces the pipeline. Set the three AUTHSIGNAL_ values as environment variables in the QA and production workspaces, and mark the secret key as sensitive. Turn Auto-apply off and each run waits for approval.
Anyone who can queue a plan in an HCP Terraform workspace can run their own code with the secret key stored in that workspace, from their own machine or branch. If the workspace is connected to your repository, HCP Terraform also plans every pull request by default, with the secret key stored in the workspace. Turn off automatic speculative plans in the QA and production workspaces.

Reading a plan

terraform plan lists each resource it would touch, marked with a symbol:
  • + means it’s created.
  • ~ means it’s updated in place.
  • - means it’s destroyed.
  • -/+ means it’s destroyed and created again.
Resources being imported are marked will be imported. For each change, the left side is the current value in the tenant and the right side is the value from the code:
The last line sums it up, such as Plan: 0 to add, 1 to change, 0 to destroy. No changes means the tenant already matches the code. These guides save the plan with terraform plan -out=tfplan, then run terraform apply tfplan. That applies exactly the saved plan without asking you to confirm, so read the plan first. Running terraform apply on its own makes a new plan and asks you to type yes.

What not to commit

Add this to the repository’s .gitignore.
Don’t ignore .terraform.lock.hcl. Commit it, as in Environment files.