LDAPを手っ取り早く使うときのメモ

# はじめに

- spring-bootでLDAP認証したいと思ったが、実験環境が無かったのとLDAPの操作知識がなかったので勉強した。
- spring-bootでLDAP認証する部分については言及しない
- docker,docker-compose,docker-machineを利用しているがセットアップ方法は言及しない
- LDAPの用語であるところのDN,CN,DCなどについても言及しない

## 環境

- mac
- Docker version 18.03.0-ce, build 0520e24
- docker-compose version 1.20.1, build 5d8c71b

## docker-compose

- LDAP
    - port: 8389
- phpldapadmin
    - port: 8080
    - DN: cn=admin,dc=springframework,dc=org
    - PW: ldappw
- 参考
    - https://qiita.com/ir-shin1/items/12d0dbea40d1f1f06747


```docker-compose.yml
version: '2'

services:
  openldap:
    image: osixia/openldap:latest
    container_name: test1-ldap
    environment:
      LDAP_ORGANISATION: "springframework"
      LDAP_DOMAIN: "springframework.org"
      LDAP_ADMIN_PASSWORD: "ldappw"
    ports:
      - "8389:389"

  admin:
    image: osixia/phpldapadmin:latest
    container_name: test1-ldapadmin
    environment:
      PHPLDAPADMIN_LDAP_HOSTS: "ldap"
      PHPLDAPADMIN_HTTPS: "false"
    ports:
      - "8080:80"
    links:
      - "openldap:ldap"
```
```console
$ docker-compose up
```
### 補足

dockerコンテナのIPは下記で調べられる

```console
$ docker-machine env default
set -gx DOCKER_TLS_VERIFY "1";
set -gx DOCKER_HOST "tcp://192.168.99.100:2376";
set -gx DOCKER_CERT_PATH "/Users/positrium/.docker/machine/machines/default";
set -gx DOCKER_MACHINE_NAME "default";
# Run this command to configure your shell:
# eval (docker-machine env default)
```

## ldapコンテナに入る

```console
$ docker container ls
CONTAINER ID        IMAGE                        COMMAND                 CREATED             STATUS              PORTS                            NAMES
3b011e09d113        osixia/phpldapadmin:latest   "/container/tool/run"   21 hours ago        Up 21 hours         443/tcp, 0.0.0.0:8080->80/tcp    test1-ldapadmin
570508cd3d27        osixia/openldap:latest       "/container/tool/run"   21 hours ago        Up 21 hours         636/tcp, 0.0.0.0:8389->389/tcp   test1-ldap

$ docker exec -it test1-ldap bash
```

### 注意

```
# /etc/init.d/slapd restart
```
を行うと、slapdのstopは出来たがstartが出来なかった。（Dockerの特徴？イメージの問題？）


## ldapに適当なldifを読ませる

```console
$ ldapadd -x -D 'cn=admin,dc=springframework,dc=org' -W -f test-server.ldif
```

正常に入ったら中身を下記で確認できるが、userPasswordはBase64.encode()されたものが出てくる。

```console
$ slapcat
```

- 下記、コメントアウト部分はコンテナによって自動作成されている部分
- 他のエントリは[spring-security](https://github.com/spring-projects/spring-security/blob/master/ldap/src/integration-test/resources/test-server.ldif)から
    - 小話： benのuserPasswordのrawは`benspassword`の筈だが、[sha1Hex](http://hashtoolkit.com/generate-hash/?text=benspassword)すると`9c509e6d68f17da2db1c71b5424e54538b6b6ef4`なので、実は`benspassword`ではない気がする。

```test-server.ldif
# dn: dc=springframework,dc=org
# objectClass: top
# objectClass: dcObject
# objectClass: organization
# o: springframework
# dc: springframework
# 
# dn: cn=admin,dc=springframework,dc=org
# objectClass: simpleSecurityObject
# objectClass: organizationalRole
# cn: admin
# description: LDAP administrator
# userPassword:: {SSHA}uKmKC5S1n62OuXyikGfXylLLNiTkFMt7

dn: ou=people,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: people

dn: ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: organizationalUnit
ou: otherpeople

dn: uid=ben,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Ben Alex
sn: Alex
uid: ben
userPassword: {SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=

dn: uid=bob,ou=people,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword: bobspassword

dn: uid=joe,ou=otherpeople,dc=springframework,dc=org
objectclass: top
objectclass: person
objectclass: organizationalPerson
objectclass: inetOrgPerson
cn: Joe Smeth
sn: Smeth
uid: joe
userPassword: joespassword
```

## ldapsearch

### uid=bobエントリを検索

```console
$ ldapsearch -x -D 'cn=admin,dc=springframework,dc=org' -W -LLL -b 'ou=people,dc=springframework,dc=org' '(uid=bob)'
Enter LDAP Password:
dn: uid=bob,ou=people,dc=springframework,dc=org
objectClass: top
objectClass: person
objectClass: organizationalPerson
objectClass: inetOrgPerson
cn: Bob Hamilton
sn: Hamilton
uid: bob
userPassword:: Ym9ic3Bhc3N3b3Jk
```

- ldif形式で出力される
- ldif形式での`::`(userPassword:: Ym9ic3Bhc3N3b3Jk など)はバイナリ値を表す
- userPasswordは、Base64.encode()されており`Ym9ic3Bhc3N3b3Jk`だが、デコードすると平文で`bobspassword`が入っている。

## ldapcompare

### uid=benの存在有無比較

```console
$ ldapcompare -x -D 'cn=admin,dc=springframework,dc=org' -W 'uid=ben,ou=people,dc=springframework,dc=org' 'uid: ben'
Enter LDAP Password: [冒頭のphpldapadminのPWを入力]
TRUE
```

#### java code sample

```xml
<!-- https://mvnrepository.com/artifact/com.unboundid/unboundid-ldapsdk -->
<dependency>
  <groupId>com.unboundid</groupId>
  <artifactId>unboundid-ldapsdk</artifactId>
  <version>4.0.5</version>
</dependency>
```
```java
import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPAttribute;
import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPConnection;
import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPException;

public class Fuga {
    public static void main(String[] args) {
        Fuga fuga = new Fuga();
        try {
            fuga.go();
        } catch (LDAPException e) {
            e.printStackTrace();
        }
    }

    public void go() throws LDAPException {
        String address = "192.168.99.100";
        Integer port = 8389;
        String dn = "cn=admin,dc=springframework,dc=org";
        String pass = "ldappw";

        LDAPConnection c = new LDAPConnection();
        c.connect(address, port.intValue(), dn, pass);

        String user_dn = "uid=ben,ou=people,dc=springframework,dc=org";
        LDAPAttribute ldapAttribute = new LDAPAttribute("uid", "ben");

        if (c.compare(user_dn, ldapAttribute)) {
            System.out.println("OK");
        } else {
            System.out.println("NG");
        }
    }
}
```
OKとでる。

### uid=bobの平文パスワード比較

```console
$ ldapcompare -x -D 'cn=admin,dc=springframework,dc=org' -W 'uid=bob,ou=people,dc=springframework,dc=org' 'userPassword::Ym9ic3Bhc3N3b3Jk'
Enter LDAP Password:
TRUE
```

- uid=bobのuserPasswordはbase64.encode()して保存されているので、base64.encode()された文字列を渡す。
- ldifで`::`はバイナリ値を表すのと同じく、ldapcompareでも同様に`::`で値を指定する。

#### java code sample

```java
import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPAttribute;
import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPConnection;
import com.unboundid.ldap.sdk.migrate.ldapjdk.LDAPException;

public class Fuga {
    public static void main(String[] args) {
        Fuga fuga = new Fuga();
        try {
            fuga.go();
        } catch (LDAPException e) {
            e.printStackTrace();
        }
    }

    public void go() throws LDAPException {
        String address = "192.168.99.100";
        Integer port = 8389;
        String dn = "cn=admin,dc=springframework,dc=org";
        String pass = "ldappw";

        LDAPConnection c = new LDAPConnection();
        c.connect(address, port.intValue(), dn, pass);

        String user_dn = "uid=bob,ou=people,dc=springframework,dc=org";
        LDAPAttribute ldapAttribute = new LDAPAttribute("userPassword", "bobspassword");

        if (c.compare(user_dn, ldapAttribute)) {
            System.out.println("OK");
        } else {
            System.out.println("NG");
        }
    }
}
```
OKとでた。勝手にBase64してくれたらしい。

### uid=benのSHAパスワード比較

```console
$ ldapcompare -x -D 'cn=admin,dc=springframework,dc=org' -W 'uid=ben,ou=people,dc=springframework,dc=org' 'userPassword::e1NIQX1uRkNlYldqeGZhTGJISEcxUWs1VVU0dHJidlE9'
Enter LDAP Password:
TRUE
```

- userPasswordの指定に関しては前述同様
- java sample codeについては、 [小話](https://qiita.com/positrium/items/a139c8517bd1b3bc66a8#ldap%E3%81%AB%E9%81%A9%E5%BD%93%E3%81%AAldif%E3%82%92%E8%AA%AD%E3%81%BE%E3%81%9B%E3%82%8B)参照で、現状、uid=benのuserPasswordのrawが不明なので書き換えてやらないといけない。書き換えずとも、`{SHA}nFCebWjxfaLbHHG1Qk5UU4trbvQ=`を入れてやればいい筈だが、通常のパスワード入力値を想定したコードを書いているつもりなので、一手間あって面倒なので検証してない。

### その他の暗号形式の場合

未検証だが、平文、SHA同様にBase64.encode()したバイナリ値を入力すればTRUEが返るはず。

## 参考図書

- [入門LDAP/OpenLDAP ディレクトリサービス導入・運用ガイド 第3版](https://amzn.to/2uK1GGu)