Rocky Linux 9 上で LDAP サービスを構築する

# はじめに
**【スタート】**
* Rocky9 を OS とする 4 個の VM（以下 VM1, VM2, VM3, VM4 と表記）
* VM1 のホスト名は vm1.machi.city、他も同様
* 各々の IP アドレスは **172.16.10.n/24 (n=1~4)**
* デフォルトゲートウェイおよび DNS は全て **172.16.10.100**

**【ゴール】**
* VM1 を LDAP サーバにし、すべての VM に同じユーザ情報でログインできるようにする

以下、コマンドは全て **root ユーザ** として実行するものとします。
# LDAP サーバの設定【VM1】
まずはこのサイトの流れに従って、ldap ユーザを追加するところまでやります。

https://www.howtoforge.com/how-to-install-openldap-on-rocky-linux-9/

今回の例では、
```sh: sudo nano /etc/hosts
# ip - fqdn - hostname
172.16.10.1 vm1.machi.city vm1
```
となります。これに合わせて名前を適宜変更しながら実行しました。
また、ldap ユーザのホームディレクトリは「/myfs/home/<ユーザ名>」のようにしたかったので、 **adduser.ldif** の設定では

* 追加するユーザの名前は **ldapuser0**
* そのホームディレクトリは **homeDirectory: /myfs/home/ldapuser0**

としました。

## ルート CA（認証局：Certificate Authorities）の作成
### ディレクトリ構成
openssl コマンドを使用する際、/etc/pki/tls/openssl.cnfのルールが適用されます。
**参考**


なのでまずは /etc/pki/tls/openssl.cnf を読んでみます。

>めっちゃ大雑把にいうと、
[ req ] は csr を作る用で [ x509 ] は自己署名の crt を作る用、
[ ca ] は **CA:TRUE と書かれた証明書を持っている認証局**として
他者の証明書に署名してやる用
みたいな感じのことが書かれていると思います。

まず最初に作りたいのは、[ ca ] を使って他人に署名してやるルート CA です。
[ ca ] を見ると、指定されているディレクトリが /etc/pki/CA 以下になっていいます。このディレクトリは最初から存在しないので、とりあえずこれを作成しておきましょう。

```sh
※ CA 用のディレクトリを作成して、そこに移動します。
mkdir /etc/pki/CA/
cd /etc/pki/CA/

※ 配下のディレクトリ及びファイルを作っておきます。
mkdir certs
mkdir crl
touch index.txt
mkdir newcerts
echo "00" > serial
mkdir crlnumber
mkdir private
```

### ルートの証明書作成
CA の自己証明書を作成します。
例として、以下では次のような名前で作成します。

* <font color="Red">my_root_ca.key</font>：ルート CA の秘密鍵
* <font color="Red">my_root_ca.crt</font>：ルート CA の証明書（自己署名）

まず秘密鍵用のディレクトリに「<font color="Red">ca_company.key</font>」と名付けた秘密鍵を作成します。

```sh
openssl genpkey -out /etc/pki/CA/private/my_root_ca.key \
-algorithm RSA -pkeyopt rsa_keygen_bits:2048
```

この例だと、
「openssl を用いて秘密鍵を作ります、パスは /etc/pki/CA/private/my_root_ca.key です、RSA 暗号を用いて bit 長は 2048 です」
という意味。
CA 秘密鍵は厳重に管理しましょう。

```
chmod 700 /etc/pki/CA/private
```
次はお好みでやらなくてもいいですが、セキュリティが気になるならやっときましょう。これの意味は後ほど出てきます。
```shell-session: 追加項目
$ vi /etc/pki/tls/openssl.cnf

[ v3_ca ] セクションに次の行を追加！
keyUsage = cRLSign, keyCertSign, digitalSignature
```
次に自己署名証明書「<font color="Red">my_root_ca.crt</font>」を作成します。
直感的には分かりづらいステップですが、落ち着いてよく意味を考えながらやりましょう。
方法１か方法２どちらでも同じ結果になります。


```sh:【方法１】
※ 秘密鍵から直接 crt を発行（詳しくは $ openssl req -help で -x509 の意味を読む）
openssl req -x509 -new -days 3650 \
-key /etc/pki/CA/private/my_root_ca.key \
-subj "/C=JP/ST=Osaka/L=Nipponbashi/O=My-CA/CN=vm1.machi.city" \
-out /etc/pki/CA/my_root_ca.crt

※ 確認用
cat my_root_ca.crt | openssl x509 -text -noout
```

```sh:【方法２】
※ csr を作ってから
openssl req -new \
-key /etc/pki/CA/private/my_root_ca.key \
-subj "/C=JP/ST=Osaka/L=Nipponbashi/O=My-CA/CN=vm1.machi.city" \
-out /etc/pki/CA/my_root_ca.csr

※ crt を発行（設定ファイルから [ v3_ca ] セクションを参照して CA:TRUE を付ける）
openssl x509 -req -days 3650 \
-in /etc/pki/CA/my_root_ca.csr \
-signkey /etc/pki/CA/private/my_root_ca.key \
-extfile /etc/pki/tls/openssl.cnf -extensions v3_ca \
-out /etc/pki/CA/my_root_ca.crt

※ 確認用
cat my_root_ca.crt | openssl x509 -text -noout
```
ルート CA の証明書なので、有効期限は長めに 10 年くらいとってみました。
-subj に関してはテスト環境ですし、組織名は適当に「My-CA」と付けてみました。
ただし **CN** に関してはしっかりつけましょう。ここにはマシンのホスト名をドメインまで省略せずに書きます。



### [ ca ] セクションの設定
ルート CA が中間 CA やサーバーに署名する際、さきほど作った証明書および秘密鍵をデフォルトで参照するようにします。
openssl.conf を次のように書き変えます。

```console
$ vi /etc/pki/tls/openssl.cnf
```

>・・・（略）・・・
>[ CA_default ]
>
>dir             = /etc/pki/CA           # Where everything is kept
>certs           = $dir/certs            # Where the issued certs are kept
>crl_dir         = $dir/crl              # Where the issued crl are kept
>database        = $dir/index.txt        # database index file.
>#unique_subject = no                    # Set to 'no' to allow creation of
>                                        # several certs with same subject.
>new_certs_dir   = $dir/newcerts         # default place for new certs.
>
>certificate     = $dir/**<font color="Red">my_root_ca.crt</font>**   # The CA certificate
>serial          = $dir/serial           # The current serial number
>crlnumber       = $dir/crlnumber        # the current crl number
>                                        # must be commented out to leave a V1 CRL
>crl             = $dir/crl.pem          # The current CRL
>private_key     = $dir/private/**<font color="Red">my_root_ca.key</font>** # The private key
>・・・（略）・・・

これでルート CA の準備は出来ました。
認証して欲しいサーバが署名を依頼してきたら

```sh
openssl ca -policy policy_anything -days 〇〇〇 \
-in /（略）/サーバ.csr \
-out /（略）/サーバ.crt
```

とすれば良いですし、中間 CA になりたい者から署名をせがまれたら

```sh
openssl ca -policy policy_anything -days 〇〇〇 \
-extfile /etc/pki/tls/openssl.cnf -extensions v3_ca \
-in /（略）/中間ＣＡ.csr \
-out /（略）/中間ＣＡ.crt
```

とすれば OK です。



## LDAP サーバの証明書作成
例として、以下では次のような名前で作成します。

* <font color="Blue">my_ldap.key</font>：LDAP サーバの秘密鍵
* <font color="Blue">my_ldap.csr</font>：LDAP サーバの署名要求
* <font color="Blue">my_ldap.crt</font>：LDAP サーバの証明書

LDAP サーバの csr を作成します。
この csr に「My-CA」が署名をすることで、LDAP サーバの提供するディレクトリサービスが「my_root_ca.crt を持つクライアントにとって信頼できるもの」となります。

まず OpenLDAP をインストールした時点で
```shell-session
$ ls /etc/openldap/
certs/    check_password.conf    ldap.conf    schema/    slapd.d/
```
のように certs/ もしくはそれに類するフォルダが含まれているかと思います（なければ mkdir コマンドで作ってください）。折角分かりやすい名前を準備してくれているので、ここに証明書を入れることにしましょう。

```sh
※ ディレクトリを移動しておきます。
cd /etc/openldap

※ LDAP の秘密鍵用のディレクトリも作っておきましょう。
mkdir private

※ LDAP の秘密鍵
openssl genpkey -out /etc/openldap/private/my_ldap.key \
-algorithm RSA -pkeyopt rsa_keygen_bits:2048

※ csr 作成
openssl req -new \
-key /etc/openldap/private/my_ldap.key \
-subj "/C=JP/ST=Osaka/L=Umeda/O=My-LDAP/CN=vm1.machi.city" \
-out /etc/openldap/certs/my_ldap.csr
```
今回はLDAPとルートCAが同じVM1内にあるので、
次のコマンドでサーバ証明書の発行＆配置をします。
```sh
※ ルート CA に csr を渡し、署名して crt を作ってもらう。
openssl ca -policy policy_anything -days 395 \
-in /etc/openldap/certs/my_ldap.csr \
-out /etc/pki/CA/certs/my_ldap.crt

※ crt を /etc/openldap/certs/my_ldap.crt に配置。
cp /etc/pki/CA/certs/my_ldap.crt /etc/openldap/certs/my_ldap.crt

※ 認証済み証明書と LDAP 自身の秘密鍵を LDAP が扱えるようにする。
chown ldap:ldap /etc/openldap/certs/my_ldap.crt
chown ldap:ldap /etc/openldap/private/my_ldap.key
```

LDAP サーバの設定を修正する。

```sh
vi tls.ldif
```

>dn: cn=config
>changetype: modify
>add: olcTLSCACertificateFile
>olcTLSCACertificateFile: /etc/pki/CA/<font color="Red">my_root_ca.crt</font>
>\-
>add: olcTLSCertificateKeyFile
>olcTLSCertificateKeyFile: /etc/openldap/private/<font color="Blue">my_ldap.key</font>
>\-
>add: olcTLSCertificateFile
>olcTLSCertificateFile: /etc/openldap/certs/<font color="Blue">my_ldap.crt</font>

ファイル内の記述は、olcTLSCACertificateFile 達 3 種類を追加（add:）し、秘密鍵や証明書がどこにあるか認識できるように LDAP の設定（dn: cn=config）を修正（changetype: modify）するという意味。
すでに追加した olcTLSCACertificateFile 達を修正したい場合は、「add:」の部分を「replace:」にします。

次のコマンドで設定を反映させます。
```sh
ldapadd -Y EXTERNAL -H ldapi:/// -f tls.ldif
```
では、先ほど作ったルート CA の証明書を各マシンに送っておきましょう。
自分の作った環境に合わせて送り先など変更してください。
<font color="Red">※ Rocky Linux9 はセキュリティの関係上外から直接 root にアクセスできないかもしれません。その場合はrootではなくユーザーに送ってください。</font>
```sh
cp /etc/pki/CA/my_root_ca.crt /root
scp /etc/pki/CA/my_root_ca.crt root@172.16.10.2:/root
scp /etc/pki/CA/my_root_ca.crt root@172.16.10.3:/root
scp /etc/pki/CA/my_root_ca.crt root@172.16.10.4:/root
```

**参考：**
* [今度こそopensslコマンドを理解して使いたい (1) ルートCAをスクリプトで作成する](https://qiita.com/3244/items/780469306a3c3051c9fe)
* [とほほのOpenSSL入門](https://www.tohoho-web.com/ex/openssl.html)
* [CA 構築のための OpenSSL の設定](https://moca.wide.ad.jp/notes/ca_doc/openssl.html)
* [OpenSSLで作った自己署名証明書でIAM Roles Anywhereを使ってみた](https://dev.classmethod.jp/articles/iam-roles-anywhere-with-a-self-signed-certificate-created-with-openssl/)



# LDAP クライアントの設定【VM1～VM4】
ここまでで VM1 内にルート CA と LDAP サーバを準備してきました。
次は LDAP クライアントの準備です。
なお、LDAP サーバである VM1 内からも当然ディレクトリサービスにアクセスしたいので、VM2~VM4 だけでなく VM1 内でも LDAP クライアント設定もしましょう。まとめると、

* VM2、VM3、VM4 は **LDAP クライアント**
* VM1 は **ルート CA** かつ **LDAP サーバ** かつ **LDAP クライアント**

です。
**ここの設定は VM1 ～ VM4 で共通です。**

```sh
※ 受け取ったルートの証明書を信頼リストに配置
mv /root/my_root_ca.crt /etc/pki/ca-trust/source/anchors/my_root_ca.crt

※ 情報を反映
update-ca-trust
```
クライアント用の一式をインストールし、接続方法を sssd を用いるように設定します。
```sh
cd ~
dnf -y install openldap-clients sssd sssd-ldap oddjob-mkhomedir
authselect select sssd with-mkhomedir --force
```

コメントアウトを外して LDAP サーバの URL と Suffix を追記します。
```sh
vi /etc/openldap/ldap.conf
```
>URI ldap://vm1.machi.city/
>BASE dc=machi,dc=city


太字部分は先程までにつくった LDAP サーバの情報を入力。
ldap_tls_cacertdir は **demand** にしておきます。
```sh
vi /etc/sssd/sssd.conf
```
>[domain/default]
>id_provider = ldap
>autofs_provider = ldap
>auth_provider = ldap
>chpass_provider = ldap
>**ldap_uri = ldap://vm1.machi.city/**
>**ldap_search_base = dc=machi,dc=city**
>ldap_id_use_start_tls = True
>**ldap_tls_cacertdir = /etc/openldap/certs**
>cache_credentials = True
>ldap_tls_reqcert = **demand**
>
>[sssd]
>services = nss, pam, autofs
>domains = default
>
>[nss]
>**homedir_substring = /myfs/home**

設定を反映します。
```sh
chmod 600 /etc/sssd/sssd.conf
systemctl restart sssd oddjobd
systemctl enable sssd oddjobd

※ 確認用のコマンド
ldapsearch -x -b dc=machi,dc=city -ZZ
```
確認用のコマンドをうち、
```shell-session
[root@vm1 ~]# ldapsearch -x -b dc=machi,dc=city -ZZ
ldap_start_tls: Connect error (-11)
        additional info: error:0A000086:SSL routines::certificate verify failed (self-signed certificate in certificate chain)
```
のような出力になれば、おそらくルート CA 証明書に
keyUsage = cRLSign, keyCertSign, **digitalSignature**
が抜けていると考えられます。発行時に openssl.cnf をいじったのはこれを回避するためです。
上手くいかない場合は（テスト環境であれば）ldap_tls_cacertdir を allow にしてエラーを黙らせてもいいです。
では、ldap ユーザ用のホームを準備しましょう。
sssd.conf へ書きこんだ内容に沿って、ここでは /myfs/home を作成します。
```sh
mkdir -p /myfs/home
```

初ログイン時に自動で各ユーザのホームディレクトリが作成されるように /myfs/home に関して SELinux の設定を変更します。
```sh
※「/myfs/home」でやると「//home」に直せと文句を言われることがある。
※ ここはまだちょっと理解できていないが、とりあえず今回はこれで行く。
semanage fcontext -a -e / /myfs
semanage fcontext -a -t home_root_t '//home'
restorecon -R /myfs/home

※ 確認用、home_root_t が出れば OK
ls -ldZ /myfs/home
```
> ※（追記）：説明のため「/」の権限を「**権限A**」と呼ぶとする。権限Aを「myfs/」に与えた状態を目指している。イメージ的には、
ここで SELinux 的に権限を「/」＝「myfs/」と認識するようにすれば myfs/ にも権限A が付くが、その代わり「/myfs/home」ではなく「//home」と書くことになる、
みたいな感じ。

これで、VM1～VM4 に
```sh
ssh ldapuser0@172.16.10.n （例では n=1~4）
```
をすると自動でディレクトリが作成されるようになります。
（若干遠回りしましたが、ホームディレクトリを /home のままにするのであれば SELinux の部分は飛ばせると思います。今回は通常のユーザと LDAP の管理するユーザを分けたいと思い、上記の変更を加えました。）
図であらわすと次のような感じ。

<img width="500" alt="qiita2.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792673/6e9ef2fd-102a-a0ac-11d8-bd2802c54935.jpeg">

以上でどの VM にも同じ ldap ユーザとパスワードで接続できるようになりました。**完成！**

**参考：**
* [Rocky Linux 8 : NFS クライアントの設定 : Server World](https://www.server-world.info/query?os=Rocky_Linux_8&p=nfs&f=2)

# 【おまけ】
ここから先は個人的な興味として、実験的にやっていこうと思います。
間違っていましたら教えていただけると幸いです。

## NFS の設定
### ホームディレクトリをいじってみる
ホームディレクトリの本体を別のディレクトリに変更して NFS で共有し、
ログイン時の位置に本体に繋がるシンボリックリンクを置きます。

```sh
mkdir -p /myfs-real/home
mkdir /myfs
ln -s /myfs-real/home/ /myfs/home

semanage fcontext -a -t home_root_t /myfs-real/home
restorecon -R /myfs-real/home

SELinuxの権限については、ホームディレクトリになる/myfsの方に
semanage fcontext -a -e / /myfs
restorecon -R /myfs

シンボリックリンクは自分の権限を参照するので、
/myfs/home --> /myfs-real/home/
の場合は
/ = /myfs
でmyfsの設定をして、
/myfs-real/home に home_root_t を追加すると
ls -ldZ /myfs/home/ に home_root_t が確認できる。
```

### VM1（nfs サーバ、ldap サーバ）
```sh
dnf -y install nfs-utils
vi /etc/idmapd.conf
```
>5行目を変更
>Domain = machi.city
```sh
vi /etc/exports
```
> 追加する↓
>/myfs-real 172.16.10.0/24(rw,no_root_squash)
```sh
※ /myfs-real/home を共有したい
mkdir /myfs-real/home
systemctl enable --now rpcbind nfs-server

※ firewall に nfs を許可、永続化
firewall-cmd --add-service=nfs
firewall-cmd --runtime-to-permanent
```

### VM2~VM4（nfs クライアント、ldap クライアント）
```sh
dnf -y install nfs-utils
vi /etc/idmapd.conf
```
>5行目を変更
>Domain = machi.city

システム起動時に自動的に NFS 共有をマウントするようにします。
```sh
mkdir -p /myfs-real/home
mount -t nfs vm1.machi.city:/myfs-real/home /myfs-real/home
vi /etc/fstab
```
>追加する↓
>vm1.machi.city:/myfs-real/home /myfs-real/home            nfs     defaults        0 0

```sh
※ /myfs について SELinux の権限を変更
semanage fcontext -a -e / /myfs
restorecon -R /myfs

※ シンボリックリンクで本体（nfsマウントしたもの）に繋げる
ln -s /myfs-real/home/ /myfs/home

※ nfs マウントしているディレクトリは ls -ldZ /myfs-real/home すると
※ system_u:object_r:nfs_t:s0 と表示される。
※ nfs マウントしているディレクトリにも自動でホームディレクトリを作りたい。
※ getsebool -a | grep nfs コマンドを使うと、use_nfs_home_dirs --> off という表記が見つかる。
※ これを on にする。永続化のために -P をつける。

setsebool -P use_nfs_home_dirs on
```

これで、初めて使うユーザ情報でログインする際、どの VM にアクセスしても共有済みホームディレクトリが作成されるようになりました。
図にするとこのような感じです。

<img width="500" alt="qiita1.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3792673/01a2f3cf-a8a6-3b4b-5546-6b5f1ca76cae.jpeg">

今回は以上です。