【Bicep編】App ServiceのManaged IDを使ってACRからコンテナイメージをpullする

Azure Container Registryへのアクセスは、アクセスキーを使うのではなくManaged IDを使った方がセキュアな構成をとることができます。
以前 [Web App for Containers vNet統合経由でプライベートエンドポイント先のACRからイメージPullする](https://qiita.com/shingo_kawahara/items/903ab9261d0d87c1d0a2) という記事を書いて、Portalからポチポチ設定するやり方はまとめていたのですが、今回はBicepで実現する方法を残します。
なお、プライベートエンドポイントとvNet統合のコードは割愛しています。

# リポジトリ構成
* 以下のようにモジュール化、パラメータ化しています。

```リポジトリ構成
.
├── main.bicep
├── main.bicepparam
└── modules
    ├── acr.bicep
    └── appservice.bicep
```

# Bicepコード
* 全てモジュールを呼び出すようにしています。App Service Managed IDのService Principal IDをACRモジュールに渡しています。

```main.bicep
param location string

// App Service Parameters
param webAppName string
param appServicePlanSku string
param appServicePlanTier string
param appServicePlanName string
param imageRepositoryName string

// ACR Parameters
param acrName string
param acrSku string

module acrModule 'modules/acr.bicep' = {
  name: 'acrModule'
  params: {
    location: location
    acrName: acrName
    acrSku: acrSku
    webAppPrincipalId: appServiceModule.outputs.webAppPrincipalId
  }
}

module appServiceModule './modules/appservice.bicep' = {
  name: 'appServiceModule'
  params: {
    location: location
    webAppName: webAppName
    appServicePlanName: appServicePlanName
    appServicePlanSku: appServicePlanSku
    appServicePlanTier: appServicePlanTier
    imageRepositoryName: imageRepositoryName
  }
}
```

* パラメータは適宜置き換えます。

```main.bicepparam
using 'main.bicep'

param location = 'japaneast'

// App Service Parameters
param webAppName = '[Your WebAppName]'
param appServicePlanSku = 'B1'
param appServicePlanTier = 'Basic'
param appServicePlanName = '${webAppName}-plan'
param imageRepositoryName = '${acrName}.azurecr.io/test/nginx:v1'

// ACR Parameters
param acrName = '[Your ACR Name]'
param acrSku = 'Basic'
```

* App Service Managed IDに[AcrPull]権限を与えているのがポイントです。roleDefinitionsのnameは、Azureサブスクリプション共通のカスタムロールIDなので固定値となります。

```acr.bicep
param location string
param acrName string
param acrSku string
param webAppPrincipalId string

resource acrResource 'Microsoft.ContainerRegistry/registries@2023-06-01-preview' = {
  location: location
  name: acrName
  sku: {
    name: acrSku
  }
  properties: {
    adminUserEnabled: false
    publicNetworkAccess: 'Enabled'
  }
}

resource acrPullRoleDefinition 'Microsoft.Authorization/roleDefinitions@2022-05-01-preview' existing = {
  scope: acrResource
  name: '7f951dda-4ed3-4680-a7ca-43fe172d538d'
}

resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(acrResource.id, webAppPrincipalId, acrPullRoleDefinition.id)
  scope: acrResource
  properties: {
    principalId: webAppPrincipalId
    roleDefinitionId: acrPullRoleDefinition.id
    principalType: 'ServicePrincipal'
  }
}
```

* acrUseManagedIdentityCreds=trueの設定を忘れずに。

```appservice.bicep
param location string
param webAppName string
param appServicePlanSku string
param appServicePlanTier string
param appServicePlanName string
param imageRepositoryName string

resource appServicePlan 'Microsoft.Web/serverfarms@2022-09-01' = {
  name: appServicePlanName
  location: location
  kind: 'linux'
  properties: {
    reserved: true
  }
  sku: {
    name: appServicePlanSku
    tier: appServicePlanTier
  }
}

resource webApp 'Microsoft.Web/sites@2022-09-01' = {
  name: webAppName
  location: location
  properties: {
    serverFarmId: appServicePlan.id
    httpsOnly: true
    publicNetworkAccess: 'Enabled'
    siteConfig: {
      appSettings: []
      linuxFxVersion: 'DOCKER|${imageRepositoryName}'
      acrUseManagedIdentityCreds: true
      vnetRouteAllEnabled: true
      http20Enabled: true
      ftpsState: 'Disabled'
    }
  }
  identity: {
    type: 'SystemAssigned'
  }
}

output webAppPrincipalId string = webApp.identity.principalId
```

# デプロイ
* 以下のようにデプロイします。リソースグループは事前に作成済みであること前提です。

```bash
az login
az account list --output table --all
az account set --subscription [Subscrption ID]
az deployment group what-if --resource-group [Your ResourceGroup Name] --template-file main.bicep --parameters main.bicepparam
az deployment group create --resource-group [Your ResourceGroup Name] --template-file main.bicep --parameters main.bicepparam
```

# サンプルアプリケーション準備
* サンプルアプリをACRにPushします。az acr buildであれば、ACRのアクセスキーが無効でもpushできます。

```bash
echo '<h1>ACR Test v1</h1>' > index.html
echo 'FROM nginx:latest' > Dockerfile
echo 'COPY index.html /usr/share/nginx/html' >> Dockerfile
az login
az account list --output table --all
az account set --subscription [Subscrption ID]
az acr build --registry [Container Registry Name] --image test/nginx:v1 --file Dockerfile .
```

* Azure PortalからリポジトリにイメージがPushされていることを確認します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180335/082fbcb9-f5be-e817-1f9b-c823f84e69c6.png)

# 稼働確認
* App Serviceを再起動し、WebブラウザからURLにアクセスします。以下の画面が表示されればOKです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180335/78d6c4d4-78c3-383d-8ea3-48feed06ab83.png)

* ACRのIAMを見てみると、[AcrPull]ロールがこのリソースのみに割当たっていることが分かります。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/180335/f1ac6060-e4d0-6fa3-a54f-7da2b78e838b.png)


以上です