🌟

Azure Firewallの開始・停止を自動化する

2023/05/09に公開

Azure Firewallは利用料金が結構かかるので、検証環境などでは夜間・休日は停止しておきたい。
というわけで、Azure Automationを使って開始・停止を自動化する。
※祝日や非営業日(年末年始とか)は対応してないです。

手順

  • 前提
    Azure FirewallやAutomation Accountは作成済み
    Automation AccountにはManaged Idを割り当てておく
  1. Azure Firewallを開始・停止するPowershell Scriptを作成する
  2. Automation AccountのRunbookをスケジュール実行するBicep templateを作成する
  3. Automation AccountのRunbookを作成と設定
    3-1. Powershell ScriptをAutomation AccountのRunbookとしてアップロードする
    3-2. Bicep templateでRunbookのscheduleを登録する
  4. RBACを設定する

Azure Firewallを開始・停止するPowershell Script

実装例: StartStopAzureFirewall.ps1

param(
  [parameter(Mandatory = $true)]
  [string] $resourceName,

  [parameter(Mandatory = $true)]
  [string] $resourceGroupName,

  [parameter(Mandatory = $false)]
  [string] $vnetResourceName,

  [parameter(Mandatory = $false)]
  [string] $pipResourceName,

  [parameter(Mandatory = $true)]
  [string] $action
)

filter timestamp { "[$(Get-Date -Format G)]: $_" }

Write-Output "Script started." | timestamp
$ErrorActionPreference = "Stop"

Disable-AzContextAutosave -Scope Process
Connect-AzAccount -Identity

if ($action -eq 'stop') {
  $afw = Get-AzFirewall `
    -Name $resourceName `
    -ResourceGroupName $resourceGroupName

  $afw.Deallocate()

  Set-AzFirewall -AzureFirewall $afw
}
else {
  $afw = Get-AzFirewall `
    -Name $resourceName `
    -ResourceGroupName $resourceGroupName

  $vnet = Get-AzVirtualNetwork `
    -Name $vnetResourceName `
    -ResourceGroupName $resourceGroupName

  $pip = Get-AzPublicIpAddress `
    -Name $pipResourceName `
    -ResourceGroupName $resourceGroupName

  $afw.Allocate($vnet, $pip)

  # $afw.IpConfigurations[0].Name = "ipconfig"

  Set-AzFirewall -AzureFirewall $afw
}

Write-Output "Script finished." | timestamp
  • actionパラメータでFirewallを開始/停止を使い分ける
  • リソースにはManaged Idを使ってアクセス
  • Firewallは開始にすると構成情報の一部がデフォルトに戻るので、必要に応じて作り込みが必要
    • 例えば、Public IP構成の名前など
      # $afw.IpConfigurations[0].Name = "ipconfig"
  • VNETとPublicIPは、Firewallと同じリソースグループにあるものとする

Automation AccountのRunbookをスケジュール実行するBicep template

Runbookのスケジュール登録

実装例: aa-schedule.bicep

param today string = utcNow('yyyy-MM-dd')
param aa_name string
param start_schedule_name string
param start_schedule_time string
param start_description string = 'start schedule'
param stop_schedule_name string
param stop_schedule_time string
param stop_description string = 'stop schedule'

var _start_schedule_time = dateTimeAdd('${today}T${start_schedule_time}', 'P1D')
var _stop_schedule_time = dateTimeAdd('${today}T${stop_schedule_time}', 'P1D')
// correction: 'Asia/Tokyo'
var _start_local_time = dateTimeAdd(_start_schedule_time, '-PT9H')
var _stop_local_time = dateTimeAdd(_stop_schedule_time, '-PT9H')

resource start_schedule 'Microsoft.Automation/automationAccounts/schedules@2022-08-08' = {
  name: start_schedule_name
  parent: automation
  properties: {
    description: start_description
    startTime: _start_local_time
    expiryTime: '9999-12-31T23:59:59.9999999+00:00'
    frequency: 'Week'
    interval: 1
    advancedSchedule: {
      weekDays: [
        'Monday'
        'Tuesday'
        'Wednesday'
        'Thursday'
        'Friday'
      ]
    }
    timeZone: 'Asia/Tokyo'
  }
}

resource stop_schedule 'Microsoft.Automation/automationAccounts/schedules@2022-08-08' = {
  name: stop_schedule_name
  parent: automation
  properties: {
    description: stop_description
    startTime: _stop_local_time
    expiryTime: '9999-12-31T23:59:59.9999999+00:00'
    frequency: 'Day'
    interval: 1
    advancedSchedule: null
    timeZone: 'Asia/Tokyo'
  }
}

resource automation 'Microsoft.Automation/automationAccounts@2022-08-08' existing = {
  name: aa_name
}
  • スケジュールの日時設定を日本時間に合わせるようにvarの定義でゴニョゴニョやってます(良い方法が思いつかなかった
  • 開始のスケジュールは、月~金曜日を指定
  • 停止のスケジュールは、毎日(休日出勤した人の停止忘れ防止用

RunBookのジョブスケジュール登録

実装例: aa-job-schedule.bicep

param now string
param aa_name string
param job_schedules array

@batchSize(1)
resource job_schedule 'Microsoft.Automation/automationAccounts/jobSchedules@2022-08-08' = [for job in job_schedules: {
  name: guid(subscription().subscriptionId, aa_name, job.runbook_name, job.schedule_name, now)
  parent: automation
  properties: {
    parameters: {
      ResourceGroupName: job.target_rg_name
      ResourceName: job.target_resource_name
      VnetResourceName: job.target_vnet_name
      PipResourceName: job.target_pip_name
      Action: job.target_action
    }
    runbook: {
      name: job.runbook_name
    }
    schedule: {
      name: job.schedule_name
    }
  }
}]

resource automation 'Microsoft.Automation/automationAccounts@2022-08-08' existing = {
  name: aa_name
}
  • 特にコメントはなし
    • 開始と停止の2つを登録するのでjob_scheduleでfor文を使用しているぐらい

開始・停止スケジュール登録用

実装例: aa-fw-start-stop.bicep

param now string = utcNow('yyyyMMdd-HHmmss')
param aa_name string
param run_book_name string
param schedules_name string = 'afw-weekday'
param rg_name string = 'Firewallのリソースグループ名'
param fw_name string = 'Firewallのリソース名'
param vnet_name string = 'Firewallを配置するVNETのリソース名'
param pip_name string = 'Firewallに割り当てるPublicIPのリソース名'

var _start_schedule_name = 'start-schedule-${schedules_name}'
var _stop_schedule_name = 'stop-schedule-${schedules_name}'
var _start_schedule_time = '08:30:00'
var _stop_schedule_time = '18:00:00'

var _job_schedules = [
  {
    runbook_name: run_book_name
    schedule_name: _start_schedule_name
    target_rg_name: rg_name
    target_resource_name: fw_name
    target_vnet_name: vnet_name
    target_pip_name: pip_name
    target_action: 'start'
  }
  {
    runbook_name: run_book_name
    schedule_name: _stop_schedule_name
    target_rg_name: rg_name
    target_resource_name: fw_name
    target_vnet_name: null
    target_pip_name: null
    target_action: 'stop'
  }
]

module aa_schedule './aa-schedule.bicep' = {
  name: 'aa-schedule'
  params: {
    aa_name: aa_name
    start_schedule_name: _start_schedule_name
    start_schedule_time: _start_schedule_time
    stop_schedule_name: _stop_schedule_name
    stop_schedule_time: _stop_schedule_time
  }
  scope: resourceGroup()
}

module aa_job_schedule './aa-job-schedule.bicep' = {
  name: 'aa-job-schedule'
  dependsOn: [ aa_schedule ]
  params: {
    now: now
    aa_name: aa_name
    job_schedules: _job_schedules
  }
  scope: resourceGroup()
}
  • Automation Accountのリソース名とRunbook名はparam指定とした
    • 開始・停止のPowershellをアップロードを外出ししているため
  • その他のparam指定は、環境に応じて書き換える
  • Azure Firewallの開始時間の指定
    var _start_schedule_time = '08:30:00'
  • Azure Firewallの停止時間の指定
    var _stop_schedule_time = '18:00:00'

Automation AccountのRunbookの作成と設定

実装例

$aaResourceName = "Automation Accountのリソース名"
$aaResourceGroup = "Automation Accountのリソースグループ名"
$runBookName = "StartStop-AzureFirewall" # Runbook名

Import-AzAutomationRunbook `
  -AutomationAccountName $aaResourceName `
  -ResourceGroupName $aaResourceGroup `
  -Name $runBookName `
  -Path "./StartStopAzureFirewall.ps1" `
  -Type PowerShell `
  -Published `
  -Force

$deployName = "deploy-aa-schedule-" + (Get-Date).ToString("yyyy-MM-dd-HHmmss")
New-AzResourceGroupDeployment `
  -Name $deployName `
  -ResourceGroupName $aaResourceGroup `
  -TemplateFile "aa-fw-start-stop.bicep" `
  -aa_name $aaResourceName `
  -run_book_name $runBookName
  • Import-AzAutomationRunbookを使って、Powershellをアップロード
    • Runbookがなければ作成される
    • 既存のRunbookがあれば、Powershell Scriptを上書きするようである
  • New-AzResourceGroupDeploymentを使って、Runbookのスケジュール登録を行う
  • Runbookのスケジュール登録は翌日からになっているはず・・・


RBACの設定

関連リソースのIAM設定でAutomation AccountのManaged Idに対してNetwork Contributorを割り当てる
Firewall Policyを使っている場合は、Firewall Policyにもロールを割り当てる必要


以上、ここまで

Discussion