Azure AD のサービスプリンシパルを 3 つのユースケースから眺めてみた

# はじめに

本記事は [Microsoft Tech Advent Calendar](https://qiita.com/advent-calendar/2019/microsoft-azure-tech) の 11 日目の記事です。

どうも @watahani です。普段は Azure AD B2C とか ID 周りをイジイジして遊んでいるものです。

さて、皆さん Azure は使っていますか？
このアドベントカレンダーシリーズを見ている人は、Azure をバリバリ使いこなしてくださっているものと思います。

では、改めて。

皆さん、サービス プリンシパルは使ってますか？

この質問に対し、あなたは何を思い浮かべたでしょう？ `az ad sp create-for-rbac` コマンドでしょうか、エンタープライズ アプリケーションでしょうか、あるいはマネージド ID を利用してるので関係ないとお思いでしょうか。

## 本記事の目的

本記事では、非常にわかり辛いと思われがちなサービス プリンシパルについて、色々な角度から眺めてみることで、少し理解した気持ちになれるよう解説してみました。

とはいえ正直サービス プリンシパルの機能は多岐にわたっており、すべてを一つの記事で網羅することは出来ません。

今回はサービス プリンシパルの概念や、よくある使われ方について、少しでも理解の助けになればと思い、いくつかのユースケースを取り上げ、その結果何が行われているのか Azure ポータルを確認する形で、サービス プリンシパルの正体に迫ってみたいと思います。

サービス プリンシパル コワクナイヨー

## Case 1. サービス プリンシパルで Azure リソースを操作する

サービス プリンシパルと聞いて、最も多くの方が想像するのがこの利用方法ではないかと思います。
Azure でリソースに対する操作を自動化したいときに、スクリプトにユーザーの資格情報を埋め込みレガシー認証を行うことは推奨されません。
そこで、サービス プリンシパルを作成し、サービス プリンシパルにサブスクリプションの権限を付与し、サービス プリンシパルの権限でリソースを操作する、ということが行われます。

やり方はいくつかありますが、今回は `az` コマンドでの方法と、その結果何が出来上がるかを見ていきましょう。

- [Azure CLI で Azure サービス プリンシパルを使用する | Microsoft Docs](https://docs.microsoft.com/ja-jp/cli/azure/create-an-azure-service-principal-azure-cli?view=azure-cli-latest)

> PowerShell (Az モジュール) での手順は、[方法:Azure PowerShell を使用して資格情報でのサービス プリンシパルを作成する](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/howto-authenticate-service-principal-powershell) をご確認ください。

### サービス プリンシパルの作成

まずは管理ユーザーでサインインし、サービス プリンシパルを作成します。

```sh
$ az login

$ az account list

# サブスクリプションのリスト
# A few accounts are skipped as they don't have 'Enabled' state. Use '--all' to display them.
# 
# [
#   {
#     "cloudName": "AzureCloud",
#     "id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx",
#     "isDefault": true,
#     "name": "My Subscription Name",
#     "state": "Enabled",
#     "tenantId": "xxxxxxxx-xxx-xxxx-xxxx-xxxxxxxxxxxx",
#     "user": {
#       "name": "xxxxxxxx@example.com",
#       "type": "user"
#     }
#   }
# ]

$ az ad sp create-for-rbac --name ServicePrincipalName --role Contributor
# Changing "ServicePrincipalName" to a valid URI of "http://ServicePrincipalName", which is the required format used for service # principal names
# Retrying role assignment creation: 1/36
# Retrying role assignment creation: 2/36
# {
#   "appId": "a5213985-28bf-44fb-952f-97b245dead3a",
#   "displayName": "ServicePrincipalName",
#   "name": "http://ServicePrincipalName",
#   "password": "296b700b-055e-44c4-9e9f-6349f8703314",
#   "tenant": "b9e35cac-a7b8-48dd-a102-bba18eaca524"
# }
```

上記コマンドを実行すると、サービス プリンシパルが作成されます。

> azure-cli バージョン 2.32.0 からは、--role オプションなしの場合、従来の --skip-assignment オプション付与時と同様にサブスクリプションのロール割り当てをスキップするようになりました。

### サービス プリンシパルでのリソース操作

では実際に、サービス プリンシパルを利用してリソースを操作してみましょう。

```sh
# サービス プリンシパルでログイン
$ az login --service-principal --username a5213985-28bf-44fb-952f-97b245dead3a --password 296b700b-055e-44c4-9e9f-6349f8703314 --tenant b9e35cac-a7b8-48dd-a102-bba18eaca524
# [
#   {
#     "cloudName": "AzureCloud",
#     "id": "124bd76d-010e-48b8-9ef7-8a96bbe89831",
#     "isDefault": true,
#     "name": "Visual Studio Enterprise",
#     "state": "Enabled",
#     "tenantId": "b9e35cac-a7b8-48dd-a102-bba18eaca524",
#     "user": {
#       "name": "a5213985-28bf-44fb-952f-97b245dead3a",
#       "type": "servicePrincipal"
#     }
#   }
# ]

# リソースグループの作成
$ az group create --name sp-sample --location eastus
# {
#   "id": "/subscriptions/124bd76d-010e-48b8-9ef7-8a96bbe89831/resourceGroups/sp-sample",
#   "location": "eastus",
#   "managedBy": null,
#   "name": "sp-sample",
#   "properties": {
#     "provisioningState": "Succeeded"
#   },
#   "tags": null,
#   "type": null
# }

$ az network vnet create -n sp-vnet -g sp-sample
# {
#   "newVNet": {
#     "addressSpace": {
#       "addressPrefixes": [
#         "10.0.0.0/16"...
 ```

リソースが作成されましたね。

### Azure ポータル上での確認

では Azure ポータルを開き、何が起こったかを確認してみましょう。

まずは、アプリの登録を確認します。ServicePrincipalName というアプリが作成されていることがわかります。

![2019-12-08_17h36_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/47507261-20ad-2785-f14a-6e266337758d.png)


先ほどログインに利用したアプリケーション ID が確認できます。

>　アプリケーション ID の URI として `http://ServicePrincipalName` が入力されていますが、これはアプリの登録 v1 時代の名残です。リソースを操作するためにサービス プリンシパルを扱う上では、今は特に必須のものでは無いのですが、az コマンドを実行すると自動で作成されます。
> > 2021-07-08 追記
> > [az cli 2.25.0](https://docs.microsoft.com/ja-jp/cli/azure/release-notes-azure-cli?tabs=azure-cli#june-15-2021) からは アプリケーション ID の URI は自動生成されなくなり、`http://ServicePrincipalName` のような ID を利用したサインインは、非推奨となりました。

![2019-12-08_17h58_56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/527e7820-1cd9-121e-76a5-cb973636836f.png)

次に、証明書とシークレットの項目を確認してみましょう。
`az ad sp create-for-rbac` コマンドは、規定で 1 年の有効期限を持つクライアント シークレットを作成します。
このシークレットは作成時のみ表示され、あとから参照することはできません。

![2019-12-08_18h04_09_kzzm2owcd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/10f19709-99b8-ee67-c22b-ef80e69c410a.png)

次にサブスクリプションを確認してみましょう。

サブスクリプションを確認すると、作成したサービス プリンシパルが共同作成者として登録されていることが確認できます。

![2019-12-08_18h27_09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/7c25c1da-e431-a0d4-7f98-e9d6e61966e7.png)

>azure-cli バージョン 2.32.0 より以前では、サービス プリンシパルを `az ad sp crate-for-rbac` で作成すると、利用しているサブスクリプションの `共同作成者` に自動で割り当てられます。共同作成者の権限は非常に強く、サブスクリプション以下のリソースすべてに対し管理権限を持つため、通常は RBAC を利用して特定のリソースや操作を限定して権限を委譲します。
> おまけに RBAC の設定方法を記載しているので確認してみてください。

では、アプリケーションがサービス プリンシパルの実態なのでしょうか。

それを確かめるために、エンタープライズ アプリの一覧に移動します。
すべてのアプリを選択すると、ここにも ServicePrincipalName のアプリが存在するのが確認できます。

![2019-12-08_18h09_40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/178e7634-8bd5-53ff-6e3c-a677e3d73475.png)

アプリケーション ID は先ほどアプリの登録で確認したアプリと同じです。

### サービス プリンシパルの削除

試しにこのエンタープライズ アプリケーションに存在するアプリを削除してみましょう。削除は [プロパティ] から可能です。

![2019-12-08_18h16_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/66019e6b-16fc-96b3-b0a5-b8d80aaaa081.png)

> サービス プリンシパルの削除はテスト環境で行ってくださいね。

エンタープライズ アプリケーションからアプリを削除した状態でもう一度サービス プリンシパルでサインインしてみましょう。

```sh
> az account clear
> az login --service-principal --username a5213985-28bf-44fb-952f-97b245dead3a --password 296b700b-055e-44c4-9e9f-6349f8703314 --tenant b9e35cac-a7b8-48dd-a102-bba18eaca524
# The received access token is not valid: at least one of the claims 'puid' or 'altsecid' or 'oid' should be present. If you are accessing as application please make sure service principal is properly created in the tenant.
```

おっと、サービス プリンシパルとしてサインインができなくなってしまいました。
なるほど、エンタープライズ アプリケーションがサービス プリンシパルの実体だったようです。

次に、サブスクリプションを確認してみると、ID が存在しなくなりましたと表示されます。
![2019-12-08_18h51_24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/2136d096-4bbf-0586-85af-810291e459c6.png)

RBAC はアプリケーションではなく、サービス プリンシパルに紐づいているようです。

さて、実は、エンタープライ アプリケーションはアプリの登録画面から再作成ができます。
ローカルディレクトリのマネージド アプリケーションからサービス プリンシパルの作成をクリックします。

![2019-12-08_18h54_10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/905534a5-dab3-da28-6ad6-cf67c17b96a3.png)

再作成したのでサインインをしてみましょう。

```sh
$ az account clear
$ az login --service-principal --username a5213985-28bf-44fb-952f-97b245dead3a --password 296b700b-055e-44c4-9e9f-6349f8703314 --tenant b9e35cac-a7b8-48dd-a102-bba18eaca524
# No subscriptions were found for 'ba0b6d93-1e81-400d-a92c-43566f73ff28'. If this is expected, use '--allow-no-subscriptions' to have tenant level accesses
```

--allow-no-subscription オプションを指定することでログインできるようですが、サブスクリプションの権限は失われてしまったようです。

### サービス プリンシパルとアプリケーションの関係

作成されたエンタープライズ アプリケーションをよく確認すると Object ID が変わっています。
つまりサービス プリンシパルが一度削除され、新しく作られたために、今まで紐づいていたデータがなくなってしまったということです。

このことから、サブスクリプションのロール (RBAC) は、サービス プリンシパルに割り当てられており、アプリケーションには紐づいていないことがわかります。

逆にアプリケーション ID は変わっていないことが分かります。
アプリケーションとサービス プリンシパルは、アプリケーション ID で紐づいていることが分かります。

ここまでの作業で、エンタープライズ アプリケーションに表示されるオブジェクトがサービス プリンシパルであり、サブスクリプションのロール (RBAC) を割り当てられていること、サービス プリンシパルに割り当てられたロールでサインインをするための資格情報はアプリケーション オブジェクトに登録されていること、サービス プリンシパルとアプリケーションはアプリケーション ID で紐づいていることが理解できました。

### まとめ -RBAC-

- サブスクリプションのロール (RBAC) は、サービス プリンシパルに割り当たる
- アプリケーションにシークレット (資格情報) が保存されている
- アプリケーションとサービス プリンシパルは 1 対 1 で対応している
- アプリケーションの資格情報でサービス プリンシパルで割り当てられたロールにサインインできる

![2019-12-10_21h48_06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/e7b3ee9e-a309-c686-b553-ead767123c40.png)

次にサービス プリンシパルで Azure AD のディレクトリ操作やを行う方法を確認し、サービス プリンシパルがほかに何を出来るか確認してみましょう。

## Case 2. サービス プリンシパルで Azure AD を操作する

先ほどまでのサービス プリンシパルは、アプリケーション開発者になじみが深いモノだったと思いますが、ここからはシステム管理者がよく扱う分野になります。(もちろん同じサービス プリンシパルなのですが)

今回はサブスクリプションではなく、ディレクトリ ロールを割り当てるので、アプリケーションにディレクトリ ロールを割り当てられる権限が必要です。

ディレクトリ ロールとサブスクリプションのロールの違いについては、弊社チームブログをご覧ください。

- Azure サブスクリプションと Azure AD の管理者 | Japan Azure Identity Support Blog
  https://jpazureid.github.io/blog/azure-active-directory/subscription-azure-ad-relationship/

今までは az コマンドを利用していましたが、今回は Azure AD PowerShell モジュールを使います。しかし登場人物は同じなので、あまり構えずにお願いします。

今回のコードは以下のドキュメントに記載されていたものを少し修正しています。

- Connect-AzureAD (AzureAD) | Microsoft Docs
  https://docs.microsoft.com/ja-jp/powershell/module/azuread/connect-azuread?view=azureadps-2.0

### 証明書の作成

Azure AD モジュールでは、先ほど利用したパスワード認証は利用できず、証明書による認証が必要です。
そのためまずは証明書を作成します。

```powershell
# 証明書の作成
$currentDate = Get-Date
$endDate  = $currentDate.AddYears(1)
$notAfter  = $endDate.AddYears(1)
$pwd  = "<password>"
$selfCert = New-SelfSignedCertificate -CertStoreLocation cert:\CurrentUser\my -DnsName com.foo.bar -KeyExportPolicy Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -KeySpec Signature -NotAfter $notAfter
$thumb = $selfCert.Thumbprint
$pwd = ConvertTo-SecureString -String $pwd -Force -AsPlainText
Export-PfxCertificate -cert "cert:\CurrentUser\my\$thumb" -FilePath c:\temp\examplecert.pfx -Password $pwd
```

上記コマンドで自己証明書が作成され、ユーザー証明書に保存されました。
また、他の PC でも利用できるよう、パスワードで保護した状態で秘密鍵を Export しました。

続けて以下のコマンドで証明書をアップロードするために、証明書をロードします。

```powershell
$cert  = New-Object System.Security.Cryptography.X509Certificates.X509Certificate("C:\temp\examplecert.pfx", $pwd)
$keyValue = [System.Convert]::ToBase64String($cert.GetRawCertData())
```

### アプリケーションとサービス プリンシパルの作成

次にアプリケーションとサービス プリンシパルを作成します。az コマンドでは両方が一度に作られましたが、Azure AD PowerShell では、一つ一つ作成します。

> 実は Azure ポータルからアプリの登録を行うと az コマンド同様に、アプリケーションとサービス プリンシパルが同時に作られます。

アプリケーションの作成には New-AzureADApplication コマンドを利用します。証明書のアップロードには New-AzureADApplicationKeyCredential コマンドを利用します。

```powershell
# 管理アカウントでサインイン
Connect-AzureAD

# アプリケーション オブジェクトの作成
$application = New-AzureADApplication -DisplayName "test123" -IdentifierUris "https://test123"

# 証明書の登録
New-AzureADApplicationKeyCredential -ObjectId $application.ObjectId -CustomKeyIdentifier "Test123" -StartDate $currentDate -EndDate $endDate -Type AsymmetricX509Cert -Usage Verify -Value $keyValue
```

証明書のアップロードが終わったら、サービス プリンシパルを作成します。
サービス プリンシパルは必ずアプリケーションに紐づくので、サービス プリンシパルの作成時はアプリケーション ID を指定します。

> さきほどポータルの画面から作成した際にも、アプリケーションの画面から作成しましたね。

```powershell
# サービス プリンシパルの作成
$sp = New-AzureADServicePrincipal -AppId $application.AppId
````

### ディレクトリ ロールの割り当て

そして、[ディレクトリ ロール](https://docs.microsoft.com/ja-jp/azure/active-directory/users-groups-roles/directory-assign-admin-roles) を作成したサービス プリンシパルに割り当てます。

今回は、作成したサービス プリンシパルでユーザーを作成しようと思うので、ユーザー管理者の権限を割り当てます。

```powershell
# ディレクトリ ロールの検索, 残念ながら日本語で検索はできないため、Azure ポータルの URL で確認するか、英語で検索します。
Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq "User Account Administrator"}

# 戻り値がない場合は、ディレクトリ ロールを有効化します。
Get-AzureADDirectoryRole | Where-Object { $_.DisplayName -eq "User Account Administrator" }
$userAdminRoleTemplate = Get-AzureADDirectoryRoleTemplate | Where-Object { $_.DisplayName -like "User Account Administrator" }
Enable-AzureADDirectoryRole -RoleTemplateId $userAdminRoleTemplate.ObjectId

$userAdminRole = Get-AzureADDirectoryRole | Where-Object {$_.DisplayName -eq "User Account Administrator"}

# ディレクトリ ロールの割り当て
Add-AzureADDirectoryRoleMember -ObjectId $userAdminRole.ObjectId -RefObjectId $sp.ObjectId
```

> やりたいタスク別に必要な最小限のロールは、以下のドキュメントで確認できます。
> - 管理者タスクを委任する - Azure Active Directory | Microsoft Docs
>  https://docs.microsoft.com/ja-jp/azure/active-directory/users-groups-roles/roles-delegate-by-task

ロールの割り当てが完了したら、作成したアプリケーションの資格情報を利用してサインインします。

```powershell
Connect-AzureAD -TenantId b9e35cac-a7b8-48dd-a102-bba18eaca524 -ApplicationId  $sp.AppId -CertificateThumbprint $thumb
```

最後に正しくディレクトリ ロールが割り当たっているか確認するため、 [ユーザーの作成](https://docs.microsoft.com/en-us/powershell/module/azuread/new-azureaduser?view=azureadps-2.0) を行います。

```powershell
$PasswordProfile = New-Object -TypeName Microsoft.Open.AzureAD.Model.PasswordProfile
$PasswordProfile.Password = "Test@123"

New-AzureADUser -DisplayName "New User" -PasswordProfile $PasswordProfile -UserPrincipalName "NewUser@contoso.com" -AccountEnabled $true -MailNickName "Newuser"
```

おおよそサービス プリンシパルに RBAC を割り当てた時と、流れは同じですね。

### Azure ポータルでの確認

Azure ポータル上で確認してみましょう。

まず、先ほど同様にアプリの登録から test123 アプリを選択します。証明書とシークレットの欄を見ると、証明書が登録されています。

![2019-12-08_22h14_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/b9b05a6c-726a-d7d5-dd80-fd6165bccebc.png)


あれ…、なぜか New-AzureADServicePrincipalCredential コマンドから証明書をアップロードすると、証明書の拇印がバグってますね。
正確にはこうなるはずです…。以下の画面は、証明書ストアから証明書を秘密鍵なしでエクスポートして Web 画面からアップロードしなおした結果です。

![2019-12-08_22h15_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/5d834367-7346-f4c0-6c4b-60b67fae1481.png)

とにかく、クライアントシークレットの代わりに証明書がアップロードされています。

次にディレクトリ ロールを確認してみましょう。
ディレクトリ ロールへは Azure Active Directory 配下の [ロールと管理者]　から確認できます。

![2019-12-08_22h39_14.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/dfe4e0db-c835-200e-d80b-1a032b0ff784.png)

先ほど割り当てたロールは、ユーザー管理者なので詳細を確認してみましょう。

![2019-12-08_22h41_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/7d552e47-8188-3cbb-d843-dbc744f680a8.png)

たしかに先ほど作成したサービス プリンシパルに、ディレクトリ ロールが割り当てられていることを確認します。

ここでは割愛しますが、サービス プリンシパルを削除すると先ほど同様に認証が行えなくなり、割り当てたロールの情報も削除されてしまいました。

ここまでの手順を振り返りましょう。

今回は、az コマンドで行った際と異なり、アプリケーションと、サービス プリンシパルを別々に作成しました。

アプリケーションの資格情報としてはシークレットの他に、証明書で認証出来ることも分かりました。
また、RBAC に加えて、ディレクトリ ロールもサービス プリンシパルに割り当てることができると分かりました。

### まとめ -ディレクトリ ロール-

- ディレクトリ ロールは、サービス プリンシパルに割り当たる
- アプリケーションに証明書 (資格情報) が保存されている
- アプリケーションの資格情報で、サービス プリンシパルに割り当てられたディレクトリ ロールにサインインできる

![2019-12-09_22h14_38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/c884b420-1ba0-9ee4-900c-3883335a7df7.png)

> ここまでの手順は GUI でも行えます。詳しくは [ポータルで Azure アプリの ID を作成する - Microsoft identity platform | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/howto-create-service-principal-portal) をご確認ください。

## Case 3. サービス プリンシパルをアプリの認証/認可に使用する

ここまでの作業で、サービス プリンシパルが認証に必要なこと、対応するアプリケーションのシークレットや、証明書などの資格情報を利用して、サービス プリンシパルに割り当てられた権限でサインインができることを確認しました。

次の作業は、アプリケーション開発者、特に ID 周りをいじる方にはなじみがある、アプリの認証にサービス プリンシパルを使う例を紹介します。
この作業ではサービス プリンシパルに何が紐づくのでしょうか。

アプリケーションの例として、今回は Graph Explorer を利用しましょう。

- Graph エクスプローラー - Microsoft Graph
  https://developer.microsoft.com/ja-jp/graph/graph-explorer

### アプリへのアクセス

まずは Graph Explorer にアクセスしてみましょう。

![2019-12-09_00h10_11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/7413ec1b-0254-d585-dc11-1c45666b2f62.png)

サインインをクリックして、今回は **ユーザーの資格情報** を入力します。
すると、Graph Explorer がアクセス許可を要求します。

![2019-12-09_00h10_53.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/281d60a5-9355-37f9-0796-4db0d887b2be.png)


そろそろ、勘のいい皆様ならお判りでしょうか。
そう、今回、サービス プリンシパルに紐づくのはアプリケーションのアクセス許可、です。

グローバル管理者でサインインしたので、"組織の代理として同意する" のチェックボックスがありますが、今回はチェックをせずにアクセス許可の要求を承諾します。
これをユーザーの同意、あるいは User Consent と呼びます。

同意後アプリケーションに戻ると、先ほどのユーザー権限で各種 Graph API が叩けるようになっています。

> これは実際にはアクセストークンをとっているだけなので、サインインというと警察の方に怒られるのですが、ここでは認証と認可の違いには触れずに進みます。

![2019-12-09_00h23_31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/5413dc8d-e4fb-4e84-d036-f8947f0b2a1c.png)


さて、この時、サービス プリンシパルはどこで使われていたのでしょうか。
また、サービス プリンシパルに何が起きたのでしょうか。

### Azure ポータルでの確認

今回もまた Azure ポータルで確認してみましょう。

まず、エンタープライズ アプリケーションを確認します。すべてのアプリケーションを選択し、Graph Explorer を探しましょう。

![2019-12-09_00h29_44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/73338150-bfe3-1305-bcdc-7fc73e087b12.png)

Graph Explorer のサービス プリンシパルが確認できま、アプリケーション ID は "de8bc8b5-d9f9-48b1-a8ad-b748da725064" であることが分かります。

詳細から、アクセス許可を確認します。ユーザーの同意タブを確認すると、先ほど同意した API の一覧が表示されています。

![2019-12-09_21h17_45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/54b7c9ab-0218-4a10-d629-22d79e54091f.png)

API のアクセス許可についても、サービス プリンシパルに保存されるということですね。

![2019-12-09_22h18_20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/7a02dfc0-249c-f188-8743-503eb9bd0917.png)

ここで一つ注意をして頂きたいことは、今までの例とは異なりユーザーの資格情報でサインインを行ったという点です。
つまり、ユーザーの同意タブにあるのは、先ほどまでのように、アプリケーションの資格情報を利用してアクセス可能な権限ではなく、アプリケーションが **ユーザーの権限で** アクセスする許可です。

この例では、アプリケーションはあくまで、アプリに権限を委譲したユーザー権限でのみアクセスが可能です。
サービス プリンシパル、アプリケーションに加えて、ユーザーが登場人物として現れました。

ユーザーはサービス プリンシパルを通じて自身の資格情報を入力することで、アプリケーションに対しユーザーの権限を委譲することが可能です。

![2019-12-10_21h51_37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/b88c65a1-60f2-ecd1-6c3b-c38c5feee6dc.png)


> 先ほどまでの例と同様に、アプリケーションの資格情報を利用し、API にアクセスできるよう API のアクセス許可を構成することも可能です。


### API 権限の追加

さて、ユーザーの権限でアプリに API のアクセス許可を与えていることが分かったら、試しに、承認されていない API を叩いてみましょう。
例えばテナントのアプリケーション一覧を取得します。

![2019-12-09_00h48_35.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/42a81f03-d65c-41b4-2214-b4be2c5328dc.png)


今回、グローバル管理者でサインインしているため、ユーザーにはアプリケーション一覧を取得する権限があるはずですが、API は権限不足で失敗します。

これはアプリケーション一覧を取得する権限が、サービス プリンシパルに割り振られていないからです。
Graph Explorer の場合、modify your permission からサービス プリンシパルに権限を追加することができます。

![2019-12-09_00h53_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/a89dd113-0840-a47a-72f1-9328a70f7bc1.png)

Directory.Read.All を選択しアクセス権の修正をクリックすると、改めて同意画面が現れます。

![2019-12-09_00h54_23.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/9d3e36fb-6c38-de3c-e0d3-77b211127277.png)


同意を行うとアプリに戻るため、先ほど失敗したアプリの一覧を取得する API をコールします。
先ほどと違い、アプリケーションの一覧が表示されることが分かります。

![2019-12-09_00h54_39.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/0db87f23-a116-e063-84ba-8ef0a145e7bc.png)

Azure ポータルからアクセス許可を確認すると、Directory.Read.All が新しくアクセス許可として追加されていることが確認できます。

![2019-12-09_00h55_16.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/d7e5b35c-24ff-0109-2231-d3cecaac857e.png)


> これは Microsoft ID プラットフォーム v2 で可能になった、差分同意の機能を利用しています。
> アプリケーションに対する Consent の話だけで、一記事書けてしまうのですが、ここでは割愛します。

以上のことから、サービス プリンシパルにはユーザーや管理者が同意したアプリケーションのアクセス許可も保存されることを確認しました。

### マルチテナント アプリケーション

ところで、先ほどまでの RBAC と ディレクトリ ロールを割り当てた手順と大きく異なる点が一つあります。
それはアプリケーションを作成していないということです。

実は今回の手順で作成したサービス プリンシパルに対応するアプリはこのテナントに存在しません。

結論から言うと、このアプリはマルチテナントアプリとして、他テナントで登録されたアプリケーションです。
詳細は以下の記事を確認していただきたいのですが、マルチテナントアプリの場合、別テナントのアプリケーションに紐づくサービス プリンシパルが作成されます。
別テナントのアプリケーションは、サービス プリンシパルを通じて、テナント ユーザーから権限を委譲してもらうことができます。

![2019-12-10_21h52_21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/95a354ca-d7a6-b950-4461-0bbad221b035.png)

- Azure Active Directory のアプリケーション オブジェクトとサービス プリンシパル オブジェクト - Microsoft identity platform | Microsoft Docs
  https://docs.microsoft.com/ja-jp/azure/active-directory/develop/app-objects-and-service-principals

### まとめ -アプリの認証/認可-

- サービス プリンシパルに対し、ユーザーの資格情報でサインインをして API をコールできる
- サービス プリンシパルには API のアクセス許可が保存されている
- ユーザーの資格情報でサインインした場合、ユーザーが持っている権限に加え、サービス プリンシパルで許可された API のみコールできる
- マルチテナント アプリの場合、アプリケーションオブジェクトは他テナントにある


## 最後に

さて、3 つのユースケースを紹介し、サービス プリンシパルが果たしている役割を紹介しました。
サービス プリンシパルは Azure AD で認証する際に必須のオブジェクトであることが理解できたかと思います。

またサービス プリンシパルには、アプリケーションの権限 (RBAC, ディレクトリ ロール, API のアクセス許可)、ユーザーの権限 (API のアクセス許可) に関する情報が紐づいていることも確認しました。

サービス プリンシパルはアプリケーション、あるいはユーザーの資格情報でサインインした際に、利用可能なデータや割り当てられた権限を制御します。
今回紹介したサービス プリンシパルの役割の他にも、まだまだ紹介できていない役割もありますが、アプリケーションとサービス プリンシパル、ユーザー、そして保存されるデータの関係は、他のデーターの場合もおおむね同じです。

今回紹介した内容が少しでもサービス プリンシパルの理解の助けになりましたら幸いです。

## おまけ

### RBAC の細かい割り当て方法

念のため RBAC の割り当ての方法を紹介しておきます。
az コマンドでは以下のように割り当てます。

```sh
# RBAC の削除
$ az role assignment delete --assignee http://ServicePrincipalName 

# 読み取り権限のみ追加 (アプリケーション ID も指定できます)
$ az role assignment create --assignee a5213985-28bf-44fb-952f-97b245dead3a --role Reader

# リソースグループ以下の共同作成者の権限を追加
$ az role assignment create --assignee a5213985-28bf-44fb-952f-97b245dead3a --role Contributor --scopes /subscriptions/124bd76d-010e-48b8-9ef7-8a96bbe89831/resourceGroups/sp-sample

```

上記コマンド実行後、リソースグループから今回作成した sp-sample を確認すると、確かにリソースグループ以下の管理権限と、サブスクリプション全体の閲覧権限が割りあたっていることが確認できます。

![2019-12-08_18h45_30.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/83984/0c7b736d-0bc5-4778-8920-70d0d295840a.png)


一般的には初めから RBAC の権限を指定してサービス プリンシパルを作成するほうが自然ですね。

```sh
$ az ad sp create-for-rbac --name ResourceReader --role Reader
```

### 認証方法

今回紹介した 3 つのケースは、いずれも OAuth のフローでアクセス トークンを取得しています。
詳しくは [Microsoft ID プラットフォームの進化 - Azure | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/active-directory/develop/about-microsoft-identity-platform) の OAuth の項目が参考になりますが、話し出すと長くなるので気になる人は調べてみてください。

図にチラッと乗っけたアプリケーションに設定可能な Reply URI や、あらかじめ設定しておける静的なアクセス許可 (v1) の話もしたかったのですが、記事が長くなってまとめきれなさそうなので断念しました…。

### サービス プリンシパル そのほかの機能

他にも私が知っているサービス プリンシパルの機能や、使い方を少し紹介します。

#### 拡張属性

[extensionProperty と呼ばれる拡張属性](https://docs.microsoft.com/ja-jp/graph/api/resources/extensionproperty?view=graph-rest-1.0
) を保存できます。
サービス プリンシパルを消すと失われるので、多分サービス プリンシパルに保存されています。


#### ギャラリーアプリとノンギャラリーアプリ

- [ギャラリー アプリを追加する - Azure Active Directory | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/active-directory/manage-apps/add-gallery-app)

SAML や、SCIM により Azure AD と統合可能なアプリケーションをギャラリーアプリとして追加することが可能です。
ユーザーやグループの割り当て、SAML や SCIM の設定もサービス プリンシパルに保存されます。

#### Microsoft アプリケーション

Microsoft が管理しているサービスプリンシパルで、新しい機能が作成されると、勝手にテナントに追加されたりします。
公開情報が殆ど無いのですが Office 系のアプリは "ユーザーの割り当て" でなく、ライセンスでユーザーを割り振れたりと、ちょっと特殊なサービス プリンシパルであることが伺えます。

#### マネージド ID

- [Azure リソースのマネージド ID | Microsoft Docs](https://docs.microsoft.com/ja-jp/azure/active-directory/managed-identities-azure-resources/overview)

シークレットを持たない特別なサービス プリンシパルで、VM に割り当てて利用します。
資格情報を管理しないでよいため、セキュアに利用できます。
トークンは専用のエンドポイントに VM からアクセスすることで取得できます。