Azure Infrastructure Automation with Terraform ( Part 2 )

Infrastructure as Code (IaC)

ยท

7 min read

Azure Infrastructure Automation with Terraform ( Part 2 )

Part 1:rohitexplainstech.hashnode.dev/azure-infras..

  • We are going to create two important Bastion Resources
  1. Azure Bastion Host

  2. Azure Bastion Service

  • We are going to use the following Azure Resources for the same.
  1. Terraform Input Variables

  2. azurerm_public_ip

  3. azurerm_network_interface

  4. azurerm_linux_virtual_machine

  5. Terraform Null Resource null_resource

  6. Terraform File Provisioner

  7. Terraform remote-exec Provisioner

  8. azurerm_bastion_host

  9. Terraform Output Values

Steps:

  1. Bastion Linux VM input variables ( Create File: 8_01_bastion_host_input_variables.tf )

     # Bastion linux vm input variables
    
     variable "bastion_service_subenet_name" {
       description = "Bastion service subnet name"
       default = "AzureBastionSubnet"
     }
    
     variable "bastion_service_address_prefixs" {
       description = "Bastion Service address prefixs"
       default = ["10.0.101.0/27"]
     }
    
  2. Bastion Host Linux Virtual Machine ( Create File: 8_02_bastion_host_linuxvm.tf )

     # create Public ip address
     resource "azurerm_public_ip" "bastion_host_publicip" {
       name = "${local.resource_name_prefix}-bastion_host_publicip"
       resource_group_name = azurerm_resource_group.rg.name
       location = azurerm_resource_group.rg.location
       allocation_method = "Static"
       sku = "Standard"
     }
    
     # Create network interface
     resource "azurerm_network_interface" "bastion_host_linuxvm_nic" {
       name = "${local.resource_name_prefix}-bastion_host_linuxvm_nic"
       location = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
    
       ip_configuration {
         name = "bastion-host-ip-1"
         subnet_id = azurerm_subnet.bastionsubnet.id
         private_ip_address_allocation = "Dynamic"
         public_ip_address_id = azurerm_public_ip.bastion_host_publicip.id
    
       }
     }
    
     # Azure Linux Virtual Machine - Bastion Host
     resource "azurerm_linux_virtual_machine" "bastion_host_linuxvm" {
       name = "${local.resource_name_prefix}-bastion_host_linuxvm"
       resource_group_name = azurerm_resource_group.rg.name
       location = azurerm_resource_group.rg.location
       size = "Standard_DS1_v2"
       admin_username = "azureuser"
       network_interface_ids = [azurerm_network_interface.bastion_host_linuxvm_nic.id]
       admin_ssh_key {
         username = "azureuser"
         public_key = file("${path.module}/ssh-keys/terraform-azure.pub")
       }
    
       os_disk {
         caching = "ReadWrite"
         storage_account_type = "Standard_LRS"
       }
    
       source_image_reference {
         publisher = "RedHat"
         offer = "RHEL"
         sku = "83-gen2"
         version = "latest"
       }
     }
    
  3. Add Null Provider in c1-versions.tf

     # Terraform Settings Block
     terraform {
       required_providers {
         azurerm = {
           source = "hashicorp/azurerm"
           version = "3.110.0"
         }
          random = {
           source = "hashicorp/random"
           version = "3.6.2"
         }
         null = {
           source = "hashicorp/null"
           version = ">= 3.0"
         }
       }
     }
    
     # Providers-block
     provider "azurerm" {
      features {}
     }
    
  4. Move the ssh key to the bastion host VM, Create Null Resource, Connection Block, File and Remote Exec Provisioner.

    ( Create file: 8_03_move_ssh_key_to_bastion_host.tf )

     # Create a null resource and provisioners
     resource "null_resource" "null_copy_ssh_key_to_bastion" {
       depends_on = [ azurerm_linux_virtual_machine.bastion_host_linuxvm ]
    
       # Connection Block for Provisioners to connect to Azure VM Instance
         connection {
           type = "ssh"
           host = azurerm_linux_virtual_machine.bastion_host_linuxvm.public_ip_address
           user = azurerm_linux_virtual_machine.bastion_host_linuxvm.admin_username
           private_key = file("${path.module}/ssh-keys/terraform-azure.pem")
         }
       #  File Provisioner: Copies the terraform-key.pem file to /tmp/terraform-key.pem
       provisioner "file" {
         source = "ssh-keys/terraform-azure.pem"
         destination = "/tmp/terraform-azure.pem"
       }
       ## Remote Exec Provisioner: Using remote-exec provisioner fix the private key permissions on Bastion Host
       provisioner "remote-exec" {
         inline = ["sudo chmod 400 /tmp/terraform-azure.pem"]
       }
     }
    

    Till this, we have completed the bastion host configuration.

  5. Azure Bastion Service Resources.

    (Create file: 8_04_AzureBastionService.tf)

     # Azure Bastion Service - Resources
     ## Resource -1 : Azure Bastion Subnet
    
     resource "azurerm_subnet" "bastion_service_subnet" {
       name = var.bastion_service_subenet_name
       resource_group_name = azurerm_resource_group.rg.name
       virtual_network_name = azurerm_virtual_network.vnet.name
       address_prefixes = var.bastion_service_address_prefixs
     }
    
     ## Resource - 2: Azure Bastion Public IP
    
     resource "azurerm_public_ip" "bastion_service_publicip" {
       name = "${local.resource_name_prefix}-bastion_service_publicip"
       location = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
       allocation_method = "Static"
       sku = "Standard"
     }
    
     ## Resource -3: Azure Bastion Service Host
    
     resource "azurerm_bastion_host" "bastion_host" {
       name = "${local.resource_name_prefix}-bastion_service"
       location = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
    
       ip_configuration {
         name = "configuration"
         subnet_id = azurerm_subnet.bastion_service_subnet.id
         public_ip_address_id = azurerm_public_ip.bastion_service_publicip.id
       }
    
     }
    
  6. Remove Public Access to Web Linux VM

    ( Comment 7-02-web-linuxvm-publicip.tf )

     /*
     #Resorce 1: Public-IP
     resource "azurerm_public_ip" "web_linuxvm_publicip" {
       name = "${local.resource_name_prefix}-web_linuxvm_publicip"
       resource_group_name = azurerm_resource_group.rg.name
       location = azurerm_resource_group.rg.location
       allocation_method = "Static"
       sku = "Standard"
       domain_name_label = "app1-vm-${random_string.my-random.id}"
       tags = local.comman_tags
     } 
     */
    
  7. In 7-03-web-linuxvm-network-interface.tf comment public IP association-related argument in Network Interface Resource public_ip_address_id = azurerm_public_ip.web_linuxvm_publicip.id

     #Resource:Network Interface {NIC}
     resource "azurerm_network_interface" "web_linuxvm_nic" {
       name = "${local.resource_name_prefix}-web_linuxvm_nic"
       location = azurerm_resource_group.rg.location
       resource_group_name = azurerm_resource_group.rg.name
    
       ip_configuration {
         name = "web-linuxvm-ip-1"
         subnet_id = azurerm_subnet.websubnet.id
         private_ip_address_allocation = "Dynamic"
         # public_ip_address_id = azurerm_public_ip.web_linuxvm_publicip.id 
       }
     }
    

    In c7-06-web-linuxvm-outputs.tf comment Outputs related to Public IP Address.

     # Public IP Outputs
    
     /*
     ## Public IP Address
     output "web_linuxvm_public_ip" {
       description = "Web Linux VM Public Address"
       value = azurerm_public_ip.web_linuxvm_publicip.ip_address
     }
     */
    
     # Network Interface Outputs
     ## Network Interface ID
     output "web_linuxvm_network_interface_id" {
       description = "Web Linux VM Network Interface ID"
       value = azurerm_network_interface.web_linuxvm_nic.id
     }
     ## Network Interface Private IP Addresses
     output "web_linuxvm_network_interface_private_ip_addresses" {
       description = "Web Linux VM Private IP Addresses"
       value = [azurerm_network_interface.web_linuxvm_nic.private_ip_addresses]
     }
    
     # Linux VM Outputs
    
     ## Virtual Machine Public IP
     /*
     output "web_linuxvm_public_ip_address" {
       description = "Web Linux Virtual Machine Public IP"
       value = azurerm_linux_virtual_machine.web_linuxvm.public_ip_address
     }
     */
    
     ## Virtual Machine Private IP
     output "web_linuxvm_private_ip_address" {
       description = "Web Linux Virtual Machine Private IP"
       value = azurerm_linux_virtual_machine.web_linuxvm.private_ip_address
     }
     ## Virtual Machine 128-bit ID
     output "web_linuxvm_virtual_machine_id_128bit" {
       description = "Web Linux Virtual Machine ID - 128-bit identifier"
       value = azurerm_linux_virtual_machine.web_linuxvm.virtual_machine_id
     }
     ## Virtual Machine ID
     output "web_linuxvm_virtual_machine_id" {
       description = "Web Linux Virtual Machine ID "
       value = azurerm_linux_virtual_machine.web_linuxvm.id
     }
    
  8. Update terraform.tfvars

     business_division = "hr"
     environment = "dev"
     resource_group_name = "rg"
     resource_group_location = "eastus"
    
     vnet_name = "vnet"
     vnet_address_space = ["10.1.0.0/16"]
    
     web_subnet_name = "websubnet"
     web_subnet_address = ["10.1.1.0/24"]
    
     app_subnet_name = "appsubnet"
     app_subnet_address = ["10.1.11.0/24"]
    
     db_subnet_name = "dbsubnet"
     db_subnet_address = ["10.1.21.0/24"]
    
     bastion_subnet_name = "bastionsubnet"
     bastion_subnet_address = ["10.1.100.0/24"]
    
     # Newly added
     bastion_service_subenet_name = "AzureBastionSubnet"
     bastion_service_address_prefixs = ["10.1.101.0/27"]
    
  9. Create file: 8-05-bastion-outputs.tf for Bastion Host Public IP Output

     ## Bastion Host Public IP Output
     output "bastion_host_linuxvm_public_ip_address" {
       description = "Bastion Host Linux VM Public Address"
       value = azurerm_public_ip.bastion_host_publicip.ip_address
     }
    
  10. Execute Terraform Commands.

    # Terraform Initialize
    terraform init
    
    # Terraform Validate
    terraform validate
    
    # Terraform Plan
    terraform plan
    
    # Terraform Apply
    terraform apply -auto-approve
    
    ## -- Important Note ---
    # --> Azure Bastions Service takes 10 to 15 minutes to create.
    

  11. Verify Resources - Bastion Host

    # Verify Resources - Virtual Network
    1. Azure Resource Group
    2. Azure Virtual Network
    3. Azure Subnets (Web, App, DB, Bastion)
    4. Azure Network Security Groups (Web, App, DB, Bastion)
    5. View the topology
    6. Verify Terraform Outputs in Terraform CLI
    
    # Verify Resources - Web Linux VM 
    1. Verify Network Interface created for Web Linux VM
    2. Verify Web Linux VM
    3. Verify Network Security Groups associated with VM (web Subnet NSG)
    4. View Topology at Web Linux VM -> Networking
    5. Verify if only private IP associated with Web Linux VM
    
    # Verify Resources - Bastion Host
    1. Verify Bastion Host VM Public IP
    2. Verify Bastion Host VM Network Interface
    3. Verify Bastion VM
    4. Verify Bastion VM -> Networking -> NSG Rules
    5. Verify Bastion VM Topology
    
    # Connect to Bastion Host VM
    1. Connect to Bastion Host Linux VM
    ssh -i ssh-keys/terraform-azure.pem azureuser@<Bastion-Host-LinuxVM-PublicIP>
    sudo su - 
    cd /tmp
    ls 
    2. terraform-azure.pem file should be present in /tmp directory
    
    # Connect to Web Linux VM using Bastion Host VM
    1. Connect to Web Linux VM
    ssh -i ssh-keys/terraform-azure.pem azureuser@<Web-LinuxVM-PrivateIP>
    sudo su - 
    cd /var/log
    tail -100f cloud-init-output.log
    cd /var/www/html
    ls -lrt
    cd /var/www/html/app1
    ls -lrt
    exit
    exit
    

    Connecting to bastion host Linux VM.

    Now ssh to the web-linuxvm using its private IP

    Now we can connect web-linuxvm through bastion-host-linuxvm.

  12. Verify Resources - Bastion Service

    # Verify Azure Bastion Service
    1. Go to Azure Management Porta Console -> Bastions
    2. Verify Bastion Service -> hr-dev-bastion-service
    3. Verify Settings -> Sessions
    4. Verify Settings -> Configuration
    
    # Connect to Web Linux VM using Bastion Service
    1. Go to Web Linux VM using Azure Portal Console
    2. Portal Console -> Virtual machines -> hr-dev-web-linuxvm ->Settings -> Connect
    3. Select "Bastion" tab -> Click on "Use Bastion"
    - Open in new window: checked
    - Username: azureuser
    - Authentication Type: SSH Private Key from Local File
    - Local File: Browse from ssh-keys/terraform-azure.pem
    - Click on Connect
    4. In new tab, we should be logged in to VM "hr-dev-web-linuxvm" 
    5. Run additional commands
    sudo su - 
    cd /var/www/html
    ls 
    cd /var/www/html/app1
    ls
    
    # Verify Bastion Sessions 
    1. Go to Azure Management Porta Console -> Bastions
    2. Verify Bastion Service -> hr-dev-bastion-service
    3. Verify Settings -> Sessions
    

    Now go to hr-dev-web-linuxvm and click on connect.

    Click on Go to Bastion

    Now we can connect web-linuxvm through Azure-Bastion-Service.

  13. Check for the session in Azure Bastion Service.

Delete Resources

# Delete Resources
terraform destroy 
[or]
terraform apply -destroy -auto-approve

# Clean-Up Files
rm -rf .terraform* 
rm -rf terraform.tfstate*

We have completed the implementation of the Bastion host on Linux VM and also implemented Azure Bastion Service.๐ŸŽ‰

In the next blog, we will set up the Azure Standard LoadBalancer.

GitHub Repo Link: https://github.com/DeoreRohit4/Azure-Infrastructure-Automation-with-Terraform-


Keep Exploring...

ย