Search code examples
azureterraformazure-rm

Personal Access Token from Azure Key Vault


I am using an Azure keyvault to manage some secrets I am using in a deployment pipeline. All of the secrets are working except for a personal access token from Azure DevOps. I am using the personal access token to install an ADO agent on the virtual machines

data "azurerm_key_vault" "keyvault" {
  name                = "keyvault"
  resource_group_name = "keyault-RG"
}

data "azurerm_key_vault_secret" "pat" {
  name         = "ADOPAT"
  key_vault_id = data.azurerm_key_vault.keyvault.id
}

For example, in my virtual machine module I have something like this:

resource "azurerm_virtual_machine_extension" "ado" {
  count                      = length(var.VMs)
  name                       = "${element(var.VMs, count.index)}-TeamServicesAgent"
  virtual_machine_id         = azurerm_virtual_machine.VMs[count.index].id
  publisher                  = "Microsoft.VisualStudio.Services"
  type                       = "TeamServicesAgent"
  type_handler_version       = "1.0"
  auto_upgrade_minor_version = true

  settings = <<SETTINGS
 {
  "PATToken": "${data.azurerm_key_vault_secret.pat.value}",
  "VSTSAccountName": "orgname",
  "TeamProject": "${var.ado_project}",
  "DeploymentGroup": "${var.deployment_group}"
 }
SETTINGS


  tags = var.tags
}

When I define the PAT as a variable, everything works fine. It's only when I implement the key vault the extension is unable to provision. I don't want to define the PAT as a variable because then I would have the PAT as a plaintext in my code. I don't see many examples online of folks using the ado agent extension, but does anyone have any ideas of what may be causing the issue?

I've tried setting the data.azurerm_key_vault_secret.pat.value to a locals variable but same issue. The issue does not happen when I define the PAT as a variable.


Solution

  • If the extension is not getting installed, Add depends_on=[ azurerm_virtual_machine.example ] parameter in azurerm_virtual_machine_extension block , as it depends on the VM creation.

    Note: Make sure the keyvault is given proper permissions in the keyvault block

    I tried below code:

    resource "azurerm_key_vault" "example" {
      name                        = "kasaraexmplkeyvault"
      location                    = data.azurerm_resource_group.example.location
      resource_group_name         = data.azurerm_resource_group.example.name
      enabled_for_disk_encryption = true
      tenant_id                   = data.azurerm_client_config.current.tenant_id
      soft_delete_retention_days  = 7
      purge_protection_enabled    = false
      sku_name = "standard"
    
      access_policy {
        tenant_id = data.azurerm_client_config.current.tenant_id
       object_id = data.azurerm_client_config.current.object_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",
          "Recover",
          "Restore",
          "Set",
        ]
        storage_permissions = [
          "Get","Set"
        ]
      }
    
      
    }
    
    
    
    
    resource "azurerm_key_vault_secret" "pat" { 
      name         = "patvalue"
      value        =  "gsdnjgsgjh3.3jgjshgfdj"
      key_vault_id = azurerm_key_vault.example.id
    }
    
    resource "azurerm_virtual_network" "example" {
      name                = "acctvnkav"
      address_space       = ["10.0.0.0/16"]
      location            = data.azurerm_resource_group.example.location
      resource_group_name = data.azurerm_resource_group.example.name
    }
    
    resource "azurerm_subnet" "example" {
      name                 = "acctsubkav"
     resource_group_name      = data.azurerm_resource_group.example.name
      virtual_network_name = azurerm_virtual_network.example.name
      address_prefixes     = ["10.0.2.0/24"]
    }
    
    resource "azurerm_network_interface" "example" {
      name                = "acctnickav"
      location            = data.azurerm_resource_group.example.location
      resource_group_name = data.azurerm_resource_group.example.name
    
      ip_configuration {
        name                          = "testconfiguration1"
        subnet_id                     = azurerm_subnet.example.id
        private_ip_address_allocation = "Dynamic"
      }
    }
    
    resource "azurerm_storage_account" "examplen" {
      name                     = "kasaraaccsa"
      resource_group_name      = data.azurerm_resource_group.example.name
      location                 = data.azurerm_resource_group.example.location
      account_tier             = "Standard"
      account_replication_type = "LRS"
    
      tags = {
        environment = "staging"
      }
    }
    
    resource "azurerm_storage_container" "example" {
      name                  = "vhdskav"
      storage_account_name  = azurerm_storage_account.examplen.name
      container_access_type = "private"
    }
    
    resource "azurerm_virtual_machine" "example" {
      name                  = "acctvmkav"
      resource_group_name      = data.azurerm_resource_group.example.name
      location                 = data.azurerm_resource_group.example.location
      network_interface_ids = [azurerm_network_interface.example.id]
      vm_size               = "Standard_F2"
    
      storage_image_reference {
        publisher = "Canonical"
        offer     = "UbuntuServer"
        sku       = "16.04-LTS"
        version   = "latest"
      }
    
      storage_os_disk {
        name          = "myosdisk1"
        //vhd_uri       = "${azurerm_storage_account.examplen.primary_blob_endpoint}${azurerm_storage_containern.example.name}/myosdisk1.vhd"
        caching       = "ReadWrite"
        create_option = "FromImage"
      }
    
      os_profile {
        computer_name  = "hostname"
        admin_username = "testadmin"
        admin_password = "Password1234!"
      }
    
      os_profile_linux_config {
        disable_password_authentication = false
      }
    
      tags = {
        environment = "staging"
      }
    }
    
    resource "azurerm_virtual_machine_extension" "example" {
      name                 = "hostname"
      virtual_machine_id   = azurerm_virtual_machine.example.id
      publisher                  = "Microsoft.VisualStudio.Services"
      type                       = "TeamServicesAgent"
      type_handler_version       = "1.0"
      auto_upgrade_minor_version = true
      # publisher            = "Microsoft.Azure.Extensions"
      # type                 = "CustomScript"
      # type_handler_version = "2.0"
    
      settings = <<SETTINGS
     {
      "PATToken": "${azurerm_key_vault_secret.pat.value}",
      "VSTSAccountName": "orgname",
      "TeamProject": "someproject",
      "DeploymentGroup": "somegroup"
     }
    SETTINGS
    
      tags = {
        environment = "Production"
      }
    
    depends_on=[ azurerm_virtual_machine.example ]
    }
    

    authenticating_using_the_personal_access_token | terraform registry

    enter image description here

    • PAT may require additional steps to be able to access it from the key vault. PAT-Azure Devops | Microsoft Learn

    • Try to Create an new Azure AD application in your Azure Active Directory.

    Assign the "Key Vault Secrets User" role to that application in the Access policies of your Azure Key Vault.

    • Generate a new client secret for the Azure AD application.

    • Grant this application the necessary permissions to read secrets from the Key Vault.

    Use the Azure CLI to set environment variables for the PAT:

    AZURE_TENANT_ID: "" ,AZURE_CLIENT_ID:"", AZURE_CLIENT_SECRET: "",
    KEYVAULT_NAME: "",SECRET_NAME: "".
    

    In your Terraform code, use the "azurerm_key_vault_secret" data source to retrieve the PAT from the key vault:

        data "azurerm_key_vault_secret" "ado_pat" {
          name         = var.secret_name
          key_vault_id = var.key_vault_id
        }
    
    resource "azurerm_virtual_machine_extension" "ado_agent" {
      name                 = "ado_agent_installation"
      virtual_machine_id   = azurerm_virtual_machine.vm.id
      publisher            = "Microsoft.Compute"
      type                 = "CustomScriptExtension"
      type_handler_version = "1.9"
    
      settings = <<SETTINGS
        {
            "commandToExecute": "wget https://vs......tar.gz -P /tmp/ && cd /tmp && tar zxvf ........tar.gz && ./config.sh --unattended --url https://dev.azure.com/your-organization --auth pat --token ${data.azurerm_key_vault_secret.ado_pat.value} --pool your-pool --agent your-agent-name .."
        }
    SETTINGS
    

    }

    Extensions:

    enter image description here

    Also check Automate Azure DevOps self-hosted agent installation using Terraform - DEV Community