0

Im looking for some guidance on how to configure a devops project in terraform. The issue im having is to create an SPN and Client Secret and store the secret in the vault and allow the keyvault to use the secret in the devops project. The same applies to the storage account access key aswell.

Any help would be appreciated!

Error

 Error:  failed expanding repository resource data (ProjectID:  azuredevops_project.adoproj.id, Repository: ADO Native Repo) Error: invalid UUID length: 30    
│
│   with azuredevops_git_repository.new_repo,
│   on ado_repo.tf line 1, in resource "azuredevops_git_repository" "new_repo":
│    1: resource "azuredevops_git_repository" "new_repo" {
│
╵
╷
│ Error: Expanding variable group resource data: Failed to get the Azure Key valut.  Erroe: ( code: badRequest, messge: Failed to obtain the Json Web Token(JWT)
 using service principal client ID. Exception message: A configuration issue is preventing authentication - check the error message from the server for details.
 You can modify the configuration in the application registration portal. See https://aka.ms/msal-net-invalid-client for details.  Original exception: AADSTS700
0215: Invalid client secret provided. Ensure the secret being sent in the request is the client secret value, not the client secret ID, for a secret added to app 'b826b4f8-0e00-493d-b892-d6ffba03f36c'.
│ Trace ID: a08e3154-e688-4379-a6fa-0d4b20b35700
│ Correlation ID: c7a270dd-b770-4a49-9251-fc302f0d191d
│ Timestamp: 2023-04-02 22:59:38Z )
│
│   with azuredevops_variable_group.varGroup,
│   on ado_variables.tf line 1, in resource "azuredevops_variable_group" "varGroup":
│    1: resource "azuredevops_variable_group" "varGroup" {
│
╵
╷
│ Error: failed waiting for Key Vault Access Policy (Object ID: "248be4d9-230a-4286-b26b-849f1e22d92f") to apply: context deadline exceeded
│
│   with azurerm_key_vault_access_policy.Current,
│   on main.tf line 35, in resource "azurerm_key_vault_access_policy" "Current":
│   35: resource "azurerm_key_vault_access_policy" "Current" {
│
╵
╷
│ Error: updating Access Policy (Object ID "f19e5047-68a1-4cfa-ae69-3d5d360f98c9" / Application ID "") for Vault: (Name "kv01h" / Resource Group "rg01"): keyvault.VaultsClient#UpdateAccessPolicy: Failure sending request: StatusCode=0 -- Original Error: context deadline exceeded
│
│   with azurerm_key_vault_access_policy.SPNAccess,
│   on main.tf line 41, in resource "azurerm_key_vault_access_policy" "SPNAccess":
│   41: resource "azurerm_key_vault_access_policy" "SPNAccess" {

Providers

terraform {
  required_providers {
    azuredevops = {
      source  = "microsoft/azuredevops"
      version = ">= 0.4.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.49.0"
    }
  }
}

provider "azurerm" {
  features {}
}

provider "azuredevops" {
  org_service_url       = var.AZDO_ORG_SERVICE_URL
  personal_access_token = var.AZDO_PERSONAL_ACCESS_TOKEN
}

Main.tf

### RESOURCE GROUP
resource "azurerm_resource_group" "resourcegroup" {
  location = "norwayeast"
  name     = "rg01"
}
### STORAGE ACCOUNT
resource "azurerm_storage_account" "stg" {
  name                     = var.storageAccount_name
  resource_group_name      = azurerm_resource_group.resourcegroup.name
  location                 = var.location
  account_tier             = "Standard"
  account_replication_type = "LRS"
}
### CONTAINER
resource "azurerm_storage_container" "cont" {
  name                  = var.container_name
  storage_account_name  = azurerm_storage_account.stg.name
  container_access_type = "private"
}
### KeyVault
resource "azurerm_key_vault" "kv" {
  name                            = var.keyvault_name
  location                        = var.location
  resource_group_name             = azurerm_resource_group.resourcegroup.name
  tenant_id                       = var.tenant_id
  sku_name                        = "standard"
  enabled_for_deployment          = true
  enabled_for_disk_encryption     = true
  enabled_for_template_deployment = true
  purge_protection_enabled        = false
}

# Access Policy KV

resource "azurerm_key_vault_access_policy" "Current" {
  key_vault_id = azurerm_key_vault.kv.id
  tenant_id    = var.tenant_id
  object_id    = data.azurerm_client_config.current.object_id
}

resource "azurerm_key_vault_access_policy" "SPNAccess" {
  key_vault_id = azurerm_key_vault.kv.id
  tenant_id    = azurerm_key_vault.kv.tenant_id
  object_id    = azuread_service_principal.tfazSPN.object_id

  secret_permissions = [
    "Get",
    "List"
  ]

  storage_permissions = [
    "Get",
    "List"
  ]
}


# SPN

resource "azuread_application" "tfazSPN" {
  display_name = var.AzureAD_ApplicationName
}

resource "random_string" "password" {
  length  = 32
  special = false
}

resource "random_password" "password" {
  length           = 32
  special          = true
  override_special = "!@#%&*-_+:?"
}

resource "azuread_service_principal" "tfazSPN" {
  application_id = azuread_application.tfazSPN.application_id
}

resource "azuread_service_principal_password" "tfazSPN" {
  service_principal_id = azuread_service_principal.tfazSPN.id
  end_date             = var.AzureAD_SPN_Password_Expiry
}

resource "azurerm_role_assignment" "main" {
  principal_id         = azuread_service_principal.tfazSPN.id
  scope                = azurerm_key_vault.kv.id
  role_definition_name = "Contributor"
}

Variable Group

resource "azuredevops_variable_group" "varGroup" {
  project_id   = azuredevops_project.adoproj.id
  name         = "Terraform Sensitive Variables"
  description  = "This Variable Group should be linked to an Azure Key Vault"
  allow_access = true #Boolean that indicate if this variable group is shared by all pipelines of this project.

  key_vault {
    name                = azurerm_key_vault.kv.name
    service_endpoint_id = azuredevops_serviceendpoint_azurerm.AzSPN.id
  }

  variable {
    name = "SASKey"
  }
  variable {
    name = "SPNPwd"
  }
  variable {
    name = "VMAdminPwd"
  }
}

Variables

variable "client_secret" {
  type        = string
  default     = ""
  description = "description"
}

variable "tenant_id" {
  type        = string
  default     = "tentant_id"
  description = "description"
}

variable "subscription_id" {
  type        = string
  default     = "subscription_id"
  description = "description"
}

variable "client_id" {
  type        = string
  default     = ""
  description = "description"
}

variable "object_id" {
  type        = string
  default     = ""
  description = "description"
}

variable "environment" {
  type        = string
  default     = "dev"
  description = "description"
}

variable "location" {
  type        = string
  default     = "norwayeast"
  description = "description"
}

variable "storageAccount_name" {
  type        = string
  default     = "tfaz01stg"
  description = "description"
}

variable "container_name" {
  type        = string
  default     = "cont01"
  description = "description"
}

variable "keyvault_name" {
  type        = string
  default     = "kv01h"
  description = "description"
}

variable "AZDO_PERSONAL_ACCESS_TOKEN" {
  type        = string
  default     = "*******"
  description = "description"
}

variable "AZDO_ORG_SERVICE_URL" {
  type        = string
  default     = "https://dev.azure.com/*****"
  description = "description"
}

variable "AZDO_GITHUB_SERVICE_CONNECTION_PAT" {
  type        = string
  default     = "***"
  description = "description"
}


variable "name" {
  type        = string
  default     = ""
  description = "description"
}

variable "AzureAD_ApplicationName" {
  type        = string
  default     = "tfazSPN"
  description = "description"
}

variable "AzureAD_SPN_Password_Expiry" {
  type        = string
  default     = "2028-01-01T01:02:03Z"
  description = "The End Date which the Password is valid until, formatted as a RFC3339 date string (e.g. 2018-01-01T01:02:03Z)."
}

Tried various commands to implement these resources via terraform but no luck

HSar
  • 13
  • 5

1 Answers1

0

I have used the following Terraform code to create an Azure DevOps variable group using a Key Vault secret

provider "azurerm" {
  features {
    key_vault {
      purge_soft_deleted_secrets_on_destroy = true
      recover_soft_deleted_secrets          = true
    }
  }
}
provider "azuredevops" {
  org_service_url = "https://dev.azure.com/MTUser"
  personal_access_token = "xxxxxxxxxxxq2illtqxbywgpwhecbfsdog7s6vcndta"
}


terraform {
  required_providers {
    azuredevops = {
      source  = "microsoft/azuredevops"
      version = ">= 0.4.0"
    }
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.49.0"
    }
  }
}



data "azurerm_client_config" "current" {}
data "azuread_client_config" "current"{}

resource "azuread_application" "example" {
  display_name = "example"
  owners       = [data.azuread_client_config.current.object_id]
}

resource "azuread_service_principal" "example" {
  application_id               = azuread_application.example.application_id
  app_role_assignment_required = false
  owners                       = [data.azuread_client_config.current.object_id]
}


resource "azuread_service_principal_password" "example" {
  service_principal_id = azuread_service_principal.example.id
}

resource "azurerm_resource_group" "sampleresourcegroup" {
  name     = "samplw-resource-group"
  location = "West US"
}

resource "azurerm_user_assigned_identity" "venkat" {
  resource_group_name = azurerm_resource_group.sampleresourcegroup.name
  location            = azurerm_resource_group.sampleresourcegroup.location
  name                = "venkat-keyvault"
}

resource "azurerm_key_vault" "venkattest" {
  name                       = "thejademovenkatsamplee"
  location                   = azurerm_resource_group.sampleresourcegroup.location
  resource_group_name        = azurerm_resource_group.sampleresourcegroup.name
  tenant_id = data.azurerm_client_config.current.tenant_id
  sku_name = "standard"
  access_policy {
    object_id    = data.azurerm_client_config.current.object_id
    tenant_id    = data.azurerm_client_config.current.tenant_id

    certificate_permissions = [
      "Create",
      "Delete",
      "DeleteIssuers",
      "Get",
      "GetIssuers",
      "Import",
      "List",
      "ListIssuers",
      "ManageContacts",
      "ManageIssuers",
      "Purge",
      "SetIssuers",
      "Update"
    ]

    key_permissions = [
      "Backup",
      "Create",
      "Decrypt",
      "Delete",
      "Encrypt",
      "Get",
      "Import",
      "List",
      "Purge",
      "Recover",
      "Restore",
      "Sign",
      "UnwrapKey",
      "Update",
      "Verify",
      "WrapKey"
    ]

    secret_permissions = [
      "Backup",
      "Delete",
      "Get",
      "List",
      "Purge",
      "Restore",
      "Restore",
      "Set"
    ]
  }

  access_policy {
    object_id    = azurerm_user_assigned_identity.venkat.principal_id
    tenant_id    = data.azurerm_client_config.current.tenant_id

    secret_permissions = [
      "Get"
    ]
  }
}
resource "azurerm_key_vault_secret" "venkattest123" {
  name         = "secret-sauce"
  value        = "szechuan"
  key_vault_id = azurerm_key_vault.venkattest.id
}

resource "azuredevops_project" "devopsproject" {
  name               = "ExampleProject"
  work_item_template = "Agile"
  version_control    = "Git"
  visibility         = "private"
  description        = "Managed by Terraform"
}
resource "azuredevops_serviceendpoint_azurerm" "example" {
  project_id            = azuredevops_project.devopsproject.id
  service_endpoint_name = "Example AzureRM"
  description           = "Managed by Terraform"
  credentials {
    serviceprincipalid  = azuread_service_principal.example.application_id
    serviceprincipalkey = azuread_service_principal_password.example.value
  }
  azurerm_spn_tenantid      = "1810a95e-99f3-46e0-84e8-8a2aee05d830"
  azurerm_subscription_id   = "8ae0844f-6525-411f-8df8-7a7e012aaa16"
  azurerm_subscription_name = "Visual Studio Enterprise Subscription"
}

resource "azuredevops_variable_group" "samplegroup123" {
  project_id   = azuredevops_project.devopsproject.id
  name         = "Example Variable Group"
  description  = "Example Variable Group Description"
  allow_access = true
  variable {
    name = azurerm_key_vault_secret.venkattest123.name
    value = azurerm_key_vault_secret.venkattest123.value
    is_secret = true
  }
}

Terraform apply:

enter image description here

After running the above code Azure DevOps variable group was created with the Key Vault Secret

enter image description here

Reference: Configuring Azure DevOps Services Access

Venkat V
  • 2,197
  • 1
  • 1
  • 10
  • Hi VenkatV, thank you for the reply! I'll take a look at your guide and see if i can set it up :) – HSar Apr 05 '23 at 11:24