dexidp/dexの最新版で、LDAPのBindに失敗する事例への対応

# はじめに

dexidp/dexを最新版にしてDockerコンテナをbuildしたところ、普通にgit clone & makeでビルドすることにも驚いたのですが、ldapサーバーへの接続(anonymous bind)に失敗するようになりました。

これはBindDN, BindPWを使用しないAnonymous Bindの場合にだけ遭遇する現象です。

```text:dexサーバーが出力したメッセージ
Internal Server Error

Login error: ldap: initial anonymous bind failed: LDAP Result Code 206 "Empty password not allowed by the client": ldap: empty password not allowed by the client
```

このメッセージは、go-ldapライブラリ([https://github.com/go-ldap/ldap/blob/master/error.go](https://github.com/go-ldap/ldap/blob/master/error.go))で定義されています。

dexのコードではEmpty Passwordに該当しそうな処理は以下のようになっています。

```go:dex/connector/ldap/ldap.goから抜粋
        // If bindDN and bindPW are empty this will default to an anonymous bind.
        if err := conn.Bind(c.BindDN, c.BindPW); err != nil {
                if c.BindDN == "" && c.BindPW == "" {
                        return fmt.Errorf("ldap: initial anonymous bind failed: %v", err)
                }
                return fmt.Errorf("ldap: initial bind for user %q failed: %v", c.BindDN, err)
        }
```

go-ldapライブラリ([https://github.com/go-ldap/ldap/blob/master/v3/bind.go](https://github.com/go-ldap/ldap/blob/master/v3/bind.go))では、パスワードを指定しない場合は、以下のようにBind()ではなく、UnauthenticatedBind()を呼ぶように記述されています。

```go:go-ldap/ldap/v3/bind.goから抜粋
// Bind performs a bind with the given username and password.
//
// It does not allow unauthenticated bind (i.e. empty password). Use the UnauthenticatedBind method
// for that.
func (l *Conn) Bind(username, password string) error {
	req := &SimpleBindRequest{
		Username:           username,
		Password:           password,
		AllowEmptyPassword: false,
	}
	_, err := l.SimpleBind(req)
	return err
}
```

LDAPのディレクトリ情報が外部ユーザーの認証などに利用する場合は、接続できるクライアントを限定するためにbindを利用することは理解できます。ただ、組織内部のユーザーを対象としたディレクトリで、情報が基本的に公開されている場合には、bindにあまり意味がない場合もあります。

# dexidp/dexへのパッチ作成

設定ファイルにanonymousBindを利用するかどうかbool変数を追加し、設定に応じてBind()、UnauthenticatedBind()を呼ぶように変更してみました。

```diff:ldap.goへのanonymousBind機能の追加
diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go
index d3bc20ab..9e61bc86 100644
--- a/connector/ldap/ldap.go
+++ b/connector/ldap/ldap.go
@@ -79,6 +79,9 @@ type Config struct {
        // protocol.
        StartTLS bool `json:"startTLS"`
 
+       // Use the UnauthenticatedBind()
+       AnonymousBind bool `json:"anonymousBind"`
+
        // Path to a trusted root certificate file.
        RootCA string `json:"rootCA"`
        // Path to a client cert file generated by rootCA.
@@ -331,11 +334,17 @@ func (c *ldapConnector) do(_ context.Context, f func(c *ldap.Conn) error) error
        defer conn.Close()
 
        // If bindDN and bindPW are empty this will default to an anonymous bind.
-       if err := conn.Bind(c.BindDN, c.BindPW); err != nil {
-               if c.BindDN == "" && c.BindPW == "" {
-                       return fmt.Errorf("ldap: initial anonymous bind failed: %v", err)
+       if c.AnonymousBind {
+               if err := conn.UnauthenticatedBind(""); err != nil {
+                       return fmt.Errorf("ldap: initial bind for user %q failed: %v", c.BindDN, err)
+               }
+       } else {
+               if err := conn.Bind(c.BindDN, c.BindPW); err != nil {
+                       if c.BindDN == "" && c.BindPW == "" {
+                               return fmt.Errorf("ldap: initial anonymous bind failed: %v", err)
+                       }
+                       return fmt.Errorf("ldap: initial bind for user %q failed: %v", c.BindDN, err)
                }
-               return fmt.Errorf("ldap: initial bind for user %q failed: %v", c.BindDN, err)
        }
 
        return f(conn)
```

これを反映して、config-ldap.yamlなどの設定ファイルに、```anonymousBind: true``` を設定しておきます。
Dockerコンテナのイメージを構築したところ無事に動作するようになりました。

# Anonymous Bindが行なえなくなった背景

問題なく動いていた頃のブランチを確認すると、利用していたライブラリが違うようです。

```golang:v2.19.0のldap.goの先頭部分
import (
        "context"
        "crypto/tls"
        "crypto/x509"
        "encoding/json"
        "fmt"
        "io/ioutil"
        "net"

        "gopkg.in/ldap.v2"

        "github.com/dexidp/dex/connector"
        "github.com/dexidp/dex/pkg/log"
)
```

ここで指定されているgopkg.in/ldpa.v2は、現在利用しているgo-ldapライブラリのv2.5.1ブランチが該当するようです。

* [https://github.com/go-ldap/ldap/tree/v2.5.1](https://github.com/go-ldap/ldap/tree/v2.5.1)

このライブラリのbind.goの実装では、anonymous bindとsimple bindを区別していないようにみえます。anonymous bindを特別に扱うようになった最新版のライブラリに変更しつつ、従来のコードをそのまま利用しているため、今回の不具合に遭遇することとなったと思われます。

ここら辺はどういうポリシーがあるのか良く分からないので、とりあえずPRを出して様子をみるのも良さそうかなと思っています。

> 【2021/04/30追記】Github上でコードをforkしてPRを出しました。([PR#2084](https://github.com/dexidp/dex/pull/2084)) 特に問題なければv2.29.0で取り込まれると思われます。

以上