証明書認証のサービスプリンシパルを使ってBlobにアクセス

Azure外部（オンプレミス）の端末からPowerShellスクリプトを使ってBlobにアクセスし、ファイルをアップロードする要件があり、認証方式を検討しました。スクリプトで非対話ログオンする場合、サービスプリンシパルが使えます。サービスプリンシパルで認証するにはシークレット、証明書が利用できます。

| 方式 | メリット | デメリット |
|:-|:-|:-|
| シークレット | 証明書よりは設定が簡単  | 最大有効期限が2年なので定期的な更新が必要  |
| 証明書 | 10年など期限を長く設定可能 <br> シークレットよりセキュア  | 設定作業はシークレットより多い |

今回はシークレットの定期更新運用を回避するため、証明書認証を選択しました。証明書は自己署名証明書を作成して設定します。

# 設定
## 自己署名証明書の作成
PowerShellを使って自己署名証明書を作成します。

以下のコマンドを実行し、自己署名証明書を作成します。以下のコマンドでは有効期限を10年に設定しています。

   ~~~
   $certname = "blob-uploader"
   $cert = New-SelfSignedCertificate -Subject "CN=$certname" -CertStoreLocation "Cert:\CurrentUser\My" -KeyExportPolicy Exportable -KeySpec Signature -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256 -NotAfter (Get-Date).AddYears(10)
   ~~~

後の手順でアプリに登録するため、作成した証明書をcer形式で出力します。

   ~~~
   Export-Certificate -Cert $cert -FilePath ".\$certname.cer"
   ~~~

スクリプト側の認証処理で使うため、作成した証明書をpfx形式で出力します。証明書を作成した端末、作成したユーザでスクリプトを動作させる場合、この手順は省略可能です。

   ~~~
   $mypwd = ConvertTo-SecureString -String "<pfxのパスワードを指定>" -Force -AsPlainText
   Export-PfxCertificate -Cert $cert -FilePath ".\$certname.pfx" -Password $mypwd
   ~~~

 後の手順で使うため、証明書の拇印を控えておいてください。
 
   ~~~
   $cert.Thumbprint
   ~~~


## アプリの登録
サービスプリンシパルを登録します。

1. Microsoft Entra IDの管理画面を開き、[アプリの登録] -> [+新規登録] を選択します。
![01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/a19e195f-4680-01e6-4a64-d90f4cf08b5d.png)
　

1. 表示名は用途に応じたわかりやすい名前をつけておきます。サポートされているアカウントの種類は「この組織ディレクトリの身に含まれるアカウント」にしておきます。リダイレクトURIは空欄のままでOKです。
![02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/a44f77ca-7329-1999-c70c-e42db74afea7.png)
　
1. 認証で利用する証明書をサービスプリンシパルに登録します。作成したアプリの管理画面を開き、[証明書とシークレット] -> [証明書のアップロード]を選択します。
![08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/652386bc-35f9-db7c-7c8d-5d4f0a79c016.png)
　
1. 作成した自己署名証明書をアップロードします。アップロードするのは「.cer」のほうです。
![09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/258735e4-07f4-2a5d-94f7-6abfca907ece.png)
　
1. 証明書が登録されました。
![10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/db297bfa-b5bc-670b-e24a-8c66587e7252.png)
　
1. 後ほどスクリプトで記載する必要があるので、概要ページから「アプリケーション（クライアント）ID」、「ディレクトリ（テナント）ID」を控えておきます。
![11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/a978d3ce-850d-a328-dc04-a56dea84f972.png)

## BlobのIAM設定
サービスプリンシパルがBlobを操作できるように権限を付与します。

1. 操作対象のBlobの管理画面を開き、[アクセス制御（IAM）] -> [+追加] -> [ロールの割り当ての追加]を選択します。
![03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/0fba2a99-2872-3b60-cbe1-af4c60853673.png)
　
1. Blobの読み書きを許可したいので、「ストレージBLOBデータ共同作成者」を選択します。用途に応じて選択してください。
![04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/18d85772-945f-ad31-ff5b-fb59a4eee3e4.png)
　
1. さきほど作成したサービスプリンシパルを選択します。
![05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/e4341e5a-cb85-3a4a-3cbe-fb79bfda72e1.png)
　
1. ウィザードを最後まで進めてロールの割り当てを完了させます。

# スクリプト作成
## 事前準備
スクリプトからBlobへアクセスして操作するのにはazcopyを使います。
以下のページよりazcopyをダウンロードしておきます。
本記事はv10.23で動作確認しています。



https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azcopy-v10#download-azcopy

ダウンロードしたzipファイルを解凍してazcopy.exeを取り出しておきます。

:::note warn
2024/9/21追記
おそらくazcopy v10.24以降ですが、新しいazcopyを使うとPowershell 7.xがインストールされていないと実行時にエラーが出るようになりました。（pwshがない、という趣旨のエラーが出ます）
azcopyのドキュメントにはPowershell 7.xが要件という記載はないですが、pwshがないというエラーが出た場合はPowershell 7.xをインストールしてください。
:::

## Blobへファイルをアップロードするスクリプト
まずはAzure PowerShellでサービスプリンシパルの認証を通します。

~~~
$appId = "<サービスプリンシパルのアプリケーションID>"
$tenantId ="<テナントID>"
$certificateThumbprint = "<証明書の拇印>"

Connect-AzAccount -ServicePrincipal -ApplicationId $appId -Tenant $tenantId -CertificateThumbprint $certificateThumbprint
~~~

証明書の拇印を控えていない場合、証明書マネージャより確認できます。
![13.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2850747/3f3a4f16-c52c-59fd-6784-a97c944c44ad.png)

Azure PowerShellで通した認証を使ってazcopyでの認証を通すには以下の一文を書きます。
~~~
$Env:AZCOPY_AUTO_LOGIN_TYPE="PSCRED"
~~~

これでazcopyを使える状態になりました。
Blobにファイルをアップロードするには以下のように記載します。

~~~
# uploadコンテナにtest.txtをアップロード
.\azcopy.exe copy .\test.txt https://blobuploadtest01.blob.core.windows.net/upload
~~~

その他の使い方はazcopyのドキュメントをご参照ください。
https://learn.microsoft.com/ja-jp/azure/storage/common/storage-ref-azcopy

:::note warn
azcopy v10.21以前では、PSCREDによるauto loginが未実装のため本手順は動作しません。
:::

:::note warn
azcopy loginによる認証はv10.22以降非推奨となったようです。auto-loginを使うことが推奨されています。
:::

:::note warn
2024/09/21追記
azcopyのPSCREDでのログインは認証キャッシュを参照しているようです。
Disable-AzContextAutosaveオプションを有効にして認証キャッシュが保存されないようにしているとazcopy実行時に認証エラーが出ます。
:::

## おまけ：証明書ファイルパスを指定して認証を通す方法
Windows証明書ストアに登録されている証明書ではなく、pfxファイルを直接指定して認証を通すこともできます。この方法だと、pfxファイルのパスワードをスクリプト上に書かないといけない点がセキュリティ上の懸念です。

~~~
$env:AZCOPY_AUTO_LOGIN_TYPE = "SPN"
$env:AZCOPY_SPA_APPLICATION_ID = "<サービスプリンシパルのアプリケーションID>"
$env:AZCOPY_SPA_CERT_PATH = "<自己署名証明書：pfxファイルのパス>"
$env:AZCOPY_SPA_CERT_PASSWORD = "<pfxファイルに指定したパスワード>"
$env:AZCOPY_TENANT_ID = "<テナントID>"

# uploadコンテナにtest.txtをアップロード
.\azcopy.exe copy .\test.txt https://blobuploadtest01.blob.core.windows.net/upload
~~~


# 参考
[AzCopy と Microsoft Entra ID を使用して BLOB へのアクセスを承認する](https://learn.microsoft.com/ja-jp/azure/storage/common/storage-use-azcopy-authorize-azure-active-directory#authorize-with-azure-powershell)