midPoint からActive Directoryにプロビジョニングする（AD互換環境の準備編）

[midPoint by OpenStandia Advent Calendar 2024](https://qiita.com/advent-calendar/2024/midpoint-by-openstandia) の15日目は、新たな連携先としてActive Directory（以下、AD）を追加していくにあたって、まずはAD互換環境を準備します。Windows ServerをたててADをインストールするでもよいのですが、今回のAdvent CalendarではDocker Composeを使って開発/検証環境を構築していますので、AD環境もその上で準備できると色々捗ります。幸いなことに、Linux上で動作するAD互換ドメインコントローラーのOSS実装である[Samba4](https://www.samba.org/)がありますので、これをDocker Compose環境に組み込みます。


:::note info
14日目までの環境を前提としています。
:::


# Dockerfileの作成

`ad`ディレクトリを作成し、配下に`Dockerfile`とSamba4構築のための各種ファイルを作成していきます。

:::note info
https://github.com/myrjola/docker-samba-ad-dc など、Samba4を使ったドメインコントローラーのDockernizeに取り組まれている先人達がいらっしゃいますので、本記事でもそれらを参考にしています。なお、本記事ではあくまで開発/検証用途でSamba4を使用しています。実際に運用するSamba4を使ったドメインコントローラーの構築方法を紹介しているわけではありませんので、ご注意ください。特に、LDAPSではなくLDAP接続を許可したりとセキュリティレベルを下げている点に注意してください。
:::

`Dockerfile`の中身は以下のとおりです。

```Dockerfile:./ad/Dockerfile
FROM ubuntu:24.04

# Install supervisor
RUN apt-get update
RUN apt-get install -y supervisor
RUN mkdir -p /var/log/supervisor

# Install samba and dependencies to make it an Active Directory Domain Controller
RUN apt-get install -y bind9 samba smbclient attr winbind krb5-config krb5-user ldb-tools dnsutils

# Install utilities needed for setup
RUN apt-get install -y expect pwgen

# Install rsyslog to get better logging of ie. bind9
RUN apt-get install -y rsyslog

# Install ldap-utils
RUN apt-get install -y ldap-utils

# Add bind9 config
ADD named.conf* /etc/bind/

# Add supervisord
ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf

# Add scripts
COPY --chmod=755 entrypoint.sh /entrypoint.sh

EXPOSE 22 53 389 88 135 139 138 445 464 3268 3269

ENTRYPOINT ["/entrypoint.sh"]
CMD ["app:start"]
```

`Dockerfile`内で追加しているその他のファイルは、以下のとおりです。


<details><summary>./ad/entrypoint.sh</summary>

```sh:./ad/entrypoint.sh
#!/bin/bash

set -e

SAMBA_DOMAIN=${SAMBA_DOMAIN:-SAMDOM}
SAMBA_REALM=${SAMBA_REALM:-SAMDOM.EXAMPLE.COM}
LDAP_ALLOW_INSECURE=${LDAP_ALLOW_INSECURE:-false}
SETUP_LOCK_FILE="/var/lib/samba/private/.setup.lock.do.not.remove"

if [[ $SAMBA_HOST_IP ]]; then
    SAMBA_HOST_IP="--host-ip=${SAMBA_HOST_IP}"
fi

appSetup () {
    echo "Initializing samba database..."

    SAMBA_ADMIN_PASSWORD=${SAMBA_ADMIN_PASSWORD:-$(pwgen -cny 10 1)}
    export KERBEROS_PASSWORD=${KERBEROS_PASSWORD:-$(pwgen -cny 10 1)}
    echo Samba administrator password: $SAMBA_ADMIN_PASSWORD
    echo Kerberos KDC database master key: $KERBEROS_PASSWORD

    # Workaround for some environment
    # https://github.com/lxc/lxc/issues/2708
    sed -i "s|lowerBound: 3000000|lowerBound: 655|" /usr/share/samba/setup/idmap_init.ldif
    sed -i "s|upperBound: 4000000|upperBound: 65533|" /usr/share/samba/setup/idmap_init.ldif

    # Provision Samba
    rm -f /etc/samba/smb.conf
    rm -rf /var/lib/samba/*
    mkdir -p /var/lib/samba/private
    samba-tool domain provision \
      --use-rfc2307 \
      --domain=$SAMBA_DOMAIN \
      --realm=$SAMBA_REALM \
      --server-role=dc \
      --option="vfs objects = acl_xattr xattr_tdb" \
      --option="idmap config * : range = 655-65533" \
      --dns-backend=BIND9_DLZ \
      --adminpass=$SAMBA_ADMIN_PASSWORD \
      $SAMBA_HOST_IP
    cp /var/lib/samba/private/krb5.conf /etc/krb5.conf

    # Configure smb.conf
    # Allow schema extension
    sed -i "s|\[global\]|[global]\n\t# allow schema extension\n\tdsdb:schema update allowed = true|" /etc/samba/smb.conf
    # Allow unencrypted connection 
    sed -i "s|\[global\]|[global]\n\t# allow unencrypted connection\n\tldap server require strong auth = no|" /etc/samba/smb.conf

    touch "${SETUP_LOCK_FILE}"
}

appStart () {
    if [ ! -f "${SETUP_LOCK_FILE}" ]; then
        appSetup
    fi
    exec /usr/bin/supervisord -c /etc/supervisor/conf.d/supervisord.conf
}

appHelp () {
	echo "Available options:"
	echo " app:start          - Starts all services needed for Samba AD DC"
	echo " app:setup          - First time setup."
	echo " app:setup_start    - First time setup and start."
	echo " app:help           - Displays the help"
	echo " [command]          - Execute the specified linux command eg. /bin/bash."
}

case "$1" in
	app:start)
		appStart
		;;
	app:setup)
		appSetup
		;;
	app:setup_start)
		appSetup
		appStart
		;;
	app:help)
		appHelp
		;;
	*)
		if [ -x $1 ]; then
			$1
		else
			prog=$(which $1)
			if [ -n "${prog}" ] ; then
				shift 1
				$prog $@
			else
				appHelp
			fi
		fi
		;;
esac

exit 0

```
</details>



<details><summary>./ad/named.conf.options</summary>

```./ad/named.conf.options
acl localnet {
    10.254.249.0/24;
    127.0.0.1;
};

options {
        directory "/var/cache/bind";

        listen-on port 53 {
                localnet;
        };

        allow-query {
                localnet;
        };

        allow-transfer {
                localnet;
        };


        // If there is a firewall between you and nameservers you want
        // to talk to, you may need to fix the firewall to allow multiple
        // ports to talk.  See http://www.kb.cert.org/vuls/id/800113

        // If your ISP provided one or more IP addresses for stable
        // nameservers, you probably want to use them as forwarders.
        // Uncomment the following block, and insert the addresses replacing
        // the all-0's placeholder.

        forwarders {
                127.0.0.11;
        };

        //========================================================================
        // If BIND logs error messages about the root key being expired,
        // you will need to update your keys.  See https://www.isc.org/bind-keys
        //========================================================================
        dnssec-validation no;

        listen-on-v6 { none; };

        tkey-gssapi-keytab "/var/lib/samba/bind-dns/dns.keytab";
};

```
</details>



<details><summary>./ad/supervisord.conf</summary>

```./ad/supervisord.conf
[supervisord]
nodaemon=true

[program:bind9]
command=/usr/sbin/named -c /etc/bind/named.conf -u bind -f

[program:samba]
command=/usr/sbin/samba -i

[program:syslog]
command=/usr/sbin/rsyslogd -n

```
</details>

# AD初期データの登録

次回以降の記事で、ADのグループとユーザーをプロビジョニングする予定です。そのための準備として、プロビジョニング対象のエントリを配置するOUを初期データとして登録しておきます。

初期データ登録用のシェルスクリプトとして、`ad-init.sh`を作成します。このシェルスクリプトでは以下のように、`ldapsearch`が成功したら（ADがLDAPリクエストを受け付け可能な状態になったら）`ldapadd`を実行し、いくつかOUを作成しておきます。

```sh:./ad/ad-init.sh
#!/bin/sh

set -e
  
cmd="$@"
  
until ldapsearch -x -D $ADMIN_BIND_DN -w $SAMBA_ADMIN_PASSWORD -b $ADMIN_BIND_DN cn=Administrator; do
  echo "AD is unavailable - sleeping"
  sleep 5
done
  
echo "AD is up - executing command"

ldapadd -D $ADMIN_BIND_DN -w $SAMBA_ADMIN_PASSWORD <<EOF
dn: OU=IDM,$BASE_DN
objectClass: organizationalUnit

dn: OU=Groups,OU=IDM,$BASE_DN
objectClass: organizationalUnit

dn: OU=Users,OU=IDM,$BASE_DN
objectClass: organizationalUnit
EOF
```

`./ad/supervisord.conf`を修正し、`ad-init.sh`をワンショットで実行するようにしておきます。

```diff:./ad/supervisord.conf
[program:syslog]
command=/usr/sbin/rsyslogd -n

+[program:ad_init]
+command=/ad-init.sh
+autorestart=false
```

`Dockerfile`を修正し、`add-init.sh`をコンテナ内にコピーするようにします。

```diff_docker:./ad/Dockerfile
# Add scripts
COPY --chmod=755 entrypoint.sh /entrypoint.sh
+COPY --chmod=755 ad-init.sh /ad-init.sh

EXPOSE 22 53 389 88 135 139 138 445 464 3268 3269
```


# docker-compose.ymlの修正

準備したSamba4コンテナをDocker Composeに組み込みます。今回、構築するドメインコントローラのドメインは`ad.example.com`としています。

```diff:./docker-compose.yml
      - ./hr:/var/lib/hr
      - ./addressbook:/var/lib/addressbook
 
+  ad:
+    cap_add:
+      - SYS_ADMIN
+    build:
+      context: ./ad
+    environment:
+      - SAMBA_DOMAIN=AD
+      - SAMBA_REALM=AD.EXAMPLE.COM
+      - SAMBA_ADMIN_PASSWORD=p@ssw0rd
+      - KERBEROS_PASSWORD=p@ssw0rd
+      - SAMBA_HOST_IP=10.254.249.100
+      # for ad-init
+      - ADMIN_BIND_DN=cn=Administrator,cn=Users,dc=ad,dc=example,dc=com
+      - BASE_DN=dc=ad,dc=example,dc=com
+    ports:
+      - "1389:389"
+    networks:
+      net:
+      ad_network:
+        ipv4_address: 10.254.249.100
+
 networks:
   net:
     driver: bridge
+  ad_network:
+    ipam:
+      driver: default
+      config:
+        - subnet: 10.254.249.0/24
 
 volumes:
   midpoint_data:
```

# 起動

`docker compose up -d --build`でビルドしつつ環境を起動します。

# 稼働確認

`docker compose exec ad bash`で起動したSamba4コンテナ（`ad`コンテナ）の中に入り、`ldapsearch`コマンドを実行して、Samba4に対してLDAP検索できることを確認しておきます。正常に起動して初期データが投入されていれば、以下のような結果になるはずです。


```bash
root@fd451d1f2926:/# ldapsearch -D cn=Administrator,cn=Users,dc=ad,dc=example,dc=com -w p@ssw0rd -b ou=IDM,dc=ad,dc=example,dc=com dn
# extended LDIF
#
# LDAPv3
# base <ou=IDM,dc=ad,dc=example,dc=com> with scope subtree
# filter: (objectclass=*)
# requesting: dn
#

# Groups, IDM, ad.example.com
dn: OU=Groups,OU=IDM,DC=ad,DC=example,DC=com

# IDM, ad.example.com
dn: OU=IDM,DC=ad,DC=example,DC=com

# Users, IDM, ad.example.com
dn: OU=Users,OU=IDM,DC=ad,DC=example,DC=com

# search result
search: 2
result: 0 Success

# numResponses: 4
# numEntries: 3
```

# フォレスト構成について補足

大規模な組織では、複数のADでフォレスト間信頼を構成しているケースが考えられます。また、FSP（Foreign Security Principal）[^1]を使用して、別ドメインのユーザーを自ドメイン内のセキュリティグループに追加し、リソースに対するパーミッションを付与する運用をしているケースも想定されます。そのようなケースであっても、Samba4で開発/検証環境を構築することが可能です。

今回の記事では単体のSamba4のみ構築していますが、これをベースに別のSamba4を追加してフォレスト構成設定を行うことで、Docker Compose環境でもフォレスト構成を構築することができます。Samba4でフォレスト間信頼を設定するには、[こちらのStefan Kania氏のサイト](https://www.kania-online.de/veroeffentlichungen/)で公開されている[Seting up trusts between two Samba-domains](https://www.kania-online.de/wp-content/uploads/2019/06/trusts-tutorial-en.pdf)を参考にするとよいでしょう。

[^1]:FSPについては、Microsoftのページ（ https://learn.microsoft.com/en-us/archive/technet-wiki/51367.active-directory-foreign-security-principals-and-special-identities ）を参照するとよいでしょう。


また、FSPを使ってセキュリティグループのメンバー管理をmidPointで行うには、少々複雑な設定が必要になります。過去に、以下の記事を書いたことがありますので、参考にしていただければと思います。

https://qiita.com/wadahiro/items/42c35db33507f87f094f


# Microsoft Exchange Server用属性について補足

ADを運用されている組織において、Microsoft Exchange Serverも合わせて導入されているケースも多いでしょう。その場合、Microsoft Exchange Server用の拡張属性をADにインストールしているケースがあります。そのようなケースであっても、Samba4でも対応は可能です。Samba4ではスキーマの拡張機能が提供されており、以下のドキュメントに記載があります。

https://wiki.samba.org/index.php/Samba_AD_schema_extensions

例えば、`extensionAttribute1`属性を追加したい場合、AD起動後に以下のLDIFで`ldapmodify`により更新を行うことで、拡張属性の追加が可能です（ドメインが`ad.example.com`の場合）。

```
dn: CN=extensionAttribute1,CN=Schema,CN=Configuration,DC=ad,DC=example,DC=com
changetype: add
objectClass: attributeSchema
attributeID: 1.2.840.113556.1.2.423
lDAPDisplayName: extensionAttribute1
description: extensionAttribute1
attributeSyntax: 2.5.5.12
isSingleValued: TRUE

dn: CN=msExchCustomAttributes,CN=Schema,CN=Configuration,DC=ad,DC=example,DC=com
changetype: add
objectClass: classSchema
governsID: 1.2.840.113556.1.5.7000.62.6
lDAPDisplayName: msExchCustomAttributes
subClassOf: top
objectClassCategory: 3
description: msExchCustomAttributes
mayContain: extensionAttribute1
```

Microsoft Exchange Server用の拡張属性のスキーマ情報は、以下のサイトに記載されています。利用されているAD環境に合わせて、必要な拡張属性をSamba4に追加しておくとよいでしょう。

https://learn.microsoft.com/en-us/previous-versions/office/developer/exchange-server-2003/ms869146(v=exchg.65)


# まとめ

15日目では、midPointからADへのプロビジョニングを行うための準備作業として、Docker Compose環境にAD互換環境となるSamba4を追加しました。開発者個人ごとに専用の環境としてADを準備するのは中々大変ですが、Samba4を使うと個人のDocker Compose環境に組み込むこともできて、midPointとともに即座にセットアップ可能という大きなメリットがあります。ただし、あくまでAD互換環境であることはお忘れのないようにご注意ください。最終的な連携テストは実際のADと繋げて実施するようにしましょう。

明日は、本日の環境を使ってリソース設定を追加し、AD（Samba4）にプロビジョニングを実際に行う予定です。お楽しみに！