BackwindowでSalesforce組織へのログインURLをセキュアに共有する

## Saleforce CLIに対しての注意喚起

つい先日、Salesforce CLIの問題について[CERT/CC](https://www.kb.cert.org/vuls/)が注意喚起しました。

https://www.security-next.com/130396

https://kb.cert.org/vuls/id/883754

これは、ちょっと内容がわかりにくいですが、Salesforce CLIが持つログインURLの生成機能（`force:org:open --urlonly` コマンド）について示しています。このURLはいわゆる [frontdoor.jsp](https://help.salesforce.com/s/articleView?id=sf.security_frontdoorjsp.htm&type=5) URLとして知られており、有効なセッションID文字列をURLから受け取ってWebブラウザのログインセッションに変換するためのSalesforce公式のインターフェースです。このURLにアクセスするだけで、ユーザはパスワードなどによる認証処理の必要なく、Salesforceにログインすることが可能になります。

その仕組み上、セッションの有効期限の間はURLを知っている人は誰でもSalesforceにアクセス可能となってしまい、かつ認証後のセッションであるためログ監査もできず脆弱であるとしてCERT/CCは注意喚起を行ったわけです。なおSalesforce側はこの指摘に対し[脆弱性ではなくアクセス権限の誤った設定によるものである](https://help.salesforce.com/s/articleView?id=000363271&type=1)としています（その主張が妥当かどうかは本稿の範疇外）が、最近バージョンアップされたCLIでは同コマンドの実行時に以下の警告メッセージが出るようになっています。

> WARNING: This command will expose sensitive information that allows for subsequent activity using your current authenticated session.
> Sharing this information is equivalent to logging someone in under the current credential, resulting in unintended access and escalation of privilege.
> 警告：このコマンドは、現在の認証されたセッションを使用して後続の活動を可能にする機密情報を公開します。
> この情報を共有することは、現在の認証情報を使って誰かをログインさせることと同じであり、意図しないアクセスや特権の昇格につながります。

## Scratch組織開発での組織共有をどうするか？

いずれにせよ、このコマンドで生成されたログインURLは、メンバー間で共有するには不向きのように思えます。

しかしながら、Scratch組織を使って開発していると、例えばCI上で生成された組織に対してユーザが実際にアクセスし、動作確認やレビューを行いたいというケースはままあるのではないかと思います。

Scratch組織を生成した際に作成されるユーザ名/パスワードはランダムであるため、アクセスが必要な人がそのパスワードを記憶するというのは難しいです。組織作成後にAPIなどでパスワードを強制的に変更して共通パスワードにしてもいいのですが、それもちょっとセキュリティ的にどうかなという感じですね。

なお以前から提供されていたユーザ名/パスワードをクエリに埋め込む形式のログインURLは、つい先日 Spring '22で無効化されるとのアナウンスがありました。こちらはfrontdoor.jspと違いセッション期限もないのでURLが漏れたらさらに大変です。いろいろ便利なのは便利だったのですがまあ仕方ないですね。

[Login Credentials Using URL Query Strings Are Disabled (Release Update)
](https://help.salesforce.com/s/articleView?id=release-notes.rn_security_disable_login_credentials_in_query_string.htm&type=5&release=234)


## Backwindowの紹介

以上の問題点を解決するためのソリューションとして開発されたのが[Backwindow](https://github.com/stomita/salesforce-backwindow-web)です。

https://github.com/stomita/salesforce-backwindow-web

Backwindowは、URL中にセッションIDやパスワードなどのセキュリティ機密情報を埋め込むことなく、共有可能なURLによって特定のメンバーのみScratch組織へログインさせることができるサービスです。URLアクセス前にユーザを認証することで、あらかじめ管理者が指定したIDを持つユーザだけがログイン可能になります。

ユーザの認証は外部IDプロバイダで行います。現在GoogleとSalesforceのIDでのサインインに対応しています。

ちなみに名前は「Frontdoor」のもじりですね。

## Backwindowのセットアップ

### Webサービスの起動

BackwindowはオープンソースのWebアプリケーションソフトウェアですが、Webサービスとして利用するにはHosted Serviceを利用するかDeploy to Herokuを使うのが手っ取り早いです。

* [Hosted Service 版 Backwindow](https://backwindow.herokuapp.com/)
* [Backwindow の Deploy to Heroku リンク](https://heroku.com/deploy?template=https://github.com/stomita/salesforce-backwindow-web)

Hosted Serviceの方は簡単に試せますが、ソース公開されているとはいえ責任を持って運用されているものではないので、企業による本番利用には向かないでしょう。

Deploy to Herokuについては、Herokuのアカウントを保持していればすぐに利用できますが、Salesforceの接続アプリ登録やGoogleサインインのセットアップなど必要になるので、多少手間がかかります。詳細はREADMEを確認してください。

### JWT Bearer Flow の有効化

Backwindowは内部で[JWT Bearer Flow](https://help.salesforce.com/s/articleView?id=sf.remoteaccess_oauth_jwt_flow.htm&type=5)を使ってScratch組織へのログインのためのトークンを取得しています。なのでDevHub組織には前もってOpenSSLなどで証明書を生成するなどのセットアップをする必要があります。ただこのあたりはCIによる自動化を行っていればすでにセットアップ済みの場合も多いのではないかと思います。

[Authorize an Org Using the JWT Bearer Flow](https://developer.salesforce.com/docs/atlas.en-us.sfdx_dev.meta/sfdx_dev/sfdx_dev_auth_jwt_flow.htm)

### 管理ユーザとしてログイン

続いて、BackwindowのWebアプリケーションにアクセスします。ログインを求める画面が表示されるので、「Sign in with Salesforce」を選んで、DevHub組織の管理ユーザアカウントを利用してログインします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6495/f0151392-a846-7f70-fd89-55e9f8faf6a3.png)


ログインが完了したらヘッダ内のメニューにある"Admin"リンクをクリックして、Backwindowのセットアップを行います。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6495/4fc886d4-f2c6-c03b-7957-f528cb177729.png)

### 接続アプリケーション情報の登録

管理画面では、まず最初にJWT Bearer Authorizationフローに利用する接続アプリケーションの情報を登録します。
「Connected App Client ID」にはすでにセットアップ済みの接続アプリケーションからOAuthのClient ID（Consumer Key / コンシューマ鍵）をコピーして入力します。
「JWT Private Key File」には、JWT Bearer Authで用いるプライベートキーのファイル内容をコピーし、貼り付けて入力し、保存します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6495/9a3fb67f-c1f4-518a-861c-7764b338ebad.png)

### ログイン可能なユーザの登録

続いて、誰がこのBackwindowサービスを利用してScratch組織にログインできるかを登録します。IDプロバイダとしてはGoogleとSalesforceに対応していますが、それぞれのアクセス可能なユーザを識別するものとして以下のデータを登録します。

- **Googleでログインするユーザ:** メールアドレス
- **Salesforceでログインするユーザ:** ユーザ名（ログイン時に使うメールアドレス形式のもの）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6495/6ddeb6dc-60a5-0c02-a201-7cb4b3245a7e.png)

## Backwindow ログインURLの生成

Backwindowのセットアップが完了したら、ログインURLを作成します。
Homeリンクに移動すると、UIから必要な情報を入力してBackwindowのログインURLを生成できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6495/fbec1f55-984b-9367-29a8-0b19c7b6cab6.png)

「DevHub Org ID」には18桁のDevHubの組織IDを入力します。Scratch組織の組織IDではないので注意が必要です。
「Login Username」にはログインしたいScratch組織のユーザのユーザ名を指定します。
「Environment」は、組織が本番環境かSandbox環境か、どちらの環境に作られているかを指定します。通常Scratch組織はSandbox環境に作成されるので、「Sandbox」を選択します。

:::note warn
Backwindowでログイン可能なScratch組織は、JWT Bearer Auth(`force:auth:jwt:grant`)でDevHubに接続した状態で作成されたScratch組織のみです。DevHubに`force:auth:web:login`で接続した状態で作成されたScratch組織ではログインできません（明示的に接続アプリをインストールする必要があります）
:::

実際にはこのURL生成はCIで使う場合がほとんどだと思うので、Github ActionsでこのURLを生成してプルリクエストにコメントを追加するステップをサンプルで記載しておきます。

```yaml
- name: Post Scratch Org URL
  env:
    PULL_REQUEST_NUMBER: ${{ github.event.pull_request.number }}
  run: |
    DEVHUB_ORG_ID=`sfdx force:org:display -u DevHubAlias --json | jq -r ".result.id"`
    SCRATCH_ORG_USERNAME=`sfdx force:user:display -u ScratchOrgAlias -v DevHubAlias --json | jq -r ".result.username"`
    BACKWINDOW_LOGIN_URL="https://backwindow.herokuapp.com/backwindow?hub=${DEVHUB_ORG_ID}&un=${SCRATCH_ORG_USERNAME}&ls=sandbox
    gh pr comment ${PULL_REQUEST_NUMBER} --body "Login to Scratch Org: ${BACKWINDOW_LOGIN_URL}"
```

こんな感じでプルリクエストのコメントにURLが追加されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/6495/dcdd5695-da2b-dbf5-8770-98048ed880db.png)


## まとめ

Backwindowは、複数メンバーでの開発時のSalesforce組織（特にCIで作成されたScratch組織）へのログイン共有をセキュアかつ簡単にします。Salesforce CLIで生成されるfrontdoor.jspのURLと違ってセッション情報が埋め込まれないため、不適切な共有による機密漏洩の可能性がありません。さらにURLにセッション期限がないため利用しやすく、かつアクセスの際にユーザ認証が適用されるので安全です。