avatarBill WANG

Free AI web copilot to create summaries, insights and extended knowledge, download it at here

5246

Abstract

ures { resource_group { prevent_deletion_if_contains_resources = <span class="hljs-literal">false</span> } } }

<span class="hljs-comment"># Configuration for the "secondary" subscription</span> <span class="hljs-comment"># Share image gallery from master subscription, where the images are built</span> provider <span class="hljs-string">"azurerm"</span> { alias = <span class="hljs-string">"secondary"</span> subscription_id = <span class="hljs-keyword">var</span>.subscription_secondary

features { resource_group { prevent_deletion_if_contains_resources = <span class="hljs-literal">false</span> } }

}

data <span class="hljs-string">"azurerm_shared_image_version"</span> <span class="hljs-string">"this"</span> { provider = azurerm.secondary name = <span class="hljs-string">"latest"</span> image_name = <span class="hljs-keyword">var</span>.image_name gallery_name = <span class="hljs-keyword">var</span>.gallery_name resource_group_name = <span class="hljs-keyword">var</span>.gallery_rg }</pre></div><p id="c880">Explanation:</p><ul><li><b>name</b> is <code>latest</code> , so when new images are ready, you don’t need update your terraform codes, it will always pick up the latest image.</li><li><b>image_name</b> is azure image definition name</li><li><b>gallery_name</b> is the azure computer gallery name</li><li><b>resource_group_name</b> is the computer gallery’s resource group name</li><li>normally images are built in a master subscription, and shared to client subscripotions. <b>Secondard provider</b> defines the access to master subscrioption</li></ul><p id="bea8">Save the file to <code>main.tf</code> , prepare <b>variables.tf</b> ( <a href="https://github.com/ozbillwang/terraform-azurerm-virtual-machine-aad/blob/master/linux-vm-aad-gallery/variables.tf">refer the code</a> ) dry-run ( <code>terraform plan</code> ) to check if you can get the image id.</p><div id="3b73"><pre>az account <span class="hljs-keyword">set</span> <span class="hljs-comment">--subscription <replace_with_your_subscrption_id></span> az account <span class="hljs-keyword">show</span>

terraform fmt terraform init <span class="hljs-operator">-</span>reconfigure terraform validate

terraform plan</pre></div><p id="b75c">If the command is fine, you should see the output of the image id.</p><p id="9ec6">Then you can move on</p><h1 id="b900">Deploy VM with the shared image id</h1><div id="e716"><pre>module <span class="hljs-string">"linux-vm-aad"</span> { source = <span class="hljs-string">"git::https://github.com/Azure/terraform-azurerm-virtual-machine.git"</span>

location = <span class="hljs-keyword">var</span>.location image_os = <span class="hljs-string">"linux"</span> resource_group_name = local.name #checkov:skip=CKV_AZURE_50:Demo <span class="hljs-keyword">for</span> extension allow_extension_operations = <span class="hljs-literal">true</span> boot_diagnostics = <span class="hljs-literal">false</span>

identity = <span class="hljs-keyword">var</span>.identity

new_network_interface = { ip_forwarding_enabled = <span class="hljs-literal">false</span> ip_configurations = [ { primary = <span class="hljs-literal">true</span> } ] } admin_username = <span class="hljs-string">"azureuser"</span> admin_ssh_keys = [ { public_key = tls_private_key.<span class="hljs-keyword">this</span>.public_key_openssh } ] name = local.name os_disk = { caching = <span class="hljs-string">"ReadWrite"</span> storage_account_type = <span class="hljs-string">"Standard_LRS"</span> }

source_image_id = <span class="hljs-keyword">data</span>.azurerm_shared_image_version.<span class="hljs-keyword">this</span>.id

size = <span class="hljs-keyword">var</span>.size subnet_id = <span class="hljs-keyword">data</span>.azurerm_subnet.<span class="hljs-keyword">this</span>.id extensions = [ { name = <span class="hljs-string">"AADSSHLoginForLinux"</span> publisher = <span class="hljs-string">"Microsoft.Azure.ActiveDirectory"</span>, type = <span class="hljs-string">"AADSSHLoginForLinux"</span>, type_handler_version = <span class="hljs-string">"1.0"</span> }, ] }

resource <span class="hljs-string">"azurerm_role_assignment"</span> <span class="hljs-string">"this"</span> { principal_id = <span class="hljs-keyword">data</span>.azuread_groups.<span class="hljs-keyword">this</span>.object_ids[<span class="hljs-number">0</span>] role_definition_name = <span class="hljs-string">"Virtual Machine Administrator Login"</span> scope = azurerm_resource_group.<span class="hljs-keyword">this</span>.id }</pre></div><p id="3930">Explanations</p><ul><li>Use <a href="https://github.com/Azure/terraform-azurerm-virtual-machine">Azure official terraform module</a> to create virtual machine</li><li>Enable “<b>System assigned</b>” identity with code: <code><b>identity = var.identity</b></code></li><li>Enable AzureAD extension with part of code:</li></ul><div id="4c40"><pre><span class="hljs-attr">

Options

extensions</span> = [ { name = <span class="hljs-string">"AADSSHLoginForLinux"</span> publisher = <span class="hljs-string">"Microsoft.Azure.ActiveDirectory"</span>, type = <span class="hljs-string">"AADSSHLoginForLinux"</span>, type_handler_version = <span class="hljs-string">"1.0"</span> }, ]</pre></div><ul><li>use image id we got from shared gallery: <code><b>source_image_id = data.azurerm_shared_image_version.this.id</b></code></li><li>Assign role of <code>Virtual Machine Administrator Login </code>, so any one in that group can login the vm with AzureAD account and sudo to root</li></ul><div id="e26d"><pre>resource <span class="hljs-string">"azurerm_role_assignment"</span> <span class="hljs-string">"this"</span> { principal_id = <span class="hljs-keyword">data</span>.azuread_groups.<span class="hljs-keyword">this</span>.object_ids[<span class="hljs-number">0</span>] role_definition_name = <span class="hljs-string">"Virtual Machine Administrator Login"</span> scope = azurerm_resource_group.<span class="hljs-keyword">this</span>.id }</pre></div><p id="846f">While there are other virtual machine related roles to assign, for the sake of simplicity, I typically grant administrator login directly to everyone allowed to access that VM. This minimizes the need for additional permission requests later on. :-)</p><ul><li><b>Virtual Machine User Login</b> — View Virtual Machines in the portal and login as a regular user.</li><li><b>Virtual Machine Administrator Login</b> — View Virtual Machines in the portal and login as administrator</li></ul><h1 id="9873">Set Variable Definitions (.tfvars) Files</h1><div id="15a9"><pre>$ cat dev.tfvars

<span class="hljs-comment"># adjust with your environment</span> location=<span class="hljs-string">"australiaeast"</span> subnet_name=<span class="hljs-string">"AppSubnet"</span> vnet_name=<span class="hljs-string">"vnet-dev"</span> vnet_rg=<span class="hljs-string">"vnet-rg"</span> image_name=<span class="hljs-string">"Golden_Image_Redhat_RHEL_9"</span> gallery_name=<span class="hljs-string">"Golden_Images_prod"</span> gallery_rg=<span class="hljs-string">"Golden_Image-Builder"</span></pre></div><h1 id="c25a">Dry-run the codes</h1><div id="4be3"><pre><span class="hljs-meta">#! /usr/bin/env bash</span>

az account <span class="hljs-built_in">set</span> --subscription <replace_with_your_subscrption_id> az account show

terraform <span class="hljs-built_in">fmt</span> terraform init -reconfigure terraform validate

terraform plan -var-file=dev.tfvars -out=<span class="hljs-string">'planfile'</span> <span class="hljs-comment"># terraform apply "planfile"</span></pre></div><p id="89d7">If it works as expect, you can apply the change by command</p><div id="002b"><pre>terraform apply <span class="hljs-string">"planfile"</span></pre></div><h1 id="6182">Login the VM with AAD account</h1><div id="128e"><pre>$ <span class="hljs-built_in">cat</span> aad-login-vm.sh

<span class="hljs-comment"># adjust with your environment</span> subscription=<replace_with_your_subscrption_id> az account <span class="hljs-built_in">set</span> -s <span class="hljs-variable">${subscription}</span> az account show

bastionName=<span class="hljs-string">"<replace_with_bastion_name>"</span> bastionRG=<span class="hljs-string">"<replace_with_bastion_resource_group>"</span>

<span class="hljs-comment"># replace with the virtual machine name and its resource group.</span> vmName=<span class="hljs-string">"vm-dev-4134"</span> vmRG=<span class="hljs-string">"vm-dev-4134"</span>

vmId=(az vm list --resource-group <span class="hljs-variable">{vmRG}</span> --query <span class="hljs-string">"[?name=='<span class="hljs-variable">vmName</span>'].id"</span> --output tsv) <span class="hljs-built_in">echo</span> <span class="hljs-variable">vmId</span>

az network bastion ssh --name <span class="hljs-string">"<span class="hljs-variable">{bastionName}</span>"</span> --resource-group <span class="hljs-string">"<span class="hljs-variable">{bastionRG}</span>"</span> --target-resource-id <span class="hljs-string">"<span class="hljs-variable">${vmId}</span>"</span> --auth-type AAD</pre></div><p id="8a45">If everything is fine, you should be fine to run it and login the vm with your AzureAD account</p><div id="c11a"><pre>./aad-login-vm.sh

<span class="hljs-punctuation">...</span>

<span class="hljs-punctuation">[</span>bill.wang<span class="hljs-meta">@example</span>.au<span class="hljs-meta">@vm</span>-dev-fc30-vm000000 ~<span class="hljs-punctuation">]</span><span class="hljs-variable">$</span></pre></div><p id="a0f6">If you’ve been successful, after logging in, your AzureAD account (typically your Outlook mailbox) should appear as the login account in the shell prompt.</p><h1 id="7b56">Reference</h1><p id="1f0c"><a href="https://damn.engineer/2022/01/24/terraform-multiple-azure-subscriptions">ACCESSING MULTIPLE AZURE SUBSCRIPTIONS IN A SINGLE TERRAFORM RUN</a></p><p id="fbf2">[github repo for this blog] <a href="https://github.com/Azure/terraform-azurerm-virtual-machine">Terraform Azure RM Virtual Machine Module</a></p></article></body>

[Terraform] Deploy Azure Virtual Machine with AAD enabled

Follow up on my blogs regarding Azure AD (Microsoft Entra ID) login to Azure Virtual Machines

Several series for the topic:

  • Stage 1 — Login to a Azure Virtual Machine (VM) using Azure AD Account on Linux , in the blog
  • Stage 2 — AzureAD support for Azure VMSS (virtual machine scale set), in the blog
  • Stage 3 — [Terraform] Deploy Azure Virtual Machine with AAD enabled, in current blog

Reference repository: https://github.com/ozbillwang/terraform-azurerm-virtual-machine-aad

Background

In many scenarios, Azure Virtual Machines are created using images obtained from the Azure Marketplace. However, this isn’t always the norm.

In large organizations, it’s often necessary to manage custom ‘Golden’ images. These images are tailored to meet specific requirements, such as applying CIS (center for internet security) hardening, installing the latest patches, configuring anti-virus agents, and adding other necessary applications unique to the organization.

Once these custom images are ready, they are shared through Azure Compute Gallery (formerly known as Shared Image Gallery) across all subscriptions within the same Azure Tenant.

Secondly, in line with security compliance requirements, we avoid creating local accounts or sharing private SSH keys. Therefore, AzureAD login is essential for the new virtual machines we create.

Terraform the IaC is our preferred tool for deploying these virtual machines, with the following key requirements:

  1. Enabling Azure Active Directory (AzureAD / AAD) , now it is called Microsoft Entra ID, for enhanced security.
  2. Creating virtual machines using our own ‘Golden’ images, which have been built and shared within the Azure Compute Gallery.
  3. Manage Azure groups that have permissions to log in to virtual machines with AzureAD accounts. It is strongly advised not to assign roles to individual users. Even if only one user needs access to the VM, it’s recommended to create a group, include the users, and assign permissions to that group.

In my previous blog, I introduced the manual process of enabling Azure Active Directory (AAD) for virtual machines. This time, I’ll demonstrate the automated approach using Infrastructure as Code (IaC).

Prerequsites

Get shared image id from azure computer gallery

terraform {

  required_version = ">= 1.2"

  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = ">= 3.11, < 4.0"
    }
  }
}

provider "azurerm" {
  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }
}

# Configuration for the "secondary" subscription
# Share image gallery from master subscription, where the images are built
provider "azurerm" {
  alias           = "secondary"
  subscription_id = var.subscription_secondary

  features {
    resource_group {
      prevent_deletion_if_contains_resources = false
    }
  }

}

data "azurerm_shared_image_version" "this" {
  provider            = azurerm.secondary
  name                = "latest"
  image_name          = var.image_name
  gallery_name        = var.gallery_name
  resource_group_name = var.gallery_rg
}

Explanation:

  • name is latest , so when new images are ready, you don’t need update your terraform codes, it will always pick up the latest image.
  • image_name is azure image definition name
  • gallery_name is the azure computer gallery name
  • resource_group_name is the computer gallery’s resource group name
  • normally images are built in a master subscription, and shared to client subscripotions. Secondard provider defines the access to master subscrioption

Save the file to main.tf , prepare variables.tf ( refer the code ) dry-run ( terraform plan ) to check if you can get the image id.

az account set --subscription <replace_with_your_subscrption_id>
az account show

terraform fmt
terraform init -reconfigure
terraform validate

terraform plan

If the command is fine, you should see the output of the image id.

Then you can move on

Deploy VM with the shared image id

module "linux-vm-aad" {
  source = "git::https://github.com/Azure/terraform-azurerm-virtual-machine.git"

  location            = var.location
  image_os            = "linux"
  resource_group_name = local.name
  #checkov:skip=CKV_AZURE_50:Demo for extension
  allow_extension_operations = true
  boot_diagnostics           = false

  identity = var.identity

  new_network_interface = {
    ip_forwarding_enabled = false
    ip_configurations = [
      {
        primary = true
      }
    ]
  }
  admin_username = "azureuser"
  admin_ssh_keys = [
    {
      public_key = tls_private_key.this.public_key_openssh
    }
  ]
  name = local.name
  os_disk = {
    caching              = "ReadWrite"
    storage_account_type = "Standard_LRS"
  }

  source_image_id = data.azurerm_shared_image_version.this.id

  size      = var.size
  subnet_id = data.azurerm_subnet.this.id
  extensions = [
    {
      name                 = "AADSSHLoginForLinux"
      publisher            = "Microsoft.Azure.ActiveDirectory",
      type                 = "AADSSHLoginForLinux",
      type_handler_version = "1.0"
    },
  ]
}

resource "azurerm_role_assignment" "this" {
  principal_id         = data.azuread_groups.this.object_ids[0]
  role_definition_name = "Virtual Machine Administrator Login"
  scope                = azurerm_resource_group.this.id
}

Explanations

  • Use Azure official terraform module to create virtual machine
  • Enable “System assigned” identity with code: identity = var.identity
  • Enable AzureAD extension with part of code:
extensions = [
    {
      name                 = "AADSSHLoginForLinux"
      publisher            = "Microsoft.Azure.ActiveDirectory",
      type                 = "AADSSHLoginForLinux",
      type_handler_version = "1.0"
    },
  ]
  • use image id we got from shared gallery: source_image_id = data.azurerm_shared_image_version.this.id
  • Assign role of Virtual Machine Administrator Login , so any one in that group can login the vm with AzureAD account and sudo to root
resource "azurerm_role_assignment" "this" {
  principal_id         = data.azuread_groups.this.object_ids[0]
  role_definition_name = "Virtual Machine Administrator Login"
  scope                = azurerm_resource_group.this.id
}

While there are other virtual machine related roles to assign, for the sake of simplicity, I typically grant administrator login directly to everyone allowed to access that VM. This minimizes the need for additional permission requests later on. :-)

  • Virtual Machine User Login — View Virtual Machines in the portal and login as a regular user.
  • Virtual Machine Administrator Login — View Virtual Machines in the portal and login as administrator

Set Variable Definitions (.tfvars) Files

$ cat dev.tfvars

# adjust with your environment
location="australiaeast"
subnet_name="AppSubnet"
vnet_name="vnet-dev"
vnet_rg="vnet-rg"
image_name="Golden_Image_Redhat_RHEL_9"
gallery_name="Golden_Images_prod"
gallery_rg="Golden_Image-Builder"

Dry-run the codes

#! /usr/bin/env bash

az account set --subscription <replace_with_your_subscrption_id>
az account show

terraform fmt
terraform init -reconfigure
terraform validate

terraform plan -var-file=dev.tfvars -out='planfile'
# terraform apply "planfile"

If it works as expect, you can apply the change by command

terraform apply "planfile"

Login the VM with AAD account

$ cat aad-login-vm.sh

# adjust with your environment
subscription=<replace_with_your_subscrption_id>
az account set -s ${subscription}
az account show

bastionName="<replace_with_bastion_name>"
bastionRG="<replace_with_bastion_resource_group>"

# replace with the virtual machine name and its resource group.
vmName="vm-dev-4134"
vmRG="vm-dev-4134"

vmId=$(az vm list --resource-group ${vmRG} --query "[?name=='$vmName'].id" --output tsv)
echo $vmId

az network bastion ssh --name "${bastionName}" --resource-group "${bastionRG}" --target-resource-id "${vmId}" --auth-type AAD

If everything is fine, you should be fine to run it and login the vm with your AzureAD account

./aad-login-vm.sh

...

[bill.wang@example.au@vm-dev-fc30-vm000000 ~]$

If you’ve been successful, after logging in, your AzureAD account (typically your Outlook mailbox) should appear as the login account in the shell prompt.

Reference

ACCESSING MULTIPLE AZURE SUBSCRIPTIONS IN A SINGLE TERRAFORM RUN

[github repo for this blog] Terraform Azure RM Virtual Machine Module

Terraform
Azure
Azure Ad
DevOps
Best Practices
Recommended from ReadMedium