Azure Resource Group & Policy

  • Terraform
  • VNet
  • Resource Group & Policy

    Resource Group と Policy の関係性について理解するためには、MSラーニングパスの「Azure Resource Manager」を見て頂くと分かりやすいと思います。

    Azure Policy(管理ポリシー)

    Azure の管理ポリシーは面白い機能を提供してくれます。

    例えば、社内検証用のサブスクリプションに対して、使用できるリソースの種類、場所、マシン性能などを制限すると、そこで作成される Azure Resource Manager 内のリソースはその制限を継承します。

    Azure には、管理グループ、サブスクリプション、リソース グループ、およびリソースという4つのレベルのスコープが用意されています。

    title

    それぞれのレベルで、管理ポリシー(Azure Policy)を定義することができ、下位レイヤーの全ての要素に継承されます。

    Resource Group

    MSラーニングパスの「Azure Resource Manager」の内容としては、Resource Group の利用方法やベストプラクティスについての解説が中心でした。

    概要を箇条書きします。

    • Resource Group は入れ子にできない。
    • Resource Group にタグを付与できるが、Resource Group 内のリソースに継承されない。
    • Azure Policy を利用することで、Resource Group 内のリソースに制限を与えられる。
    • リソースの種類のインスタンスを最大 800 個までデプロイできる。

    Azure Resource Manager とは何なのか

    Azure Policy や Resource Group を調べているとよく出てくるワードが「Azure Resource Manager(ARM)」です。これは、AWS で言うところの CloudFormation です。

    まさに、リソースの作成・変更・削除を行う司令塔のような役割を果たしているものです。

    az_resource_manager_02

    Resource Manager は Azure ポータル、Azure ツール、API、SDK から要求を受信し、その要求に対して認証と承認を行います。

    その後、Resource Manager は各 Azure サービスに要求を送信します。

    すべての要求は同じ API を介して処理されるため、異なるツールであっても一貫した結果と機能が得られます。

    すごいですね~。

    試してみる

    Resource Group に Azure Policy をアタッチしてみて動作を確認してみましょう。

    前提

    Azure を操作するツールは色々ありますが、今回は Terraform を使っていきます。

       
    • azcli はインストール済み
    • ADアカウント、サブスクリプションは作成済み

    terraform の認証方法(Service Principal with a Client Secret)

    いくつかやり方はありますが、簡単なのは App registrations を作成する方法です。

    言い換えれば、terraform 用の実行ユーザーを作成してしまうということです。併せて、リソース作成権限(ロール "Contributor")も付与します。

    PS C:\Users\...\terraform_azure_v1> az login PS C:\Users\...\terraform_azure_v1> az account set --subscription=$SUBSCRIPTION_ID PS C:\Users\...\terraform_azure_v1> az ad sp create-for-rbac ` >> --role="Contributor" ` >> --name="terraform-principal" ` >> --years="1" ` >> --scopes="/subscriptions/$SUBSCRIPTION_ID { "appId": "00000000-0000-0000-0000-000000000000", "displayName": "terraform-principal", "name": "terraform-principal", "password": "0000-0000-0000-0000-000000000000", "tenant": "00000000-0000-0000-0000-000000000000" }

    詳細はこちらを確認ください。

    Terraform のファイル構成

    今回は Resource Group を作成するだけのシンプルな構成です。

    azure_tf_test01/ |-- provider.tf |-- main.tf |-- variable.tf |-- terraform.tfvars

    tf ファイルの中身

    provider.tf

    # tfstate on storage account
    terraform {
      required_version = "= 0.13.5"
    
      backend "azurerm" {
        resource_group_name  = "atsushi.koizumi.data"
        storage_account_name = "terraform0tfstate"
        container_name       = "tfstate"
        key                  = "test01.tfstate"
      }
    }
    
    # provider azurerm
    # https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/guides/service_principal_client_secret
    # az ad sp create-for-rbac
    provider "azurerm" {
      version = "=2.38.0"
      subscription_id = var.subscription_id
      client_id       = var.client_id
      client_secret   = var.client_secret
      tenant_id       = var.tenant_id
    
      features {}
    }

    main.tf

    # Create a resource group
    resource "azurerm_resource_group" "test01" {
      name     = "atsushi.koizumi.test01"
      location = "East US"
      tags = {
        "Owner" = "koizumi",
        "Env"   = "test01"
      }
    }
    
    # Policy assignment
    resource "azurerm_policy_assignment" "Allowed_locations" {
      name                 = "Allowed-locations"
      scope                = azurerm_resource_group.test01.id
      policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/e56962a6-4747-49cd-b67b-bf8b01975c4c"
      description          = "This policy enables you to restrict the locations your organization can specify when deploying resources. Use to enforce your geo-compliance requirements. Excludes resource groups, Microsoft.AzureActiveDirectory/b2cDirectories, and resources that use the 'global' region."
      display_name         = "Allowed locations"
      identity {
          type = "SystemAssigned"
      }
      location = "eastus"
      parameters = <<PARAMETERS
        {
          "listOfAllowedLocations": {
            "value": [ "East US","East US 2" ]
          }
        }
      PARAMETERS
    }
    
    resource "azurerm_policy_assignment" "inherit_tag_from_rg" {
      name                 = "Inherit-tag-from-resourcegroup"
      scope                = azurerm_resource_group.test01.id
      policy_definition_id = "/providers/Microsoft.Authorization/policyDefinitions/cd3aa116-8754-49c9-a813-ad46512ece54"
      description          = "Adds or replaces the specified tag and value from the parent resource group when any resource is created or updated. Existing resources can be remediated by triggering a remediation task."
      display_name         = "Inherit a tag from the resource group"
      identity {
          type = "SystemAssigned"
      }
      location = "eastus"
      parameters = <<PARAMETERS
        {
          "tagName": {
            "value": "Owner"
          }
        }
      PARAMETERS
    }
    
    # Create a virtual network within the resource group
    resource "azurerm_virtual_network" "test01" {
      name                = "test01"
      resource_group_name = azurerm_resource_group.test01.name
      location            = azurerm_resource_group.test01.location
      address_space       = ["10.0.0.0/16"]
    }
    
    # Create a virtual network within the resource group
    resource "azurerm_virtual_network" "test02" {
      name                = "test02"
      resource_group_name = azurerm_resource_group.test01.name
      location            = "East US 2"
      address_space       = ["10.0.0.0/16"]
    }
    
    # Create a virtual network within the resource group
    resource "azurerm_virtual_network" "test03" {
      name                = "test03"
      resource_group_name = azurerm_resource_group.test01.name
      location            = "West US 2"
      address_space       = ["10.0.0.0/16"]
    }

    variable.tf

    variable subscription_id {}
    variable client_id {}
    variable client_secret {}
    variable tenant_id {}

    terraform.tfvars

    # azure AD
    tenant_id = "00000000-0000-0000-0000-000000000000"
    
    # Subncriotsion
    subscription_id = "00000000-0000-0000-0000-000000000000"
    
    # server principals
    client_id = "00000000-0000-0000-0000-000000000000"
    client_secret = "00000000000000000000000000000000"

    ポイント

    大事なところは以下の3点です。

    • policy_definition_id において buil-in Policy は MS で用意してくれている固定値を指定
    • buil-in Policy の JSON ソースコードを読み解いてパラメータを指定
    • azurerm_policy_assignment で identity とlocation を指定

    Terraform apply

    リソース作成

    上記のファイルを使ってリソースを作成してみます。

    PS C:\Users\...\terraform_azure_v1> terraform init PS C:\Users\...\terraform_azure_v1> terraform plan PS C:\Users\...\terraform_azure_v1> terraform apply

    Error: creating/updating Policy Assignment

    権限不足の Error で失敗しました。

    Error: creating/updating Policy Assignment "Inherittagfromresourcegroup" (Scope "/subscriptions/...1"): policy.AssignmentsClient#Create: Failure responding to request: StatusCode=403 -- Original Error: autorest/azure: Service returned an error. Status=403 Code="AuthorizationFailed" Message="The client '...' with object id '141acabc-a72e-46b4-bc31-62d01eda748a' does not have authorization to perform action 'Microsoft.Authorization/policyAssignments/write' over scope '/subscriptions/...' or the scope is invalid. If access was recently granted, please refresh your credentials."

    Policy をアタッチするためには、Owner または、User Access Administrator の権限が必要です。

    MS の公式サイトにトラブルシューティングが載っていました。

    Service principal にロールを追加します。

    PS C:\Users\...\terraform_azure_v1> az role assignment create ` >> --assignee="00000000-0000-0000-0000-000000000000" ` >> --role="User Access Administrator" ` >> --scope="/subscriptions/00000000-0000-0000-0000-000000000000" { "canDelegate": null, "condition": null, "conditionVersion": null, "description": null, "id": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleAssignments/00000000-0000-0000-0000-000000000000", "name": "00000000-0000-0000-0000-000000000000", "principalId": "00000000-0000-0000-0000-000000000000", "principalName": "http://terraform-principal", "principalType": "ServicePrincipal", "roleDefinitionId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Authorization/roleDefinitions/00000000-0000-0000-0000-000000000000", "roleDefinitionName": "User Access Administrator", "scope": "/subscriptions/00000000-0000-0000-0000-000000000000", "type": "Microsoft.Authorization/roleAssignments" }

    リソース作成(リトライ)①

    上記のファイルを使ってリソースを作成してみます。

    そのまま Tearrform apply してしまうと、Azure policy のアタッチに1分程度の時間が掛かってしまい、先に Virtual Network が作成されてしまいます。

    それでは、Resource group に設定した Azure Policy がリソースに適用されません。

    以下の順で明示的にリソースを指定して作成します。

    PS C:\Users\...\terraform_azure_v1> terraform init PS C:\Users\...\terraform_azure_v1> terraform plan PS C:\Users\...\terraform_azure_v1> terraform apply ` >> -target azurerm_resource_group.test01 ` >> -target azurerm_policy_assignment.Allowed_locations ` >> -target azurerm_policy_assignment.inherit_tag_from_rg ... Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Releasing state lock. This may take a few moments...

    実行すると、Resource group に Policy がアタッチされていることが画面で確認できます。

    title

    リソース作成(リトライ)②

    残りの Virtual Network を作成していきます。

    ちなみに、Resource group に適用した Policy の内容は以下です。

    • Resource group の所有するタグ Owner を継承する。
    • リソースが作成可能なリージョン指定 East US ,East US 2

    ということなので、terraform apply の実行結果は、3つの Virtual Network に対してタグ Owner が継承され、test03(West US 2)の作成に失敗、と想定しています。

    やってみましょう。

    PS C:\Users\...\terraform_azure_v1> terraform apply Plan: 3 to add, 0 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value: yes azurerm_virtual_network.test02: Creating... azurerm_virtual_network.test01: Creating... azurerm_virtual_network.test03: Creating... azurerm_virtual_network.test01: Still creating... [10s elapsed] azurerm_virtual_network.test02: Still creating... [10s elapsed] ... Error: Error Creating/Updating Virtual Network "test03" (Resource Group "atsushi.koizumi.test01"): network.VirtualNetworksClient#CreateOrUpdate: Failure sending request: StatusCode=403 -- Original Error: Code="RequestDisallowedByPolicy" Message="Resource 'test03' was disallowed by policy..." ... on main.tf line 67, in resource "azurerm_virtual_network" "test03": 67: resource "azurerm_virtual_network" "test03" {

    結果は想定通り

    エラーコード「Code="RequestDisallowedByPolicy"」と表示されています。

    また、タグの継承についても画面で確認することができました。

    title

    title

    おまけ

    実は、Terraform でタグ継承の Policy 運用は難しいということが分かりました。

    試しに

    上記の実行結果のあと、再度、terraform apply してみますと、

    PS C:\Users\...\terraform_azure_v1> terraform apply Plan: 1 to add, 2 to change, 0 to destroy. Do you want to perform these actions? Terraform will perform the actions described above. Only 'yes' will be accepted to approve. Enter a value:

    このように、タグの有無が差分として出てきてしまい、terraform としては継承したタグを消しにいく動きを見せます。

    main.tf の中身と実体の差分を埋めるように動いているので当然の動きとなります。

    terraform で Policy を使用する場合は、何かを継承するようなものではなく、リソースの作成を制限するような機能を利用すると良いと思われます。

    今回は以上です。

    Icons made by Flat Icons from www.flaticon.com

  • Terraform
  • VNet