SonarqubeのLDAPマルチ構成について

# 概要
現場で利用しているsonarqubeで2台のADサーバーを参照する設定に変更する必要があり、
その際の調査結果などをまとめました。

一部ログは現場のログなので貼れません。

この記事はそのメモやまとめです。
すでに設定済みの項目がいくつかある。
試行錯誤しながらやったので、手順として不要かもしれない。**あくまでメモ用。**

# sonarqube
sonarqubeはコード分析ツールです。  
こちらの紹介が1ページにまとまっていてわかりやすいと思いました。  

https://www.issoh.co.jp/column/details/2988/

私が所属しているチームでは利用しているというよりかは、利用するチームに払い出しているだけですので、実施に利用はしていません……。  
これをきっかけに一度自宅で構築してみるのもありだと思いました。

## 構成
ざっくりとですが、現場でのsonarqubeとLDAPの構成です。
### sonarqube
versions: sonarqube 9.9.0 community  
SonarQube deployed: Docker compose  
Docker compose AWSのEC2上で稼働  
ユーザー認証にはLDAPを利用している  
Docker composeはnginx → sonarqubeという順でアクセスする構成になっている

### LDAP
2台あり、1台目が正系、2台目が副系として利用している。  
現状は1台目のみを参照するように設定している。  
同じLDAP情報を持っている。  
LDAPは別チームが管理しており、設定の変更などはこちらで行うことができない。

## 調査
まずこちらのLDAP Multiple Serversの情報を基にコードを編集しました。  

https://docs.sonarsource.com/sonarqube/9.9/instance-administration/authentication/ldap/#multiple-servers  

この設定を行う上での注意事項は以下の通り、起動時点で設定しているServerすべてが疎通可能な状態で有るということです。  
一個でも起動時の疎通確認が出来ないとそもそもsonarqubeが起動しません。
```
Authentication will be tried on each server, in the order they are listed in the configurations until one succeeds.

Note that all the LDAP servers must be available while (re)starting the SonarQube server.

↓
認証は、設定に記載されている順番に、各サーバで成功するまで試行されます。
SonarQube サーバの(再)起動時には、すべての LDAP サーバが使用可能になっている必要があります。
```

記述したコードはほとんどサンプルと同じですが、違う点はURLが異なるだでそれ以外の接続設定は1台目も2台目も同じであるという点です。
```
# List the different servers
ldap.servers=server1,server2

# Configure server1
ldap.server1.url=ldap://server1:1389
ldap.server1.user.baseDn=dc=dept1,dc=com
...

# Configure server2
ldap.server2.url=ldap://server2:1389
ldap.server2.user.baseDn=dc=dept2,dc=com
...
```
この設定でsonarqubeを起動させ、1台目/2台目へ疎通確認がOKであることを確認します。OKだとこんな感じのログが出力されます。
```
2024.10.03 15:xx:xx INFO web[][o.s.a.l.LdapContextFactory] Test LDAP connection on ldap://xxxx.xxxx.xxxx.x: OK
2024.10.03 15:xx:xx INFO web[][o.s.a.l.LdapContextFactory] Test LDAP connection on ldap://yyyy.yyyy.yyyy.y: OK
```
この後LDAP1台目の設定をdocker composeが稼働しているInstanceで、iptablesの設定を変更し経路を遮断しました。  
docker composeのコンテナの外部への通信を遮断するには普通とは違った設定が必要でした。具体的には以下のような感じです。  
`xx.xx.xx.xx`は今回は1台目のLDAPサーバーになります。
```
# 現状の設定確認と遮断設定
sudo iptables -L
sudo iptables -I DOCKER-USER -d xx.xx.xx.xx -j DROP

# 設定解除
sudo iptables -L DOCKER-USER --line-numbers
sudo iptables -D DOCKER-USER 1
```

その状態でsonarqubeのWEBページにアクセスします。想定では2台目のLDAPサーバーの認証情報を利用してログインできるはずでした。  
しかしWEBページ1️分近く真っ白で最終的には504 errorが表示されました。  
ログを確認してみると裏では`1台目への認証開始　→　失敗　→2台目への認証開始　→　認証成功`が走っていました。
```
2024.10.03 15:20:16 DEBUG web[<hidden>][o.s.a.l.LdapContextFactory] Initializing LDAP context {java.naming.referral=follow, java.naming.security.principal=CN=****,OU=****,OU=****,DC=****,DC=****, com.sun.jndi.ldap.connect.pool=true, java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, java.naming.provider.url=ldap://xxxx.xxxx.xxxx.x, java.naming.security.authentication=simple}
2024.10.03 15:33:30 DEBUG web[<hidden>][o.s.a.l.DefaultLdapAuthenticator] User u12345 not found in server <default>: javax.naming.NamingException: LDAP connection has been closed; remaining name 'OU=****,OU=****,DC=****,DC=****'
2024.10.03 15:33:30 DEBUG web[<hidden>][o.s.a.l.LdapSearch] Search: LdapSearch{baseDn=OU=****,OU=****,DC=****,DC=****, scope=subtree, request=(&(objectClass=user)(sAMAccountName={0})), parameters=[u12345], attributes=null}
2024.10.03 15:33:16 DEBUG web[<hidden>][o.s.a.l.LdapContextFactory] Initializing LDAP context {java.naming.referral=follow, java.naming.security.principal=CN=****,OU=****,OU=****,DC=****,DC=****, com.sun.jndi.ldap.connect.pool=true, java.naming.factory.initial=com.sun.jndi.ldap.LdapCtxFactory, java.naming.provider.url=ldap://yyyy.yyyy.yyyy.y, java.naming.security.authentication=simple}
++++++++++
2024.10.03 15:33:16 DEBUG web[<hidden>][auth.event] login success[method|BASIC][provider|REALM|ldap][IP|****.****.****.****|****.****.****.****, ****.****.****.****][login|u12345] 
```
ログを見ると1台目への認証失敗と判定されるまで10分近くかかっており、その間にWEBページでは504を返してしまっているという状況です。  

ですので、sonarqube側でLDAPへの認証タイムアウト時間を短く出来ないか仕様の調査をしました。

## LDAP設定
LDAPを利用するためのプラグインは以下です。  

https://github.com/SonarSource/sonar-ldap  

しかし、このプラグインは
```
This plugin is compatible with SonarQube up to 7.9.X, and won't be compatible with the next SonarQube versions as it's now a built-in feature of SonarQube 8 and later.
↓
このプラグインは7.9.XまでのSonarQubeと互換性があり、SonarQube 8以降のSonarQubeのビルトイン機能であるため、次のSonarQubeのバージョンとは互換性がありません。
```
とあるように、sonarqubeに内包されているようです。  
sonarqubeの設定ができるsonar propertiesの設定などを見ましたがLDAP認証のタイムアウト関連の項目はなく、またググってもそれらしい情報は出てこなかったので思い切ってsonarqubeのコミュニティに聞いてみることにしました。

## コミュニティで質問
sonarqubeのコミュニティは結構開発者の方が返答してくれるようなので、  
思い切ってLDAP認証タイムアウトの設定がどこからか設定できないか聞いてみました。

https://community.sonarsource.com/t/sonarqube-not-switching-quickly-to-second-ldap-server/127588  

すぐさま解答がつき、一つの投稿のリンクが送られてきました。  
```
SonarQube’s LDAP support is not designed to connect to multiple LDAP servers in a failover mode. Instead, the feature is designed to allow companies with different LDAP servers hosting different users to allow users of both LDAPs to connect.

For security reasons, the user provisioned via LDAP server 2 must always connect with the same LDAP server. Otherwise, this would allow people to create a matching user on LDAP server 1 and potentially impersonate the account (assuming the 2 LDAP servers are owned.

For your scenario, a possibility would be to hide your two LDAP nodes behind a reverse proxy.
↓
SonarQubeのLDAPサポートは、フェイルオーバーモードで複数のLDAPサーバーに接続するようには設計されていません。この機能は、異なるユーザをホストする異なるLDAPサーバを持つ企業が、両方のLDAPのユーザを接続できるように設計されています。

セキュリティ上の理由から、LDAPサーバー2経由でプロビジョニングされたユーザーは、常に同じLDAPサーバーと接続しなければならない。そうでなければ、LDAPサーバー1に一致するユーザーを作成し、そのアカウントになりすますことが可能になります（2つのLDAPサーバーが所有されていると仮定して）。

あなたのシナリオでは、2つのLDAPノードをリバースプロキシの後ろに隠すことが考えられます。
```
要約すると、
- LDAPは異なる情報をもつLDAPサーバーの複数接続に対応しているが、LDAPサーバーのフェイルオーバーには対応していない
- もしそうしたかったらリバースプロキシなどLDAP側でなんとかしてくれ

とのことでした。  
つまり、今回の要件を達成したい場合、sonarqube側の設定変更ではどうすることもできないようです。  
その後利用しているnginxでのLDAPのリバースプロキシでなんとか出来ないか少し試してみましたが、nginxでは無理だったので、結局今回のsonarqube LDAP2台構成は実現不可とチームで結論付けました。  
（提示されたLDAPの投稿自体は質問前に確認し把握していましたが、公式からはっきりと提示され無理だよと言われた形です）

## 終わりに
初めてOSSのコミュニティに質問をしました。爆速で解答が返ってきたのでびっくりしましたが、質問をしたことがいい経験になりました。  
その後質問の閲覧数が凄まじい数になっていたので、きっと同じようなことをしたい人がたくさんいたのだと思います。  
（viewsでみると現時点で一番閲覧数が多い……）  
![001.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185852/205e8fbb-0012-9237-8b7a-2ad07517999b.png)