Connection au remote backend depuis GitHub

Les fichiers states permettent de déterminer les modifications à apporter à une infrastructure en comparant la configuration Terraform à l’existant. Le backend Azurerm permet de stocker les fichiers states dans un Azure storage, dont L'authentification peut être réalisée de différentes manières :

  • Azure CLI

  • Service Principal

  • Azure AD

  • OIDC

  • SAS Token

  • MSI (Managed security identity)

Shared Access Signature (SAS) Tokens

Les SAS tokens permettent de déléguer l’accès au storage account, au conteneur ou même à un blob, tout en permettant un contrôle poussé de l’accès (durée, filtrage d'IP…​). Il impose par contre de gérer le renouvellement du jeton et ainsi que sa sécurité. Pour ce dernier point, Terraform recommande d’utiliser la configuration partielle du backend et de définir le SAS token dans les variables d’environnement: ARM_SAS_TOKEN.

Générer le SAS token pour le conteneur
END_DATE=$(date -u -d "+2 years" +%Y-%m-%dT%H:%MZ) (1)
ARM_SAS_TOKEN=$(az storage container generate-sas -n $CONTAINER_NAME --account-key $ACCOUNT_KEY --account-name $TF_STORAGE_ACCOUNT --https-only --permissions dlrw --expiry $END_DATE -o tsv)
1 Période de validité de 2 ans

Dans le repo GitHub du projet, aller dans Settings  Secrets  Actions  New Repository Secret pour ajouter un nouveau secret nommé ARM_SAS_TOKEN et ayant pour valeur, le token généré.

Créer ensuite un nouveau workflow GitHub :

terraform.yml
name: 'Terraform'

on:
  push:
    branches:
    - main
  pull_request:
  workflow_dispatch:

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: production
    env:
      ARM_SAS_TOKEN: ${{ secrets.ARM_SAS_TOKEN }}

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3

    # Install the needed version of Terraform CLI
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: 1.2.1

    - name: Terraform Init
      run: terraform init

Après exécution du workflow, le state doit être présent dans le conteneur.

Service Principal

Un service principal est une identité utilisée par les applications pour accéder aux ressources Azure. Cet accès est limité par les rôles (RBAC), ce qui permet de contrôler l’accessibilité aux ressources. Lorsque l’on crée un service principal, on obtient les informations d’identification.

Création du service principal
$ az ad sp create-for-rbac --name="SP_NAME" --role="Contributor" --scopes="/subscriptions/SUBSCRIPTION_ID"
Sortie incluant les secrets à protéger
{
  "appId": "00000000-0000-0000-0000-000000000000",
  "displayName": "SP_NAME",
  "password": "0000-0000-0000-0000-000000000000",
  "tenant": "0000-0000-0000-0000-000000000000"
}

Il est possible de renseigner ces identifiants de différentes manières dans Terraform, mais il est recommandé d’utiliser les variables d’environnement. Dans GitHub, ajouter les secrets suivants :

ARM_CLIENT_ID="00000000-0000-0000-0000-000000000000"
ARM_CLIENT_SECRET="00000000-0000-0000-0000-000000000000"
ARM_SUBSCRIPTION_ID="00000000-0000-0000-0000-000000000000"
ARM_TENANT_ID="00000000-0000-0000-0000-000000000000"

Le workflow est ensuite modifié pour inclure ces valeurs :

terraform.yml
name: 'Terraform'

on:
  push:
    branches:
    - main
  pull_request:
  workflow_dispatch:

jobs:
  terraform:
    name: 'Terraform'
    runs-on: ubuntu-latest
    environment: production
    env:
      ARM_CLIENT_ID: ${{ secrets.ARM_CLIENT_ID }}
      ARM_CLIENT_SECRET: ${{ secrets.ARM_CLIENT_SECRET }}
      ARM_SUBSCRIPTION_ID: ${{ secrets.ARM_SUBSCRIPTION_ID }}
      ARM_TENANT_ID: ${{ secrets.ARM_TENANT_ID }}

    # Use the Bash shell regardless whether the GitHub Actions runner is ubuntu-latest, macos-latest, or windows-latest
    defaults:
      run:
        shell: bash

    steps:
    # Checkout the repository to the GitHub Actions runner
    - name: Checkout
      uses: actions/checkout@v3

    # Install the needed version of Terraform CLI
    - name: Setup Terraform
      uses: hashicorp/setup-terraform@v2
      with:
        terraform_version: 1.2.1

    - name: Terraform Init
      run: terraform init

Azure AD

La configuration de l’authentification Azure AD est assez proche de celle utilisant le service principal, sauf qu’elle n’utilise pas les access keys mais l'AD pour se connecter au backend.

main.tf
  backend "azurerm" {
    storage_account_name = "ststate18465"
    container_name       = "tfstate"
    key                  = "terraform.tfstate"
    use_azuread_auth     = true (1)
  }
1 Impose l’authentification Azure AD pour accéder au blob storage account
A la place de la propriété use_azuread_auth, il est aussi possible de définir la variable d’environnement ARM_USE_AZUREAD

L’utilisation de l’authentification Azure AD nécessite d’assigner le rôle Storage Blob Data Owner au service principal dans le storage. Dans le cas contraire, on obtient l’erreur suivante :

StatusCode=403 -- Original Error: autorest/azure: Service returned an error. Status=403 Code="AuthorizationPermissionMismatch" Message="This request is not authorized to perform this operation using this permission.
Assignation du rôle
az role assignment create \
  --role "Storage Blob Data Owner" \
  --assignee 00000000-0000-0000-0000-000000000000 \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/$RESOURCE_GROUP_NAME/providers/Microsoft.Storage/storageAccounts/$TF_STORAGE_ACCOUNT/blobServices/default/containers/$CONTAINER_NAME"

La commande suivante permet de lister les particularités du rôle.

Afficher les permissions du rôle
az role definition list \
  --name "Storage Blob Data Owner" \
  --output json \
  --query '[].{actions:permissions[0].actions, notActions:permissions[0].notActions}'

La propagation du rôle peut prendre plusieurs minutes avant d’être effective.

Lors du développement, les utilisateurs doivent également posséder ce rôle pour se connecter au conteneur. C’est un bon moyen pour protéger l’accès, mais cela peut être fastidieux à configurer. Il reste possible d’utiliser la configuration partielle et un SAS token.