Azure App Service から Azure Database for PostgreSQL にパスワードレス認証

一応できたものの、まだ全容を掴めていないのでかなりメモっぽい感じでまとめる。

## やりたいこと
Azure App Service から Azure Database for PostgreSQL にパスワードレス認証したい。

現状は普通に PostgreSQL ユーザを作成してその認証情報を App Service に渡して接続しているが、これをやめたい。

## ざっくり仕組み解説
- アクセス元リソース (App Service) の「マネージド ID」を発行して「サービスプリンシパル」として扱えるようにしておく
- データベース側でアクセスを許可する接続元として「サービスプリンシパル」を登録しておく
- 接続時は「マネージド ID」をもとに一時アクセストークンを入手し、それをパスワードとして使用することでデータベース側で認証できる

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/16369/9fdd6b5b-d769-aa3a-a3bf-307d19a67afe.png)

※ 図は [Azure の公式ドキュメント](https://learn.microsoft.com/ja-jp/azure/postgresql/flexible-server/concepts-azure-ad-authentication) から引用
※ この図はユーザプリンシパルのケースを説明しているため Step1 で `az login` しているが、今回のケースではこれに相当する操作は不要


## 初期設定手順
Azure Portal の画面から操作した手順を記載する。

### 1. App Service 側でマネージド ID を有効化する
`App Service` > `ID` > `システム割り当て済み` で `オン` に設定する。

これによって App Service の「オブジェクト ID (プリンシパル ID)」が発行される。

### 2. データベースの認証方式として Microsoft Entra 認証を有効化する
`Azure Database for PostgreSQL` > `認証` > `アクセスの割り当て先` で `PostgreSQL と Microsoft Entra 認証` に設定する。

この設定を変更するとデータベースサーバが再起動する。

### 3. App Service に紐づく (？) サービスプリンシパルを管理者として追加する
`Azure Database for PostgreSQL` > `認証` > `Microsoft Entra 管理者` でサービスプリンシパルを追加する。 `1.` で発行されたオブジェクト ID を入力したらいけた。

### 4. データベース内の権限を付与
`3.` までで認証が通ってデータベースに接続できるようにはなるのだが、このままだとテーブルなどの操作権限がなくて何もできない。

普通の PostgreSQL のパスワード認証で管理者ユーザで接続して、下記 SQL を実行して権限を付与する。

``` sql
GRANT ALL PRIVILEGES ON DATABASE <DB_NAME> TO "<SERVICE_PRINCIPAL_NAME>";
GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO "<SERVICE_PRINCIPAL_NAME>";
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT ALL ON TABLES TO "<SERVICE_PRINCIPAL_NAME>";
```

※ この例では何でもできる権限をつけているが、必要に応じて制限した方がよさそう。

## 接続手順
今回の App Service では Python アプリが動いていたので、Python から接続するパターンを考える。

### 1. 一時アクセストークンを取得する
`azure-identity` ってモジュールを pip で入れておけば、以下のノリでデータベース接続用の一時アクセストークンが得られる。

``` python
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
token = credential.get_token("https://ossrdbms-aad.database.windows.net/.default")

print(token.token)
```

実際は `get_connection` みたいな関数に包むなどしてもうちょっといい感じに書く。

### 2. データベースに接続する
以下の情報を使って PostgreSQL クライアントを使って接続する。

- ユーザ名として、初期設定の際にデータベースの Microsoft Entra 管理者として追加したサービスプリンシパルの **名前** (オブジェクト ID ではない) を使用
- パスワードとして、上記 `1.` で取得した一時アクセストークンを使用

注意点としては、アプリケーション起動時にトークンを取得するような作りにしてしまうと **トークンの期限が切れたときに更新されず、再接続時に死ぬ** ので、必ず「接続が作られるとき」にトークンを取得するようにやる。例えば SQLAlchemy であれば `do_connect` イベントとか `creator`, `async_creator` オプションとかを使うとよい。

## わかっていないこと
- この Managed Identity による方法はいわゆる「RBAC (Role Based Access Control)」とは異なる概念ってことでいいのだろうか？ 今回特にロールは触ってないし...
- 今回はデータベース内の操作権限を付与するために通常の PostgreSQL ユーザを作成したが、これを回避して Microsoft Entra 認証だけで初期設定することは出来ないか？
- 今回の方法とは別に App Service の「サービスコネクタ」を作成する方法もあるようだが、何が違うのか？

## 参考資料
- [Active Directory 認証 - Azure Database for PostgreSQL (フレキシブル サーバー) | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/postgresql/flexible-server/concepts-azure-ad-authentication)
    - 概要図がわかりやすかった
- [Microsoft Entra ID のアプリケーションとサービス プリンシパル | Microsoft Learn](https://learn.microsoft.com/ja-jp/entra/identity-platform/app-objects-and-service-principals?tabs=browser)
    - サービスプリンシパルには「アプリケーション」と「マネージド　ID」の2種類があって、今回は「マネージド ID」の方式でやった
- [Azure Database for PostgreSQL - フレキシブル サーバーでの認証に Microsoft Entra ID を使用する | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/postgresql/flexible-server/how-to-configure-sign-in-azure-ad-authentication)
    - Azure CLI (az コマンド) からアクセストークンを取得する方法が記載されている
- [Azure SDK for Python を使用して Azure リソースに対して Azure でホストされるアプリを認証する - Python on Azure | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/developer/python/sdk/authentication-azure-hosted-apps?tabs=azure-portal%2Cazure-app-service)
    - Azure Database ではなく Blob Storage に App Service + Python からアクセスする方法が書かれている (ほとんど一緒だが、アクセストークンを使用する箇所の手順が少し異なる)
- [マネージド ID を使用して Flask Python Web アプリを作成して Azure にデプロイする - Python on Azure | Microsoft Learn](https://learn.microsoft.com/ja-jp/azure/developer/python/tutorial-python-managed-identity-cli)
    - 今回のやり方とは別で、「サービスコネクタ」というやつを使った手順 (サービスコネクタの作成はうまく出来なくて諦めた)
- [Azure-Samples/msdocs-flask-web-app-managed-identity: Python Flask app that uses Azure managed identity.](https://github.com/Azure-Samples/msdocs-flask-web-app-managed-identity/tree/main)
    - マネージド ID を使って Azure Database for PostgreSQL に接続する Python アプリのサンプル