AlmaLinux 9 で 389 Directory Server を構築してみる

自分用の備忘録です。

学習のために Virtual Box 上の AlmaLinux 9 に、LDAP サーバである 389 Directory Server (389ds) を構築します。

## 環境

### 物理マシン

この記事の本質とはあまり関係ありませんが、一部 Windows 側での操作があるので念のために記載します。

- Windows 10 上の Oracle VM VirtualBox


### 仮想マシン1 (LDAPサーバにするホスト)

389 Directory Server を構築する、今回のメインとなるホストです。

- OS: AlmaLinux9
    - 「最小インストール」でインストール済み
- ユーザ : se (管理者)
    - 基本的には `sudo -s` で root になった状態で操作します
    - ほかにユーザがあってもよいですが使用しません
- 仮想ネットワーク設定: NAT, ホストオンリーアダプタ
    - インターネット接続と、物理マシン・他の仮想マシンのホストとの通信が可能であればよい

### 仮想マシン2~ (LDAPサーバを利用するホスト)

今回、ここまで扱いません。将来、このホストへの sshd ログインの認証を LDAP でできるようにしてみることを考えています。


## 構成の計画

この記事の手順で、下記のように構築します

### 389ds の設定値の計画

ここでは次のように設定します。

- **インスタンス名**: ds01 (1つのみ)
- **ベースドメイン**: dc=example,dc=com
    - 通常は保有しているドメインを使用すると思います
- **(389ds の) root ユーザ**: cn=Manager
- **(389ds の) root ユーザのパスワード**: password

### Windows 側の GUI LDAP クライアント

LDAP に登録されているデータをわかりやすくするために、GUI クライアントツールである [Apache Directory Studio](https://directory.apache.org/studio/downloads.html) を導入します。

## 1. 389ds のインストール

- 参考
    - [あぱーブログ](https://blog.apar.jp/linux/15731/) (CentOS 8 の事例のため、適宜読み替える)

`dnf` でインストールできます。

```console
# dnf install 389-ds-base

（略）
アップグレード済み:
  audit-3.1.5-4.el9.x86_64               audit-libs-3.1.5-4.el9.x86_64        libdb-5.3.28-55.el9.x86_64
  libselinux-3.6-3.el9.x86_64            libselinux-utils-3.6-3.el9.x86_64    libsemanage-3.6-5.el9_6.x86_64
  openldap-2.6.8-4.el9.x86_64            openssl-1:3.2.2-6.el9_5.1.x86_64     openssl-libs-1:3.2.2-6.el9_5.1.x86_64
  python3-libselinux-3.6-3.el9.x86_64
インストール済み:
  389-ds-base-2.6.1-6.el9_6.x86_64                          389-ds-base-libs-2.6.1-6.el9_6.x86_64
  bash-completion-1:2.11-5.el9.noarch                       checkpolicy-3.6-1.el9.x86_64
  cyrus-sasl-gssapi-2.1.27-21.el9.x86_64                    cyrus-sasl-md5-2.1.27-21.el9.x86_64
  cyrus-sasl-plain-2.1.27-21.el9.x86_64                     libdb-utils-5.3.28-55.el9.x86_64
  libicu-67.1-9.el9.x86_64                                  libpkgconf-1.7.3-10.el9.x86_64
  libtool-ltdl-2.4.6-46.el9.x86_64                          nspr-4.35.0-17.el9_2.x86_64
  nss-3.101.0-10.el9_2.x86_64                               nss-softokn-3.101.0-10.el9_2.x86_64
  nss-softokn-freebl-3.101.0-10.el9_2.x86_64                nss-sysinit-3.101.0-10.el9_2.x86_64
  nss-tools-3.101.0-10.el9_2.x86_64                         nss-util-3.101.0-10.el9_2.x86_64
  openldap-clients-2.6.8-4.el9.x86_64                       openssl-perl-1:3.2.2-6.el9_5.1.x86_64
  perl-Algorithm-Diff-1.2010-4.el9.noarch                   perl-Archive-Tar-2.38-6.el9.noarch
  perl-AutoLoader-5.74-481.el9.noarch                       perl-B-1.80-481.el9.x86_64
  perl-Carp-1.50-460.el9.noarch                             perl-Class-Struct-0.66-481.el9.noarch
  perl-Compress-Raw-Bzip2-2.101-5.el9.x86_64                perl-Compress-Raw-Lzma-2.101-3.el9.x86_64
  perl-Compress-Raw-Zlib-2.101-5.el9.x86_64                 perl-DB_File-1.855-4.el9.x86_64
  perl-Data-Dumper-2.174-462.el9.x86_64                     perl-Devel-Peek-1.28-481.el9.x86_64
  perl-Digest-1.19-4.el9.noarch                             perl-Digest-MD5-2.58-4.el9.x86_64
  perl-Encode-4:3.08-462.el9.x86_64                         perl-Errno-1.30-481.el9.x86_64
  perl-Exporter-5.74-461.el9.noarch                         perl-Fcntl-1.13-481.el9.x86_64
  perl-File-Basename-2.85-481.el9.noarch                    perl-File-Find-1.37-481.el9.noarch
  perl-File-Path-2.18-4.el9.noarch                          perl-File-Temp-1:0.231.100-4.el9.noarch
  perl-File-stat-1.09-481.el9.noarch                        perl-FileHandle-2.03-481.el9.noarch
  perl-Getopt-Long-1:2.52-4.el9.noarch                      perl-Getopt-Std-1.12-481.el9.noarch
  perl-HTTP-Tiny-0.076-462.el9.noarch                       perl-IO-1.43-481.el9.x86_64
  perl-IO-Compress-2.102-4.el9.noarch                       perl-IO-Compress-Lzma-2.101-4.el9.noarch
  perl-IO-Socket-IP-0.41-5.el9.noarch                       perl-IO-Socket-SSL-2.073-2.el9.noarch
  perl-IO-Zlib-1:1.11-4.el9.noarch                          perl-IPC-Open3-1.21-481.el9.noarch
  perl-MIME-Base64-3.16-4.el9.x86_64                        perl-Mozilla-CA-20200520-6.el9.noarch
  perl-NDBM_File-1.15-481.el9.x86_64                        perl-Net-SSLeay-1.94-1.el9.x86_64
  perl-POSIX-1.94-481.el9.x86_64                            perl-PathTools-3.78-461.el9.x86_64
  perl-Pod-Escapes-1:1.07-460.el9.noarch                    perl-Pod-Perldoc-3.28.01-461.el9.noarch
  perl-Pod-Simple-1:3.42-4.el9.noarch                       perl-Pod-Usage-4:2.01-4.el9.noarch
  perl-Scalar-List-Utils-4:1.56-462.el9.x86_64              perl-SelectSaver-1.02-481.el9.noarch
  perl-Socket-4:2.031-4.el9.x86_64                          perl-Storable-1:3.21-460.el9.x86_64
  perl-Symbol-1.08-481.el9.noarch                           perl-Term-ANSIColor-5.01-461.el9.noarch
  perl-Term-Cap-1.17-460.el9.noarch                         perl-Term-ReadLine-1.17-481.el9.noarch
  perl-Text-Diff-1.45-13.el9.noarch                         perl-Text-ParseWords-3.30-460.el9.noarch
  perl-Text-Tabs+Wrap-2013.0523-460.el9.noarch              perl-Tie-4.6-481.el9.noarch
  perl-Time-Local-2:1.300-7.el9.noarch                      perl-URI-5.09-3.el9.noarch
  perl-base-2.27-481.el9.noarch                             perl-constant-1.33-461.el9.noarch
  perl-debugger-1.56-481.el9.noarch                         perl-if-0.60.800-481.el9.noarch
  perl-interpreter-4:5.32.1-481.el9.x86_64                  perl-libnet-3.13-4.el9.noarch
  perl-libs-4:5.32.1-481.el9.x86_64                         perl-meta-notation-5.32.1-481.el9.noarch
  perl-mro-1.23-481.el9.x86_64                              perl-overload-1.31-481.el9.noarch
  perl-overloading-0.02-481.el9.noarch                      perl-parent-1:0.238-460.el9.noarch
  perl-podlators-1:4.14-460.el9.noarch                      perl-sigtrap-1.09-481.el9.noarch
  perl-subs-1.03-481.el9.noarch                             perl-threads-1:2.25-460.el9.x86_64
  perl-threads-shared-1.61-460.el9.x86_64                   perl-vars-1.05-481.el9.noarch
  pkgconf-1.7.3-10.el9.x86_64                               pkgconf-m4-1.7.3-10.el9.noarch
  pkgconf-pkg-config-1.7.3-10.el9.x86_64                    policycoreutils-python-utils-3.6-2.1.el9.noarch
  python3-argcomplete-1.12.0-5.el9.noarch                   python3-audit-3.1.5-4.el9.x86_64
  python3-cffi-1.14.5-5.el9.x86_64                          python3-cryptography-36.0.1-4.el9.x86_64
  python3-distro-1.5.0-7.el9.noarch                         python3-ldap-3.4.3-2.el9.x86_64
  python3-lib389-2.6.1-6.el9_6.noarch                       python3-libsemanage-3.6-5.el9_6.x86_64
  python3-ply-3.11-14.el9.noarch                            python3-policycoreutils-3.6-2.1.el9.noarch
  python3-pyasn1-0.4.8-6.el9.noarch                         python3-pyasn1-modules-0.4.8-6.el9.noarch
  python3-pycparser-2.20-6.el9.noarch                       python3-setools-4.4.4-1.el9.x86_64
  python3-setuptools-53.0.0-13.el9.noarch

完了しました!
```


## 2. 389ds の初期設定 (インスタンスの作成)

- 参考
    - [あぱーブログ](https://blog.apar.jp/linux/15731/) (CentOS 8 の事例のため、適宜読み替える)

LDAP はツリー構造のデータを管理・提供しますが、389ds では systemctl のインスタンスごとにツリーを持つことができます。ここでは「ds01」という名称のインスタンスを1つだけ作成します。

### 準備 (パスワードのハッシュ化)

インスタンス作成は、インスタンスの定義ファイルを作成してそれを適用することで完了します。早速定義ファイルを作成しますが、定義ファイルには 389ds のルートユーザのパスワードを記載するので、先にパスワードのハッシュ値を生成しておきます。「password」をパスワードとする場合のハッシュ生成は下の通りです。

```console
# pwdhash password    # 「password」が設定するパスワード
{PBKDF2-SHA512}100000$cS0/6JJW+h6nRDFpTsJne9F5pBEk3YKV$h6U/RwM5MdId6s0W/PZf8FEZFYuBEBbtWn25b/fbUZXnNDvJuRHMbDfCKGaG/KKTXuDzFmiAvzh/FygNF5TPIA==
```

### 定義ファイル作成

まず、インスタンス作成に使用する定義ファイルを作成します。一から作ることもできますが、`dscreate` を使用することで、テンプレートを生成してくれます。

```console
# dscreate create-template /root/ds01.inf
```

テンプレートから自動生成した `/root/ds01.inf` を `vi` 等で編集します。

```diff_ini
# インスタンス名
- ;instance_name = localhost
+ instance_name = ds01

# ds389 の root ユーザパスワードを設定
- root_password = Directory_Manager_Password
+ root_password = {PBKDF2-SHA512}100000$cS0/6JJW+h6nRDFpTsJne9F5pBEk3YKV$h6U/RwM5MdId6s0W/PZf8FEZFYuBEBbtWn25b/fbUZXnNDvJuRHMbDfCKGaG/KKTXuDzFmiAvzh/FygNF5TPIA==
# root ユーザ名を設定 (デフォルトは cn=Directory Manager)
+ root_dn = cn=Manager

# 自己署名証明書の有効期限の変更（2年→30年）
- ;self_sign_cert_valid_months = 24
+ self_sign_cert_valid_months = 360

# ベースDNを作成（しない→する）
- ;create_suffix_entry = False
+ create_suffix_entry = True

# ベースDNの指定
- ;suffix =
+ suffix = dc=example,dc=com
```

ここでは次の箇所を設定しました。

- インスタンス名
    - 計画通り ds01 としました
- root パスワード
    - 前の手順で作成したハッシュ値を指定しました
- root ユーザ名
    - デフォルトの「cn=Directory Manager」だとスペースが混ざっていてコマンドで扱いづらいため、「cn=Manager」に変更しました
- 自己署名証明書の有効期限
    - 今回はセキュリティ面を重視しないので証明書の有効期限は長くしています
- ベースDN
    - 計画通り、「dc=example,dc=com」にしています。通常は保有しているドメイン名を使用すると思います

### インスタンス作成

定義ファイルを作成したら、`dscreate from-file` でインスタンスを作成します。

```console
# dscreate from-file /root/ds01.inf
Starting installation ...
Validate installation settings ...
Create file system structures ...
Create self-signed certificate database ...
Perform SELinux labeling ...
Create database backend: dc=example,dc=com ...
Perform post-installation tasks ...
Completed installation for instance: slapd-ds01
```

### 動作確認

インスタンスが作成されると自動で起動されるはずなので、下記のように `systemctl` コマンドで確認します。

```console
# systemctl status dirsrv@ds01
● dirsrv@ds01.service - 389 Directory Server ds01.
     Loaded: loaded (/usr/lib/systemd/system/dirsrv@.service; enabled; preset: disabled)
    Drop-In: /usr/lib/systemd/system/dirsrv@.service.d
             └─custom.conf
     Active: active (running) since Sun 2025-05-25 12:06:11 JST; 2min 55s ago
（略）
```

「Active: 」の欄が「active」になっていることから、起動していることがわかります。また、「Loaded」の行の「enabled」から、OS起動時にインスタンスが自動起動する設定になったことも確認できます。

:::note info
インスタンスの起動・停止等の操作は `dsctl` でできます。ds01 を操作する場合のコマンドは次のようになります。

```console
# dsctl ds01 start
# dsctl ds01 stop
# dsctl ds01 restart
```
:::


この状態であれば LDAP の検索も実行できるはずなので、`ldapserach` を使ってみましょう。パスワードを聞かれるので、上で設定した root のパスワードを入力します。

```console
# ldapsearch -x -W -D cn=Manager -b cn=config
Enter LDAP Password: password
（略）
dn: cn=config
cn: config
（略）
```

このように、`cn=config` の情報が出力されるはずです。

## 3. ポート開放

LDAP の検索などでは TCP のポート 389 もしくは 636 を用いて通信します。他のホストからもアクセスできるようにするため、ポートを開放します。

```console
# firewall-cmd --add-port=389/tcp --permanent
# firewall-cmd --add-port=636/tcp --permanent
# firewall-cmd --reload 
```

:::note warn
上記の設定の場合、到達可能なすべてのホストから LDAP 接続が可能になります。実際の運用では、必要に応じてアクセス元IPアドレスの制限等を加えてください。
:::

## 4. (windows側) Apache Directory Studio のインストール

LDAP サーバが稼働したので、この後はデータの追加や利用を考えていきますが、そのすべてをコマンドで実施するのは初学者には難しく、データを間違えて登録してしまっても気づくチャンスがあまりありません。また、LDAPに登録できる情報の構成の理解もコマンドやテキストデータでは進みづらいです。これらを解消するために GUI ベースの LDAP クライアントの1つである [Apache Directory Studio](https://directory.apache.org/studio/downloads.html) を準備します。

今回は Windows 上で仮想 Linux マシンを起動している構成のため、Windows 側にインストールします。Linux, macOS 用もあるため、GUI が利用可能であり ds389 をインストールしたホストと通信可能であればそれらにインストールしても構いません。

### インストーラのダウンロード

[ダウンロード](https://directory.apache.org/studio/downloads.html) ページから Windows 用インストーラを取得します。

### インストーラの実行

ダウンロードしたインストーラを実行することで、Apache Directory Studio をインストールします。

### 設定変更

`ApacheDirectoryStudio.ini` を編集することで設定を変更できます。私の場合は `C:\Program Files\Apache Directory Studio\` にありました。

変更する設定は次の1つです。

- 使用する JVM の指定 (設定せずとも起動できるなら不要)

:::note info
言語設定もここでできますが、en (英語), fr (フランス語), de (ドイツ語) のみ用意されていて、日本語は指定できません。
:::


まず、インストールした Apache Directory Studio を実行してみましょう。ここで**問題なく起動できた場合、JVMの指定は不要です。** 私の場合、「Version 1.8.0_211 of the JVM is not suitable for this product. Version: 11 or greater is reqquired.」(JVM のバージョンは 11 以上でなければならない) というメッセージが表示されて起動できなかったため、設定ファイルを編集して Java のバージョンを明示しました。

設定ファイルを管理者権限で開いたら、次の箇所を編集します。

```diff:ApacheDirectoryStudio.ini
###
#Uncomment_to_configure_Java_version_to_use
#https://directory.apache.org/studio/faqs.html#how-to-set-the-java-vm-to-use
- #-vm
- #/usr/bin/java
+ -vm
+ C:\Program Files\Eclipse Adoptium\jdk-17.0.3.7-hotspot\bin\javaw.exe
```

JVMについては、私の場合、過去に「C:\Program Files\Eclipse Adoptium\jdk-17.0.3.7-hotspot」に JDK を置いていたので、それを使用するように設定しました。「java.exe」ではなく「javaw.exe」を指定することで、不要なコンソールウィンドウが表示されないようにしています。


### LDAP サーバの登録

設定が完了したら Apache Directory Studio を起動します。

起動できたら、メニューの「File」->「New...」で新規作成ウィザードを開きます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147867/7fd76dd7-6329-42e5-95d8-621462993665.png)

「LDAP Connection」をクリックして「Next >」ボタンをクリックすると下の画面が表示されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147867/827c9d7d-5f92-47db-b05c-6fcb940b32ea.png)

この画面では次の内容を入力します。

- Connection name: この登録情報を後で一意に判別できるよう名前を付けます
- Hostname: 接続先 LDAP サーバを指定します。ここでは先ほど 389ds をインストールしたホストを IP アドレスで指定していますが、名前解決ができるならホスト名でも指定できます
- Port: ポート番号を指定します。389ds 側でポート番号は変更していないので、デフォルトの 389 でアクセスできます

これらを入力したら「Check Network Parameter」ボタンで接続を確認します。失敗のメッセージが表示される場合、入力内容に誤りが無いか、389dsは起動しているか、そこまで到達できるネットワーク構成になっているか、等を確認してください。

接続が成功していたら、「Next >」をクリックして下の画面に進みます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147867/7cf9781e-3516-4f64-90b7-5f0bef380a78.png)

この画面では次の内容を入力します。

- Bind DN or user: アクセスに使用するユーザを指定します。ここでは root ユーザである `cn=Manager` を指定します
- Bind password: パスワードを入力します。ここでは root ユーザのパスワード `password` を入力しました

これらを入力したら「Check Authentication」ボタンで認証を確認します。失敗のメッセージが表示される場合は入力内容が 389ds のインスタンス作成時に指定したものと相違ないかを確認してください。


接続が成功していたら、「Finish」をクリックしてコネクションの定義を完了します。「Next >」ボタンをクリックすればさらに細かい設定をできますが、ここでは実施しないことにします。

コネクションの定義が完了すると、下記画像のように「Connections」ビューに作成したコネクション定義が追加されます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147867/ef5a9072-ebea-4242-a8b5-1e555c9b0ff2.png)

この定義をダブルクリックすると、未接続状態であれば接続し、下記画像のように「LDAP Browser」ビューに登録済みの内容が表示されます。今回の場合、ルート DN として登録した「dc=example,dc=com」のみが確認できます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/147867/05970d87-72bb-4100-9fca-77599870f19d.png)

## 次のステップへ

ここまでで、LDAPサーバ 389ds を構築し、そのデータを閲覧する準備ができました。

ここからは LDAP の利用と、そのためのデータ登録やクライアント側の設定を実施していくことになります。

## (付録) 関連コマンド

上記の作業に関連するコマンドの備忘録です。

### 389ds のインスタンスの確認

どのようなインスタンスが作られているかを確認できます。

```console
$ systemctl list-units --type=service | grep 'dirsrv@'
```