Azure Database for PostgreSQL への Azure Service Connector の作成に失敗する（回避策あり）

これはほぼ個人的なメモです。もしかしたら誰かの役に立つことがあるかもしれませんが、その可能性は限りなく低いでしょう。（実は MS 社内の技術ロールの人には役に立つ可能性が高いのですけど）
もし、役に立ったなら是非いいねしていただけると嬉しいです。

# 結論

お急ぎの方は結論からどうぞ。

## 事象と原因

- Azure Spring Apps から Azure Database for PostgreSQL への Service Connector の作成に失敗した
- 原因はログインユーザーが Azure B2B で招待されたユーザーであるため、ログイン時のIDとユーザープリンシパル名が異なること
- もし、認証方式として Azure AD 認証を使用していない場合はそれも作成失敗の原因だが、その場合はエラーメッセージが異なる（はず）

## 回避策

1. Azure AD にユーザーを作成できるなら、新しくユーザーを作る
    1. そのユーザーに Azure Spring Apps と Azure Database for PostgreSQL へのアクセス権を付与する
    1. Azure Portal で Azure Database for PostgreSQL を開き、左メニューの認証を選択する。右画面の下に「Azure Active Directory 管理者 (Azure AD 管理者)」というセクションがあるので、そこに追加した新しいユーザーを追加する
    1. 追加した新しいユーザーで Azure CLI にログインし、 Service Connector 作成のコマンドを流せば成功する（はず）。これが一番簡単（なはず）。
1. Azure AD にユーザーを作成できない場合はサービスプリンシパルを作成する。Azure CLI で次のコマンドを流す。SP名は自由なものでOK。サブスクIdを置き換えること
    1. ```az ad sp create-for-rbac --name <your-sp-name> --role Contributor --scopes /subscriptions/00000000-0000-0000-0000-000000000000```
    1. 作成後に表示される appId と password を控えておく
    1. Azure Database for PostgreSQL に対して既に Azure Portal で登録済みの Azure AD　ユーザーで Azure AD 認証でログインをする。psql や pgAdmin などを使う。
        - Azure AD ユーザーをまだ登録していない場合は Azure Portal で Azure Database for PostgreSQL を開き、左メニューの認証を開く。右画面の下の方に「Azure Active Directory 管理者 (Azure AD 管理者)」というセクションがあるので、そこから登録する
    1. postgres データベースで次の SQL を流す。サービスプリンシパル作成後に表示された appId をセットすること。
        - ```select * from pgaadauth_create_principal_with_oid('<appId>', '<appId>', 'service', true, false);```
    1. Azure CLI をログアウトし、サービスプリンシパルで再度ログインする。appId、password はサービスプリンシパル作成後に表示されたものをセットする。
        - ```az logout```
        - ```az login  --service-principal -u <appId> -p <password> -t <tenantId>```
    1. Service Connector 作成のコマンドを流せば成功する
    1. 作業に使ったサービスプリンシパルを消す前に、Azure Database for PostgreSQLに作ったAzure AD 認証用のログインユーザーも消しておいた方が良い。サービスプリンシパルの appId がユーザー名なのでそれを Drop すれば良い（pgAdminなら右クリック→削除で消せるので簡単）


# Azure Service Connector とは

https://learn.microsoft.com/ja-jp/azure/service-connector/overview

　Azure Service Connctor を理解するには、 Managed Id について本当は理解しなければなりません。 Azure には Managed Id というリソースがあります。これは Azure 内部の リソースAからリソースBへ接続をする場合に、リソースBに接続するための接続文字列やキーをソースで直接使用することなくアクセスできるようにした仕組みです。

Managed ID は特定リソースに紐づいていて作成されます（System Managed ID の場合）。リソースAからリソースBへアクセスするならリソースAの Managed Id を有効することで Managed Id が作成されます。そして、リソースBはその Managed Iｄ に対して自身へのアクセス権を付与します。

すると、リソースAは自身に紐づいている　Managed Iｄ を使って　リソースBへアクセスすることができるようになります。

と、文字でここまで説明しても、よく分からないですよね。ただ、よくわからないけど便利そうなことはなんとなく伝わるかと思います。かつ、接続文字列やキーをソースで直接扱わないのでとてもセキュアな仕組みです。なので、このよくわからない Managed Id という仕組みを是非皆さんに使っていただきたいのですが、仕組みも構築もややこしいので、代わりに一発で構築してあげよう、というのが　Service Connector なのです。

# Azure Spring Apps で Server Connector の作成に失敗した

　Service Connector は Azure のリソースの中でも限られたリソースしか対応していません。2023年8月現在ではまだ次の3つしか対応していません。

- Azure App Service
- Azure Container Apps
- Azure Spring Apps

今回、　Azure Spring Apps で Service Connector の作成を試みたところ、失敗しました。今回の原因はズバリ、Azure AD B2B だったのですが、Azure CLI コマンドのバグが本当の原因です。

## 現象

Azure Spring Apps に関わらず、 Azure Portal で Service Connector を作成しようとすると、他とは異なって最後は　Cloud Shell もしくは自身のローカル環境で実行する Azure CLI のコマンドが作成されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/222127/314c7e14-da94-9220-64bc-6ffb040d1d08.png)

ところが、このコマンドを Cloud Shell だろうかローカル環境の Azure CLI だろうが、実際に流すとエラーとなったのです。

　私の場合は　Azure Spring Apps から Azure Database for PostgreSQL への接続する Service Connector を作ろうとしたのですが、Azure CLI コマンドを流すと　Database への接続ができないようでした。コマンドは DB の Filewall に IP アドレスを登録して再度 DB への接続を試みますが、それでも接続ができず、最終的にエラーとなってコマンドが終了していました。

## 原因

　原因は Azure CLI 使用時に Azure にログインしていたユーザーが Azure Database for PostgreSQL に Azure AD 認証で接続できないことは明らかです。でももちろん、Azure Database for PostgreSQL で Azure AD 認証ができるように設定してありましたし、管理者として自身の Azure AD ユーザーも登録してありました。なのにどうして DB に接続できなかったのでしょうか。

　結論から言うと、これはバグです。Azure CLI でログインしているユーザーが　Service Connector 作成コマンドを流すと、どうやらそのコマンドは ログインユーザーの**ログイン Id** を使って Azure Database for PostgreSQL へ接続を試みるようです。ところが、Azure Database for PostgreSQL 側に作成されている Azure AD 認証ユーザーのユーザー名は ログイン　Id ではなく、**ユーザープリンシパル名**なのです。

　ログイン Id はメールアドレスの同じ「Alias@ドメイン」と言う形式ですが、ユーザープリンシパル名はログイン Id と必ず同じとは限りません。そして、 **Azure AD B2B で招待されたユーザーはログイン Id とユーザープリンシパル名は必ず異なります。** そのため、B2Bで招待されたユーザーでは Service Connector を作成するコマンドは失敗してしまうのです。

# 回避策は？

　Azure AD 管理者として Azure Database for PostgreSQL に登録するユーザーのログイン Id とユーザープリンシパル名が一致さえしていれば良いのです。なので、もし Azure AD にユーザーを作成できるのであればそれが一番簡単で早いでしょう。ユーザーを作成し、Azureの権限を付与し、Azure Database for PostgreSQL　のAzure AD 管理者として登録すればいいのです。後はそのユーザーで Azure CLI でログインし、 Service Connector 作成の Azure CLI コマンドを流すだけです。

　問題は、Azure AD にユーザーを作成する権限がない場合です。この場合はサービスプリンシパルを作り、サービスプリンシパルについて手動で Azure Database for PostgreSQL　の Azure AD 管理者として SQL を使って登録しなければなりません。

## 1. サービスプリンシパルを作成する

Azure CLI で次のコマンドを流します。SP名は自由なものでOKです。サブスクIdをご自身のものに置き換えてください。作成後に表示される appId と password を控えておきましょう。

```zsh:zsh
az ad sp create-for-rbac --name <your-sp-name> --role Contributor --scopes /subscriptions/00000000-0000-0000-0000-000000000000
```

## 2. Azure Database for PostgreSQL にサービスプリンシパルを Azure AD 管理者ユーザーとして作成する

　この作業の目玉です。なぜならここで説明するやリ方はおそらくどこにも記載がないからです。Try & Error で正解に辿り着くのは大変な苦労でしたよ！

　まず、Azure Database for PostgreSQL に対して既に Azure Portal で登録済みの Azure AD　ユーザーで Azure AD 認証でログインをしてください。 pgAdmin を使うことをお勧めします。 Azure AD 認証でログインする場合は　　AccessToken の取得が必要です。詳しいやり方は [Microsoft 公式ページ](https://learn.microsoft.com/ja-jp/azure/postgresql/flexible-server/how-to-configure-sign-in-azure-ad-authentication#authenticate-with-azure-ad)をご確認ください。

　ログインができたら、postgres データベースで次の SQL を流します。appId にはサービスプリンシパル作成後に表示された appId をセットしてください。（前後の<>は削除してください）

```sql:sql
select * from pgaadauth_create_principal_with_oid('<appId>', '<appId>', 'service', true, false);
```

select 文にも関わらず、これを流すと Azure AD 認証のユーザー名が appId（ClientId）で作成されます。ここユーザー名をわざわざ appId で作る点がポイントで、Service Connector が Azure Database for PostgreSQL に接続する時にユーザー名として appId を使用しているのが理由です。

## 3. Service Connector 作成のコマンドを流す

　Azure CLI をログアウトして、準備が完了したサービスプリンシパルで再度ログインします。appId、password はサービスプリンシパル作成後に表示されたものをセットしてください。
```zsh:zsh
az logout
az login  --service-principal -u <appId> -p <password> -t <tenantId>
```

ログインができたら、Azure Portal で Azure Database for PostgreSQL を開き、Service Connector の作成パネルを表示し、最後の Azure CLI コマンドを入手してください。それをこのサービスプリンシパルでログイン済みの状態で流せば、無事に Service Connector が作成されます。

## 4. 後片付け

　作業に使ったサービスプリンシパルを消す前に Azure Database for PostgreSQLに作ったAzure AD 認証用のログインユーザーを先に消しましょう。サービスプリンシパルの appId がユーザー名なのでそれを Drop すればOKです。pgAdminなら右クリック→削除で消せるので簡単です。


# GitHub Issue

いつ対応してくれるのかは不明ですが、一応 Issue をあげておきました。

https://github.com/Azure/azure-cli/issues/27294