Skip to content

Commit

Permalink
docs(backends): managing multiple terraform state files (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
julie-ng committed Jun 16, 2021
1 parent 0a063b6 commit a1357ad
Show file tree
Hide file tree
Showing 2 changed files with 114 additions and 30 deletions.
134 changes: 108 additions & 26 deletions backends/README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,132 @@
# Terraform State Management
# Terraform - Remote State Management

## Local State - Default
_Last Updated: 16 June 2021
Description: the document below describes how Terraform State is managed in this project. Your use case may differ._

By default this project uses local state for low barrier to entry for you to deploy and learn about end to end governance.
By default this project uses local state for low barrier to entry for you to deploy and learn about end to end governance.

In this way, you would only* need the following Terraform commands.
When integrating CI/CD automation or collaborating across Teams, Terraform needs a remote backend to keep track of your infrastructre's state. The Azure Remote Backend is an Azure Storage Account.

## Use Multiple Storage Accounts

Although it is possible to use a single Storage Account to back multiple Terraform state files, that should only be done if the security boundary is the same. In the diagram below, the non-production storage account named `devstorageaccount` hosts state files for two environments:
- `dev.tfstate`
- `staging.tfstate`

Following security best practices and Principle of Least Privilege, the production state is separated into its own Storage Account named `prodstorageaccount`.

![RBAC'd State Management](./../images/tf-state-rbac.svg)

Using multiple accounts gives us _different scopes_ for apply Role Based Access Control (RBAC), so we can limit access to production resources. In the diagram above there are two different Service Principals whose access is limited to their respective production vs non-production scopes.

_Note: the diagram above assumes all non-production environments have the same security boundary. If there are business and security requirements to separate the `dev` and `staging` environments, then 3 different storage accounts would be required._

## Working with Multiple Backends/Environments

### Create Backend Configuration Files

To work with multiple Terraform remote backends, we will also need multiple configurations. Because they contain tokens or access keys, be careful not to accidentally check them into git. This project ignores all `*.backend.hcl` files.

To get started, create a new file, for example `dev.backend.hcl` and copy the contents from `backend.hcl.sample`. Replace the placeholder values with your own configuration. Create separate configuration files per backend.

### Target Different Backends

When you initialize the Terraform project, you can specify which backend configuration should be used, for example to target or **development** setup:

```bash
terraform init
terraform plan
terraform apply
terraform init -backend=true -backend-config=dev.backend.hcl
```

_*assuming you've already logged into Azure and set your Azure DevOps Personal Access Token (PAT)_
### Toggling Different Backends

## Remote State - for CI
If we want to target a different state file, we would change the `-backend-config` flag.

If running with CI or sharing between engineers, you would choose to store state in an Azure Storage Account.
Please note you may need to remove your local `.terraform` folder when switching backends. A scenario for this is testing newer versions of Terraform or [official azurerm provider](https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs) on non-production resources (usually on a different git branch), before applying them to production resources.

And to comply with governance best practices, we have 2 different storage accounts to create a security boundary between production and non-production resources.
---

![RBAC'd State Management](./../images/tf-state-rbac.svg)
If you do not have any storage accounts, follow the instructions below to create one you can use for testing. Do not use in production without evaluating the configuration for your use case.

## Create Storage Accounts with Azure CLI

| Step in Azure Portal | [Azure CLI](./setup.azcli)|
|:--|:--|
| [Create a storage account*](https://docs.microsoft.com/en-us/azure/storage/common/storage-account-create?tabs=azure-portal) | `az storage account create …` |
| [Create Blob container](https://docs.microsoft.com/en-us/azure/storage/blobs/storage-blobs-introduction#containers) | `az storage container create …` |
| [Generate SAS token](https://docs.microsoft.com/en-us/rest/api/storageservices/delegate-access-with-shared-access-signature) for this storage account | `az storage account generate-sas …` |
### Create a Storage Acount

*Don't forget to [disable public read access](https://docs.microsoft.com/en-us/azure/storage/blobs/anonymous-read-access-configure?tabs=portal) - otherwise everyone can read your credentials!
If you need to create a storage account for your Terraform state files, you can do so with the Azure CLI.

### Setup
Configure some resource names to your liking:

Copy `backend.hcl.sample` to for example, `dev.backend.hcl` and edit the contents to point to your storage account and state file.
```bash
export TF_BACKEND_RG_NAME=terraform-workspaces-rg
export TF_BACKEND_STORAGEACCT_NAME=yourstorageaccountname # must be globally unique
export TF_BACKEND_CONTAINER_NAME=terraform
export TF_BACKEND_LOCATION=eastus
```

### Multiple environments
### Create a Resource Group

To target **development** setup:
We'll create a dedicated resource group for our state files.

```bash
az group create \
--name $TF_BACKEND_RG_NAME \
--location $TF_BACKEND_LOCATION
```
terraform init -backend=true -backend-config=./backends/dev.backend.hcl

### Create a Storage Account

This holds your actual Terraform state file. Note that we **explicitly disable public access**.

```bash
az storage account create \
--name $TF_BACKEND_STORAGEACCT_NAME \
--resource-group $TF_BACKEND_RG_NAME \
--kind StorageV2 \
--sku Standard_LRS \
--https-only true \
--allow-blob-public-access false
```

### Create a Blob Storage Container

Blobs always need a parent container.

```bash
az storage container create \
--name $TF_BACKEND_CONTAINER_NAME \
--account-name $TF_BACKEND_STORAGEACCT_NAME \
--public-acces off \
--auth-mode login
```

### Get Credentials for Storage Account

#### Option A - Get Storage Account Key

To get the Storage Account key, we'll pipe the Azure CLI response to [jq](https://stedolan.github.io/jq/) to extract just the *first* storage account key.

```bash
az storage account keys list \
--resource-group $TF_BACKEND_RG_NAME \
--account-name $TF_BACKEND_STORAGEACCT_NAME \
| jq -r '.[0].value'
```

To target **production** setup:
If you use an Access Key, uncomment the `access_key="…"` line in your backend config file.

Please protect this access key, which allows access to your *entire* Storage Account, including [Files](https://azure.microsoft.com/services/storage/files/), [Tables](https://azure.microsoft.com/services/storage/tables/) and [Queues](https://docs.microsoft.com/azure/storage/queues/storage-queues-introduction).

#### Option B - Shared Access Signature (SAS Token)

Instead of using the access key, we will create a Shared Access Signature (SAS) token for just the Azure Blob Service that expires in 7 days.

```bash
az storage account generate-sas \
--permissions cdlruwap \
--account-name $TF_BACKEND_STORAGEACCT_NAME \
--services b \
--resource-types sco \
--expiry $(date -v+7d '+%Y-%m-%dT%H:%MZ') \
-o tsv
```
terraform init -backend=true -backend-config=./backends/prod.backend.hcl
```

If you use an SAS token, uncomment the `sas_token="…"` line in your backend config file.
10 changes: 6 additions & 4 deletions backends/backend.hcl.sample
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
storage_account_name="azurestorageaccountname"
container_name="storagecontainername"
key="project.tfstate"
sas_token="?sv=2019-12-12…"
storage_account_name="STORAGE_ACCOUNT_NAME"
container_name="STORAGE_CONTAINER_NAME"
key="FILENAME.tfstate"
# To authenticate to the Storage account, pick and uncomment one of the options below:
# sas_token="?sv=2019-12-12…" # or account key
# access_key="…" # or SAS token

0 comments on commit a1357ad

Please sign in to comment.