DockerでopenLDAPコンテナを構築する

debian8のベースイメージをベースにopenLDAPコンテナを構築する手順をまとめました。

##openLDAP（オープン・エルダップ）とは

**LDAP（Lightweight Directory Access Protocol, エルダップ）**

- ディレクトリデータベース（アカウント管理に特化した専用データベース）へアクセスするためのプロトコル

<sub>
[LDAPとは何をするもの？ | Think IT（シンクイット）](https://thinkit.co.jp/free/tech/18/1/1.html)
</sub>

**openLDAP（オープン・エルダップ）**

- オープンソースのLDAP（Lightweight Directory Access Protocol）サーバ
- SSO（シングルサインオン）の認証基盤となり、redmineやGitLabなどのサービスを一つのアカウントでまとめて管理することができます。
- ライセンス：OpenLDAP Public License（独自BSDライセンス）

<sub>[OpenLDAP, Public License for 2.4.45](http://www.openldap.org/software/release/license.html)</sub>



## DockerでopenLDAPコンテナを作成する

###ディレクトリ構成

```
.
├── Dockerfile
├── config
│   └── custom_inetorgperson.ldif
├── docker-compose.yml
└── entrypoint.sh
```

**custom_inetorgperson.ldif**  
独自スキーマを定義したLDAP設定ファイル

**entrypoint.sh**  
パッケージをインストールするときにコマンド入力が必要となるインタラクティブな項目（管理者パスワード等）を自動設定するためのラッパー・スクリプト

### DockerFile

```
FROM debian:8
ENV DEBIAN_FRONTEND noninteractive
RUN apt-get update && apt-get install -y \
        slapd=2.4.40+dfsg-1+deb8u3 \
        ldap-utils=2.4.40+dfsg-1+deb8u3 && \
    apt-get clean && \
    rm -rf /var/lib/apt/lists/*
COPY ./config/custom_inetorgperson.ldif /etc/ldap/schema/
COPY ./entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
```

**ENV DEBIAN_FRONTEND noninteractive**

```ENV DEBIAN_FRONTEND```はインストーラのユーザーインターフェースを制御する環境変数です。slapdパッケージはインストールの際にインタラクティブな回答が必要となるため、 
```DEBIAN_FRONTEND=noninteractive```と設定することで、インタラクティブな設定をしなくなり（＝入力待ちでブロックしなくなる）、自動インストールができます。

[5.3. ブートパラメータ](https://www.debian.org/releases/jessie/armel/ch05s03.html.ja#ftn.idp43410400)
[DEBIAN_FRONTEND=noninteractive ってなんだ - Qiita](http://qiita.com/udzura/items/576c2c782adb241070bc)

**RUN apt-get update && apt-get install**

apt-getの記述はDocker公式ドキュメントのベストプラクティスで推奨された記法に従います。

> RUN apt-get update && apt-get install -y を使うことで、最新バージョンのパッケージを、追加の記述や手動作業なく利用できます。  
バージョンを指定すると、何がキャッシュされているか気にせずに、特定バージョンを取得した上での構築を強制します。このテクニックは、必要なパッケージの予期しない変更によって引き起こされる失敗を減らします。  
apt キャッシュをクリーンにし、 /var/lib/apt/lits を削除することで、イメージのサイズを減らします。 RUN 命令は apt-get update から開始されるので、 apt-get install でインストールされるパッケージは、常に新鮮なものです。

[Dockerfile のベストプラクティス — Docker-docs-ja 1.9.0b ドキュメント](http://docs.docker.jp/engine/articles/dockerfile_best-practice.html)

**slapd**

OpenLDAPサーバのパッケージ。
[Debian -- stretch の slapd パッケージに関する詳細](https://packages.debian.org/stretch/slapd)

**ldap-utils**

ローカルまたはリモートの LDAP サーバにアクセスできます。LDAP サーバへのアクセスに必要なクライアントプログラムのすべてが含まれます。

[Debian -- jessie の ldap-utils パッケージに関する詳細](https://packages.debian.org/ja/jessie/ldap-utils)

### entrypoint.sh

```

echo "slapd slapd/internal/adminpw password ${LDAP_ADMIN_PASSWORD}" | debconf-set-selections
echo "slapd slapd/internal/generated_adminpw password ${LDAP_ADMIN_PASSWORD}" | debconf-set-selections
echo "slapd slapd/password1 password ${LDAP_ADMIN_PASSWORD}" | debconf-set-selections
echo "slapd slapd/password2 password ${LDAP_ADMIN_PASSWORD}" | debconf-set-selections
echo "slapd slapd/domain string ${LDAP_DOMAIN}" | debconf-set-selections
echo "slapd shared/organization string ${LDAP_ORGANISATION}" | debconf-set-selections
dpkg-reconfigure -f noninteractive slapd
service slapd start

#add custom schema
ldapadd -Y EXTERNAL -H ldapi:// -f /etc/ldap/schema/custom_inetorgperson.ldif

/bin/bash
```

**entrypoint.shに実行権限を与える**

```
$ chmod +x entrypoint.sh 
```

```
$ ls -alt entrypoint.sh 
-rwxrwxr-x. 1 centos centos 710  7月 30 08:36 entrypoint.sh
```

##### debconf-set-selections

- debconf-set-selectionsはパッケージをインストールするときにインタラクティブに設定する項目を事前に設定するコマンドです。
- debconf-set-selectionsはdebconfパッケージに含まれており、 **debconfパッケージはDebianのデフォルトでインストールされています**。

```
# dpkg -S /usr/bin/debconf-set-selections
debconf: /usr/bin/debconf-set-selections
```

### docker-compose.yml

```
version: '3'
services:
  web:
    build: .
    tty: true
    container_name: openldap
    environment:
      - LDAP_ADMIN_PASSWORD=admin
      - LDAP_DOMAIN=example.org
      - LDAP_ORGANISATION=Example Inc.
    networks:
      - ldap_nw
    ports:
      - "389:389"
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    volumes:
      - '/srv/ldap:/var/lib/ldap'
networks:
  ldap_nw:
    external: true
```

**tty**  
コンテナ内の標準入出力をDockerホストのコンソールにアタッチするための設定です。

**logging**  
ログファイルのサイズと世代管理を指定しないと、ログが溜まりつづけてホスト側のディスクを圧迫する恐れがあるので必ず設定します。

[Compose file version 3 reference | Docker Documentation](https://docs.docker.com/compose/compose-file/#logging)
[docker logs で表示されるログの保存場所とローテート方法 - Qiita](http://qiita.com/tily/items/adb433505da6c7812725)

**networks**  
以下のコマンドを入力し、OpenLDAPコンテナと連携するその他のコンテナ（phpLDAPadmin, GitLab, redmineなど）をUNIXソケットで通信できるようにするためのコンテナ・ネットワーク（ブリッジ）を作成します。

[network create — Docker-docs-ja 1.13.RC ドキュメント](http://docs.docker.jp/engine/reference/commandline/network_create.html)  
Docker公式によるコマンドラインリファレンス（日本語版）



```
$ docker network create --driver bridge ldap_nw
```




コンテナ・ネットワークを作成することで、同一ネットワーク上のコンテナに **コンテナ名でアクセスすることができる** ようになります。 ネットワーク名は任意ですが、ここでは```ldap_nw```としました。

作成したコンテナ・ネットワークは以下のコマンドで確認できます。

```
$ docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
45d08214f6c2        ldap_nw             bridge              local
```

 [Compose のネットワーク機能 — Docker-docs-ja 1.13.RC ドキュメント](http://docs.docker.jp/compose/networking.html)
 [docker-compose で別の docker-compose.yml で作ったコンテナとリンクする (ネットワークを繋げる) - Qiita](http://qiita.com/reneice/items/20e981062b093264cd0a)

以下のコマンドを入力し、コンテナを生成します。

```
$ docker-compose up -d
```

以下のコマンドを入力し、コンテナが起動しているか確認します。

```
$ docker ps
CONTAINER ID        IMAGE                     COMMAND                  CREATED             STATUS                  PORTS                                                           NAMES
375e6ed11be8        openldap_web              "/entrypoint.sh"         44 minutes ago      Up 44 minutes           0.0.0.0:389->389/tcp                                            openldap
```

## 動作確認をする

openldapコンテナにログインします。

```
$ docker exec -it openldap bash
```

データベースの中身を確認します。

<pre>
# slapcat
<span style="color:#d14">dn: dc=example,dc=org</span>
objectClass: top
objectClass: dcObject
objectClass: organization
<span style="color:#d14">o: Example Inc.</span>
<span style="color:#d14">dc: example</span>
structuralObjectClass: organization
entryUUID: 04a6e1de-0918-1037-9448-af08113323d1
<span style="color:#d14">creatorsName: cn=admin,dc=example,dc=org</span>
createTimestamp: 20170730021035Z
entryCSN: 20170730021035.599610Z#000000#000#000000
<span style="color:#d14">modifiersName: cn=admin,dc=example,dc=org</span>
modifyTimestamp: 20170730021035Z

<span style="color:#d14">dn: cn=admin,dc=example,dc=org</span>
objectClass: simpleSecurityObject
objectClass: organizationalRole
<span style="color:#d14">cn: admin</span>
description: LDAP administrator
userPassword:: e1NTSEF9K1JCRG1NT0Y3UGEyNWdPZ25hQVR2N3ZmVy9mU0pwN3c=
structuralObjectClass: organizationalRole
entryUUID: 04a8fb72-0918-1037-9449-af08113323d1
<span style="color:#d14">creatorsName: cn=admin,dc=example,dc=org</span>
createTimestamp: 20170730021035Z
entryCSN: 20170730021035.613439Z#000000#000#000000
<span style="color:#d14">modifiersName: cn=admin,dc=example,dc=org</span>
modifyTimestamp: 20170730021035Z
</pre>

```
# ldapsearch -x -D "cn=admin,dc=example,dc=org" -w "admin"
# extended LDIF
#
# LDAPv3
# base <> (default) with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# search result
search: 2
result: 32 No such object

# numResponses: 1
```
- -x  SASL の代わりに簡易認証を使う
- -D  LDAP ディレクトリにバインドする識別名 binddn を指定
- -w  簡易認証のためのパスワードを指定

スキーマの確認

<pre>
# ldapsearch -Q -LLL -Y EXTERNAL -H ldapi:/// -b cn=config dn
dn: cn=config

dn: cn=module{0},cn=config

dn: cn=schema,cn=config

dn: cn={0}core,cn=schema,cn=config

dn: cn={1}cosine,cn=schema,cn=config

dn: cn={2}nis,cn=schema,cn=config

dn: cn={3}inetorgperson,cn=schema,cn=config

<span style="color:#d14">dn: cn={4}custom_inetorgperson,cn=schema,cn=config</span>

dn: olcBackend={0}mdb,cn=config

dn: olcDatabase={-1}frontend,cn=config

dn: olcDatabase={0}config,cn=config

dn: olcDatabase={1}mdb,cn=config

</pre>


## 付録：Dockerで使用した設定ファイルについての補足

### entrypoint.sh

entrypoint.shで```debconf-set-selections```を使用して設定した```slapd slapd/internal/adminpw password```などの項目ですが、ウェブで検索してもなかなかヒットしません。設定値を調べるにあたって、以下のコマンドを使用しました。

**debconf-get-selections**

debconf-get-selectionsはパッケージをインストールするときにインタラクティブに設定する項目を調べるコマンドです。debconf-get-selectionsはdebconf-utilsパッケージに含まれていますが、 **debconf-utilsパッケージはDebianのデフォルトではインストールされていない**  ため、別途インストールする必要があります。


```
# dpkg -S /usr/bin/debconf-get-selections
debconf-utils: /usr/bin/debconf-get-selections
```

debconf-get-selectionsコマンドを使うことで、以下のように
slapdをインストールするときにインタラクティブに設定できる項目を調べることができます。今回作成したentrypoint.shではopenLDAPを構築する上で最低限必要となる下記の赤字の項目を設定しました。

<pre>
# debconf-get-selections | grep slapd
<span style="color:#d14">slapd slapd/internal/generated_adminpw  password</span>
<span style="color:#d14">slapd slapd/password1 password</span>
<span style="color:#d14">slapd slapd/internal/adminpw  password</span>
<span style="color:#d14">slapd slapd/password2 password</span>
# Do you want the database to be removed when slapd is purged?
slapd slapd/purge_database  boolean false
slapd slapd/backend select  MDB
slapd slapd/no_configuration  boolean false
slapd slapd/dump_database_destdir string  /var/backups/slapd-VERSION
slapd slapd/dump_database select  when needed
slapd slapd/upgrade_slapcat_failure error 
<span style="color:#d14">slapd slapd/domain  string  nodomain</span>
slapd slapd/allow_ldap_v2 boolean false
<span style="color:#d14">slapd shared/organization string  nodomain</span>
slapd slapd/invalid_config  boolean true
slapd slapd/password_mismatch note  
# Potentially unsafe slapd access control configuration
slapd slapd/unsafe_selfwrite_acl  note  
slapd slapd/move_old_database boolean true
</pre>

<sub>
[Debconf-utilsを使ってインストール時の質問に自動で答える - Qiita](http://qiita.com/t_cyrill/items/634f813e955d73da3162)
</sub>

### custom_inetorgperson.ldif
デフォルトのinetOrgPersonクラスでは```sn（Sir Name：姓）```と```cn（Common Name：名）```のみが必須入力項目に定義されています。そこでinetOrgPersonクラスで既に定義されている```userPassword```と```mail```を新たに必須入力項目とするLDAP設定ファイル（ldifファイル）を作成します。

> LDIFとは LDIF(LDAP Data Interchange Format)は、ディレクトリのエントリをテキスト形式で記述するための標準の書式で、RFC2849で定義されています。 リポジトリにエントリを追加したり、リポジトリ内のエントリ情報を変更したりするために使用します。

[4.2.2.1 LDIFとは](http://software.fujitsu.com/jp/manual/manualfiles/M050008/B1WN5851/01Z200/irep04/irep0128.htm)

**スキーマ定義を作成する**

任意のディレクトリにcustom_inetorgperson.schemaを作成します。ここではinetOrgPersonを継承し、userPasswordとmailを新たに入力が必須な項目として設定しています。

<sub>/etc/ldap/schema/custom_inetorgperson.schema</sub>

```
objectclass     ( 1.1.2.2.2
    NAME 'customInetOrgPerson'
        DESC 'Custom Internet Organizational Person'
    SUP ( inetOrgPerson )
    STRUCTURAL
        MUST (
                givenName $ userPassword $ mail )
)
```

任意のディレクトリにinclude.confを作成します。

<sub>/etc/ldap/include.conf</sub>

```
include /etc/ldap/schema/core.schema
include /etc/ldap/schema/cosine.schema
include /etc/ldap/schema/nis.schema
include /etc/ldap/schema/inetorgperson.schema
include /etc/ldap/schema/custom_inetorgperson.schema
```

以下のコマンドを入力し、ldifファイルを作成します。

```
etc/ldap# slaptest -f /etc/ldap/include.conf -F /etc/ldap/slapd.d
config file testing succeeded
```

成功していれば、以下のように新たにldifファイルが作成されているはずです。

<pre>
etc/ldap/slapd.d# tree
.
|-- cn=config
|   |-- cn=module{0}.ldif
|   |-- cn=schema
|   |   |-- cn={0}core.ldif
|   |   |-- cn={1}cosine.ldif
|   |   |-- cn={2}nis.ldif
|   |   |-- cn={3}inetorgperson.ldif
|   |   `-- <span style="color:#d14">cn={4}custom_inetorgperson.ldif</span>
|   |-- cn=schema.ldif
|   |-- olcBackend={0}mdb.ldif
|   |-- olcDatabase={-1}frontend.ldif
|   |-- olcDatabase={0}config.ldif
|   `-- olcDatabase={1}mdb.ldif
`-- cn=config.ldif
</pre>

cn={4}custom_inetorgperson.ldifを以下のように書き換えます。

<sub>cn={4}custom_inetorgperson.ldif（修正後）</sub>

```
# AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
# CRC32 29881081
dn: cn=custom_inetorgperson,cn=schema,cn=config
objectClass: olcSchemaConfig
cn: custom_inetorgperson
olcObjectClasses: {0}( 1.1.2.2.2 NAME 'customInetOrgPerson' DESC 'Custom Int
 ernet Organizational Person' SUP inetOrgPerson STRUCTURAL MUST ( givenName
 $ userPassword $ mail ) )
```

<sub>cn={4}custom_inetorgperson.ldif.origin（修正前）とcn={4}custom_inetorgperson.ldif（修正後）の差分</sub>

```
# diff -u cn\=\{4\}custom_inetorgperson.ldif.origin cn\=\{4\}custom_inetorgperson.ldif
--- cn={4}custom_inetorgperson.ldif.origin  2017-07-30 10:08:05.024987637 +0000
+++ cn={4}custom_inetorgperson.ldif 2017-07-30 10:10:16.848343049 +0000
@@ -1,15 +1,8 @@
 # AUTO-GENERATED FILE - DO NOT EDIT!! Use ldapmodify.
 # CRC32 29881081
-dn: cn={4}custom_inetorgperson
+dn: cn=custom_inetorgperson,cn=schema,cn=config
 objectClass: olcSchemaConfig
-cn: {4}custom_inetorgperson
+cn: custom_inetorgperson
 olcObjectClasses: {0}( 1.1.2.2.2 NAME 'customInetOrgPerson' DESC 'Custom Int
  ernet Organizational Person' SUP inetOrgPerson STRUCTURAL MUST ( givenName 
  $ userPassword $ mail ) )
-structuralObjectClass: olcSchemaConfig
-entryUUID: 8a096328-095a-1037-9504-4b6d37bb3025
-creatorsName: cn=config
-createTimestamp: 20170730100646Z
-entryCSN: 20170730100646.165408Z#000000#000#000000
-modifiersName: cn=config
-modifyTimestamp: 20170730100646Z
```

上記の手順で作成したcn={4}custom_inetorgperson.ldifをcustom_inetorgperson.ldifとしてDockerでコンテナを作成する際に使用しました。

## 参考文献

- [OpenLDAPの設定（IdPv3） - GakuNinShibInstall - meatwiki](https://meatwiki.nii.ac.jp/confluence/pages/viewpage.action?pageId=20021639)
- [OpenLDAP 2.2 Administrator's Guide: Schema Specification](http://www.openldap.org/doc/admin22/schema.html)
- [CentOS6 OpenLDAPのスキーマ管理](http://www.unix-power.net/linux/openldap_schema.html)
- [OpenLDAPサーバーで、属性をスキーマに追加 - Qiita](http://qiita.com/gigatune/items/969b6b0015e09de930a8)
- [OpenLDAP再構築 - 備忘録](http://himajin.ddo.jp/blog/archives/174)
- [LDAPでのオブジェクト・クラスの管理 - Oracle Directory Server Enterprise Edition管理ガイド](https://docs.oracle.com/cd/E35622_01/html/b66720/bcati.html)
- [Docker: DockerfileでExpectスクリプトを使うと複雑になる例 - Narrow Escape](https://www.hiroom2.com/2017/02/24/docker-dockerfile%E3%81%A7expect%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%82%92%E4%BD%BF%E3%81%86%E3%81%A8%E8%A4%87%E9%9B%91%E3%81%AB%E3%81%AA%E3%82%8B%E4%BE%8B/)
- [Linuxの対話がめんどくさい?そんな時こそ自動化だ！-expect編- - Qiita](http://qiita.com/ine1127/items/cd6bc91174635016db9b)
- [Mistoreaa Blog [Shell]対話的シェルを自動実行させる方法](http://mistoreaa.blog.fc2.com/blog-entry-99.html)
- [linux - How to do an initial setup of slapd OLC with ldapmodify - Stack Overflow](https://stackoverflow.com/questions/28937889/how-to-do-an-initial-setup-of-slapd-olc-with-ldapmodify)
- [[SOLVED] Script install slapd with admin ldap password](https://ubuntuforums.org/showthread.php?t=2054067)
- [docker-openldap/startup.sh at stable · osixia/docker-openldap](https://github.com/osixia/docker-openldap/blob/stable/image/service/slapd/startup.sh)
- [CentOS 7 : OpenLDAP : LDAPサーバーの設定 ： Server World](https://www.server-world.info/query?os=CentOS_7&p=openldap)
- [OpenLDAP サーバ](https://help.ubuntu.com/lts/serverguide/openldap-server.html#openldap-server-installation)
- [ldapsearch コマンド - IBM Knowledge Center](https://www.ibm.com/support/knowledgecenter/ja/9117-MMC/p7hcg/ldapsearch.htm)
- [ldapsearch の使用例 - IBM Knowledge Center](https://www.ibm.com/support/knowledgecenter/ja/SSKTMJ_8.5.3/com.ibm.help.domino.admin85.doc/H_EXAMPLES_EXPORTING_THE_CONTENTS_OF_AN_LDAP_DIRECTORY_2804_STEPS.html)
- [【LDAP環境構築:6】 コマンドまとめ - Qiita](http://qiita.com/mykysyk@github/items/5496ad57ac589119ff54)
- [LDAP - もわの書斎](https://mowa-net.jp/wiki/LDAP)