Jingle All the Way to Savings: Automate Azure Bastion with Azure Automation!

In my blog post contribution for this year’s Festive Tech Calendar, I’ll show you how to reduce Azure costs by automating the runtime of your Azure Bastion host(s) with Azure Automation.

The Festive Tech Calendar is a collaborative community initiative founded by Gregor Suttie (Azure Greg)Richard Hooper (Pixel Robots)Keith AthertonSimon LeeLisa Hoving, and Matt Boyd.

Running throughout December, this initiative brings together a rich and diverse array of content contributed by tech enthusiasts from around the globe. It offers a vibrant mix of videos and blog posts, all designed and shared by the community.

You can find all the videos and blog posts on https://festivetechcalendar.com/ and the Festive Tech YouTube Channel.

You can also stay updated by following @_CloudFamily or keeping an eye on X for hashtags like #festivetechcalendar2024 and #CloudFamily.

But let’s get started and learn how to optimize Bastion usage costs, cut expenses, and save big not just this holiday season but all year round.

Why use Azure Automation with Azure Bastion

Most of us who are already familiar with Azure Bastion use it in our own or customer Azure environments. It is a fully managed platform-as-a-service (PaaS) from Microsoft Azure, providing secure, seamless remote access to virtual machines (VMs) without exposing them to the public internet. It is commonly deployed in personal Azure setups or customer environments.

However, depending on which SKU you choose, the pricing for Bastion hosts can add up significantly on your Azure bill, especially if you don’t use them constantly. To get an idea of the current pricing, you can check the current pricing via this link: https://azure.microsoft.com/en-us/pricing/details/azure-bastion/

To help save money when using Azure Bastion in an environment, I typically configure it to only run during business hours. This means you only pay for the hours when the Azure Bastion host is actively in use, which occurs only during business hours. This can result in significant cost savings over time.

To effectively manage costs with Azure Bastion, you can use Azure Automation. In this blog post, I’ll show you how to configure it to operate Azure Bastion only during business hours, ensuring you only pay for its active usage.

Azure prerequisites

  • An Azure subscription is required, preferably multiple subscriptions if you are following the Cloud Adoption Framework (CAF) enterprise-scale architecture. This includes a connectivity and/or management subscription, along with at least one corp subscription (landing zone) for deploying your Azure VM resources.
  • An Azure Administrator account with the appropriate RBAC roles, such as Owner or Contributor at the subscription or resource group level.
  • An Azure Bastion host* (Basic or Standard), preferably running in the management subscription, should be part of the Hub VNet with peering configured to the VNets in the corporate or other subscriptions hosting your Azure VMs.

*If you’d like to create an Azure Bastion host using Azure PowerShell, you can refer to this blog post I wrote previously.

Create an Azure Automation Account

To get started, we’ll need an Azure Automation Account. For those unfamiliar with it, the easiest way to create one is via the Azure Portal. However, for more advanced users, it’s preferable and more secure to use automation to deploy it with all preferred settings applied at once. You can use Bicep or Terraform for this, but Azure PowerShell is also an option.

I have already written a blog post demonstrating how to use an Azure PowerShell script to create an Azure Automation Account within a specific subscription. You can access it via the following link: https://wmatthyssen.com/2024/02/22/create-an-azure-automation-account-with-azure-powershell/.

You can simple run the script via Windows Terminal or your preferred method.

Before proceeding to the next step, ensure that the Azure Automation system-assigned identity has the necessary Azure role assignments. Typically, the Contributor role on the Azure subscription hosting the Azure Bastion host is sufficient.

However, if the resource group containing the Azure Bastion resources is protected by a resource lock, the identity must be assigned the Owner role to allow it to remove the lock.

Once your Azure Automation Account is available and the correct role assignments have been applied in your management subscription, or if these were already in place, you can move on to the next step.

Create an Azure Bastion delete runbook

Once you have an Azure Automation Account in your environment, the next step is to create an Azure Bastion delete runbook. This involves writing a PowerShell script that deletes your Azure Bastion resource within the Azure Automation Account. The runbook can then be scheduled, for example, after business hours, or manually executed to remove the Bastion host when it is no longer needed, helping manage your Azure costs effectively.

To get started, and if it’s not already open, sign in to the Azure Portal with your credentials.

Next, use the global search bar or navigate through the portal to go to your Azure Automation Account.

Then, on the Automation Account blade, click on “Runbooks” in the left-hand menu, and then select “Create a runbook”.

On the “Create a runbook” page, fill in all the required details such as NameRunbook type (select PowerShell), Runtime version, and provide a clear description for the runbook. Then click Next.

On the next page, specify all the required tags that you use in your environment. After adding the necessary tags, click Next again.

Then click Create, and wait for the runbook to be created. Once it’s created, the runbook editor will open.

In the runbook editor, enter the following PowerShell script to delete the Azure Bastion instance, and click Save to save it.

Just adjust the two variables at the top of the script with your own values. If you don’t have a resource lock, you can remove the variable and the corresponding part of the script.

## Variables
$bastionName = "bas-hub-myh-01"
$lockName = "DoNotDeleteLock"
 
## Connect using a Managed Service Identity
try {
    $AzureConnection = (Connect-AzAccount -Identity).context
}
catch {
    Write-Output "There is no system-assigned user identity. Aborting."
    exit
}
 
## Change the current context to use a management subscription
$subNameManagement = Get-AzSubscription | Where-Object {$_.Name -like "*management*"}
Set-AzContext -SubscriptionId $subNameManagement.SubscriptionId | Out-Null
 
## Get the Bastion Resource
$bastion = Get-AzBastion | Where-Object Name -Match $bastionName
 
## Check if the Bastion resource group has a resource lock; if so, remove the resource lock
$rgNameBastion = $bastion.ResourceGroupName
$lock = Get-AzResourceLock -ResourceGroupName $rgNameBastion
# Check if resource lock exists
if ($null -ne $lock){
    # Remove the resource lock
    Remove-AzResourceLock -LockName $lockName -ResourceGroupName $bastion.ResourceGroupName -Force | Out-Null
} 
 
## Delete the Azure Bastion host
if ($bastion) {
    # Delete the Bastion
    $bastionName = $bastion.Name
    Remove-AzBastion -InputObject $bastion -Force | Out-Null
} else {
    Write-Output "Azure Bastion '$BastionName' not found in resource group '$ResourceGroupName'."
}

After you have saved the runbook, you can test the code in the “Test pane” by selecting the Test pane. In the test pane, click Start to initiate the execution of your runbook.

The runbook will be queued for execution. If the runbook is successful, it will also delete the Bastion host specified in the variable you provided.

Once the runbook completes, the results will be displayed on the same page, providing feedback on the success or failure of the operation.



After testing, if the result is satisfactory (shows as completed without any errors), you can close the “Test pane” to return to the main runbook editor view.

Before setting up a schedule, make sure the runbook is published. To do this, click on Publish in the runbook editor. Publishing the runbook makes it available for execution based on the schedule you will set up. Click Yes to proceed.

The last step is to add a schedule by selecting the “Schedules” menu item and selecting “Add a schedule“.

This allows you to automate the execution of your runbook at specific times or intervals, ensuring the Azure Bastion delete runbook runs as needed without manual intervention.

When adding a new schedule, there are two options: “Schedule” and “Parameters and run settings”. Since the current runbook script does not require any parameters, the second option is not relevant. In this case, we only need to create a schedule and link it to the runbook.

So, select “Link a schedule to your runbook” and then click on “Add a schedule“.



Fill in the “New Schedule” parameters by providing a name, a description, and the start date. Set the desired hour, specify the correct time zone, and choose the recurrence pattern, ideally set it to recur every day. After configuring all these settings, click “Create” to save the schedule.

Then click “OK” the schedule the runbook.

Once the schedule is linked to the runbook, you can move on to the next step where we will configure and set up the Create-Bastion runbook.

Create an Azure Bastion create runbook

In addition to the Azure Bastion delete runbook, you also need to create an Azure Bastion create runbook. This runbook enables you to manually redeploy the Azure Bastion host whenever needed, or for example, a quarter before business hours start each day.

To begin, go to Runbooks once again, and select Create a runbook.

On the Create a runbook page, fill in all the required details such as NameRunbook type (select PowerShell), Runtime version, and provide a clear description for the runbook. Then click Next.

On the next page, enter all the required tags used in your environment. After adding the necessary tags, click “Next” once more.


Then click “Create” to finalize and create the runbook.

In the runbook editor, paste the following PowerShell script into the editor to (re-)create the Azure Bastion instance, and click Save to save it.

Make sure to adjust all the variables at the top of the script to match your own values.


## Variables
$spoke = "hub"
$rgNameBastion = "rg-hub-myh-bastion-01"
$bastionName = "bas-hub-myh-01"
$lockName = "DoNotDeleteLock"
$purpose = "bastion"
 
$tagSpokeName = "Env"
$tagSpokeValue = "$($spoke[0].ToString().ToUpper())$($spoke.SubString(1))"
$tagCostCenterName  = "CostCenter"
$tagCostCenterValue = "23"
$tagCriticalityName = "Criticality"
$tagCriticalityValue = "High"
$tagPurposeName = "Purpose"
$tagPurposeValueBastion = "$($purpose[0].ToString().ToUpper())$($purpose.SubString(1))"
$tagVnetName = "VNet"
 
## Connect using a Managed Service Identity
try {
    $AzureConnection = (Connect-AzAccount -Identity).context
}
catch {
    Write-Output "There is no system-assigned user identity. Aborting."
    exit
}
 
## Change the current context to use a management subscription
$subNameManagement = Get-AzSubscription | Where-Object {$_.Name -like "*management*"}
Set-AzContext -SubscriptionId $subNameManagement.SubscriptionId | Out-Null
 
## Store the specified set of tags in a hash table
$tags = @{$tagSpokeName=$tagSpokeValue;$tagCostCenterName=$tagCostCenterValue;$tagCriticalityName=$tagCriticalityValue}
 
## Get the PIP and store as a variable
$pipNameBastion = Get-AzPublicIpAddress -ResourceGroupName $rgNameBastion
 
## Get the virtual network with the AzureBastionSubnet
$virtualNetwork = Get-AzVirtualNetwork | Where-Object {$_.Subnets -ne $null -and $_.Subnets.Name -contains "AzureBastionSubnet"}
 
if ($null -ne $virtualNetwork) {
    $vnetName = $virtualNetwork.Name
    $rgNameNetworking = $virtualNetwork.ResourceGroupName
} else {
Write-Host ("# Virtual network with 'AzureBastionSubnet' not found.")
}
 
## Redeploy Bastion host with Basic SKU
New-AzBastion -ResourceGroupName $rgNameBastion -Name $bastionName -PublicIpAddress $pipNameBastion -VirtualNetworkRgName $rgNameNetworking `
-VirtualNetworkName $vnetName | Out-Null
 
## Set tags on Bastion host
$bastion = Get-AzBastion -ResourceGroupName $rgNameBastion -Name $bastionName
Set-AzBastion -InputObject $bastion -Tag $tags -Force | Out-Null
 
## Lock the Azure Bastion resource group with a CanNotDelete lock
$lock = Get-AzResourceLock -ResourceGroupName $rgNameBastion
 
if ($null -eq $lock){
    New-AzResourceLock -LockName $lockName -LockLevel CanNotDelete -ResourceGroupName $rgNameBastion -LockNotes "Prevent $rgNameBastion from deletion" -Force | Out-Null
} 

After saving the runbook, you can test the code by selecting the Test pane. If everything works as expected, you can proceed to publish it.

Then create a schedule, just as you did for the Delete-Bastion runbook, but this time specify the creation time. For example, set it to start 15 minutes before the beginning of business hours.

Conclusion

Thank you for reading! I’m excited to be part of this year’s Festive Tech Calendar once again and hope you enjoy all the amazing content being shared throughout December 2024.

If you have any questions about Azure Bastion in combination with Azure Automation, don’t hesitate to reach out to me on X at @wmatthyssen or connect with me on LinkedIn.

Happy reading and viewing, take care, and have a wonderful holiday season! 🎄🎆

About the Author:

Wim Matthyssen

Wim is an Azure Technical Advisor and Trainer with over fifteen years of Microsoft technology experience. As a Microsoft Certified Trainer (MCT), his strength is assisting companies in the transformation of their businesses to the Cloud by implementing the latest features, services, and solutions. Currently, his main focus is on the Microsoft Hybrid Cloud Platform, and especially on Microsoft Azure and the Azure hybrid services.   Wim is also a Microsoft MVP in the Azure category and a founding board member of the MC2MC user group. As a passionate community member, he regularly writes blogs and speaks about his daily experiences with Azure and other Microsoft technologies.

Reference:

Matthyssen, W (2024). Jingle All the Way to Savings: Automate Azure Bastion with Azure Automation. Available at: Jingle All the Way to Savings: Automate Azure Bastion with Azure Automation! – Wim Matthyssen [Accessed: 19th December 2024].

Share this on...

Rate this Post:

Share:

Topics:

Azure