【KVM入門⑤】〜LDAPを使ってユーザ情報などを一元管理する〜

# KVM入門一覧
- [【KVM入門⓪】〜Linuxの基礎知識〜](https://qiita.com/ryutarom128/items/00ed01b0c9dd59716964)
- [【KVM入門①】〜KVMを使って仮想マシンを作成する〜](https://qiita.com/ryutarom128/items/a31cc4e296442cd0f9d6)
- [【KVM入門②】〜複数の仮想マシンを作成して仮想マシン間で通信を行えるようにする〜](https://qiita.com/ryutarom128/items/2665a0f871d90f7f5358)
- [【KVM入門③】〜DNSとhostsの設定を行う〜](https://qiita.com/ryutarom128/items/7dd3646beddb96dc647a)
- [【KVM入門④】〜NFSを使って全ての仮想マシンでファイルを共有する〜]()
- 【KVM入門⑤】〜LDAPを使ってユーザ情報などを一元管理する〜👈今ここ

# はじめに
KVMシリーズ第5回です。

今回は、LDAPを使ってユーザ情報などを一元管理できるようにしていきます。

ホスト名の設定と`/etc/hosts`の設定は終わっているものとします。

`test-vm1`をサーバ、`test-vm1`から`test-vm4`をクライアントとして、以下の図の赤色の部分の設定をするところまでをこの回の目標とします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2906464/17511aaf-43b1-8a1b-367b-77e61c79b46e.png)

# すること
## サーバ側（`test-vm1`）
### 1. パッケージのインストール

パッケージマネージャのアップデートを行う。
```bash
sudo dnf update -y
```

必要なパッケージをインストールする。
```bash
sudo dnf install -y epel-release
```

```bash
dnf install -y openldap-servers openldap-clients
```

サービスの起動と自動起動の有効化を行う。
```bash
sudo systemctl enable --now slapd
```

### 2. ファイアウォールの設定
```bash
sudo firewall-cmd --add-service={ldap,ldaps} --permanent
```

```bash
sudo firewall-cmd --reload
```

以下のように表示されればファイアウォールの設定は成功している。
```bash
[xxx@vm1 ~]# sudo firewall-cmd --add-service={ldap,ldaps} --permanent
success
[xxx@vm1 ~]# sudo firewall-cmd --reload
success
[xxx@vm1 ~]# firewall-cmd --list-all
public (active)
  target: default
  icmp-block-inversion: no
  interfaces: enp1s0
  sources:
  services: cockpit dhcpv6-client nfs ldap ldaps ssh
  ports:
  protocols:
  forward: yes
  masquerade: no
  forward-ports:
  source-ports:
  icmp-blocks:
  rich rules:

```

### 3. OpenLDAPサーバの初期設定

```bash
cd /etc/openldap/schema
```

ハッシュ値の取得を行う。
```bash
slappasswd
```

以下のように表示される。
`{SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`の部分をコピーしておく。
```bash
[xxx@vm1 schema]# slappasswd
New password:
Re-enter new password:
{SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

ルートパスワードの変更用の設定ファイルを作成する。
```bash
vi changerootpass.ldif
```

```changerootpass.ldif
dn: olcDatabase={0}config,cn=config
changetype: modify
add: olcRootPW
olcRootPW: <コピーしたハッシュ値>
```

設定ファイルから設定を追加する。
```bash
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f changerootpass.ldif
```

デフォルトで配置されている3つのファイルを読み込んで設定を追加する。
```bash
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/cosine.ldif
```
```bash
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/nis.ldif 
```
```bash
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f /etc/openldap/schema/inetorgperson.ldif
```

サービスの再起動を行う。
```bash
sudo systemctl restart slapd
```

### 4. ドメインの設定
```bash
vi setdomain.ldif
```

```setdomain.ldif
dn: olcDatabase={1}monitor,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0}to * by dn.base="gidNumber=0+uidNumber=0,cn=peercred,cn=external,cn=auth"
  read by dn.base="cn=Manager,dc=test,dc=internal" read by * none

dn: olcDatabase={2}mdb,cn=config
changetype: modify
replace: olcSuffix
olcSuffix: dc=test,dc=internal

dn: olcDatabase={2}mdb,cn=config
changetype: modify
replace: olcRootDN
olcRootDN: cn=Manager,dc=test,dc=internal

dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcRootPW
olcRootPW: <コピーしたハッシュ値>

dn: olcDatabase={2}mdb,cn=config
changetype: modify
add: olcAccess
olcAccess: {0}to attrs=userPassword,shadowLastChange by
  dn="cn=Manager,dc=test,dc=internal" write by anonymous auth by self write by * none
olcAccess: {1}to dn.base="" by * read
olcAccess: {2}to * by dn="cn=Manager,dc=test,dc=internal" write by * read
```

```bash
sudo ldapmodify -Y EXTERNAL -H ldapi:/// -f setdomain.ldif
```

以下のように表示されればドメインの設定は成功している。
```bash
[xxx@vm1 schema]# sudo ldapsearch -H ldap:// -x -s base -b "" -LLL "namingContexts"
dn:
namingContexts: dc=test,dc=internal
```

### 5. 組織とグループの設定

```bash
vi addou.ldif
```

```addou.ldif
dn: dc=test,dc=internal
objectClass: top
objectClass: dcObject
objectclass: organization
o: My test Organisation
dc: test

dn: cn=Manager,dc=test,dc=internal
objectClass: organizationalRole
cn: Manager
description: OpenLDAP Manager

dn: ou=People,dc=test,dc=internal
objectClass: organizationalUnit
ou: People

dn: ou=Group,dc=test,dc=internal
objectClass: organizationalUnit
ou: Group
```

```bash
sudo ldapadd -x -D cn=Manager,dc=test,dc=internal -W -f addou.ldif
```

以下のように表示されれば組織とグループの設定は成功している。
```bash
[xxx@vm1 schema]# sudo ldapsearch -x -b "dc=test,dc=internal" ou
# extended LDIF
#
# LDAPv3
# base <dc=test,dc=internal> with scope subtree
# filter: (objectclass=*)
# requesting: ou
#

# test.internal
dn: dc=test,dc=internal

# Manager, test.internal
dn: cn=Manager,dc=test,dc=internal

# People, test.internal
dn: ou=People,dc=test,dc=internal
ou: People

# Group, test.internal
dn: ou=Group,dc=test,dc=internal
ou: Group

# search result
search: 2
result: 0 Success

# numResponses: 5
# numEntries: 4
```

### 6. ユーザの追加

暗号化されたパスワードを取得する。

```bash
slappasswd
```

```bash
[xxx@vm1 schema]# slappasswd
New password:
Re-enter new password:
{SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
```

ユーザ作成用のLDIFファイルを作成する。
userPasswordの項目には、上で表示された`{SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx`をコピーして貼り付ける。

```bash
vi adduser.ldif
```

```adduser.ldif
dn: uid=testuser,ou=People,dc=test,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: testuser
sn: temp
userPassword: {SSHA}xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
loginShell: /bin/bash
uidNumber: 2000
gidNumber: 2000
homeDirectory: /home/testuser
shadowLastChange: 0
shadowMax: 0
shadowWarning: 0

dn: cn=rocky,ou=Group,dc=test,dc=internal
objectClass: posixGroup
cn: testuser
gidNumber: 2000
memberUid: testuser
```

```bash
sudo ldapadd -x -D cn=Manager,dc=test,dc=internal -W -f adduser.ldif
```

以下のように表示されればユーザの追加に成功している。
```bash
[xxx@vm1 schema]# sudo ldapsearch -x -b "ou=People,dc=test,dc=internal"
# extended LDIF
#
# LDAPv3
# base <ou=People,dc=test,dc=internal> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# People, test.internal
dn: ou=People,dc=test,dc=internal
objectClass: organizationalUnit
ou: People

# testuser, People, test.internal
dn: uid=testuser,ou=People,dc=test,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: testuser
sn: temp
loginShell: /bin/bash
uidNumber: 2000
gidNumber: 2000
homeDirectory: /home/testuser
shadowMax: 0
shadowWarning: 0
uid: testuser

# search result
search: 2
result: 0 Success

# numResponses: 3
# numEntries: 2
```

### 7. SSL/TLSの設定

```bash
sudo openssl req -x509 -nodes -days 365 -newkey rsa:2048 -keyout /etc/pki/tls/ldapserver.key -out /etc/pki/tls/ldapserver.crt
```

以下の3つのファイルをまとめて`/etc/openldap/certs/`にコピーする。
- `/etc/pki/tls/ldapserver.crt`
- `/etc/pki/tls/ldapserver.key`
- `/etc/pki/tls/certs/ca-bundle.crt`

```bash
sudo cp /etc/pki/tls/ldapserver.crt /etc/pki/tls/ldapserver.key /etc/pki/tls/certs/ca-bundle.crt /etc/openldap/certs/
```

```bash
sudo chown ldap:ldap /etc/openldap/certs/{ldapserver.crt,ldapserver.key,ca-bundle.crt}
```

```bash
sudo chmod 600 ldapserver.key
```

```bash
vi tls.ldif
```

```tls.ldif
dn: cn=config
changetype: modify
add: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/openldap/certs/ca-bundle.crt

dn: cn=config
changetype: modify
add: olcTLSCertificateFile
olcTLSCertificateFile: /etc/openldap/certs/ldapserver.crt

dn: cn=config
changetype: modify
add: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/openldap/certs/ldapserver.key
```

```bash
sudo ldapadd -Y EXTERNAL -H ldapi:/// -f tls.ldif
```

```bash
vi /etc/openldap/ldap.conf
```

```/etc/openldap/ldap.conf
TLS_CACERT     /etc/openldap/certs/ca-bundle.crt

# because you're using Self-Signed Certificate - you must add this
TLS_REQCERT never
```

```bash
sudo systemctl restart slapd
```

<details>
<summary>以下のように表示されればSSL/TLSの設定は成功している。</summary>

```bash
[xxx@vm1 certs]# sudo ldapsearch -x -b dc=test,dc=internal -ZZ
# extended LDIF
#
# LDAPv3
# base <dc=test,dc=internal> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# test.internal
dn: dc=test,dc=internal
objectClass: top
objectClass: dcObject
objectClass: organization
o: My test Organisation
dc: test

# Manager, test.internal
dn: cn=Manager,dc=test,dc=internal
objectClass: organizationalRole
cn: Manager
description: OpenLDAP Manager

# People, test.internal
dn: ou=People,dc=test,dc=internal
objectClass: organizationalUnit
ou: People

# Group, test.internal
dn: ou=Group,dc=test,dc=internal
objectClass: organizationalUnit
ou: Group

# testuser, People, test.internal
dn: uid=testuser,ou=People,dc=test,dc=internal
objectClass: inetOrgPerson
objectClass: posixAccount
objectClass: shadowAccount
cn: testuser
sn: temp
loginShell: /bin/bash
uidNumber: 2000
gidNumber: 2000
homeDirectory: /home/testuser
shadowMax: 0
shadowWarning: 0
uid: testuser

# search result
search: 3
result: 0 Success

# numResponses: 6
# numEntries: 5
```
</details>

## クライアント側（`test-vm1`から`test-vm4`）

### 1. パッケージのインストール

```bash
sudo dnf update -y
```

```bash
sudo dnf install -y epel-release
```

```bash
sudo dnf install -y openldap-clients sssd sssd-ldap oddjob-mkhomedir
```

### 2. SSSDの起動

```bash
sudo authselect select sssd with-mkhomedir --force
```

```bash
sudo systemctl enable --now oddjobd.service
```

### 3. OpenLDAPとSSSDの設定
```bash
sudo vi /etc/openldap/ldap.conf
```

以下の設定項目を追記する。

```/etc/openldap/ldap.conf
~~~
BASE dc=test,dc=internal
URI ldap://vm1.test.internal/
```

`sssd.conf`ファイルを編集する。

```bash
sudo vi /etc/sssd/sssd.conf
```

```/etc/sssd/sssd.conf
[domain/default]
id_provider = ldap
autofs_provider = ldap
auth_provider = ldap
chpass_provider = ldap
ldap_uri = ldap://vm1.test.internal/
ldap_search_base = dc=test,dc=internal
ldap_id_use_start_tls = True
ldap_tls_cacertdir = /etc/openldap/certs
cache_credentials = True
ldap_tls_reqcert = allow

[sssd]
services = nss, pam, autofs
domains = default

[nss]
homedir_substring = /home
```

`sssd.conf`ファイルのパーミッションを設定する。

```bash
sudo chmod 600 /etc/sssd/sssd.conf
```

sssdサービスのブート時自動起動の設定と起動を行う。

```bash
sudo systemctl enable --now sssd
```


### 4. 接続

一旦ログアウトして、作成したユーザでログインできれば成功。

```bash
[root@vm2 ~]# id
uid=0(root) gid=0(root) groups=0(root) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[root@vm2 ~]# whoami
root
[root@vm2 ~]# exit
logout

Rocky Linux 9.4 (Blue Onyx)
Kernel 5.14.0-427.18.1.el9_4.x86_64 on an x86_64

vm2 login: testuser
Password:
Last login: Sat Jun  1 04:38:42 on ttyS0
[testuser@vm2 ~]$ id
uid=2000(testuser) gid=2000 groups=2000 context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
[testuser@vm2 ~]$ whoami
testuser
```

# 解説

## NISとLDAPの違い

NISやLDAP、DNSなどのことをネームサービスという[^name-service]。
[^name-service]: NIS、NIS+、DNSなどの違いについて. http://home.t00.itscom.net/happy7/solaris/omake.htm

Network Information Service(NIS)とは、複数のコンピュータ間でシステムの設定情報を共有することができるシステムのことである[^nis]。
[^nis]: IT用語辞典. NIS 【Network Information Service】. https://e-words.jp/w/NIS.html

NISを使うことでホスト名やユーザ情報の管理ができるが、セキュリティ上の問題[^nis-security]などにより、現在はホスト名の管理にはDNS、ユーザ情報の管理にはLDAPを用いるのが主流となっている。
[^nis-security]: 3.4.2 NISの問題点. https://www.ikueikan.ac.jp/biblion/1998/netadmin/node61.html

実際、Rocky Linux 9でもNISの技術は安全とはみなされなくなったことが原因でNISパッケージが削除されており[^delete-nis]、NISの代替手段としてLDAPを挙げている[^alternative-nis]。
[^delete-nis]: Red Hat Customer Portal. 14.4 削除された機能. https://access.redhat.com/documentation/ja-jp/red_hat_enterprise_linux/9/html/considerations_in_adopting_rhel_9/ref_removed-functionality-identity-management_assembly_identity-management
[^alternative-nis]: Red Hat Customer Portal. What is the support status of Network Information Service (NIS). https://access.redhat.com/solutions/5991271

LDAPを使うことで、下の図のようにユーザ情報などの各種データを集中管理することができる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2906464/71bde3e0-11dc-6205-63c0-57ab56161864.png)
*LDAPを用いて各種データを集中管理する様子[^ldap]*
[^ldap]: そろそろLDAPにしてみないか？. 第1回 まずは使ってみよう. https://gihyo.jp/admin/serial/01/ldap/0001

## DC, OU, CNについて
LDAPを構成する要素として、Domain Composenent (DC), Organization Unit Name (OU), Common Name (CN)などがある[^dc-ou-cn]。
[^dc-ou-cn]: Active Directoryで出てくる用語 (DN, OU, DC, CN など). https://akrad.hatenablog.com/entry/2018/06/17/212505

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2906464/3af1d129-3b1f-81f9-dbc8-9735614e216e.png)
*LDAPのツリー構成[^ldap-tree]*
[^ldap-tree]: 【LDAP基礎用語】DCとは？OUとは？バインドDN,ベースDN,サフィックスとは？匿名バインドとは？ldapsearchのオプション. https://milestone-of-se.nesuke.com/l7protocol/ldap/binddn-basedn/

上の図のように、DCは組織、OUは組織内のグループ、CNはユーザを表す。

ただしDCに関しては、上の図では2つであるが、数に制限はなく、さらにドメインを使う必要もない[^ldap-tree]。

## SSL/TLSの仕組み
SSL/TLSとは、インターネットでの送受信において、なりすましや盗聴、改ざんなどを防ぐための暗号化プロトコルである[^ssl]。
[^ssl]: SSLとTLSとは？意外に知らないSSLとTLSの違い（簡単編）. https://www.idcf.jp/rentalserver/aossl/basic/ssl-tls/

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2906464/54db6f0a-875e-94c3-1c9c-c52e26fdd8e3.png)
*SSL/TLSを使った通信の流れ[^ssl-tls]*
[^ssl-tls]: HTTPS通信(SSL/TLS)の流れを5分で理解する(図解つき). https://btj0.com/blog/web/https-ssl-tls/

上の図のように、①から⑦の流れで通信を行う。

ポイントは以下の2つである。
1. 送られてきたSSL証明書と、ブラウザに最初から入ってるルート証明書を照合している点
1. クライアント側で、公開鍵を使って共通鍵を暗号化している点

以下の図は「※サーバー側で準備」と書かれている部分で省略されているサーバ証明書を発行するときの流れを示す。（※サーバ証明書とSSL証明書は同じ意味）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2906464/41d501e3-d588-c192-2e3a-8edb5ce02eab.png)
*サーバ証明書を発行するときの流れ[^ssl-cer]*
[^ssl-cer]: オレオレ証明書をopensslで作る（詳細版）. https://ozuma.hatenablog.jp/entry/20130511/1368284304

OpenLDAPではサーバ側で以下の3つを設定する。
ただし、今回はオレオレ証明書を使用するためWebサーバと認証局(CA)が同一である。
1. CA証明書（olcTLSCACertificateFile）
    - OpenLDAPサーバが信頼する認証局(CA)の証明書を格納したファイル(`*.crt`)を指定
    - /etc/pki/tls/certs/ca-bundle.crtのこと
1. サーバ証明書（olcTLSCertificateFile）
    - OpenLDAPサーバが自身を証明するために使用する証明書を格納したファイル(`*.crt`)を指定
    - 上の図でいうサーバ証明書(CRT)のこと
1. サーバ証明書の秘密鍵（olcTLSCertificateKeyFile）
    - サーバ証明書に対応する秘密鍵を格納したファイル(`*.key`)を指定
    - 上の図でいうPrivate Keyのこと

# さいごに
今回はLDAPを使ってユーザ情報などを一元管理する方法を紹介しました。

これでKVMシリーズは終了です。

読んでいただきありがとうございました！