Azure Bastion Automation with Azure DevOps

Deploying Azure Bastion via Azure DevOps: Scheduled Deployment and Destruction

Introduction

Azure Bastion is a fully managed platform-as-a-service (PaaS) that provides secure and seamless RDP/SSH connectivity to virtual machines directly in the Azure portal over SSL. By deploying Azure Bastion, you eliminate the need to expose your virtual machines to the public internet, thereby enhancing security and reducing the attack surface.

In many scenarios, it is crucial to have Azure Bastion deployed only during the hours it is needed. This approach offers several benefits:

  • Cost Efficiency: Azure Bastion incurs costs while it is running. By scheduling deployments and destructions, you can ensure that the resource is only active during necessary periods, thereby optimizing your cloud expenditure.
  • Enhanced Security: Limiting the deployment window reduces the potential attack surface. By having Azure Bastion active only during specific hours, you minimize the risk of unauthorized access during off-hours.
  • Resource Management: Automating the deployment and destruction of Azure Bastion helps in better resource management. It ensures that resources are not left running unintentionally, which can lead to unnecessary costs and potential security vulnerabilities.

This blog post will guide you through the process of deploying Azure Bastion using Azure DevOps, with a focus on scheduling deployments and destructions using Bicep templates.

Step-by-Step Implementation Guide Using Bicep

Step 1: Create Bicep Templates First, create a Bicep template for deploying Azure Bastion. Save the following code in a file named main.bicep:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
resource publicIp 'Microsoft.Network/publicIPAddresses@2022-01-01' {
  name: 'bastion-pip'
  location: resourceGroup().location
  sku: {
    name: 'Standard'
  }
  properties: {
    publicIPAllocationMethod: 'Static'
  }
}

// Import the existing vnet and subnet to get the subnet id for deployment
resource vnet 'Microsoft.Network/virtualNetworks@2022-11-01' existing = {
  name: 'vnet-ae-hub'
  scope: resourceGroup(vnetResourceGroupName)
}

resource subnet 'Microsoft.Network/virtualNetworks/subnets@2022-11-01' existing = {
  name: 'AzureBastionSubnet'
  parent: vnet
}

resource bastion 'Microsoft.Network/bastionHosts@2020-05-01' = {
  name: 'bastion'
  location: resourceGroup().location
  properties: {
    ipConfigurations: [
      {
        name: 'bastionHostIp'
        properties: {
          subnet: {
            id: subnet.id
          }
          publicIPAddress: {
            id: publicIp.id
          }
        }
      }
    ]
  }
}

Step 2: Configure Azure DevOps Pipeline for Deployment Next, configure your Azure DevOps pipeline to deploy the Bicep template. Create a new pipeline and add the following YAML configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
trigger:
- main

schedules:
- cron: "0 7 * * *" # Deploy at 7am daily
  displayName: Daily Deployment
  branches:
    include:
    - main

variables:
  action: 'deploy' # Set this to 'deploy' or 'destroy'

jobs:
- job: DeployOrDestroyBastion
  displayName: 'Deploy or Destroy Azure Bastion'
  pool:
    vmImage: 'ubuntu-latest'
  steps:
  - task: AzureCLI@2
    inputs:
      azureSubscription: 'your-service-connection'
      scriptType: 'bash'
      scriptLocation: 'inlineScript'
      inlineScript: |
        if [ $(action) == 'deploy' ]; then
          az deployment group create --resource-group rg-bastion --template-file main.bicep
        elif [ $(action) == 'destroy' ]; then
          az resource delete --resource-group rg-bastion --name bastion --resource-type "Microsoft.Network/bastionHosts"
        else
          echo "Invalid action specified. Use 'deploy' or 'destroy'."
        fi        

Step 3: Configure Azure DevOps Pipeline for Destroy Next, configure your Azure DevOps pipeline to destroy the Bicep template. Create a new pipeline and add the following YAML configuration:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
trigger:
- main

schedules:
- cron: "0 19 * * *" # Destroy at 7pm daily
  displayName: Daily Destroy
  branches:
    include:
    - main

variables:
  action: 'destroy' # Set this to 'deploy' or 'destroy'

jobs:
- job: DeployOrDestroyBastion
  displayName: 'Deploy or Destroy Azure Bastion'
  pool:
    vmImage: 'ubuntu-latest'
  steps:
  - task: AzureCLI@2
    inputs:
      azureSubscription: 'your-service-connection'
      scriptType: 'bash'
      scriptLocation: 'inlineScript'
      inlineScript: |
        if [ $(action) == 'deploy' ]; then
          az deployment group create --resource-group rg-bastion --template-file main.bicep
        elif [ $(action) == 'destroy' ]; then
          az resource delete --resource-group rg-bastion --name bastion --resource-type "Microsoft.Network/bastionHosts"
        else
          echo "Invalid action specified. Use 'deploy' or 'destroy'."
        fi        

Conclusion

Deploying Azure Bastion via Azure DevOps using Bicep templates and scheduling deployments and destructions can significantly enhance your infrastructure’s security and manageability. By automating these processes, you ensure that your virtual machines remain secure without manual intervention.

Learn More

For more detailed information and additional resources, visit the following Microsoft Learn articles: