You ever get that feeling when you're full of ambition, caffeine, and a false sense of DevOps confidence? Like, β€œYeah, I’ll deploy SCEPman with Terraform, how hard can it be?” Spoiler alert: it's that hard. Let me take you on the tragicomic journey of how I tried to automate SCEPman deployment β€” and instead deployed pain.


🧠 Context Dump Before the Fire

Let me be clear: I wasn’t new to Terraform. I’d poked at modules, tweaked variables, written main.tf files with swagger. But here’s the thing β€” I had never done a greenfield deployment before.

There’s a galaxy of difference between "following a 'Getting Started' tutorial on terraform.io" and building a real-world, integrated SCEPman setup from scratch, complete with app registrations, Key Vaults, and a healthy dose of Azure chaos. It's like practicing on a driving simulator, then being handed the keys to a Formula 1 car mid-race β€” and the pit crew only speaks YAML.


☁️ Prologue: CloudCook’s Grand Plan

It started like all bad ideas: with hope.

Cloudcook, your favorite over-caffeinated cloud alchemist, was handed the noble task of deploying SCEPman, the beloved Intune-friendly PKI-in-the-cloud. And I thought: "Let’s do it right. Let’s Terraform this bad boy. Infrastructure as Code! CI/CD pipelines! GitOps glory!"

So I spun up Azure DevOps, created a new Repo, wrote a main.tf, and mentally prepared myself for that sweet terraform apply dopamine hit.

Instead, I got terraform cry.


🏧 The Dream Setup

Here’s what I wanted:

  • SCEPman deployed in a clean, declarative Terraform way.
  • Automated App Registrations.
  • Key Vault magic.
  • Custom domain & TLS certs.
  • CI/CD pipeline to keep things nice and tidy.

I even found the official docs and figured: "Hey, they made a module! It’s gonna be easy!"

Ha. Ha. Ha.


🚦 Step 1: β€œUse the TerraformInstaller Task,” They Said

So, I used the Azure DevOps Terraform Installer task. But surprise! There are two identically named tasks:

TerraformInstaller is ambiguous.
Specify one of the following identifiers:
- JasonBJohnson.azure-pipelines-tasks-terraform.azure-pipelines-tasks-terraform-installer.TerraformInstaller
- ms-devlabs.custom-terraform-tasks.custom-terraform-installer-task.TerraformInstaller

Which one installs Terraform and which one installs pain? Both were installed as Extensions in Azure DevOps.I picked one at random because YOLO, and moved on. Mistake #1.

Spoiler: It only worked with the Azure DevLabs one. 


πŸͺ¦ Step 2: Storage Account Required… by Whom Exactly?

My pipeline died with:

Error: Input required: backendAzureRmStorageAccountName

Classic Terraform: β€œWe’re stateless! Except when we need state. Then you better pray to the storage gods.” So I added a storage account. Manually. Because what's Infrastructure-as-Code if not followed by Infrastructure-by-Handβ„’?

Still broken.


🧿 Step 3: AZ Login – But I Am Logged In?!

Terraform doesn’t trust your login unless it personally watched you type az login in front of it, while singing the Azure anthem. So it complained:

waiting for the Azure CLI: exit status 1: ERROR: Please run 'az login' to setup account.

Okay cool, except this was running in a pipeline. Not exactly keyboard-friendly.

Eventually, I remembered: service principal. 
So I made one. Gave it enough permissions to rewrite our entire tenant. You know, just in case.

Spoiler: The Servicer Prinicpal needs to have Contributor Right on the whole Subscription, Owner on the ResourceGroup and Key Vault Admin on the Resource Group. Trust me - I had to learn it the hard way.


🧱 Step 3.5: Oh Right, the Resource Group

One tiny, undocumented landmine I stumbled into: the resource group for SCEPman must already exist.

Yes, Terraform is supposed to create resources, but the module just assumes the resource group is out there, chilling in Azure, waiting to embrace its new contents like a neglected parent.

If you forget this and just hit apply, everything goes kaboom with a cryptic error about nonexistent locations or resource IDs. Ask me how I know. 🀑


πŸ‘» Step 4: Let’s Talk App Reg Permissions, Shall We?

SCEPman loves a good app reg. But Terraforming an app reg with all the right permissions feels like trying to write a symphony with a kazoo.

You need:

  • Microsoft Graph API permissions
  • Key Vault access policies
  • The patience of a Buddhist monk

One wrong permission? Boom. Deployment fails silently or weirdly. You’ll stare at the logs like you're reading ancient Sumerian.


πŸ’¨ Final Boss: Certificates and Key Vault Mayhem

SCEPman wants a certificate in your Key Vault. Fair.
Terraform wants to create the cert, reference the secret, inject it into App Service, and then sacrifice a goat.

Azure meanwhile is like:

β€œSorry, the certificate you just created isn’t usable because reasons. Please contact your spiritual advisor.”

😡 The Aftermath

I got SCEPman deployed eventually, but I rage-clicked so many times I think I summoned Clippy from the dead. He popped up like:

β€œIt looks like you're trying to deploy PKI with Terraform. Would you like to give up?”

The Pipeline ran approx. 80 Times with an Error before it worked - FUN FUN FUN FUN FUN.


🧠 Lessons Learned (aka Things I’ll Forget by Next Week)

  • Azure DevOps pipelines are allergic to ambiguity.
  • Always declare your azurerm_backend values or get ghosted.
  • Don't trust Terraform with your Key Vault unless you've babysat it yourself.
  • App registrations via Terraform are a war crime in slow motion.
  • SCEPman works great β€” once it's up.

πŸ₯² TL;DR

If you're trying to deploy SCEPman with Terraform, prepare for:

βœ… Spiritual growth
βœ… Painful debugging
βœ… Massive respect for infra engineers
βœ… One glorious moment when it actually works

Until then, you're just Terraforming your sanity into oblivion.


⛹️The only thing that matters

The Pipeline:

# .azure-pipelines.yml ─ deploy SCEPman with Terraform + OIDC
# ───────────────────────────────────────────────────────────

trigger:
  branches:            # run when you push to the scep branch
    include: [ scep ]

pool:
  vmImage: ubuntu-latest

variables:
  # 1) name of your Azure service connection (OIDC-enabled)
  azureServiceConnection: "Service Connection Name"
  # 2) Terraform CLI version – 1.7.x is the first that bundles Azurerm 3.75+
  tfVersion: '1.7.5'

steps:
# ─── 0. Install the exact Terraform version on the agent ─────────────────────
- task: TerraformInstaller@1
  inputs:
    terraformVersion: $(tfVersion)

# OPTIONAL: one-off debug – prove the creds really arrived
- script: |
    echo 'ARM_* variables now visible to this job:'
    env | grep ^ARM_ || true
  displayName: Show ARM_ env vars

# ─── 1. terraform init ─ inject creds via backendServiceArm ───────────────────
- task: TerraformCLI@1
  displayName: Terraform init
  inputs:
    command: init
    provider: azurerm                   # must be present for the task to inject
    backendServiceArm: $(azureServiceConnection)
    environmentServiceNameAzureRM: $(azureServiceConnection)
# ─── 2. terraform plan ─ creds injected via environmentServiceNameAzureRM ─────
- task: TerraformCLI@1
  displayName: Terraform plan
  inputs:
    command: plan
    provider: azurerm
    environmentServiceNameAzureRM: $(azureServiceConnection)
    commandOptions: -out=tfplan

# ─── 3. terraform apply ─ run the plan file we just created ───────────────────
- task: TerraformCLI@1
  displayName: Terraform apply
  inputs:
    command: apply
    provider: azurerm
    environmentServiceNameAzureRM: $(azureServiceConnection)
    commandOptions: tfplan            # no -auto-approve β†’ safe, interactive

The Main.tf:

terraform {
  required_providers {
    azurerm = {
      source = "hashicorp/azurerm"
      version = "4.34.0"
    }
  }
}

provider "azurerm" {
  features {}
}

module "scepman" {
  source  = "scepman/scepman/azurerm"
  version = "0.5.0"                  # pin to a tested version

  resource_group_name  = var.resource_group
  location        = var.location
  storage_account_name = var.storage_account_name
  key_vault_name = var.key_vault_name
  law_name = var.law_name
  app_service_name_primary = var.app_service_name_primary
  app_service_name_certificate_master = var.app_service_name_certificate_maser
  service_plan_name = var.service_plan_name

  # You can override defaults for Key Vault, App Service SKU, custom domain, etc.
  # See all inputs: https://registry.terraform.io/modules/scepman/scepman/azurerm/latest :contentReference[oaicite:2]{index=2}
}

The versions.tf:

terraform {
  required_version = ">= 1.5.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 4.34.0"   # 4.34+ understands OIDC
    }
  }
}

provider "azurerm" {
  features {}
}