Help us understand the problem. What is going on with this article?

Exchange Online PowerShell リモート接続のポータブル対応と完全無人自動化

概要

Exchange Online の設定を自動化するには Exchange Online PowerShell のリモート接続が利用できます。しかしながら、ドキュメントで説明されている方法には次のような問題があります。

  • Windows の PowerShell でしか利用できない
    • macOS や Linux の PowerShell Core では動かない
    • GUI のサインイン画面を出すモジュールに依存しているため?
  • 多要素認証を使用する場合、無人実行環境での自動化ができない
    • 毎回サインインを要求されるため管理自動化タスクの CI ができない

今回、これらの問題を解決した PowerShell Core によるポータブルなスクリプトを書いてみたので紹介します。

利用法

スクリプトの実行

スクリプトは GitLab の exops1demo プロジェクトにあります。 ExchangeOnlineAutomate.ps1 が本体です。 Windows PowerShell や macOS/Linux などで動作する PowerShell Core で利用できます。

スクリプトでは PartnerCenter というモジュールで Azure AD の認証を扱っています。このモジュールは PowerShell Gallery で公開されているので、次のコマンドで事前にインストールしてください。

pwsh
Install-Module PartnerCenter

単純に ExchangeOnlineAutomate.ps1 を実行すると、次のようにデバイスコード認証のサインインを要求してきます。

ターミナル
WARNING: To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code B6UD2PR7H to authenticate.

これに従って Exchange Online のユーザーがサインインすると、そのユーザーとして Exchange Online サーバに接続し、Exchange Online PowerShell コマンドレットをインポートして実行するという流れになっています。

認証トークンの永続化

さらにこのスクリプトには認証トークンを Azure Blob Storage に永続化する機能があります。これを使うと認証が完全無人自動化でき、CI での実行が実現できます。

トークンを永続化するにはスクリプトに -TokenUri パラメータで SAS つきの Blob URI を渡す必要があります。一度手動のサインインに成功すれば、次の起動からはこの URI に永続化したトークンを使って認証・接続するようになります。

SAS つき Blob URI については、無効化が必要になったときのために、保存されているアクセスポリシーを利用して生成することを強くお勧めします。 Azure CLI では次の例のようにすれば生成できます。

ターミナル
$ az storage container policy create --account-name l0wstorage --account-key xxxxxxxx --container-name tokens --name exops1demo-policy --permission crw --expiry 2100-01-01T00:00:00Z
$ az storage blob generate-sas --account-name l0wstorage --account-key xxxxxxxx --container-name tokens --name exops1demo.json --policy-name exops1demo-policy --full-uri -o tsv
https://l0wstorage.blob.core.windows.net/tokens/exops1demo.json?sr=b&si=exops1demo-policy&sig=f6XvzAkrbaRhcPZgW6SOGNhWffSHSuqFYJMjD9AoRz0%3D&sv=2018-11-09

CI の実現

.gitlab-ci.yml は GitLab CI の設定例です。プロジェクトの CI/CD 変数 TokenUri に前述の SAS つき Blob URI を設定して実行しています。

次の実行ログを見ると CI でスクリプトがどのように動作するかを確認できます。

注意事項: この種の CI を公開のプロジェクトでは実行しないでください。ちょうど上記のビルドログのように SAS つき URI などのセキュリティ情報が漏洩する危険があります。万が一漏洩した場合は直ちに次の対応を行ってください。

  • ユーザーの既存のリフレッシュトークンを無効化する。これはユーザーのパスワードを変更するのが最も簡単です。詳細についてはドキュメントを参照してください。
  • ストレージアカウントのアクセスポリシーやアクセスキーを無効化して、新しい SAS つき URI を発行する。

実装の説明

Exchange Online PowerShell への接続

次は ExchangeOnlineAutomate.ps1 のサーバ接続部分を簡略化したものです。

pwsh
$AppId = "a0c73c16-a7e3-4564-9a95-2bdf47383716"
$Scopes = @("https://outlook.office365.com/.default")
$RemoteConfig = "Microsoft.Exchange"
$RemoteUri = "https://ps.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true"

$token = New-PartnerAccessToken -ApplicationId $AppId -Scopes $Scopes -UseDeviceAuthentication
$pass = ConvertTo-SecureString "Bearer $($token.AccessToken)" -AsPlainText -Force
$cred = New-Object System.Management.Automation.PSCredential($token.Account.Username, $pass)
$session = New-PSSession -ConfigurationName $RemoteConfig -ConnectionUri $RemoteUri -Credential $cred -Authentication Basic -AllowRedirection
Import-PSSession -AllowClobber -Session $session

# これ以降 Exchange Online PowerShell コマンドレットが利用可能
Get-Mailbox | Format-Table

まず $AppId は Exchange Online PowerShell のアプリケーション ID です。これに対して New-PartnerAccessToken で OAuth2 トークン $token を取得します。 -UseDeviceAuthentication を指定するとデバイスコード認証を行います。

ユーザーのサインインが成功すると $token には次のように認証で得られた情報がすべて入ってきます。

ターミナル
PS /Users/yaegashi/git/exops1demo> $token                                                                             

RefreshToken            : OAQABAAAAAAAm-06blBE1TpVMil8KPQ41vUSEVoU9OMB0nHU_w5KJvmPxMO_epJkpIejVfp7LKF5vPhfFyXt
                          ....
AccessToken             : eyJ0eXAiOiJKV1QiLCJub25jZSI6IjdmaFZDR1ZyLWJoS1lJZXhqQXdsVmlyRXM0amJRcTYxSmVWXzZPa3NM
                          ....
IsExtendedLifeTimeToken : False
UniqueId                : d2a07c12-3806-4f0b-9f86-c39d88de1c83
ExpiresOn               : 2020/04/24 0:52:48 +00:00
ExtendedExpiresOn       : 2020/04/24 0:52:48 +00:00
TenantId                : bcd388dd-3247-4137-9a8d-a55a4b2de147
Account                 : Account username: admin@l0wdev.onmicrosoft.com environment login.windows.net home ac
                          count id: AccountId: d2a07c12-3806-4f0b-9f86-c39d88de1c83.bcd388dd-3247-4137-9a8d-a5
                          5a4b2de147
IdToken                 : eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6IkN0VHVoTUptRDVNN0RMZHpEMnYyeDNRS1NSWSJ9
                          ....
Scopes                  : {https://outlook.office365.com/.default, https://outlook.office365.com/adminapi.acce
                          ssasuser.all, https://outlook.office365.com/ffopowershell.accessasuser.all, https://
                          outlook.office365.com/remotepowershell.accessasuser.all}
CorrelationId           : 4456c6c7-e559-443f-97d2-5aefbdf3dfc2
User                    : 

アクセストークン (AccessToken) を使って Exchange Online サーバにリモート接続します。それには https://ps.outlook.com/powershell-liveid?BasicAuthToOAuthConversion=true という興味深いクエリがついた URL に対して、次のような Basic 認証で接続します。

設定
ユーザー 接続するユーザーの UPN ($token.Account.UserName)
パスワード Bearer + アクセストークン ($token.AccessToken)

最後に Import-PSSession でセッションをインポートすると、 Exchange Online PowerShell のコマンドレットが次の行から使用できるようになります。

認証トークンのリフレッシュ

アクセストークンの有効期間は 1 時間です。有効期間が過ぎると使えなくなりますので、認証をやりなおすか、リフレッシュトークンを使ってアクセストークンを更新する必要があります。アクセストークンの更新にも New-PartnerAccessToken コマンドレットが利用できます。

pwsh
$token = New-PartnerAccessToken -ApplicationId $AppId -Scopes $Scopes -RefreshToken $token.RefreshToken

リフレッシュトークンを有効利用するには、それをどこかで永続化する必要があります。一番簡単なのは $token を JSON などにシリアライズして保存することです。

保存先としては、たとえば Azure Blob Storage のようなオブジェクトストレージを使うと、CI システムの一時的な実行環境からでも利用できるようになります。書き込み可能な SAS つきの Blob URI ($TokenUri) を用意して、次のようにすれば JSON 形式にしてアップロードできます。

pwsh
$TokenUri = "https://l0wstorage.blob.core.windows.net/tokens/exops1demo.json?sr=b&si=exops1demo-policy&sig=f6XvzAkrbaRhcPZgW6SOGNhWffSHSuqFYJMjD9AoRz0%3D&sv=2018-11-09"
$token | ConvertTo-Json | Invoke-RestMethod -Uri $TokenUri -Method PUT -ContentType application/json -Headers @{"x-ms-blob-type" = "BlockBlob" }

まとめ

この記事では、次の機能を持つスクリプト ExchangeOnlineAutomate.ps1 と GitLab CI による実行デモを紹介しました。

  • macOS や Linux の PowerShell Core でも動作するポータブルな Exchange Online PowerShell リモート接続
  • PartnerCenter モジュールによる Azure AD OAuth2 認証操作 (デバイス認証許可フロー、トークンのリフレッシュ)
  • Azure Blob Storage による OAuth2 トークンの永続化

今後の改良としては、次のようなものが考えられると思います。

  • Exchange Online PowerShell リモート接続や、OAuth2 トークン永続化の機能をモジュール化して PowerShell Gallery で公開する
  • Azure Blob Storage 以外でも OAuth2 トークンの永続化ができるようにする。 OneDrive や SharePoint が使えると Office 365 で完結するのでうれしい。

特に PartnerCenter モジュールはポータブルでオープンソースで Azure AD 認証のトークンを直接扱えるとても便利なモジュールだと思いました。これを活用すれば様々な管理タスクについて PowerShell Core による自動化や CI が実現できそうです。

今回は久々に PowerShell でプログラミングしてみました。いろいろ拙いところがあると思いますので、お気づきの点はご指摘いただけると大変うれしく思います。

謝辞

この記事は次のブログ記事を参考にして書きました。ありがとうございました。

yaegashi
Linux や Unix が得意で低レベルなことが好きなエンジニアです
https://l0w.dev
bandainamcostudios
バンダイナムコスタジオは、家庭用ゲームソフト、モバイルコンテンツ、の企画・開発・運営、ゲームに関する技術研究・開発を行っている会社です。
https://www.bandainamcostudios.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした