Mr. E
Mr. E

Reputation: 525

How does one assume a contributor role in azure to deploy?

I am trying to deploy a Windows virtual machine by using a contributor role using Terraform 1.10.5. I have the client_id and subscription_id and such, but I am unsure how to apply the role so I can deploy the VM with the role permissions.

I figured out that I can use data, to reference the existing resource group and network, but applying the role is a bit of a challenge. Essentially, I need to connect with a client id and assume the contributor role to deploy the VMs.

provider.tf

terraform {
  required_version = ">=1.0"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~>3.0"
    }
    random = {
      source  = "hashicorp/random"
      version = "~>3.0"
    }
  }
}

provider "azurerm" {
  features {}

  resource_provider_registrations = "none"
}

variables.tf

variable "subscription_id" {
  default     = ""
  description = "the subscription id."
}

variable "tenet_id" {
  default     = ""
  description = "The tenet id."
}

variable "tags" {
  default     = "test"
  description = "Any tags which should be assigned to the resources in this example"
}

variable "vm_count" {
  default     = "1"
  description = "number of virtual machines to deploy."
}

variable "resource_group_name" {
  default     = "rg"
  description = "Name of the resource group."
}

variable "resource_vnet" {
  default     = "vnet"
  description = "Name of the virtual network."
}


variable "resource_subnet" {
  default     = "snet"
  description = "Name of the subnet."
}

variable "prefix_id" {
  type        = string
  default     = "vm"
  description = "Prefix of the deployments name"
}


variable "prefix_id_nsg" {
  type        = string
  default     = "vm-nsg"
  description = "Prefix of the deployments name"
}

variable "prefix_id_vms" {
  type        = string
  default     = "vm-00"
  description = "Prefix of the deployments name"
}

main.tf

# Generate random text for a unique storage account name
resource "random_password" "password" {
  length      = 5
  min_lower   = 1
  min_upper   = 1
  min_numeric = 1
  min_special = 1
  special     = true
}

data "azurerm_role_definition" "contributor" {
    name = "Contributor"
  }

data "azurerm_resource_group" "rg" {
    name     = var.resource_group_name
  }

data "azurerm_virtual_network" "my_terraform_network" {
    name                = var.resource_vnet
    resource_group_name = data.azurerm_resource_group.rg.name
  }

data "azurerm_subnet" "my_terraform_subnet" {
    name                 = var.resource_subnet
    resource_group_name  = data.azurerm_resource_group.rg.name
    virtual_network_name = data.azurerm_virtual_network.my_terraform_network.name  
}


# Create Network Security Group and rules
resource "azurerm_network_security_group" "my_terraform_nsg" {
  name                = var.prefix_id_nsg
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name

  security_rule {
    name                       = "RDP"
    priority                   = 1000
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "*"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
  security_rule {
    name                       = "web"
    priority                   = 1001
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "80"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }
}

# Create network interface
resource "azurerm_network_interface" "my_terraform_nic" {
  
  count = var.vm_count

  name                = "${var.prefix_id}-${count.index + 1}-nic"
  location            = data.azurerm_resource_group.rg.location
  resource_group_name = data.azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "ipconfig"
    subnet_id                     = var.resource_subnet
    private_ip_address_allocation = "Dynamic"
  }
}

# Connect the security group to the network interface
resource "azurerm_network_interface_security_group_association" "my_terraform_nic" {
    count = var.vm_count

  network_interface_id      =  azurerm_network_interface.my_terraform_nic[count.index]
  network_security_group_id = azurerm_network_security_group.my_terraform_nsg.id
}

# Create virtual machine
resource "azurerm_windows_virtual_machine" "main" {
  count = var.vm_count

  name                  = "${var.prefix_id_vms}${count.index + 1}"
  admin_username        = "test"
  admin_password        = random_password.password.result
  location              = data.azurerm_resource_group.rg.location
  resource_group_name   = data.azurerm_resource_group.rg.name
  network_interface_ids = [azurerm_network_interface.my_terraform_nic[count.index]]
  size                  = "Standard_DS1_v2"

  os_disk {
    name                 = "${var.prefix_id_vms}${count.index + 1}"
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter-azure-edition"
    version   = "latest"
  }
    tags = "${var.tags}"
}

output "admin_pass" {
    sensitive = true
    value     = azurerm_windows_virtual_machine.main[*].admin_password
}

# output "private_ip_addresses" {
#    value = azurerm_public_ip.public_ip[*].network_interface_private_ip
# }

output "main_hostnames" {
    value = azurerm_windows_virtual_machine.main[*].id
}

Upvotes: 0

Views: 67

Answers (1)

Vinay B
Vinay B

Reputation: 2401

Contributor role in Azure confirmation before going for deployment while using Terraform

The issue seems to be the way you use data "azurerm_role_definition" because this plugin is only used to check if a role exists and lists permissions for a role but not the role availability on the service principal or user.

Unfortunately, there is no plugin available in the Azurerm provider terraform registry to fetch the corresponding role assigned to SP directly instead you can use CLI commands passed using null resource to check and confirm the existence of role assignment to SP.

In the portal, if the PIM status was active it looks something like this mentioned below

enter image description here

So to confirm this existence you can use the configuration mentioned below

Demo configuration:

provider "azurerm" {
  features {}  
  subscription_id = var.subscription_id
  client_id       = var.client_id
  client_secret   = var.client_secret
  tenant_id       = var.tenant_id
}


resource "null_resource" "validate_pim_role" {
  provisioner "local-exec" {
    when    = create
    command = <<EOT
    $role = az role eligibility list --assignee ${var.client_id} --scope "/subscriptions/${var.subscription_id}" | ConvertFrom-Json
    if (-not $role) {
        Write-Host "ERROR: Contributor role is not activated via PIM. Please activate before running Terraform."
        exit 1
    }
    EOT
    interpreter = ["pwsh", "-Command"]
  }
}



resource "azurerm_resource_group" "rg" {
  name     = var.resource_group_name
  location = var.location

  depends_on = [null_resource.validate_role]
}


resource "azurerm_virtual_network" "vnet" {
  name                = "vnet-demo"
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  address_space       = ["10.0.0.0/16"]

  depends_on = [azurerm_resource_group.rg]
}


resource "azurerm_subnet" "subnet" {
  name                 = "subnet-demo"
  resource_group_name  = azurerm_resource_group.rg.name
  virtual_network_name = azurerm_virtual_network.vnet.name
  address_prefixes     = ["10.0.1.0/24"]

  depends_on = [azurerm_virtual_network.vnet]
}


resource "azurerm_network_security_group" "nsg" {
  name                = "nsg-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  security_rule {
    name                       = "AllowRDP"
    priority                   = 1000
    direction                  = "Inbound"
    access                     = "Allow"
    protocol                   = "Tcp"
    source_port_range          = "*"
    destination_port_range     = "3389"
    source_address_prefix      = "*"
    destination_address_prefix = "*"
  }

  depends_on = [azurerm_resource_group.rg]
}


resource "azurerm_network_interface" "nic" {
  name                = "nic-demo"
  location            = azurerm_resource_group.rg.location
  resource_group_name = azurerm_resource_group.rg.name

  ip_configuration {
    name                          = "internal"
    subnet_id                     = azurerm_subnet.subnet.id
    private_ip_address_allocation = "Dynamic"
  }

  depends_on = [azurerm_subnet.subnet]
}


resource "azurerm_network_interface_security_group_association" "nsg_association" {
  network_interface_id      = azurerm_network_interface.nic.id
  network_security_group_id = azurerm_network_security_group.nsg.id

  depends_on = [azurerm_network_security_group.nsg]
}


resource "azurerm_windows_virtual_machine" "vm" {
  name                = var.vm_name
  resource_group_name = azurerm_resource_group.rg.name
  location            = azurerm_resource_group.rg.location
  size                = "Standard_DS1_v2"
  admin_username      = "adminuser"
  admin_password      = var.admin_password

  network_interface_ids = [azurerm_network_interface.nic.id]

  os_disk {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_reference {
    publisher = "MicrosoftWindowsServer"
    offer     = "WindowsServer"
    sku       = "2022-datacenter"
    version   = "latest"
  }

  depends_on = [azurerm_network_interface.nic]
}

Deployement:

once it finds the relevant role permission it starts provisioning the resources.

Deployment only continues if the condition to have contributor role was passed

enter image description here

If the Role is not active it will through an error link this

enter image description here

Refer:

https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/pim-how-to-activate-role

https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-eligible-activate

https://learn.microsoft.com/en-us/azure/role-based-access-control/role-assignments-list-cli#list-role-assignments-for-a-user

Upvotes: 1

Related Questions