Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: rewrite Terraform backend shell script to external Bicep template #607

Merged
merged 15 commits into from
Dec 5, 2024
81 changes: 35 additions & 46 deletions scripts/terraform-backend/README.md
Original file line number Diff line number Diff line change
@@ -1,69 +1,58 @@
# Terraform backend script
# Create Terraform backend

This directory contains a script `terraform-backend.sh` that will create an Azure Storage account that can be used as a Terraform backend.

It accepts the following arguments:

1. The path of the JSON file containing the Terraform backend configuration.
1. The Azure region to create the storage account in.
1. (Optional) A space-separate string of IP addresses that should be able to bypass the storage account firewall.
This directory contains a Bicep template that will create an Azure Storage account that can be used to store Terraform state files.

## Prerequisites

- [Install Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli) - to create Azure resource group and Storage account.
- [Install jq](https://stedolan.github.io/jq/download/) - to parse JSON configuration file.
- Azure role `Owner` - to create Azure resource group and Storage account.

## Configuration specification

Example configuration:

```json
{
"resource_group_name": "tfstate",
"storage_account_name": "tfstate32417",
"container_name": "tfstate",
"use_azuread_auth": true
}
```
- Install [Azure CLI](https://learn.microsoft.com/en-us/cli/azure/install-azure-cli).

## Usage

### Create Azure Storage account

1. Login to Azure:

```console
az login
```
```console
az login
```

1. Set Azure subscription:
1. Set active subscription:

```console
az account set -s <SUBSCRIPTION_NAME_OR_ID>
```
```console
az account set --name <SUBSCRIPTION_NAME>
```

1. Configure resource group name, storage account name and container name in a file `*.azurerm.tfbackend.json`,
e.g. `dev.azurerm.tfbackend.json`.
1. Create a resource group:

1. Run the script:
```console
az group create --name tfstate --location northeurope
```

```console
./terraform-backend.sh <CONFIG_FILE> <LOCATION> [<IP_ADDRESSES>]
```
Requires Azure role `Contributor` at the subscription scope.

For example:
1. Deploy the Bicep template to the resource group:

```console
./terraform-backend.sh dev.azurerm.tfbackend.json northeurope
```
```console
az deployment group create --name terraform-backend --resource-group tfstate --template-file main.bicep --parameters storageAccountName=<STORAGE_ACCOUNT_NAME>
```

## Access control
Requires Azure role `Owner` at the resource group scope.

- If `use_azuread_auth` is set to `true` in the Terraform backend configuration, Azure role `Storage Blob Data Owner` is required at the Storage account scope or higher.
- Else, Azure role `Reader and Data Access` is required at the Storage account scope or higher.
### Configure Terraform backend

## Troubleshooting
1. In your Terraform configuration file, add the following backend configuration:

- If running the script in Git Bash, you might encounter the following error message: `The request did not have a subscription or a valid tenant level resource provider.`. To fix this error, set the following environment variable: `export MSYS_NO_PATHCONV=1`.
```terraform
terraform {
backend "azurerm" {
resource_group_name = "tfstate"
storage_account_name = "<STORAGE_ACCOUNT_NAME>"
container_name = "tfstate"
key = "terraform.tfstate"
use_azuread_auth = true
}
}
```

## References

Expand Down
110 changes: 110 additions & 0 deletions scripts/terraform-backend/main.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
@description('The name of the Storage account to create.')
param storageAccountName string

@description('An array of IP addresses or IP ranges that should be allowed to bypass the firewall of the Terraform backend. If empty, the firewall will be disabled.')
param ipRules array = []

@description('An array of object IDs of user, group or service principals that should have access to the Terraform backend.')
param principalIds array = []

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-05-01' = {
name: storageAccountName
location: resourceGroup().location
sku: {
name: 'Standard_GRS'
}
kind: 'StorageV2'
properties: {
accessTier: 'Hot'
supportsHttpsTrafficOnly: true
minimumTlsVersion: 'TLS1_2'
allowBlobPublicAccess: false
allowSharedKeyAccess: false
allowCrossTenantReplication: false
networkAcls: {
defaultAction: length(ipRules) == 0 ? 'Allow' : 'Deny'
virtualNetworkRules: []
ipRules: [
for ipRule in ipRules: {
value: ipRule
action: 'Allow'
}
]
}
}

resource blobService 'blobServices' = {
name: 'default'
properties: {
deleteRetentionPolicy: {
allowPermanentDelete: false
enabled: true
days: 30
}
containerDeleteRetentionPolicy: {
enabled: true
days: 30
}
isVersioningEnabled: true
changeFeed: {
enabled: true
}
}

resource container 'containers' = {
name: 'tfstate'
}
}

resource managementPolicy 'managementPolicies' = {
name: 'default'
properties: {
policy: {
rules: [
{
name: 'Delete old tfstate versions'
enabled: true
type: 'Lifecycle'
definition: {
actions: {
version: {
delete: {
daysAfterCreationGreaterThan: 30
}
}
}
filters: {
blobTypes: [
'blockBlob'
]
}
}
}
]
}
}
}
}

var roleDefinitionId = 'b7e6dc6d-f1e8-4753-8033-0f276bb0955b' // Storage Blob Data Owner

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = [
for principalId in principalIds: {
name: guid(storageAccount.id, principalId, roleDefinitionId)
scope: storageAccount
properties: {
principalId: principalId
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
}
}
]

resource lock 'Microsoft.Authorization/locks@2020-05-01' = {
name: 'Terraform'
scope: storageAccount
dependsOn: [storageAccount::blobService, storageAccount::managementPolicy, roleAssignment] // Lock must be created last
properties: {
level: 'ReadOnly'
notes: 'Prevent changes to Terraform backend configuration'
}
}
Loading
Loading