Azure での異なる Tenant, Subscription 間での Terraform での VNET Peering 設定

## はじめに
Azure にて異なる Tenant (Azure AD), Subscription 間で VNET Peering を
Terraform (サービスプリンシパル利用) で実施した内容を記載する

![スクリーンショット 2022-04-18 0.40.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/bc3de640-1687-ccf1-84c4-9e4465f88de3.png)



## 実施内容

マルチテナントでの Subscription 間 VNET Peering を Terraform で実施するために、下記を実施した

1. User Access Administrator 権限設定
1. Terraform で使用するサービスプリンシパルのマルチテナント化
1. spoke での Terraform サービスプリンシパルのアクセス許可
1. spoke でのサービスプリンシパルへの IAM での権限追加
1. Terraform でのマルチテナント操作での VNet Peering

![Azure_VNet-Peering_20220417.png.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/67150d64-aa5f-f374-07cf-f094bd0767fb.png)


### 1. User Access Administrator 権限設定

ロールの割り当ての追加をするには、`Microsoft.Authorization/roleAssignments/write`が必要で、
組み込みロールで言うと `Owner` もしくは `User Access Administrator` (`ユーザー アクセス管理者`) のロールを持っている必要がある
([Azure ドキュメントの Terraform サービス プリンシパルの作成](https://docs.microsoft.com/ja-jp/azure/developer/terraform/authenticate-to-azure?tabs=bash#create-a-service-principal)で例に記載されている`Contributor`/`共同作成者`では無理なので注意)

`User Access Administrator` (`ユーザー アクセス管理者`) のロールを割り当てる

サブスクリプションやリソースグループの`アクセス制御(IAM)`で設定する

![スクリーンショット 2022-04-17 20.49.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/a6c30808-420f-5c19-33f6-af796646ca1c.png)

`ユーザー アクセス管理者`をクリックして`次へ`

![スクリーンショット 2022-04-17 20.53.03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8a99733c-ece9-e6aa-0820-25a282ce447a.png)

`+メンバーを選択する`で選択するユーザをクリックして、`選択`してクリック
(ここではユーザにしているが、サービスプリンシパルで実施している場合は、サービスプリンシパルを選択)

![スクリーンショット 2022-04-17 20.55.19.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/196fbcf4-3e5c-1c2f-7f0b-6a6b12e84355.png)

`レビューと割り当て`を実施して割り当てる

![スクリーンショット 2022-04-17 20.56.46.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/066c6bbe-7f73-3c65-2ac1-d906de554c98.png)


### 2. Terraform サービスプリンシパルのマルチテナント化

`サポートされているアカウントの種類:` が`所属する組織のみ`になっている場合は、マルチテナント対応をする必要がある
`所属する組織のみ`をクリックする

![スクリーンショット 2022-04-17 19.37.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/4b3ca9bf-e280-12f7-e502-8d4a24827099.png)


下記を選択して、変更する
`任意の組織ディレクトリ内のアカウント (任意の Azure AD ディレクトリ - マルチテナント)`
![スクリーンショット 2022-04-17 19.44.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f6c80c87-5835-59a8-23a0-75aa926955b6.png)

`サポートされているアカウントの種類:` が `複数の組織`となっていることを確認

![スクリーンショット 2022-04-17 19.49.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/06ba107e-ccb1-f5d9-317e-99b7d0bbd0f8.png)

`API のアクセス許可` で `Microsoft Graph` の `User.Read` 権限を追加 (最初からマルチテナントでサービスプリンシパル作成しているとデフォルトで設定済)

![スクリーンショット 2022-04-17 20.02.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/be36594b-313c-9912-019c-f690f6369657.png)

![スクリーンショット 2022-04-17 20.05.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ee8bb238-35ad-32c1-f99a-c75fefe22717.png)

![スクリーンショット 2022-04-17 20.14.51.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/663dc5c6-507a-2f4a-23c9-4f38b067f2fb.png)



### 3. spoke への Hub Terraform サービスプリンシパルのアクセス許可

マルチテナント化した Hub 側のサービスプリンシパルのアプリケーション ID (Application(client) ID) を参照する

![スクリーンショット 2022-04-17 19.54.39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/e6d20b88-43a5-41fd-c76d-62f6d87e2652.png)

spoke の`ディレクトリ(テナント)ID`(`Service Tenant ID`) を取得する

![スクリーンショット 2022-04-17 19.57.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ffcb4206-904e-f1eb-43f1-d1366c2a870b.png)


```
https://login.microsoftonline.com/<Service Tenant ID>/oauth2/authorize?client_id=<Application (client) ID>&response_type=code&redirect_uri=https%3A%2F%2Fwww.microsoft.com%2F
```

ブラウザでアクセスして`承認`する

![スクリーンショット 2022-04-17 20.16.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/049f7d00-8723-3960-f7fd-003060186609.png)

`サインイン`で`エラー`となるが気にせずこのタブは閉じて良い

![スクリーンショット 2022-04-17 20.18.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/ab259e86-2175-ab0c-263e-e60a5ac1346d.png)

spoke 側の `エンタープライズ アプリケーション`に追加した`アプリケーション ID` でサービスプリンシパルが追加されていることが確認できる

![スクリーンショット 2022-04-17 20.20.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/fe738ebc-599f-5249-a9c3-2ac23891ceaa.png)


### 4. spoke でのサービスプリンシパルへの IAM での権限追加

ひとつ前で追加した、Hubのアプリケーションのサービスプリンシパルへ spoke の VNET の`ネットワーク共同作成者`権限を付与する

#### GUI (Azure Portal) で実施する場合

spoke の VNET から`アクセス制御 (IAM)`へ移動して`ロールの割り当ての追加`を実施する

![スクリーンショット 2022-04-16 13.07.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/72d3b855-053a-f98a-9017-c24e933054f8.png)

`ネットワーク共同作成者`を選択する

![スクリーンショット 2022-04-17 21.45.57.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/f00f1f25-1c35-468d-bce6-7f0ff42e86ea.png)


`+メンバーを選択する`をクリックして、追加したサービスプリンシパルをクリックして、`選択`をクリックして、
最後に`次へ`をクリック

![スクリーンショット 2022-04-17 21.47.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/444561ec-cf72-07e4-ec29-9d203ba81ca9.png)


`レビューと割り当て`をクリックして割り当てる

![スクリーンショット 2022-04-17 21.48.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/da833d8f-88c1-9708-b55c-c750d0716c37.png)



#### CLI (az-cli) で実施する場合

Spoke VNET の`プロパティ`で`リソース ID` を取得する

![スクリーンショット 2022-04-17 21.33.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/8a879247-b2a2-7c9e-6ec9-1914c9ee53a7.png)


下記コマンドを`ユーザー アクセス管理者`ロールを持つユーザでログインした状態で実施

```sh
az role assignment create --role "Network Contributor" --assignee-object-id <サービスプリンシパルのオブジェクトID> --scope <Spoke VNET リソース ID> --assignee-principal-type ServicePrincipal
```

下記実行出力例

![スクリーンショット 2022-04-17 21.59.13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/dd98191c-4895-fc05-edcf-54bad9cedccf.png)

### 5. Terraform での VNet Peering 作成

Terraform で VNet Peering を作成する

サービスプリンシパルの ID/Password は環境変数に設定してから実施する
(パスワードは[作成時](https://qiita.com/suzuyui/items/2eca3dd3937d64c40fd4#4-%E3%82%B5%E3%83%BC%E3%83%93%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%82%B7%E3%83%91%E3%83%AB%E4%BD%9C%E6%88%90)を参照)

```sh
export ARM_CLIENT_ID=81XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX9
export ARM_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
```

下記、Terraform コード例
マルチテナント操作のため、provider を alias で複数定義して、`auxiliary_tenant_ids` を使用する

```tf:env/test/japaneast/hub/vnet/spoke-peering-different-subscription/vnet-peering.tf
# Spoke との Peering
module "spoke-peering-infratest2" {
  source = "../../../../../../modules/hub/vnet/spoke-peering-different-subscription"

  hub_resource_group_name  = var.resource_group_name
  hub_virtual_network_name = var.virtual_network_name
  hub_tenant_id            = "ceXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX1"
  hub_subscription_id      = "38XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX7"

  spoke_resource_group_name  = "rg-testinfra2-japaneast-001"
  spoke_virtual_network_name = "vnet-testinfra2-japaneast-001"
  spoke_tenant_id            = "edXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX4"
  spoke_subscription_id      = "a4XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXX9"
}
```

```tf:modules/hub/vnet/spoke-peering-different-subscription/main.tf
# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs
provider "azurerm" {
  alias                      = "hub"
  tenant_id                  = var.hub_tenant_id
  subscription_id            = var.hub_subscription_id
  skip_provider_registration = true
  auxiliary_tenant_ids       = [var.spoke_tenant_id, ]
  features {}
  # client_id                  = var.client_id     # ARM_CLIENT_ID
  # client_secret              = var.client_secret # ARM_CLIENT_SECRET
}

provider "azurerm" {
  alias                      = "spoke"
  tenant_id                  = var.spoke_tenant_id
  subscription_id            = var.spoke_subscription_id
  skip_provider_registration = true
  auxiliary_tenant_ids       = [var.hub_tenant_id, ]
  features {}
  # client_id                  = var.client_id     # ARM_CLIENT_ID
  # client_secret              = var.client_secret # ARM_CLIENT_SECRET
}

# https://registry.terraform.io/providers/hashicorp/azurerm/3.1.0/docs/data-sources/virtual_network
data "azurerm_virtual_network" "hub" {
  name                = var.hub_virtual_network_name
  resource_group_name = var.hub_resource_group_name
  provider            = azurerm.hub
}

data "azurerm_virtual_network" "spoke" {
  name                = var.spoke_virtual_network_name
  resource_group_name = var.spoke_resource_group_name
  provider            = azurerm.spoke
}


# https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/virtual_network_peering
resource "azurerm_virtual_network_peering" "hub-to-spoke" {
  name                         = "peer-${data.azurerm_virtual_network.hub.name}-to-${data.azurerm_virtual_network.spoke.name}"
  resource_group_name          = data.azurerm_virtual_network.hub.resource_group_name
  virtual_network_name         = data.azurerm_virtual_network.hub.name
  remote_virtual_network_id    = data.azurerm_virtual_network.spoke.id
  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
  allow_gateway_transit        = true
  use_remote_gateways          = false
  provider                     = azurerm.hub
}

resource "azurerm_virtual_network_peering" "spoke-to-hub" {
  name                         = "peer-${data.azurerm_virtual_network.spoke.name}-to-${data.azurerm_virtual_network.hub.name}"
  resource_group_name          = data.azurerm_virtual_network.spoke.resource_group_name
  virtual_network_name         = data.azurerm_virtual_network.spoke.name
  remote_virtual_network_id    = data.azurerm_virtual_network.hub.id
  allow_virtual_network_access = true
  allow_forwarded_traffic      = true
  allow_gateway_transit        = false
  use_remote_gateways          = true
  provider                     = azurerm.spoke
}

```

```tf:modules/hub/vnet/spoke-peering-different-subscription/variables.tf
# Local Param
variable "hub_resource_group_name" {}

variable "hub_virtual_network_name" {}

# spoke Param
variable "spoke_virtual_network_name" {}

variable "spoke_resource_group_name" {}


# hub SP Param
variable "hub_tenant_id" {}

variable "hub_subscription_id" {}

# spoke SP Param
variable "spoke_tenant_id" {}

variable "spoke_subscription_id" {}
```

`terraform apply` を実行すると、Peering が完了する
下記は spoke 側で vnet の `ピアリング`から今回作成のピアリング状態が`接続済み`となっていることを確認した

![スクリーンショット 2022-04-17 23.05.49.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/326183/4d94a52a-cbec-a64e-549e-b7862fa8ad26.png)

## おわりに
マルチテナントでの VNET Peeirng を実施した
サービスプリンシパルが複雑で、実施するのに非常に苦労した
記載内容も正規手順ではないかもしれないので、ご参考程度でお願いします

## 参考

https://docs.microsoft.com/en-us/azure/virtual-network/create-peering-different-subscriptions#portal

https://registry.terraform.io/modules/farroar/multi-tenant-peering/azurerm/latest

https://docs.microsoft.com/ja-jp/cli/azure/role/assignment?view=azure-cli-latest