【20180519 改】AccessToken を使用して Login-AzureRMAccount / Add-AzureRMAccount を実行する

##Introduction

<font color="Red">
※ 20180519 以前書いた方法が仕様変更によってできなくなっているという報告がありましたので修正しました
</font>

PowerShell を使用して Azure サブスクリプションにログインする場合、通常はユーザーIDとパスワードを使用します。

```
Login-AzureRMAccount 
```

パラメタを何も指定しない場合、ログインダイアログボックスが表示され、手動でユーザーIDとパスワードを入力します。

<img width="200" src="https://qiita-image-store.s3.amazonaws.com/0/134539/fd15e726-3b5e-62c0-e391-af8277bc1a8b.png">

-Credential パラメタを使用すればダイアログボックスの表示を省略することも可能です。

```
$UserID = "userid@company.com"
$UserPass = ConvertTo-SecureString "password" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PsCredential $UserID,$UserPass
Login-AzureRmAccount -Credential $cred
```
ただ、この方法では多要素認証（MFA, Multi Factor Authentication) に対応できません。ユーザーIDに多要素認証が設定されている場合、以下のようなエラーが出力されます。

```
Login-AzureRmAccount : AADSTS50079: The user is required to use multi-factor authentication.
Trace ID: 7c047e1d-4da8-4c7a-8e0e-b7bdcf1a2200
Correlation ID: 8732b1bc-4247-4e7f-876d-9a1ee75e18d5
Timestamp: 2017-08-31 15:55:25Z: リモート サーバーがエラーを返しました: (400) 要求が不適切です
発生場所 行:4 文字:1
+ Login-AzureRmAccount -Credential $cred
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : CloseError: (:) [Add-AzureRmAccount]、AadAuthenticationFailedException
    + FullyQualifiedErrorId : Microsoft.Azure.Commands.Profile.AddAzureRMAccountCommand
 
Get-AzureRmVM : Run Login-AzureRmAccount to login.
発生場所 行:5 文字:1
+ Get-AzureRmVM
+ ~~~~~~~~~~~~~
    + CategoryInfo          : InvalidOperation: (:) [Get-AzureRmVM]、PSInvalidOperationException
    + FullyQualifiedErrorId : InvalidOperation,Microsoft.Azure.Commands.Compute.GetAzureVMC
```

この課題を解決する方法はいくつかありますが、ここでは -AccessToken を使用する方法を紹介します。

-AccessToken パラメタには”アクセストークン”と呼ばれるチケット的なものを指定します。このチケットは誰が発行してくれるのかといえば Azure AD です。Azure AD にチケットを発行してもらうには、発行先（つまりチケットを使う人）を明確にしておかなければなりません。発行されたチケットを他人が使用することはできないのです。

発行先を明確にするためには、”サービスプリンシパル”を登録する必要があります。

##サービスプリンシパルとはなんぞや

サービスプリンシパルとは”サービスの識別子”です。と書いてもちょっとわかりずらいんですよね。なので、ここはぶっちゃけましょう。”サービス（プログラム）”を”ユーザーの一人”だと考えましょう。今回は PowerShell がサービスに相当するので、PowerShell を一人のユーザーだと考えます。こんなこと書くとその筋の識者に怒られますがww

そう、”PowerShellちゃん” です。みんな大好き、擬人化です。せっかくなので萌え絵でも描いておきますか。

<img height=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/85d8cfb0-c558-9929-73fa-e7232f82df25.png">

”PowerShellちゃん”は、Azureサブスクリプションにログインして、Azure のリソースに触りたいんです。VM を作ったりとか、WEBアプリを再起動したりとか。

<img height=100 src="https://qiita-image-store.s3.amazonaws.com/0/134539/0f076f65-d9a2-d14f-7ee9-ddc326c361bd.png">

でも、”PowerShellちゃん”自身にはそんな権限はありません。そもそも Azure にログインすることができません。

なので、普段は管理者の人にログインしてもらって、その権限を借りて操作していました。

<img height=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/74773bbf-7ff1-1b2c-3e85-530a65d46263.png">

管理者が不在のときは、パスワードを事前に教えてもらっておき、-Credential パラメタを使用して好き勝手出来ました。

<img height=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/06d28b3a-82d2-dd43-ab7e-d7c4ca8caaa2.png">

そこに「ちょっとまった！」をかけたのがAzure AD の"多要素認証"です。

Azure AD は Azure の番人です。いままでは、管理者の認証によって発行された”権限”を、PowerShell が流用することを許可していました。が、管理者に多要素認証が必要になると、権限の流用が不可能になってしまったのです。

<img height=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/a5e22ed0-42db-330b-899d-a1d5528576e3.png">

”PowerShellちゃん” は困りました。毎回毎回、真夜中のタスクを実行するために管理者のスマートフォンに電話をして PIN を入力してもらうなんてことは不可能だからです。

そこで、”PowerShell ちゃん”を Azure の利用者として登録し、きちんとAzureのリソースにアクセスできる権限を与えてあげることにしましょう。

##PowerShell を Azure AD に登録する（サービスプリンシパルを作成する）

まずは、Azure AD に PowerShell を登録します。

といっても、Azure AD は登録されたサービスが WEBアプリなのか PowerShell なのかについては意識していません。Azure AD にサービスを登録して”サービスプリンシパル（ユーザー ID みたいなもの）”が作成されると、Application ID と Key が発行されます。これらが、UserID と Password に相当します。PowerShell スクリプトの中で Application ID と Key を使用することで Azure にログインできるようになるのです。

多要素認証はサービスプリンシパルには適用されないので、UserID と Password のようにエラーが返されることはありません。

サービスプリンシパルを作成するための手順を以下に示します。

1. Azure ポータルを開く  
2. "Azure Active Directory"を開く  
<img width="200" src="https://qiita-image-store.s3.amazonaws.com/0/134539/9b974211-884e-18bc-8da5-e5a55ebbffd9.png">
3. "App Registration" を開く  
<img width="200" src="https://qiita-image-store.s3.amazonaws.com/0/134539/274c39e2-e080-26bb-9947-037090320ac4.png">
4. "New Application Registration" をクリック  
<img width=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/87c74f77-2f00-6863-b3c4-e45c0d8f6c0b.png">
5. サービスプリンシパルに必要な情報を入力  
サービスの名前は好きなものを指定してください。このサービスプリンシパルを使用するアプリケーションの名前を指定するのがよいでしょう。
アプリケーションのタイプは”Native”を指定してください。PowerShellもネイティブアプリの一種です。
リダイレクトURI ですが、ここを説明しはじめると OAuth 2.0 について説明しなけれならなず時間がかかるので、ここでは ”ネイティブアプリの識別子” だと考えてください。ひとまず、以下の形式で指定しておいてください。 このURIも後で使用するので控えておきましょう。 
https://< Name >  
<img height=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/0f48c606-89c2-7829-a2ff-3cc120f008de.png">
  詳しく知りたい方は、以下を起点として理解を深めましょう。  
  Redirect URLs for Native Apps 
  https://www.oauth.com/oauth2-servers/redirect-uris/redirect-uris-native-apps/
  OAuth 2.0の代表的な利用パターンを仕様から理解しよう 
  www.buildinsider.net/enterprise/openid/oauth20 
  「OAuth 2.0」の基本動作を知る 
  http://www.atmarkit.co.jp/ait/articles/1708/31/news124.html

6. "Create" をクリック  
7. 今登録したサービスプリンシパルが存在するか、念のために検索してみましょう。  
<img height=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/013651fc-985e-c64f-43e3-7190e59ed204.png">  
8. 検索結果に表示されたサービスをクリックして開き "Application ID" を控えておいてください。これが UserID の代わりになります。  
<img height="200" src="https://qiita-image-store.s3.amazonaws.com/0/134539/e6060069-eb3b-af5a-85c5-7f0b3f6a813e.png">  
~~9. 次に、"Keys" をクリック~~
<font color=red>※Native ClientではKeyが取得できなくなりました。よって、この手順は不要です。なぜこのようなことになったのかと言えば、そもそも Native Client で Client ID と Secret の組み合わせでログインできてしまうのがおかしかったのですね。じゃ、代わりに何を使うかと言えば、ここでは Redirect URI を使用しています。正直これが完全にセキュアかと言えば、当然、全然そんなことは無いです。いずれにしてもスクリプトに特定の文字列をうめこまなければならないという方法そのものが脆弱であると言えます。誰でもスクリプトをコピペできてしまうわけですからね。本当に安全な方法をとるのであれば、Azure Automationや Azure Function という Azure 上のスクリプト実行環境を使用してください。これは完全に Azure AD の管理下にあり、スクリプトの作成から実行まで、全てのプロセスがAzure ADの認可のもとにあります。</font>
<img height=250 src="https://qiita-image-store.s3.amazonaws.com/0/134539/1cf1bb37-b793-7074-ed0e-c8da27101c04.png">
10. "Description" にキーの名前を適当に指定し、"EXPIRES"からキーの有効期限を選択します。キーは言うまでもなくパスワードに相当します。この時点ではキーそのものは表示されません。  
<img height=150 src="https://qiita-image-store.s3.amazonaws.com/0/134539/da629308-1cf0-e5ab-7165-bc7bc9862cb7.png">  
11. "Save" をクリックすると "VALUE" にキーが表示されるので、これをコピーしてどこかに控えておいてください。  
<img height=150 src="https://qiita-image-store.s3.amazonaws.com/0/134539/cdc8ddb9-19d1-e090-ad93-5845bf6bde61.png">  

<font color=red>※以下の手順を追加しました 20180519</font>
11. 「Required Permission」をクリックし、このアプリケーションからアクセスするAPIとして「Windows Azure Management API」を追加します。
<img height=250 src="https://qiita-image-store.s3.amazonaws.com/0/134539/806dc36f-1f42-6cd9-ee30-0e0e03908739.png">


以上でサービスプリンシパルの作成が完了しました。そして、サービスプリンシパルを特定するための情報として、Application ID と Key が発行されました。

これで終わりかといえばそうではありません。サービスプリンシパルは、発行されただけでは Azure に対して何の権限も持っていません。これを有効活用するためには、PowerShell が使うであろう権限を割り当てなければならないのです。  

##サービスプリンシパルに権限を割り当てる

Azure AD にユーザーを作成する場合は、その過程で”ロール”を割り当てることができます。ロールとはその名の通り”役割”のことです。役割には事前に”権限”が割り当てられています。

ユーザーに直接権限を割り当てるのではなく、[ユーザー]-[ロール]-[権限] のように、ロールを介して権限を割り当てる手法を RBAC（Role-Based Access Control）と言います。

ロールを介することで、ユーザーと権限を直結させる必要がなくなります。この方式の何が素敵かと言えば、「ユーザーが認証されてロールが決定される」というプロセスと、「リソースにアクセスする際にロールを見て権限を判定する」というプロセスを完全に分離することができるのです。

つまり、認証基盤側でロールさえ変えてしまえば、リソース側の権限が自動的に変わる。。。ということです。これを「認証と認可の分離」などと言ったりしますが、これを説明しはじめると休憩込みで3日ほどかかるので、ここは割愛。

以下の手順で、サービスプリンシパルにロールを割り当てましょう。

1. Azure Portal を開く 
2. "サブスクリプション（Subscription）"  を開く  
<img width=300 src="https://qiita-image-store.s3.amazonaws.com/0/134539/fb8efd7a-3c16-b5af-93a7-7393afeb2c75.png">  
3. サブスクリプションを選択  
<img width=300 src="https://qiita-image-store.s3.amazonaws.com/0/134539/8f2422ac-3103-2987-1ffa-8281bce3fbaa.png">  
4. "Access Control(IAM)" を選択  
<img width=200 src="https://qiita-image-store.s3.amazonaws.com/0/134539/00b361d0-7cea-a70b-e16d-166cca17b425.png">  
5. "追加（Add）"  
<img width=100 src="https://qiita-image-store.s3.amazonaws.com/0/134539/50f7dc35-7485-7817-1648-b22e82866922.png">   
6. PowerShell が必要とするロール（Role）を選択  
<img width=300 src="https://qiita-image-store.s3.amazonaws.com/0/134539/fcfdf6a7-1b16-f5fd-ba50-1fec00f71e14.png">  
7. そのロールを使用するサービスプリンシパルを選択  
<img width=300 src="https://qiita-image-store.s3.amazonaws.com/0/134539/8f137dd6-ff38-8305-bccc-507f5a4b0950.png">   
8. "Save"  

ここは考慮のしどころです。ロールの選択によっては必要以上の権限を与えてしまうことになるので、本当にPowerShellが欲している権限を与えるようにしましょう。  

Azure ロールベースのアクセス制御の組み込みロール 
https://docs.microsoft.com/ja-jp/azure/active-directory/role-based-access-built-in-roles
Azure のロールベースのアクセス制御のためのカスタム ロールを作成する 
https://docs.microsoft.com/ja-jp/azure/active-directory/role-based-access-control-custom-roles 

以上でAzure上のリソースに対してロールを割り当てることができました。 

##Tenant ID と Subscription ID を控える 

サービスプリンシパルを作成したAzure ADと、割り当てたサブスクリプションを正確に識別するために、スクリプトの中では Tenant ID と Subscription ID を明示する必要があります。そのため、以下からそれぞれの情報を控えておきましょう。

1. Tenant ID(Directory ID)
<img height=400 src="https://qiita-image-store.s3.amazonaws.com/0/134539/b0a335a9-9aa6-25ed-0292-20a4758b3569.png">

2. Subscription ID  
<img height=300 src="https://qiita-image-store.s3.amazonaws.com/0/134539/cea0c304-5c6d-99be-05a1-ab3f04b17c5d.png">

##スクリプトを作成する
ここまでに取得した情報を使用して、以下のようなスクリプトを作成しましょう。
これで、パスワードを使用することなく、MFAに邪魔されることなく、スクリプトを実行することができます。

<font color=red>※現在は以下のスクリプトは動作しません 20180519</font>

```
$ClientId = "09f50028-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$ClientSecret = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx="
$SubscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
$TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

$ClientCredential = new-object "Microsoft.IdentityModel.Clients.ActiveDirectory.ClientCredential" $ClientId,$ClientSecret

$authority = "https://login.windows.net/" + $TenantId
$Resource = "https://management.azure.com/"
$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" $authority,$false
$authResult = $authContext.AcquireToken($Resource, $ClientCredential)

Login-AzureRmAccount -AccessToken $authResult.AccessToken -AccountId $AuthClientId -SubscriptionId $SubscriptionId
```

<font color=red>※以下のスクリプトに修正しました 20180519</font>

```
$ClientId = "xxxxxxxx-xxxx-xxxx-xxxx-fe7037d73d68"
$redirectUri = "https://Login-AzureRMAccount"
$SubscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-bd98aef0acbe"
$TenantId = "xxxxxxxx-xxxx-xxxx-xxxx-c2df404a71ee"

$authority = "https://login.windows.net/" + $TenantId
$resourceId = "https://management.azure.com/"

$authContext = New-Object "Microsoft.IdentityModel.Clients.ActiveDirectory.AuthenticationContext" $authority,$false
$authResult = $authContext.AcquireToken($resourceId, $ClientId, $redirectUri) 

Login-AzureRmAccount -AccessToken $authResult.AccessToken  -AccountId $ClientId -SubscriptionId $SubscriptionId
```

ここで疑問がでますよねこの方法って安全なの？？？

それについては、別の投稿で解説することにします。