Ubuntu

Ubuntu22.04 DockerでSamba-ad-dc

旧バージョンのページ。

[追記 2022/10/08]
この記事に掲載しているファイルをダウンロードできるようにした(設定を簡単にするため一部を改変している)。

目指す構成

公式のDockerイメージを探してみたけれど、見つからなかった。
とても勉強になるDockerのイメージがあり、これを参考に色々とやっていく。
GitHub – Fmstrat/samba-domain: Samba Active Directory Domain Controller for Docker

実際にSamba ad dcを動かすにあたっては、過去にやったSamba ad dcのインストール手順を確認しながら進める。

Dockerのインストール自体は、以前整理した手順を使う。

まずはプライマリーDCを構成して、色々動くようにする。
もしもセカンダリーDCが作りたくなったら、後から追加する。
DCが壊れたら、リストアドDCを立ち上げて復旧する。

ホストコンテナのホスト名IP Address説明
mirror192.168.110.4ネットワークに向けてプライマリーDCとして振る舞うように設定する。
addcを動かすホスト。
Ubuntu 20.04と22.04のリポジトリ(ミラーサーバー)。
mirroraddc172.26.0.101mirrorで動くSamba ad dc(プライマリーDC)。
nanashi192.168.110.34ネットワークに向けてセカンダリーDCとして振る舞うように設定する。
addc2を動かすホスト。
nanashiaddc2172.26.0.102nanasiで動くSamba ad dc(セカンダリーDC)。
work192.168.110.3Ubuntu 20.04のサーバー。ドメインに参加するテスト用。
WinTemp192.168.110.21Windows 10。ドメインに参加するテスト用。
router192.168.110.1プロバイダーから貸与されているルーター。
プロバイダーが用意してくれているDNSで名前解決する。
classc192.168.110.10ネットワークに向けてリストアドDCとして振る舞うように設定する。
addcrを動かすホスト。
classcaddcr172.26.0.103classcで動くSamba ad dc(リストアドDC)。

コンテナのIPアドレスは同じネットワークにいるように見えて、別のホストなのでつながってはいない。
Samba ad dc同士が接続したときに、コンテナのIPアドレスを伝えたりするので、問題を防止するために明示的に別のIPアドレスを割り振った。

現在、ホームラボで稼働中のSamba ad dcをバージョンアップするにあたり、セカンダリーDCを作って、プライマリーDCを削除…とDCが新しくなるシナリオなの?と思っていたので、セカンダリーDCについて真面目に整理してみた。
もちろんこの方法も可能だったが、リストアドDCを立てるやり方もある。

構成するサービスと共に図にしてみると、このようなイメージ。

┌───┐
│router│
└─┬─┘
    │
    └┬───────────┬──────┬──────┬───────────┬────────┬───────────┬─… work, WinTemp, etc.
┌──┼───────────┼──────┼──┐┌──┼───────────┼────┐┌──┼───────────┼────┐
│┌─┴──┐                │            │    ││┌─┴──┐                │        ││┌─┴──┐                │        │
││Apache2 ├─┐            │            │    │││Apache2 ├─┐            │        │││Apache2 ├─┐            │        │
│└────┘  │            │            │    ││└────┘  │            │        ││└────┘  │            │        │
│              │ - Docker - │            │    ││              │ - Docker - │        ││              │ - Docker - │        │
│          ┌─┴──┐┌──┴───┐┌─┴─┐││          ┌─┴──┐┌──┴───┐││          ┌─┴──┐┌──┴───┐│
│          │Apache2 ││Samba ad dc ││rsync │││          │Apache2 ││Samba ad dc │││          │Apache2 ││Samba ad dc ││
│          └─┬──┘└──────┘└───┘││          └─┬──┘└──────┘││          └─┬──┘└──────┘│
│    ┌────┴─────┐                    ││    ┌────┴─────┐          ││    ┌────┴─────┐          │
│    │LDAP Account Manager│                    ││    │LDAP Account Manager│          ││    │LDAP Account Manager│          │
│    └──────────┘                    ││    └──────────┘          ││    └──────────┘          │
│         mirror.hogeserver.hogeddns.jp          ││    nanasi.hogeserver.hogeddns.jp     ││    classc.hogeserver.hogeddns.jp     │
│           addc.hogeserver.hogeddns.jp          ││     addc2.hogeserver.hogeddns.jp     ││     addcr.hogeserver.hogeddns.jp     │
└────────────────────────┘└───────────────────┘└───────────────────┘
                   [Primary DC]                                  [Secondary DC]                             [Restored DC]

プライマリーDC

DockerでSamba ad dcが動くイメージを作る。

  • Ubuntu 22.04をベースイメージとして、用意されているパッケージを使用する。
  • DNSはSamba内蔵のものとBIND9を選択可能。
  • コンテナの中でphpLDAPadminとLDAP Account Managerを動作させる。
    phpLDAPadminは1.2.3(古いバージョン)となっている。

作業のベースとなるイメージの作成

何度も試行錯誤することになったので、ベースとなるイメージを作り、自作のスクリプトだけを差し替えられるようにした。

~/samba/baseimage/Dockerfile

FROM ubuntu:jammy
USER root
ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
    apt upgrade -y && \
    apt install -y \
      apache2 \
      bind9 \
      dnsutils \
      iproute2 \
      iputils-ping \
      krb5-user \
      ldap-account-manager \
      ldap-utils \
      ldb-tools \
      libnss-winbind \
      libpam-winbind \
      locales \
      phpldapadmin \
      rsync \
      samba \
      smbclient \
      tzdata \
      vim \
      winbind && \
    echo "deb https://ppa.launchpadcontent.net/ondrej/php/ubuntu/ jammy main" > /etc/apt/sources.list.d/ondrej-ubuntu-php-jammy.list && \
    echo "# deb-src https://ppa.launchpadcontent.net/ondrej/php/ubuntu/ jammy main" >> /etc/apt/sources.list.d/ondrej-ubuntu-php-jammy.list && \
    gpg --keyserver hkps://keyserver.ubuntu.com --recv-key 4F4EA0AAE5267A6C && \
    gpg -a --export 4F4EA0AAE5267A6C | gpg --dearmour -o /etc/apt/trusted.gpg.d/ondrej.gpg && \
    apt update && \
    apt install -y \
      php7.3 php7.3-ldap php7.3-xml php7.3-imagick php7.3-mbstring php7.3-gmp php7.3-zip && \
    locale-gen en_US.UTF-8

ベースとなるイメージを作る。

$ sudo docker build -t custom/samba:0.0.1 -f ~/samba/baseimage/Dockerfile .

ファイル構成

ファイル構成は以下の通り。

~/samba/
   ├ docker-compose.yml
   ├ Dockerfile
   ├ entrypoint.sh ← 実行権限を付ける
   └ packages/
       ├ config-primary.sh ← 実行権限を付ける
       ├ phpLDAPadmin-1.2.3.tar.gz ← あればphpLDAPadminが使えるようにする
       └ cert ← あればLDAPSを使えるようにする
           ├ ca.crt
           ├ server.crt
           ├ server.key ← アクセス権限 0600
           └ ca.crl

phpLDAPadminについては、現在のメンテナーさんが持っている一番古い1.2.3を使わせていただいている。
この後のバージョン(1.2.4)からは、ログイン情報を入れてもanonymousになってしまう問題が解消できず、上手く使える状態にならなかったため。
こちらのリンクから1.2.3のtar.gzをダウンロードしている。
Github / leenooks / phpLDAPadmin – tags

docker-compose.yml

架空のレルム HOGESERVER.HOGEDDNS.JP (HOGEDOMAIN) を運営する。

環境に合わせて変更するのはこのファイル。
後のファイルは、この設定に合わせて動作するようになっている。

SMB_HOSTIPでは、このコンテナを動かすホストのIPアドレスを指定しており、プロビジョン時に使う。
コンテナで動作させる=NAT配下にいるようなもの なので、この指定が必要。

SMB_PRIMARYでは、このコンテナがプライマリーDCであることを指定している。
セカンダリーDCリストアドDCについては後述。

hostname: addcとしている、このaddcがこのサーバーの名前になる。
addc.hogeserver.hogeddns.jp
運用上、付けたい名前があれば、ここに設定しておく。

~/samba/docker-compose.yml

version: "3.9"
services:

  samba:
    build: ./
    image: custom/samba:1.0.0
    container_name: samba
    restart: unless-stopped
    environment:
      TZ: Asia/Tokyo
      SMB_REALM: HOGESERVER.HOGEDDNS.JP
      SMB_DOMAIN: HOGEDOMAIN
      SMB_ADMINPASS: p@ssword123
      SMB_HOSTIP: 192.168.110.4
      SMB_RPC_PORTS: 49152-49200
      SMB_PURPOSE: "primary"
      SMB_USEBIND9: "false"
#      RSY_SECONDARY: 192.168.110.34
#      RSY_PASS: p@ssword234
    volumes:
      - samba_etc:/etc/samba
      - samba_lib:/var/lib/samba
      - bind_etc:/etc/bind
      - bind_lib:/var/lib/bind
      - lam:/var/lib/ldap-account-manager
    networks:
      samba:
        ipv4_address: 172.26.0.101
    ports:
      - 192.168.110.4:53:53       #DNS
      - 192.168.110.4:53:53/udp   #DNS
      - 192.168.110.4:135:135     #End Point Mapper(WINS)
      - 192.168.110.4:137:137/udp #NetBIOS Name Service
      - 192.168.110.4:138:138/udp #NetBIOS Datagram
      - 192.168.110.4:139:139     #NetBIOS Session
      - 192.168.110.4:445:445     #SMB over TCP
      - 192.168.110.4:389:389     #LDAP
      - 192.168.110.4:389:389/udp #LDAP
      - 192.168.110.4:636:636     #LDAPS
      - 192.168.110.4:88:88       #Kerberos
      - 192.168.110.4:88:88/udp   #Kerberos
      - 192.168.110.4:464:464     #Kerberos kpasswd
      - 192.168.110.4:464:464/udp #Kerberos kpasswd
      - 192.168.110.4:3268:3268   #Global Catalog
      - 192.168.110.4:3269:3269   #Global Catalog SSL
                                  #RPC The same value as SMB_RPC_PORTS.
      - 192.168.110.4:49152-49200:49152-49200
      - 873:873 #rsync
      - 8081:80 #phpLDAPadmin & LDAP Account Manager
    hostname: addc
    dns:
      - 192.168.110.1
    dns_search:
      - hogeserver.hogeddns.jp
    privileged: true
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN

networks:
  samba:
    ipam:
      config:
        - subnet: 172.26.0.0/16
          gateway: 172.26.0.1

volumes:
  samba_etc:
  samba_lib:
  bind_etc:
  bind_lib:
  lam:

Dockerfile

最初に作ったベースイメージに、今回使用するいくつかのファイルを追加する。

~/samba/Dockerfile

FROM custom/samba:0.0.1
USER root
ENV LANG=en_US.UTF-8 \
    LANGUAGE=en_US:en \
    LC_ALL=en_US.UTF-8
ADD entrypoint.sh /
ENTRYPOINT ["/entrypoint.sh"]
COPY ./packages /root/packages

entrypoint.sh

コンテナが起動したときに呼び出され、各サービスを起動して無限ループに入る。
終了のシグナルを受け取ったら、各サービスを止める。

supervisorを使ったプロセス管理が格好良さそうだけれど、実際に動かしてみるとこの無限ループ、たいした負荷ではない。
何らかの理由でSamba ad dcが落ちていても、調査のためにログインできる良さもあるので、割り切りとしている。

~/entrypoint.sh

#!/bin/bash

echo "Start Samba container with parameter : $@"

trap sig_term SIGTERM

sig_term() {
    echo "CATCH SIGTERM"
    pkill -SIGTERM ^samba$
    /usr/sbin/apachectl stop
    case $SMB_PURPOSE in
        "primary")
            pkill -SIGTERM ^rsync$
        ;;
        "secondary")
            pkill -SIGTERM ^cron$
            ;;
    esac
    if [ $SMB_USEBIND9 = "true" ]; then
        /usr/sbin/rndc stop
    fi
    wait
    exit 0
}

# Make configuration
case $SMB_PURPOSE in
    "primary")   /root/packages/config-primary.sh;;
    "secondary") /root/packages/config-secondary.sh;;
    "restore")   /root/packages/config-restore.sh;;
    *) echo "Purporse do not match. : "$SMB_PURPOSE
esac

# Execute paramater.
exec "$@"

# Start services.
/usr/sbin/samba --interactive --no-process-group &
/usr/sbin/apachectl start
case $SMB_PURPOSE in
    "primary")
        /usr/bin/rsync --daemon --no-detach &
        ;;
    "secondary")
        /usr/sbin/cron
        ;;
esac
if [ $SMB_USEBIND9 = "true" ]; then
    /usr/sbin/named -u bind
fi

# Infinity roop.
while : ; do sleep 1 ; done

ファイルを作ったら、実行権を付けておく。

$ chmod +x ~/entrypoint.sh

config-primary.sh

コンテナが起動したときに呼び出されるスクリプトで、コンテナとボリュームの状況に合わせて、必要な初期設定を実行する。

フェーズボリュームコンテナ動作
1なしなしSamba ad dcドメインの初期設定を実行する。
/etc/samba/smb.conf はここで生成されたものを永続化している。
続けて、フェーズ2を実行する。
2ありなし認証設定(Winbindを追加)、Kerberos設定、rsync設定、phpLDAPadminの設定を実行する。
これらは永続化していないので、コンテナが作られる度に再設定をする。
続けて、フェーズ3を実行する。
3ありありコンテナ内部のリゾルバを127.0.0.11→127.0.0.1に向ける。
これはコンテナを実行する度に設定が必要。
結果として、Samba ad dcが名前解決をするようになる。

Provisionの際のパラメーターは、スクリプトの最初の方で作っているので、必要に応じて修正する。
とはいえ、/etc/sambaディレクトリを永続化しているので、致命的な問題でなければ「後からどうにでもできる」想定。

名前解決は、通常
 コンテナの中のプログラム → 127.0.0.11 → docker-compose.ymlで指定したDNS
で行われる。

Samba ad dcを運用する場合、これでは具合が悪いので、
 コンテナの中のプログラム → 127.0.0.1(=Samba ad dc) → 127.0.0.11 → docker-compose.ymlで指定したDNS
という名前解決順序になるよう、コンテナを起動する度に/etc/resolv.confを書き換えている。

なお、外部からの問い合わせには、
 他のホスト → 192.168.110.4 → 127.0.0.1 → Samba ad dc → …
という順序で名前を解決する。

~/samba/packages/config-primary.sh

#!/bin/bash
echo "Primary domain controller settings."

#----------------------------------------------------------------------
# New volumes.
#----------------------------------------------------------------------
if [ -z "$(ls /var/lib/samba/private)" ]; then
    echo "New volumes."

    # Make provision parameters.
    SMB_TMP_PARAM="
        --use-rfc2307
        --realm=$SMB_REALM
        --domain=$SMB_DOMAIN
        --server-role=dc
        --adminpass=$SMB_ADMINPASS
        --option=\"dns forwarder = 127.0.0.11\"
        --option=\"dns update command = /usr/sbin/samba_dnsupdate --current-ip $SMB_HOSTIP\"
        --option=\"template homedir = /home/%D/%U\"
        --option=\"template shell = /bin/bash\"
        --option=\"winbind enum users = yes\"
        --option=\"winbind enum groups = yes\"
        --option=\"idmap config $SMB_DOMAIN:unix_nss_info = yes\"
        --option=\"idmap config $SMB_DOMAIN:unix_primary_group = yes\"
        --option=\"rpc server dynamic port range = $SMB_RPC_PORTS\"
        --host-ip=$SMB_HOSTIP
    "
    if [ $SMB_USEBIND9 = "true" ]; then
        SMB_TMP_PARAM+=" --dns-backend=BIND9_DLZ"
    else
        SMB_TMP_PARAM+=" --dns-backend=SAMBA_INTERNAL"
    fi

    # LDAPS settings.
    mkdir /var/lib/samba/private/tls/
    TMP_LDAPS=0
    cp -a /root/packages/cert/ca.crt /usr/local/share/ca-certificates/ && \
        update-ca-certificates && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x01)) && \
        SMB_TMP_PARAM+=" --option=\"tls cafile   = /usr/local/share/ca-certificates/ca.crt\""
    cp -a /root/packages/cert/server.crt /var/lib/samba/private/tls/ && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x02)) && \
        SMB_TMP_PARAM+=" --option=\"tls certfile = /var/lib/samba/private/tls/server.crt\""
    cp -a /root/packages/cert/server.key /var/lib/samba/private/tls/ && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x04)) && \
        chmod 600 /var/lib/samba/private/tls/server.key && \
        SMB_TMP_PARAM+=" --option=\"tls keyfile  = /var/lib/samba/private/tls/server.key\""
    cp -a /root/packages/cert/ca.crl /var/lib/samba/private/tls/ && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x08)) && \
        SMB_TMP_PARAM+=" --option=\"tls crlfile  = /var/lib/samba/private/tls/ca.crl\""

    if [ $(($TMP_LDAPS & 0x07)) -eq 7 ]; then
        echo "Enable LDAPS."
        SMB_TMP_PARAM+=" --option=\"tls enabled  = true\"
                 --option=\"tls verify peer = as_strict_as_possible\"
        "
    else
        echo "Disable Strong Auth."
        SMB_TMP_PARAM+="
            --option=\"ldap server require strong auth = no\"
        "
    fi

    set -f
    SMB_TMP_PARAM=$(echo $SMB_TMP_PARAM)
    #echo "provision parameters: $SMB_TMP_PARAM"
    set +f

    # Domain service settings.
    mv --backup=numbered /etc/samba/smb.conf /etc/samba/smb.conf.bak
    eval samba-tool domain provision "$SMB_TMP_PARAM"
    if [ $? -ne 0 ]; then exit 0; fi

    # Stop needlessly complicated passwords.
    samba-tool domain passwordsettings set \
      --complexity=off \
      --history-length=0 \
      --min-pwd-length=8 \
      --min-pwd-age=0 \
      --max-pwd-age=0

fi

#----------------------------------------------------------------------
# Volumes is left.
#----------------------------------------------------------------------
if [ ! -e /root/packages/configured ]; then
    echo "New container."

    # Register CA certificates.
    cp -a /root/packages/cert/ca.crt /usr/local/share/ca-certificates/ && \
        update-ca-certificates

    # Authentication sttings.
    sed -i "s/^\(passwd: \+\)[a-z ]\+$/\1compat winbind/" /etc/nsswitch.conf
    sed -i "s/^\(group: \+\)[a-z ]\+$/\1compat winbind/" /etc/nsswitch.conf

    # Copy krb5.conf
    mv --backup=numbered /etc/krb5.conf /etc/krb5.conf.bak
    cp /var/lib/samba/private/krb5.conf /etc/

    # Make rsync configuration.
cat <<EOF > /etc/rsyncd.conf
[SysVol]
path = /var/lib/samba/sysvol/
comment = Samba Sysvol Share
uid = root
gid = root
hosts allow = $RSY_SECONDARY
hosts deny = *
read only = yes
auth users = sysvol-replication
secrets file = /etc/rsyncd.secret
EOF

cat <<EOF > /etc/rsyncd.secret
sysvol-replication:$RSY_PASS
EOF
    chmod 600 /etc/rsyncd.secret

    # Suppress apache warning.
    echo "ServerName localhost" | tee /etc/apache2/conf-available/fqdn.conf
    a2enconf fqdn

    # Setup phpLdapAdmin.
    if [ -e /root/packages/phpLDAPadmin-1.2.3.tar.gz ]; then
        a2dismod php8.1
        a2enmod php7.3

        tar zxf /root/packages/phpLDAPadmin-1.2.3.tar.gz -C /var/www/
        mv /var/www/phpLDAPadmin-1.2.3 /var/www/phpldapadmin
        cp /etc/phpldapadmin/apache.conf /etc/phpldapadmin/apache.conf.bak
        sed -i "s@/usr/share/phpldapadmin/htdocs@/var/www/phpldapadmin@g" /etc/phpldapadmin/apache.conf
        cp /var/www/phpldapadmin/config/config.php.example /var/www/phpldapadmin//config/config.php
        if [ $(grep "tls verify peer = as_strict_as_possible" /etc/samba/smb.conf -c) -ne 0 ]; then
            sed -i "$ i\$servers->setValue('server','host','ldaps://$(hostname).${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        else
            sed -i "$ i\$servers->setValue('server','host','ldap://$(hostname).${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        fi
        sed -i "$ i\$servers->setValue('login','bind_id','administrator@${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        sed -i "$ i\$config->custom->appearance['hide_template_warning'] = true;" /var/www/phpldapadmin/config/config.php
        sed -i "s/\$servers->setValue('server','name','My LDAP Server');/\$servers->setValue('server','name','$SMB_DOMAIN');/" /var/www/phpldapadmin/config/config.php

        # Customize phpLDAPadmin
        # for PHP7.0
        sed -i "s/password_hash/password_hash_custom/g" /var/www/phpldapadmin/lib/*
        sed -i '2567d; 2568d; 2569i \\t\tforeach ($dn as $key => $rdn) {\n\t\t\t$a[$key] = preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return '\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $rdn\'');\n\t\t}' /var/www/phpldapadmin/lib/functions.php
        sed -i '2574c \\t\treturn  preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return'\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $dn);' /var/www/phpldapadmin/lib/functions.php
        sed -i '1119d; 1120d; 1121i \\t\t\tforeach ($dn as $key => $rdn) {\n\t\t\t\t$a[$key] = preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return '\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $rdn\'');\n\t\t\t}' /var/www/phpldapadmin/lib/ds_ldap.php
        sed -i '1126c \\t\t\treturn  preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return'\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $dn);' /var/www/phpldapadmin/lib/ds_ldap.php
        # for PHP7.3
        sed -i '54c function my_autoload($className) {' /var/www/phpldapadmin/lib/functions.php
        sed -i '777c spl_autoload_register("my_autoload");' /var/www/phpldapadmin/lib/functions.php
        sed -i '1083c \\t\t$CACHE[$sortby] = __create_function('\''$a, $b'\'',$code);' /var/www/phpldapadmin/lib/functions.php
        sed -i '1091a function __create_function($arg, $body) {\n\tstatic $cache = array();\n\tstatic $maxCacheSize = 64;\n\tstatic $sorter;\n\n\tif ($sorter === NULL) {\n\t\t$sorter = function($a, $b) {\n\t\t\tif ($a->hits == $b->hits) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn ($a->hits < $b->hits) ? 1 : -1;\n\t\t};\n\t}\n\n\t$crc = crc32($arg . "\\\\x00" . $body);\n\n\tif (isset($cache[$crc])) {\n\t\t++$cache[$crc][1];\n\t\treturn $cache[$crc][0];\n\t}\n\n\tif (sizeof($cache) >= $maxCacheSize) {\n\t\tuasort($cache, $sorter);\n\t\tarray_pop($cache);\n\t}\n\n\t$cache[$crc] = array($cb = eval('\''return function('\''.$arg.'\''){'\''.$body.'\''};'\''), 0);\n\treturn $cb;\n}\n' /var/www/phpldapadmin/lib/functions.php
    fi

    # Mark as configured.
    touch /root/packages/configured
fi

#----------------------------------------------------------------------
# Container and Volumes is left.
#----------------------------------------------------------------------
echo "Setting to do every time"

# Resolver settings.
cp /etc/resolv.conf /root/packages/resolv.conf
sed -i "s/nameserver 127.0.0.11/nameserver 127.0.0.1/" /root/packages/resolv.conf
cat /root/packages/resolv.conf > /etc/resolv.conf

# Switch DNS backend.
if [ $SMB_USEBIND9 = "true" ]; then
    if [ ! -e /var/lib/samba/bind-dns/named.conf ]; then
        samba_upgradedns --dns-backend=BIND9_DLZ
    fi
    # Make bind9 configuration.
    if [ $(grep "bind-dns" /etc/bind/named.conf -c) -eq 0 ]; then
        cp -a /etc/bind/named.conf /etc/bind/named.conf.bak
        sed -i "\$a include \"/var/lib/samba/bind-dns/named.conf\";" /etc/bind/named.conf
        cp -a /etc/bind/named.conf.options /etc/bind/named.conf.options.bak
        sed -i "/listen-on-v6/a\\\n\tforwarders { 127.0.0.11; };\n\tallow-query { any; };\n\tallow-transfer { none; };\n\ttkey-gssapi-keytab \"/var/lib/samba/bind-dns/dns.keytab\";\n\tminimal-responses yes;" /etc/bind/named.conf.options
        cp -a /etc/bind/named.conf.local /etc/bind/named.conf.local.bak
        sed -i "s@^//include@include@" /etc/bind/named.conf.local
    fi
    if [[ $(grep -c "server services" /etc/samba/smb.conf) -eq 0 ]]; then
        sed -i "9a\\\tserver services = -dns" /etc/samba/smb.conf
    fi
else
    if [ -e /var/lib/samba/bind-dns/named.conf ]; then
        samba_upgradedns --dns-backend=SAMBA_INTERNAL
        sed -i "/server services/d" /etc/samba/smb.conf
    fi
fi

ファイルを作ったら、実行権を付けておく。

$ chmod +x ~/samba/packages/config-primary.sh

証明書類

証明書類は、なければないでLDAPが使えるように動作する。
用意しておけばLDAPSが使えるようになる。

証明書類は、以下の固定した名前で ~/samba/packages/certに入れる。
スクリプトは固定のファイル名で処理をしているので、ファイル名を変更したければ、証明書類の名前と一緒にスクリプトを修正する。

ファイル内容
ca.crt認証局の証明書。
server.crtSamba ad dcの証明書。ca.crtの認証局が署名したものを想定。
server.keySamba ad dcの秘密鍵。パスワードは外しておく。
ca.crlca.crtの認証局が発行するcertificate revocation list(CRL)。

ホームラボでは、いわゆるオレオレ認証局を運営しており、架空サイト hogeserver.hogeddns.jp と *.hogeserver.hogeddns.jp に向けたワイルドカード証明書を発行した。

スクリプトでは、ca.crt、server.crt、server.keyの3ファイルがあるとLDAPSが有効になるようにしてある。
ca.crlは無効にした証明書ができたら更新するべきものではあるが、なくても動作する。
もし更新するなら、docker cp等で放り込めば良い。

起動と確認

ファイルの準備ができたら、ファイアウォールを設定する。
Samba ad dcの機能のなかに、IPアドレスで接続してくるものがあるため。

$ sudo ufw allow from 172.26.0.101 to 192.168.110.4 comment "From container"

コンテナを起動する。

$ sudo docker compose up --build

別のターミナルを開き、様子を見ながら進める。
まずは、sambaとapacheが起動していることを確認。

$ sudo docker exec -it samba /bin/bash --login
# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   7360  3788 ?        Ss   08:37   0:00 /bin/bash /entrypoint.sh
root        1609  0.1  1.0 132160 42348 ?        S    08:37   0:00 samba: root process
root        1615  0.0  0.7 224100 28404 ?        Ss   08:37   0:00 /usr/sbin/apache2 -k start
…

※大量のプロセスが動いているが、上から3つを確認。

共有の一覧表示。

# smbclient -L localhost -N
Anonymous login successful

        Sharename       Type      Comment
        ---------       ----      -------
        sysvol          Disk
        netlogon        Disk
        IPC$            IPC       IPC Service (Samba 4.15.9-Ubuntu)
SMB1 disabled -- no workgroup available

共有への接続。

# smbclient //localhost/netlogon -U administrator -c 'ls'
Password for [HOGEDOMAIN\administrator]: ユーザーadministratorのパスワード[Enter]
  .                                   D        0  Thu Aug 11 10:09:29 2022
  ..                                  D        0  Thu Aug 11 10:09:33 2022

                19948144 blocks of size 1024. 8855556 blocks available

チケットの発行。

# kinit administrator
Password for administrator@HOGESERVER.HOGEDDNS.JP: ユーザーadministratorのパスワード[Enter]

# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: administrator@HOGESERVER.HOGEDDNS.JP

Valid starting       Expires              Service principal
08/11/2022 08:41:30  08/11/2022 18:41:30  krbtgt/HOGESERVER.HOGEDDNS.JP@HOGESERVER.HOGEDDNS.JP
        renew until 08/12/2022 08:41:22

# kdestroy

名前解決。

# dig @127.0.0.1 addc.hogeserver.hogeddns.jp +short
192.168.110.4

# dig @192.168.110.4 addc.hogeserver.hogeddns.jp +norec
…
;; ANSWER SECTION:
addc.hogeserver.hogeddns.jp. 900 IN     A       192.168.110.4

;; AUTHORITY SECTION:
hogeserver.hogeddns.jp. 3600    IN      SOA     addc.hogeserver.hogeddns.jp. hostmaster.hogeserver.hogeddns.jp. 1 900 600 86400 3600

;; Query time: 0 msec
;; SERVER: 192.168.110.4#53(192.168.110.4) (UDP)
;; WHEN: Thu Aug 11 08:55:50 JST 2022
;; MSG SIZE  rcvd: 108

ただし、ホスト名で問い合わせると、コンテナ内部のIPアドレスが表示される。
workから名前解決を試みた結果がこちら。

# dig addc
…
;; ANSWER SECTION:
addc.                   600     IN      A       172.26.0.101

;; Query time: 4 msec
;; SERVER: 127.0.0.1#53(127.0.0.1) (UDP)
;; WHEN: Sun Aug 14 17:39:58 JST 2022
;; MSG SIZE  rcvd: 38

/etc/hostsも変えてみたけれども、問い合わせの結果は変わらない。
この情報が表示される理由は明確にできなかったので、ドメイン名を付けることで問題を回避することにした。

ゾーン情報。

# samba-tool dns query localhost hogeserver.hogeddns.jp @ ALL -U administrator
Password for [HOGEDOMAIN\administrator]: ユーザーadministratorのパスワード[Enter]
  Name=, Records=3, Children=0
    SOA: serial=1, refresh=900, retry=600, expire=86400, minttl=3600, ns=addc.hogeserver.hogeddns.jp., email=hostmaster.hogeserver.hogeddns.jp. (flags=600000f0, serial=1, ttl=3600)
    NS: addc.hogeserver.hogeddns.jp. (flags=600000f0, serial=1, ttl=900)
    A: 192.168.110.4 (flags=600000f0, serial=1, ttl=900)
  Name=_msdcs, Records=0, Children=0
  Name=_sites, Records=0, Children=1
  Name=_tcp, Records=0, Children=4
  Name=_udp, Records=0, Children=2
  Name=addc, Records=1, Children=0
    A: 192.168.110.4 (flags=f0, serial=1, ttl=900)
  Name=DomainDnsZones, Records=0, Children=2
  Name=ForestDnsZones, Records=0, Children=2

問題なく起動できたようだ。

ユーザーとグループの追加

ユーザーをいくつか追加してみる。

# samba-tool user add rohhie --given-name=Rohhie --surname=Puyopuyo --mail-address=rohhie@hogeserver.hogeddns.jp
New Password: ユーザーrohhieのパスワード[Enter]
Retype Password: パスワード確認入力[Enter]
User 'rohhie' added successfully

# samba-tool user add ssoauth p@ssword123 --given-name=Authentication --surname=SystemUser --mail-address=ssoauth@hogeserver.hogeddns.jp
# samba-tool user add hoge p@ssword123 --given-name=User --surname=Hoge --mail-address=hoge@hogeserver.hogeddns.jp
# samba-tool user add hogewife p@ssword123 --given-name=User --surname=Wife --mail-address=hogewife@hogeserver.hogeddns.jp

グループを作ってみる。

# samba-tool group add parent --mail-address=parent@hogeserver.hogeddns.jp
Added group parent

グループにユーザーを追加する。

# samba-tool group addmembers parent hoge,hogewife
Added members to group parent

rohhieを管理者グループに、ssoauthをDNS管理者グループに入れる。

# samba-tool group addmembers Administrators rohhie
Added members to group Administrators
# samba-tool group addmembers "Domain Admins" rohhie
Added members to group Domain Admins
# samba-tool group addmembers DnsAdmins ssoauth
Added members to group DnsAdmins

※Windowsにログインして確認してみたら、AdministratorsにはDomain Adminsが含まれている。ここに入れないと、Windowsクライアントでは管理者にならない。
※ssoauthは認証のときにLDAPを参照するだけの人なので、DnsAdminsはやり過ぎかもしれない…

Samba ad dcでプライマリーDCを稼働させることができた。

ドメイン参加と離脱(Ubuntu)

ホームラボにあるUbuntuをドメインに参加させてみる。

元々考えていたよりはだいぶ手間取った印象。
操作自体が難しいわけではなくて、その結果がどうなるのか、というところの確認に時間が掛かっている。

ドメインへの参加

以前作っていたUbuntu 20.04サーバー(work.example.jp)をドメインに参加させてみることにした。
DNSとしてSamba ad dcを参照し、ドメイン指定がなければhoserver.hogeddns.jpを探すようにネットワーク設定を変更する。

/etc/netplan/00-installer-config.yaml

# This is the network config written by 'subiquity'
network:
  ethernets:
    ens33:
      addresses:
      - 192.168.110.3/24
      routes:
        - to: default
          via: 192.168.110.1
      nameservers:
        addresses:
        - 192.168.110.4
        search:
        - hogeserver.hogeddns.jp
      dhcp6: false
      accept-ra: false
      link-local: [ ]
  version: 2

反映。

$ sudo netplan apply

これで、DCとドメインに参加したホストの名前解決ができるようになる。

hostsファイルを修正。

/etc/hosts

127.0.0.1 localhost
#127.0.1.1 work
192.168.110.3 work.hogeserver.hogeddns.jp work

※登録順はFQDN → ホスト名としている。この順序でないと、ドメイン参加時のDNS更新に失敗する。

続いて、必要なアプリをインストールして設定していく。

$ sudo apt install samba smbclient libpam-winbind libnss-winbind krb5-user

■途中の質問
・Configuring Kerberos Authentication
ユーザがKerberosを使おうとして、プリンシパルやユーザ名を指定したときに、
そのプリンシパルがどの管理Kerberosレルムに属しているかを指定しない場合、
システムはデフォルトレルムを追加する。デフォルトのレルムは、ローカル
マシン上で動作しているKerberosサービスのレルムとして使用されることもある。
多くの場合、デフォルトのレルムは、ローカルDNSドメインの大文字版である。
デフォルトの Kerberos バージョン 5 レルム:
→ 今動かそうとしている HOGESERVER.HOGEDDNS.JP を入力しておいた。

※DeepL先生の翻訳

Sambaの設定。

/etc/samba/smb.conf

[global]
    realm = HOGESERVER.HOGEDDNS.JP
    server role = member server
    workgroup = HOGEDOMAIN
    winbind enum users = Yes
    winbind enum groups = Yes
    idmap config hogedomain : backend = rid
    idmap config hogedomain : range = 1000000-1999999
    idmap config * : range = 3000000-4999999
    idmap config * : backend = tdb
    server string = %h server (Samba, Ubuntu)
    template homedir = /home/%D/%U
    template shell = /bin/bash

    bind interfaces only = yes
    interfaces = 192.168.110.3 127.0.0.1

    log file = /var/log/samba/log.%m
    max log size = 1000
    logging = file
    panic action = /usr/share/samba/panic-action %d

[homes]
    root preexec = /usr/local/bin/samba_create_home_directory "%H" "%g" "%u"
    path = %H
    read only = No
    browseable = No

※workにはDockerがインストールしてあり、work内部でしか使えないIPアドレスを持っている。これを除外するために、bind interfacesを指定している。

ユーザーがSamba共有にアクセスしてきたら、ホームディレクトリを作成するスクリプトを作成。

/usr/local/bin/samba_create_home_directory ※新規作成

#!/bin/bash
if [ ! -d $1 ]; then
    BASEDIR=$(dirname $1)
    if [ ! -d $BASEDIR ]; then
        mkdir $BASEDIR
        chmod 755 $BASEDIR
    fi
    mkdir $1
    cp /etc/skel/.[^.]* $1
    chgrp -R "$2" $1
    chown -R "$3" $1
    chmod 700 $1
fi

スクリプトに実行権を付ける。

$ sudo chmod +x /usr/local/bin/samba_create_home_directory

設定を反映。

$ sudo systemctl restart smbd

ドメインに参加。

$ sudo net ads join -U administrator
Enter administrator's password: ユーザーadministratorのパスワード[Enter]
Using short domain name -- HOGEDOMAIN
Joined 'WORK' to dns domain 'hogeserver.hogeddns.jp'

今度は問題なく参加ができた。

Samba ad dcの認証が使えるようになったので、winbindの設定を変更する。

/etc/nsswitch.conf

…
passwd:         files systemd winbind
group:          files systemd winbind
shadow:         files
…

※赤文字を追記。

SSHでログインしたり、suコマンドでユーザーを変更したときに、ホームディレクトリを作成するように、PAMの設定を追加する。
先程のスクリプトはsmbdに接続があった場合に動作するが、これはwinbindで認証されたときに動作する。

$ sudo pam-auth-update
---- PAM configuration ----
  [*] Unix authentication
  [*] Winbind NT/Active Directory authentication
  [*] Register user sessions in the systemd control group hierarchy
  [*] Create home directory on login
  [*] Inheritable Capabilities Management

※Winbindは最初から追加されていると思われるが、一応確認。
※Create home directory on loginにチェックを入れると、ホームディレクトリが作成されるようになる。

これにより、以下に設定が書き込まれる。
/etc/pam.d/common-session

…
# and here are more per-package modules (the "Additional" block)
session required        pam_unix.so
session optional                        pam_winbind.so
session optional        pam_systemd.so
session optional                        pam_mkhomedir.so
# end of pam-auth-update config

さて、チケットの発行には、krb5.confファイルが必要。

$ sudo mv /etc/krb5.conf /etc/krb5.conf.bak

/etc/krb5.conf ※新規作成

[libdefaults]
    dns_lookup_realm = false
    dns_lookup_kdc = true
    default_realm = HOGESERVER.HOGEDDNS.JP

※作成するだけでOK。

最後に、DNSへの登録状況を確かめてみる。

$ dig @hogeserver.hogeddns.jp work.hogeserver.hogeddns.jp

…
;; ANSWER SECTION:
work.hogeserver.hogeddns.jp. 3600 IN    A       192.168.110.3

これで、参加に関わる手続きは完了。

ドメイン参加後の動作確認

このSamba ad dcにしか登録されていないaddcの名前解決ができるか。

$ dig addc.hogeserver.hogeddns.jp
…
;; ANSWER SECTION:
addc.hogeserver.hogeddns.jp. 334 IN     A       192.168.110.4

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Thu Aug 11 11:19:34 JST 2022
;; MSG SIZE  rcvd: 72

ドメイン参加したworkで、ユーザーやグループの一覧が見られるか。

$ getent passwd
…
HOGEDOMAIN\hoge:*:1001105:1000513:User Hoge:/home/HOGEDOMAIN/hoge:/bin/bash
HOGEDOMAIN\ssoauth:*:1001104:1000513::/home/HOGEDOMAIN/ssoauth:/bin/bash

$ getent group
…
HOGEDOMAIN\domain computers:x:1000515:
HOGEDOMAIN\domain users:x:1000513:

チケットを発行。

$ kinit rohhie
Password for rohhie@HOGESERVER.HOGEDDNS.JP: ユーザーrohhieのパスワード[Enter]

$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: rohhie@HOGESERVER.HOGEDDNS.JP

Valid starting       Expires              Service principal
08/11/2022 10:27:36  08/11/2022 20:27:36  krbtgt/HOGESERVER.HOGEDDNS.JP@HOGESERVER.HOGEDDNS.JP
        renew until 08/12/2022 10:27:30

$ kdestroy

ユーザーを切り替える。

$ sudo su - hogedomain\\hoge
Creating directory '/home/HOGEDOMAIN/hoge'.

HOGEDOMAIN\hoge@work:~$ ll
total 20
drwxr-xr-x 2 HOGEDOMAIN\hoge HOGEDOMAIN\domain users 4096 Sep 18 23:37 ./
drwxr-xr-x 3 root            root                    4096 Sep 18 23:37 ../
-rw-r--r-- 1 HOGEDOMAIN\hoge HOGEDOMAIN\domain users  220 Sep 18 23:37 .bash_logout
-rw-r--r-- 1 HOGEDOMAIN\hoge HOGEDOMAIN\domain users 3771 Sep 18 23:37 .bashrc
-rw-r--r-- 1 HOGEDOMAIN\hoge HOGEDOMAIN\domain users  807 Sep 18 23:37 .profile

共有にアクセスしてみる。(ユーザー切り替えで作られたディレクトリをいったん消して確かめている)

$ sudo rm -r /home/HOGEDOMAIN/

$ smbclient //localhost/hoge -U hoge -c 'ls'
Password for [HOGEDOMAIN\hoge]:
  .                                   D        0  Sun Sep 18 23:40:25 2022
  ..                                  D        0  Sun Sep 18 23:40:25 2022
  .bash_logout                        H      220  Sun Sep 18 23:40:25 2022
  .profile                            H      807  Sun Sep 18 23:40:25 2022
  .bashrc                             H     3771  Sun Sep 18 23:40:25 2022

        39887136 blocks of size 1024. 30589196 blocks available

上手く動作しているようだ。

ドメインからの離脱

Samba ad dcからの離脱自体はコマンドで簡単にできる。
このコマンドで行われるのは、コンピューターの削除処理だった。

$ sudo net ads leave -U administrator
Enter administrator's password: ユーザーadministratorのパスワード[Enter]
Deleted account for 'WORK' in realm 'HOGESERVER.HOGEDDNS.JP'

コマンド実行後、参照するDNSと、ドメイン名を付けなかった場合に検索対象となるドメインを元に戻す。

/etc/netplan/00-installer-config.yaml

…
      nameservers:
        addresses:
        - 192.168.110.1
        search:
        - example.jp
      dhcp6: false
      accept-ra: false
      link-local: [ ]
  version: 2

反映。

$ sudo netplan apply

認証に追加したwinbindを外す。

/etc/nsswitch.conf

…
passwd:         files systemd #winbind
group:          files systemd #winbind
shadow:         files
…

※赤文字を追記。この変更をするだけで、サービスの再起動は必要なし。

これだけで終わりかと思いきや…DNSのレコードが残っていた。
Samba ad dcで、DNSからworkを削除する。

# samba-tool dns delete localhost hogeserver.hogeddns.jp work A 192.168.110.3 -U administrator
Password for [HOGEDOMAIN\administrator]: ユーザーadministratorのパスワード[Enter]
Record deleted successfully

以上で離脱完了。

ドメインからの強制削除

何らかの事情で、ドメインからの離脱操作ができない場合は、Samba ad dcでコンピューターを削除する。

# samba-tool computer delete work

この場合は、DNSからもエントリーが消える。…はずだけれども、一応確認して、残っていたら消す。

ドメインへの参加(プライマリーのホスト)

結論からすると、無理。
Samba ad dcを動かしているホストな訳で、Samba clientをインストールすれば、使用するポートが重なってしまうため。

一応、よそからも名前解決だけはできるように、DNSに登録しておいてみる。

$ sudo docker exec -it samba samba-tool dns add localhost hogeserver.hogeddns.jp mirror A 192.168.110.4 -U administrator
Password for [HOGEDOMAIN\administrator]: ユーザーadministratorのパスワード[Enter]
Record added successfully

同じホストだから、これで良いかなーとも思ったが、一応、CNAMEでの指定も試してみた。

$ sudo docker exec -it samba samba-tool dns add localhost hogeserver.hogeddns.jp mirror cname addc.hogeserver.hogeddns.jp -U administrator

※FQDNでの指定が必要だった。

ドメイン参加と離脱(Windows)

Windows 10の試用版を使って試してみる。
元々、MYHOMELABドメインに入っていたので、そこから離脱して、HOGEDOMAINに参加する。

ドメインへの参加

ちょっと面倒だけれども、IPアドレスをDHCPから手動設定に変更した。
目的は、Samba ad dcで名前解決をすること。

実際に参加してみる。

これでドメインに参加することができた。

DNSには、いつ頃にこのコンピューターが登録されるのか見ていたら、 Windowsでようこそ画面が出たところだった。

なお、PowerShellによるドメイン参加は、試してみたけれども

  • 離脱はできた
  • 参加はできなかった

という結果となった。
SambaWiki / ADWS / AD Powershell compatibility

参加後の動作確認

再起動後に、ドメインに登録したユーザーでログインして、名前解決を試したところ。
DNSは手で設定しているので、名前解決自体はできて当然だが、ドメイン名がなくてもいけた。

C:\Users\rohhie.HOGEDOMAIN>nslookup work
サーバー:  UnKnown
Address:  192.168.110.4

名前:    work.hogeserver.hogeddns.jp
Addresses:  192.168.110.3

チケットを確認してみる。

C:\Users\rohhie.HOGEDOMAIN>klist

現在のログオン ID: 0:0xc84ba

キャッシュされたチケット: (6)

#0>     クライアント: rohhie @ HOGESERVER.HOGEDDNS.JP
        サーバー: krbtgt/HOGESERVER.HOGEDDNS.JP @ HOGESERVER.HOGEDDNS.JP
        Kerberos チケットの暗号化の種類: AES-256-CTS-HMAC-SHA1-96
        チケットのフラグ 0x60ac0000 -> forwardable forwarded renewable pre_authent ok_as_delegate 0x80000
        開始時刻: 8/11/2022 11:52:08 (ローカル)
        終了時刻: 8/11/2022 21:52:08 (ローカル)
        更新期限: 8/18/2022 11:52:08 (ローカル)
        セッション キーの種類: AES-256-CTS-HMAC-SHA1-96
        キャッシュ フラグ: 0x2 -> DELEGATION
        呼び出された Kdc: addc.hogeserver.hogeddns.jp
…

Windowsの管理ツールをインストールする

管理ツールをインストールすることについて、こちらで教えてくれた。
Samba Wiki / Installing RSAT

Windows 10 version 21H2の場合で、以下をインストールすると、管理がGUIからできる。

  • RSAT: Active Directory Domain Service およびライトウェイト ディレクトリ サービス ツール
  • RSAT: DNS サーバー ツール
  • RSAT: グループ ポリシー管理ツール
  • RSAT: リモート デスクトップ サービス ツール

グループポリシーは、グループポリシーの管理アプリで、

フォレスト: hogeserver.hogeddns.jp
 ドメイン
  hogeserver.hogeddns.jp
   Default Domain Policy(これはリンク)

とたどって、Default Domain Policyを右クリックし、表示されたポップアップで編集を選択すると、編集を開始できる。

テストのために、スタートアップスクリプトをユーザーに設定してみた。

\\hogeserver.hogeddns.jp\netlogon\test.bat

msg %username% このメッセージが表示されているということは、^

スタートアップスクリプトが実行されているということ。

これらの設定は、対象がコンピューターなのかユーザーなのか、というところを見極めて設定していく必要があるようだ。
簡単な動作確認を実施して、この件は終了とした。

ドメインからの離脱

ドメイン参加と反対の手順。所属するグループをhogeserver.hogeddns.jpからワークグループに変更する。

ドメインからの離脱は、PowerShellでも実行できた。
ドメインを抜けて、ワークグループに移動させてみる。
Microsoft / PowerShell / Remove-Computer

> Remove-Computer -ComputerName localhost -UnjoinDomainCredential HOGEDOMAIN\Administrator -WorkgroupName "WORKGROUP" -Force -Restart

認証情報を入力する画面が表示されたので、Administratorのパスワードを入力したところ、しばらくして再起動しはじめ、ドメインから抜けていた。

さて、ドメイン離脱後に確認してみたところ、DNSのレコードは残されていた。
以下のコマンドで削除ができる。

$ sudo docker exec -it samba samba-tool dns query localhost hogeserver.hogeddns.jp wintemp ALL -U administrator
Password for [HOGEDOMAIN\administrator]:
  Name=, Records=1, Children=0
    A: 192.168.110.21 (flags=f0, serial=110, ttl=1200)

$ sudo docker exec -it samba samba-tool dns delete localhost hogeserver.hogeddns.jp wintemp A 192.168.110.21 -U administrator
Password for [HOGEDOMAIN\administrator]:
Record deleted successfully

なお、再度ドメイン参加したときには、DNSレコードが作られた。

PLAとLAMの設定

phpLDAPadminとLDAP Account Managerをコンテナの中で動くように設定してある。
8081ポートに直接アクセスする方法でも使えるけれど、ここではApacheでSSL接続ができるようにしている。

Apacheの設定

コンテナのポート80を8081で開けているので、そこへProxyする。
今回は、ミラーサーバー(mirror.hogeserver.hogeddns.jp)への同居で、Apacheはインストール済みなので、そこに設定を追加する。

/etc/apache2/sites-available/myservice.conf

<VirtualHost *:80>
…
<VirtualHost *:443>
    ServerAdmin webmaster@hogeserver.hogeddns.jp
    ServerName addc.hogeserver.hogeddns.jp
    DocumentRoot /var/www/html

    ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog ${APACHE_LOG_DIR}/access.log combined

    ProxyPreserveHost On
    ProxyPass /phpldapadmin http://localhost:8081/phpldapadmin
    ProxyPassReverse /phpldapadmin http://localhost:8081/phpldapadmin
    ProxyPass /lam http://localhost:8081/lam
    ProxyPassReverse /lam http://localhost:8081/lam

    <Location /phpldapadmin>
        order deny,allow
        Allow from 192.168.110.0/24 fdnn:nnnn:nnnn:nnnn::/64
        Deny from all
    </Location>
    <Location /lam>
        order deny,allow
        Allow from 192.168.110.0/24 fdnn:nnnn:nnnn:nnnn::/64
        Deny from all
    </Location>

    # SSL
    SSLEngine on
    SSLCertificateFile /etc/ssl/private/wild.hoge.crt
    SSLCertificateKeyFile /etc/ssl/private/wild.hoge.key
</VirtualHost>

※このサービスにアクセスできる範囲を限定するケースがありそうなので、書き方を整理しておいた。

設定を有効化する。

$ sudo a2ensite myservice
$ sudo a2enmod proxy_http ssl
$ sudo systemctl restart apache2

phpLDAPadmin

phpLDAPadminは、初期設定が済ませてあって、サイトにアクセスするだけで使えるようになっている。
https://hogeserver.hogeddns.jp/phpldapadmin/

ログイン画面には、ユーザーとしてadministratorが入力されているので、パスワードだけを入力すれば利用可能。

細かな設定は、コンテナの中で/var/www/phpldapadmin/config/config.phpを変更すればOK。

LDAP Acount Managerの初期設定

こちらのサイトで色々と教えてくれている。
Server World / OpenLDAP : LDAP Account Manager インストール
LDAP Account Manager – Manual

まず、サイトにアクセスする。
https://hogeserver.hogeddns.jp/lam/

ログイン画面が表示されたら、右上の LAM configuration をクリックする。

一般的には、コンフィグファイルを手であれこれ修正するけれども、このツールはそれをGUIでできるようにしてくれている。
Edit server profilesをクリックする。

コンフィグファイルにlamという名前が付けられている。
パスワードlamを入力し、OKをクリックすると設定画面に入ることができる。

全般設定では、このような値を設定している。
Login methodとして、LDAPを選択することもできる。

サーバー証明書・秘密鍵を用意している場合、Server addressには証明書の発行先(Subject Alternative Name)にあったものにしておく。
今回用意したのは、SANにhogeserver.hogeddns.jp, *.hogeserver.hogeddns.jpが設定されているワイルドカードな証明書だったので、hogeserver.hogeddns.jpを設定した。
証明書の設定と違った名前でアクセスすると、「(-1) LDAP error, server says: Can’t contact LDAP server – (unknown error code)」のエラーが発生する。

アカウントタイプの設定も重要。ユーザーが一覧表でちゃんと表示されるかどうかが決まる。

モジュールは、デフォルトで入っているものを削除して、Winodwsを追加する。

保存するとログイン画面が表示されるので、Samba ad dcに登録されているAdministratorのパスワードを入力すればログインできる。

バックアップからの復元

現行システムのバックアップを復元

現在運用中のSamba ad dcは Version 4.7.6-Ubuntu であり、samba-toolに4.9から実装されているバックアップ・リストア機能が使えない。
一番心配なのが、自前で取っているバックアップがちゃんと働くのかどうか、ということだったが、過去に実現できていなかった拡張属性の復元を追加実装することとなった

現在動作中のSamba ad dcに影響を与えないように、本番環境にファイアウォールの設定を入れ、試験環境からアクセスできなくする。
その目的で以下を実行。テスト環境からの接続ができなくなる。

$ sudo ufw insert 1 deny from 192.168.110.4 comment "For test"

※これをやらずにテストしていたら、本番環境に入り込んでしまって、システムが色々と不具合を起こしはじめた…。

これまでhogeserer.hogeddns.jpとかやってきたが、ここで、復元の対象となるRealm、Domainをdocker-compose.ymlに書く。
また、当然、証明書類が違っているはずなので、使っている場合はpackage/certディレクトリに入れておく。

コンテナとボリュームを削除した上で、コンテナを起動する。

$ sudo docker compose down --volume
$ sudo docker compose up

※まっさら状態に戻るので、もしも試験環境を取っておきたいなら、バックアップを先に取っておく。

バックアップデーターをコンテナの中に入れる。

$ sudo docker cp samba4_private.yyyy-mm-dd.tar.bz2 samba:/root/
$ sudo docker cp samba4_sysvol.yyyy-mm-dd.tar.bz2 samba:/root/

コンテナに入り復元していく。

$ sudo docker exec -it samba /bin/bash --login
# cd root

Samba ad dcのプロセスを停止。

# pkill -SIGTERM ^samba$
# pkill -SIGTERM ^rsync$ ← セカンダリーDCを立てている場合

バックアップを展開。

$ tar jxvf samba4_private.yyyy-mm-dd.tar.bz2
$ tar jxvf samba4_sysvol.yyyy-mm-dd.tar.bz2

現在のデーターを削除し、展開したバックアップデーターを配置。

# rm -rf /var/lib/samba/private/*
# mv private/* /var/lib/samba/private/
# rmdir private/

# rm -rf /var/lib/samba/sysvol/*
# mv sysvol/* /var/lib/samba/sysvol/
# rmdir sysvol

LDAPSを有効にしている場合、今の操作でCAファイルが消えている(バックアップにserver.crtやserver.keyがない場合)。
/var/lib/samba/private/tlsを確認して、なければコピーする。

# cp -a packages/cert/server.crt packages/cert/server.key /var/lib/samba/private/tls/

拡張属性について、復旧する方法が分かった。samba-tool ntacl set ~ が使用できる。
過去記事を修正し、拡張属性が設定できるコマンドリストを出力するようにした。ここで、実行する。

# cd /var/lib/samba
# sudo bash sysvol/NTACL
# sudo rm sysvol/NTACL

コンテナを抜けて、再起動。

# exit
$ sudo docker compose stop
$ sudo docker compose up

※様子を見たかったので、stop→upにしたけれど、restartでも大丈夫だと思う。

復元時には、sysvolの拡張属性の再設定をするように書かれている(と思う)。
既に復元はされているはずだけれど、リセット的な意味合いもありそうなので実行しておく。
(事前に拡張属性を出力しておいて、念押し確認で一致を見るのは良いことかもしれない)

$ sudo docker exec samba net cache flush
$ sudo docker exec samba samba-tool ntacl sysvolreset

コンテナに入って、軽く試してみた。

$ sudo docker exec -it samba /bin/bash --login
# samba-tool dns zonelist localhost -U rohhie
Password for [MYHOME\rohhie]:
  16 zone(s) found

  pszZoneName                 : hogeserver.hogeddns.jp ← 実際は、ホームラボのドメインが表示されている(マスクした)
  Flags                       : DNS_RPC_ZONE_DSINTEGRATED DNS_RPC_ZONE_UPDATE_SECURE
  ZoneType                    : DNS_ZONE_TYPE_PRIMARY
  Version                     : 50
  dwDpFlags                   : DNS_DP_AUTOCREATED DNS_DP_DOMAIN_DEFAULT DNS_DP_ENLISTED
  pszDpFqdn                   : DomainDnsZones.hogeserver.hogeddns.jp
…
  pszZoneName                 : hogeserver.hogeddns.jp ← ホームラボだけで通用するように追加したゾーン
  Flags                       : DNS_RPC_ZONE_DSINTEGRATED DNS_RPC_ZONE_UPDATE_SECURE
  ZoneType                    : DNS_ZONE_TYPE_PRIMARY
  Version                     : 50
  dwDpFlags                   : DNS_DP_AUTOCREATED DNS_DP_DOMAIN_DEFAULT DNS_DP_ENLISTED
  pszDpFqdn                   : DomainDnsZones.hogeserver.hogeddns.jp
…

# dig @localhost temp.hogeserver.hogeddns.jp +short
192.168.110.2

# getent passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
…
MYHOME\rohhie:*:3000055:100::/home/rohhie:/bin/bash
…

# samba-tool user edit hogeuser
→ viでユーザーの情報が表示され、Kopano用の属性なども表示された。
# samba-tool group edit parent
→ viでグループの情報が表示され、Kopano用の属性なども表示された。

現行のシステムでも同じ情報を表示させて確認し、同じ結果が出ることを確認。
また、DNSのAレコードを書き換えて、違う結果が出ることも確認。
上手く復元ができた。

テスト環境用のバックアップ・リストア

テストが不十分なため、利用する場合は自己責任で。
Use at your own risk, Because the test is insufficient.

投稿を整理するにあたり、テスト環境は何度も何度も作り直しを繰り返していた。
一番心配だった拡張属性(NTACL)の復元ができるようになったので、テスト環境用のバックアップとリストアができるようにスクリプトを作成した。

backup.sh

#!/bin/bash

# Stop the samba process.
pkill -SIGTERM ^samba$
while
    pgrep ^samba$
    [ $? -eq 0 ]
do
    echo "wait..."
    sleep 1
done


# Create backup files.
TMP_TARGET=/root/packages/backup-$(hostname)-$(date +'%Y-%m-%d-%H-%M-%S').tar

# Samba
# Configuration.
cd /
tar -cvf $TMP_TARGET etc/samba --xattrs

# Private directory.
cd /var/
tar -uvf $TMP_TARGET lib/samba/private --xattrs --warning=no-file-ignored

# SysVol directory.
cd ./lib/samba/
find ./sysvol -exec bash -c 'TMP=$(samba-tool ntacl get "{}" --as-sddl); echo "samba-tool ntacl set \"$TMP\" \"{}\""' \; > NTACL
cd ../../
tar -uvf $TMP_TARGET lib/samba/sysvol lib/samba/NTACL lib/samba/bind-dns --xattrs
rm NTACL

# Bind
# Configuration.
cd /
tar -uvf $TMP_TARGET etc/bind --xattrs

# Lib directory
cd /var/
tar -uvf $TMP_TARGET lib/bind --xattrs

# Compress.
gzip $TMP_TARGET

# Finish.
/usr/sbin/samba --interactive --no-process-group &
echo "Backed up."

※青文字のところを上手く処理すれば、本番環境をそのまま復元するのにも使えるかもしれない。

復元は少し慎重。
バックアップファイル名を指定しただけだとドライランするだけ。バックアップファイル名と共に–executeを付けると、リストアを実行する。

restore.sh

#!/bin/bash

WORKDIR=${PWD}

# Check parameters.
if [ $# = 0 ]; then
echo "Usage: restore.sh [path to backup file] [--execute]
        --execute   Execute a restore.
                    If this parameter is not present, a dry run is performed."
    exit 0
fi
if [ ! -e $1 ]; then
    echo "File to be restored not found: $1"
    exit -1
fi

# Extract file.
if [ -d ./restore ]; then
    echo "The directory \"restore\" exists. Aborted."
    exit 0
fi
mkdir ./restore
tar -zxvf $1 -C ./restore
if [ $? -ne 0 ]; then
    echo "Failed to extract file."
    exit -1
fi

#
if [ -z $2 ]; then
    # Restore acl.
    cd restore/lib/samba/
    bash ./NTACL

    echo "Finished dry run."

elif [ $2 = "--execute" ]; then
    # Stop the samba process.
    pkill -SIGTERM ^samba$
    while
        pgrep ^samba$
        [ $? -eq 0 ]
    do
        echo "wait..."
        sleep 1
    done

    # Samba
    # Restore files.
    rm -rf /etc/samba/*
    mv restore/etc/samba/* /etc/samba/

    rm -rf /var/lib/samba/private/*
    mv restore/lib/samba/private/* /var/lib/samba/private/

    rm -rf /var/lib/samba/bind-dns
    mv restore/lib/samba/bind-dns /var/lib/samba/

    rm -rf /var/lib/samba/sysvol/*
    mv restore/lib/samba/sysvol/* /var/lib/samba/sysvol/

    # Bind
    # Restore files.
    rm -rf /etc/bind/*
    mv restore/etc/bind/* /etc/bind/

    rm -rf /var/lib/bind/*
    mv restore/lib/bind/* /var/lib/bind/

    # Delete working files.
    rm -rf ./restore

    # Restore acl.
    cd /var/lib/samba
    bash $WORKDIR/restore/lib/samba/NTACL
    cd $WORKDIR

    # Do sysvol reset.
    net cache flush
    samba-tool ntacl sysvolreset

    # Start the samba process.
    /usr/sbin/samba --interactive --no-process-group &
    if [ $SMB_USEBIND9 = "true" ]; then
        /usr/sbin/rndc stop
        /usr/sbin/named -u bind
    fi

    echo "Restored."

fi

これで、テストがやりやすくなるかなと。

セカンダリーDC

何分、セカンダリーDCを構築するのは初めてのことなので、何をやるのか整理しておく必要があった。
Samba Wiki / Joining a Samba DC to an Existing Active Directory
雑廉堂Wiki / Samba DCを既存のActive Directoryに参加させる

セカンダリーDCを立ち上げる手順をざっくり整理すると、

  • コンテナを起動すると、自動でsamba-toolを使ってプライマリーDCにjoinする。
  • idmapを合わせる。
  • プライマリーDCからsysvolを定期的に持ってくるようにする。

といったところ。

作業のベースとなるイメージの作成

こちらも何度も試行錯誤しており、ベースイメージを作った。
プライマリーDCと同じものを使っている。

ファイル構成

ファイル構成はプライマリーDCとほぼ同じだけれど、docker-compose.ymlをセカンダリーDCの設定に変更する。
また、設定用のスクリプトはセカンダリーDCに作り込んでいる。

~/samba/
   ├ docker-compose.yml
   ├ Dockerfile
   ├ entrypoint.sh ← 実行権限を付ける
   └ packages/
       ├ config-secondary.sh ← 実行権限を付ける
       ├ phpLDAPadmin-1.2.3.tar.gz ← あればphpLDAPadminが使えるようにする
       └ cert ← あればLDAPSを使えるようにする(プライマリーDCに入れたなら、こちらにも入れておく)
           ├ ca.crt
           ├ server.crt
           ├ server.key ← アクセス権限 0600
           └ ca.crl

docker-compose.yml

架空のレルム HOGESERVER.HOGEDDNS.JP (HOGEDOMAIN) に参加する。

環境に合わせて変更するのはこのファイル。
後のファイルは、この設定に合わせて動作するようになっている。

SMB_HOSTIPでは、このコンテナを動かすホストのIPアドレスを指定しており、Join時に使う。
コンテナで動作させる=NAT配下にいるようなもの なので、この指定が必要。

SMB_PRIMARYでは、このコンテナがセカンダリーDCであることを指定している。

DNSについては、hogeserver.hogeddns.jpをプライマリーDCに聞きに行く必要があるので、 参加する際にはプライマリーDCのIPアドレスを指定しておく。

hostname: addc2としている、このaddc2がこのサーバーの名前になる。
addc2.hogeserver.hogeddns.jp
運用上、付けたい名前があれば、ここに設定しておく。

~/samba/docker-compose.yml

version: "3.9"
services:

  samba:
    build: ./
    image: custom/samba:1.0.0
    container_name: samba
    restart: unless-stopped
    environment:
      TZ: Asia/Tokyo
      SMB_REALM: HOGESERVER.HOGEDDNS.JP
      SMB_DOMAIN: HOGEDOMAIN
      SMB_ADMINPASS: p@ssword123
      SMB_HOSTIP: 192.168.110.34
      SMB_RPC_PORTS: 49152-49200
      SMB_PURPOSE: "secondary"
      SMB_USEBIND9: "false"
      RSY_PRIMARY: 192.168.110.4
      RSY_PASS: p@ssword234
    volumes:
      - samba_etc:/etc/samba
      - samba_lib:/var/lib/samba
      - bind_etc:/etc/bind
      - bind_lib:/var/lib/bind
      - lam:/var/lib/ldap-account-manager
    networks:
      samba:
        ipv4_address: 172.26.0.102
    ports:
      - 192.168.110.34:53:53       #DNS
      - 192.168.110.34:53:53/udp   #DNS
      - 192.168.110.34:135:135     #End Point Mapper(WINS)
      - 192.168.110.34:137:137/udp #NetBIOS Name Service
      - 192.168.110.34:138:138/udp #NetBIOS Datagram
      - 192.168.110.34:139:139     #NetBIOS Session
      - 192.168.110.34:445:445     #SMB over TCP
      - 192.168.110.34:389:389     #LDAP
      - 192.168.110.34:389:389/udp #LDAP
      - 192.168.110.34:636:636     #LDAPS
      - 192.168.110.34:88:88       #Kerberos
      - 192.168.110.34:88:88/udp   #Kerberos
      - 192.168.110.34:464:464     #Kerberos kpasswd
      - 192.168.110.34:464:464/udp #Kerberos kpasswd
      - 192.168.110.34:3268:3268   #Global Catalog
      - 192.168.110.34:3269:3269   #Global Catalog SSL
                                   #RPC The same value as SMB_RPC_PORTS.
      - 192.168.110.34:49152-49200:49152-49200
      - 8081:80 #phpLDAPadmin & LDAP Account Manager
    hostname: addc2
    dns:
      - 192.168.110.4 #Used for domain to join
     #- 192.168.110.1 #Used for normal operation
    dns_search:
      - hogeserver.hogeddns.jp
    privileged: true
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN

networks:
  samba:
    ipam:
      config:
        - subnet: 172.26.0.0/16
          gateway: 172.26.0.1

volumes:
  samba_etc:
  samba_lib:
  bind_etc:
  bind_lib:
  lam:

config-secondary.sh

処理の流れはプライマリーDCと同様で、provitionの代わりにjoinしている。

フェーズボリュームコンテナ動作
1なしなしSamba ad dcドメインに参加する。
/etc/samba/smb.conf はここで生成されたものを永続化している。
続けて、フェーズ2を実行する。
2ありなし認証設定(Winbindを追加)、Kerberos設定、rsync設定、cron設定を実行する。
また、rsyncでsysvolを持ってきてsysvolresetを実行する。
また、cronには、5分ごとにrsyncを呼び出す設定を追加している。
これらは永続化していないので、コンテナが作られる度に再設定をする。
続けて、フェーズ3を実行する。
3ありありコンテナ内部のリゾルバを127.0.0.11→127.0.0.1に向ける。
これはコンテナを実行する度に設定が必要。
結果として、Samba ad dcが名前解決をするようになる。

プライマリーDCと同期するという観点で設定を作り込み、とりあえずは動くレベルになったかと思う。
sysvolがレプリケーションできるように設定してみているが、ホームラボでは活用していないので、上手くでできているかどうかはファイルの状態でしか判断できていない。

~/samba/packages/config-secondary.sh

#!/bin/bash
echo "Secondary domain controller settings."

#----------------------------------------------------------------------
# New volumes.
#----------------------------------------------------------------------
if [ -z "$(ls /var/lib/samba/private)" ]; then
    echo "New volumes."

    # Make join parameters.
    SMB_TMP_PARAM="
        --username=administrator
        --password=$SMB_ADMINPASS
        --realm=$SMB_REALM
        --option=\"dns forwarder = 127.0.0.11\"
        --option=\"dns update command = /usr/sbin/samba_dnsupdate --current-ip $SMB_HOSTIP\"
        --option=\"rpc server dynamic port range = $SMB_RPC_PORTS\"
        --option=\"template homedir = /home/%D/%U\"
        --option=\"template shell = /bin/bash\"
        --option=\"winbind enum users = yes\"
        --option=\"winbind enum groups = yes\"
        --option=\"idmap config $SMB_DOMAIN:unix_nss_info = yes\"
        --option=\"idmap config $SMB_DOMAIN:unix_primary_group = yes\"
        --option=\"idmap_ldb:use rfc2307 = yes\"
    "
    if [ $SMB_USEBIND9 = "true" ]; then
        SMB_TMP_PARAM+=" --dns-backend=BIND9_DLZ"
    else
        SMB_TMP_PARAM+=" --dns-backend=SAMBA_INTERNAL"
    fi

    # LDAPS settings.
    mkdir /var/lib/samba/private/tls/
    TMP_LDAPS=0
    cp -a /root/packages/cert/ca.crt /usr/local/share/ca-certificates/ && \
        update-ca-certificates && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x01)) && \
        SMB_TMP_PARAM+=" --option=\"tls cafile   = /usr/local/share/ca-certificates/ca.crt\""
    cp -a /root/packages/cert/server.crt /var/lib/samba/private/tls/ && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x02)) && \
        SMB_TMP_PARAM+=" --option=\"tls certfile = /var/lib/samba/private/tls/server.crt\""
    cp -a /root/packages/cert/server.key /var/lib/samba/private/tls/ && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x04)) && \
        chmod 600 /var/lib/samba/private/tls/server.key && \
        SMB_TMP_PARAM+=" --option=\"tls keyfile  = /var/lib/samba/private/tls/server.key\""
    cp -a /root/packages/cert/ca.crl /var/lib/samba/private/tls/ && \
        TMP_LDAPS=$(($TMP_LDAPS | 0x08)) && \
        SMB_TMP_PARAM+=" --option=\"tls crlfile  = /var/lib/samba/private/tls/ca.crl\""

    if [ $(($TMP_LDAPS & 0x07)) -eq 7 ]; then
        echo "Enable LDAPS."
        SMB_TMP_PARAM+=" --option=\"tls enabled  = true\"
                 --option=\"tls verify peer = as_strict_as_possible\"
        "
    else
        echo "Disable Strong Auth."
        SMB_TMP_PARAM+="
            --option=\"ldap server require strong auth = no\"
        "
    fi

    set -f
    SMB_TMP_PARAM=$(echo $SMB_TMP_PARAM)
    #echo "join parameters: $SMB_TMP_PARAM"
    set +f

    # Join domain settings.
    mv --backup=numbered /etc/samba/smb.conf /etc/samba/smb.conf.bak
    eval samba-tool domain join $SMB_REALM DC "$SMB_TMP_PARAM"
    if [ $? -ne 0 ]; then exit 0; fi

    # Deletion of IP addresses in the container registered in Primary DNS
    MYHOSTIP=$(grep $(hostname) /etc/hosts | sed "s/^\(.*\)\s.*/\1/")
    MYHOSTNM=$(hostname)
    samba-tool dns update $SMB_REALM \
        $SMB_REALM $MYHOSTNM \
        A $MYHOSTIP $SMB_HOSTIP \
        --username Administrator --password $SMB_ADMINPASS
    # Delete myhostip after 30 sec.
    /bin/bash -c "sleep 30;
    samba-tool dns delete localhost \
        $SMB_REALM $MYHOSTNM \
        A $MYHOSTIP \
        --username Administrator --password $SMB_ADMINPASS
    " &
fi

#----------------------------------------------------------------------
# Volumes is left.
#----------------------------------------------------------------------
if [ ! -e /root/packages/configured ]; then
    echo "New container."

    # Register CA certificates.
    cp -a /root/packages/cert/ca.crt /usr/local/share/ca-certificates/ && \
        update-ca-certificates

    # Authentication sttings.
    sed -i "s/^\(passwd: \+\)[a-z ]\+$/\1compat winbind/" /etc/nsswitch.conf
    sed -i "s/^\(group: \+\)[a-z ]\+$/\1compat winbind/" /etc/nsswitch.conf

    # Create krb5.conf
    mv --backup=numbered /etc/krb5.conf /etc/krb5.conf.bak
cat <<EOF > /etc/krb5.conf
[libdefaults]
    dns_lookup_realm = false
    dns_lookup_kdc = true
    default_realm = $SMB_REALM
EOF

    # Make rsync configuration.
cat <<EOF > /etc/rsyncd.secret.sysvol-replication
$RSY_PASS
EOF
    chmod 600 /etc/rsyncd.secret.sysvol-replication

    # Reset sysvol.
    echo "Reset sysvol."
    rsync -XAavx \
        --delete-after \
        --password-file=/etc/rsyncd.secret.sysvol-replication \
        --contimeout=10 \
        rsync://sysvol-replication@$RSY_PRIMARY/SysVol \
        /var/lib/samba/sysvol/
    samba-tool ntacl sysvolreset

    # Replicate sysvol every 5 minutes.
    echo "*/5 * * * *   root    rsync -XAavx --delete-after --password-file=/etc/rsyncd.secret.sysvol-replication rsync://sysvol-replication@$RSY_PRIMARY/SysVol /var/lib/samba/sysvol/" >> /etc/crontab

    # Suppress apache warning.
    echo "ServerName localhost" | tee /etc/apache2/conf-available/fqdn.conf
    a2enconf fqdn

    # Setup phpLdapAdmin.
    if [ -e /root/packages/phpLDAPadmin-1.2.3.tar.gz ]; then
        a2dismod php8.1
        a2enmod php7.3

        tar zxf /root/packages/phpLDAPadmin-1.2.3.tar.gz -C /var/www/
        mv /var/www/phpLDAPadmin-1.2.3 /var/www/phpldapadmin
        cp /etc/phpldapadmin/apache.conf /etc/phpldapadmin/apache.conf.bak
        sed -i "s@/usr/share/phpldapadmin/htdocs@/var/www/phpldapadmin@g" /etc/phpldapadmin/apache.conf
        cp /var/www/phpldapadmin/config/config.php.example /var/www/phpldapadmin//config/config.php
        if [ $(grep "tls verify peer = as_strict_as_possible" /etc/samba/smb.conf -c) -ne 0 ]; then
            sed -i "$ i\$servers->setValue('server','host','ldaps://$(hostname).${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        else
            sed -i "$ i\$servers->setValue('server','host','ldap://$(hostname).${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        fi
        sed -i "$ i\$servers->setValue('login','bind_id','administrator@${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        sed -i "$ i\$config->custom->appearance['hide_template_warning'] = true;" /var/www/phpldapadmin/config/config.php
        sed -i "s/\$servers->setValue('server','name','My LDAP Server');/\$servers->setValue('server','name','$SMB_DOMAIN');/" /var/www/phpldapadmin/config/config.php

        # Customize phpLDAPadmin
        # for PHP7.0
        sed -i "s/password_hash/password_hash_custom/g" /var/www/phpldapadmin/lib/*
        sed -i '2567d; 2568d; 2569i \\t\tforeach ($dn as $key => $rdn) {\n\t\t\t$a[$key] = preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return '\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $rdn\'');\n\t\t}' /var/www/phpldapadmin/lib/functions.php
        sed -i '2574c \\t\treturn  preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return'\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $dn);' /var/www/phpldapadmin/lib/functions.php
        sed -i '1119d; 1120d; 1121i \\t\t\tforeach ($dn as $key => $rdn) {\n\t\t\t\t$a[$key] = preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return '\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $rdn\'');\n\t\t\t}' /var/www/phpldapadmin/lib/ds_ldap.php
        sed -i '1126c \\t\t\treturn  preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return'\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $dn);' /var/www/phpldapadmin/lib/ds_ldap.php
        # for PHP7.3
        sed -i '54c function my_autoload($className) {' /var/www/phpldapadmin/lib/functions.php
        sed -i '777c spl_autoload_register("my_autoload");' /var/www/phpldapadmin/lib/functions.php
        sed -i '1083c \\t\t$CACHE[$sortby] = __create_function('\''$a, $b'\'',$code);' /var/www/phpldapadmin/lib/functions.php
        sed -i '1091a function __create_function($arg, $body) {\n\tstatic $cache = array();\n\tstatic $maxCacheSize = 64;\n\tstatic $sorter;\n\n\tif ($sorter === NULL) {\n\t\t$sorter = function($a, $b) {\n\t\t\tif ($a->hits == $b->hits) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn ($a->hits < $b->hits) ? 1 : -1;\n\t\t};\n\t}\n\n\t$crc = crc32($arg . "\\\\x00" . $body);\n\n\tif (isset($cache[$crc])) {\n\t\t++$cache[$crc][1];\n\t\treturn $cache[$crc][0];\n\t}\n\n\tif (sizeof($cache) >= $maxCacheSize) {\n\t\tuasort($cache, $sorter);\n\t\tarray_pop($cache);\n\t}\n\n\t$cache[$crc] = array($cb = eval('\''return function('\''.$arg.'\''){'\''.$body.'\''};'\''), 0);\n\treturn $cb;\n}\n' /var/www/phpldapadmin/lib/functions.php
    fi

    # Mark as configured.
    touch /root/packages/configured
fi

#----------------------------------------------------------------------
# Container and Volumes is left.
#----------------------------------------------------------------------
echo "Setting to do every time"

# Resolver settings.
cp /etc/resolv.conf /root/packages/resolv.conf
sed -i "s/nameserver 127.0.0.11/nameserver 127.0.0.1/" /root/packages/resolv.conf
cat /root/packages/resolv.conf > /etc/resolv.conf

# Switch DNS backend.
if [ $SMB_USEBIND9 = "true" ]; then
    if [ ! -e /var/lib/samba/bind-dns/named.conf ]; then
        samba_upgradedns --dns-backend=BIND9_DLZ
    fi
    # Make bind9 configuration.
    if [ $(grep "bind-dns" /etc/bind/named.conf -c) -eq 0 ]; then
        cp -a /etc/bind/named.conf /etc/bind/named.conf.bak
        sed -i "\$a include \"/var/lib/samba/bind-dns/named.conf\";" /etc/bind/named.conf
        cp -a /etc/bind/named.conf.options /etc/bind/named.conf.options.bak
        sed -i "/listen-on-v6/a\\\n\tforwarders { 127.0.0.11; };\n\tallow-query { any; };\n\tallow-transfer { none; };\n\ttkey-gssapi-keytab \"/var/lib/samba/bind-dns/dns.keytab\";\n\tminimal-responses yes;" /etc/bind/named.conf.options
        cp -a /etc/bind/named.conf.local /etc/bind/named.conf.local.bak
        sed -i "s@^//include@include@" /etc/bind/named.conf.local
    fi
    if [[ $(grep -c "server services" /etc/samba/smb.conf) -eq 0 ]]; then
        sed -i "9a\\\tserver services = -dns" /etc/samba/smb.conf
    fi
else
    if [ -e /var/lib/samba/bind-dns/named.conf ]; then
        samba_upgradedns --dns-backend=SAMBA_INTERNAL
        sed -i "/server services/d" /etc/samba/smb.conf
    fi
fi

ファイルを作ったら、実行権を付けておく。

$ chmod +x ~/samba/packages/config-secondary.sh

その他のファイル

その他のファイルは、プライマリーDCと同じ。
プライマリーDCからコピーしてきて配置する。

プライマリーDCでrsyncを動かす

セカンダリーDCは、プライマリーDCのsysvolを5分おきに取りに行く。
プライマリーDCでrsyncを起動して、セカンダリーDCからの要求を受け付けるようにする。

Primary: ~/samba/docker-compose.yml

…
      RSY_SECONDARY: "192.168.110.34"
      RSY_PASS: p@ssword234
…

※コメントを外して有効化する。

RSY_SECONDARYで、セカンダリーDCのIPアドレスを指定する。
そのまま/etc/rsyncd.conのhosts allowに設定するので、スペース区切りで複数指定することが可能。CIDR形式での指定も可能。

RSY_PASSはrsyncで使用するパスワードを設定しておく。

設定を変更したら、プライマリーDCのコンテナを起動し直す。

addc:~/samba$ sudo docker compose stop
addc:~/samba$ sudo docker compose up

これで、プライマリーの起動スクリプトでrsyncが設定され、rsyncが起動する。

コンテナの起動

ファイルの準備ができたら、ファイアウォールを設定する。

$ sudo ufw allow from 172.26.0.102 to 192.168.110.34 comment "From Container"

コンテナを起動すると、自動的にドメインに参加する。

$ sudo docker compose up --build

スクリプトが処理を進めていくとき、ちょっとまずそうなエラー…例えば、rsyncに失敗などが発生したときは、設定を見直してbuildすれば良い。

ドメインに参加する際、コンテナの中で割り振られたIPアドレスをプライマリーDCに通知するようだ。
このままにしておくと、このメッセージが出続けることになる。

samba  | Failed to connect host 172.26.0.102 on port 135 - NT_STATUS_HOST_UNREACHABLE
samba  | Failed to connect host 172.26.0.102 (12222878-52d1-4677-9d83-f15c6cc66119._msdcs.hogeserver.hogeddns.jp) on port 135 - NT_STATUS_HOST_UNREACHABLE.

これは、設定スクリプトでプライマリーDCのDNSを更新して対処している。
コンテナのIPアドレス → ホストのIPアドレス に更新。

また、セカンダリーDCのDNSには、コンテナのIP、ホストのIPの2レコードが存在する。
そのため、Sambaが起動した頃を見計らって(30秒後)、レコードを削除しに行っている。
同期が先に済んでしまってレコードがなくなっている場合もあるが、このコマンドが空振りするだけで問題はない。

なお、本来は、ネットワークインターフェースがいくつかあるなら、それをbind interfaces only設定で絞り込むのが、あるべき姿なのかなと。
Ubuntuのドメイン参加では、実際にそうしている

ところが、bind interfaces onlyを設定してみたところ、プライマリーDCとの通信ができなくなってしまった。
適切な設定項目が見つかったら、それを設定するとして、今回は上記の対症療法を結論とした。

bind interfaces onlyを設定し、通信ができない状態になったときに、プライマリーDCに出力されていたログをメモしておく。

samba  | Failed to connect host 192.168.110.34 on port 135 - NT_STATUS_CONNECTION_REFUSED
samba  | Failed to connect host 192.168.110.34 (3d8be95e-ad29-475b-bb3a-fd8c78c1d923._msdcs.hogeserver.hogeddns.jp) on port 135 - NT_STATUS_CONNECTION_REFUSED.

それと、コンテナが起動した後、プライマリーDCでこのエラーが出続けることがある。

samba  | Failed to bind to uuid e3514235-4b06-11d1-ab04-00c04fc2dcd2 for ncacn_ip_tcp:192.168.110.34[49153,seal,krb5,target_hostname=fc27b6f2-bd9b-4bbc-b975-373f55add1d7._msdcs.hogeserver.hogeddns.jp,target_principal=GC/addc2.hogeserver.hogeddns.jp/hogeserver.hogeddns.jp,abstract_syntax=e3514235-4b06-11d1-ab04-00c04fc2dcd2/0x00000004,localaddress=172.26.0.101] NT_STATUS_UNSUCCESSFUL

止まらないようなら、プライマリーDCのコンテナを再起動する。
チケットが上手く処理できていないように見えており、krb5.confのcheck-ticket-addressesというキーワードを発見し、noaddressesを調べるに至ったが、デフォルトでtrueになっているようだ。再起動すれば解決するので、割り切り。

idmapの一致

プライマリーDCとセカンダリーDCでIDを一致させるために、以下の処理が必要とのこと。

プライマリーDCでIDマップのバックアップを取る。

addc:~/samba$ sudo docker exec samba tdbbackup -s .bak /var/lib/samba/private/idmap.ldb
addc:~/samba$ sudo docker cp samba:/var/lib/samba/private/idmap.ldb.bak ./
addc:~/samba$ sudo docker exec samba rm /var/lib/samba/private/idmap.ldb.bak

セカンダリーDCに転送。

addc:~/samba$ sudo chown rohhie:rohhie idmap.ldb.bak ← 所有者rootで600なので、転送ができない…所有者を操作者に変更。
addc:~/samba$ scp idmap.ldb.bak addc2:/home/rohhie/samba/
addc:~/samba$ rm idmap.ldb.bak

※ファイルの転送方法は何でも良い。

セカンダリーDCで、Samba ad dcのプロセスを停止する。

$ sudo docker exec samba pkill -SIGTERM ^samba$

ファイルを配置して、リセットコマンドを投入。

$ sudo docker cp idmap.ldb.bak samba:/var/lib/samba/private/idmap.ldb
$ sudo docker exec samba chown root:root /var/lib/samba/private/idmap.ldb
$ rm idmap.ldb.bak

$ sudo docker exec samba net cache flush
$ sudo docker exec samba samba-tool ntacl sysvolreset

コンテナを停止して、改めて起動する。

$ sudo docker compose stop
$ sudo docker compose up

これで設定ができた。

双方のコンテナの中で以下を実行したところ、ユーザーとパスワードのIDが一致していた。

# getent passwd
…
HOGEDOMAIN\administrator:*:0:100::/home/HOGEDOMAIN/administrator:/bin/bash
HOGEDOMAIN\guest:*:3000012:100::/home/HOGEDOMAIN/guest:/bin/bash
HOGEDOMAIN\krbtgt:*:3000068:100::/home/HOGEDOMAIN/krbtgt:/bin/bash
HOGEDOMAIN\rohhie:*:3000023:100::/home/HOGEDOMAIN/rohhie:/bin/bash
HOGEDOMAIN\ssoauth:*:3000069:100::/home/HOGEDOMAIN/ssoauth:/bin/bash
HOGEDOMAIN\hoge:*:3000070:100::/home/HOGEDOMAIN/hoge:/bin/bash
HOGEDOMAIN\hogewife:*:3000071:100::/home/HOGEDOMAIN/hogewife:/bin/bash

# getent group
…
BUILTIN\cryptographic operators:x:3000096:
BUILTIN\event log readers:x:3000097:
BUILTIN\certificate service dcom access:x:3000098:
HOGEDOMAIN\cert publishers:x:3000099:
HOGEDOMAIN\ras and ias servers:x:3000100:
HOGEDOMAIN\allowed rodc password replication group:x:3000101:
…

DNSの修正

セカンダリーDCが立ち上がったこのタイミングでは、名前解決が以下の流れになっている。

問い合わせ(1) → [addc ] → [router]
                   ↑
問い合わせ(2) → [addc2]

自分たちで解決できる名前(この段階ではhogeserver.hogeddns.jp)については、addcとaddc2が回答してくれるが、解決できないものはrouterに聞きに行く。

addc2がドメインに参加する時、routerはaddc.hogeserver.hogeddns.jpを知らないので、DNSとしてaddc(192.168.110.4)を指定しておく必要があった。
addc2がセカンダリーDCとして認識された今、addcとaddc2はDNSが変更される度に同期している。

さて…今、もし、addcが落ちたらどうなるか。
hogeserver.hogeddns.jpの範囲はaddc2が名前解決をしてくれる。これはOK。
だけれども、この範囲を超えた名前解決をする場合(e.g. rohhie.net)、addc2は自分で情報を持っていないので、addcに聞きに行く。
addcが落ちているので、名前解決がタイムアウトする。

addcに頼る必要はないのだから、名前解決は次のような流れにするのが望ましい。

問い合わせ(1) → [addc ] → [router]
                   ||同期
問い合わせ(2) → [addc2] → [router]

そこで、セカンダリーDCのコンテナを一旦止める。

$ sudo docker compose stop

DNSをaddcからrouterに切り替える。

docker-compose.yml

version: "3.9"
services:

  samba:
…
    dns:
     #- 192.168.110.4 #Used for domain to join
      - 192.168.110.1 #Used for normal operation

セカンダリーDCを起動する。

$ sudo docker compose up

これで、addcが落ちていても、名前解決ができるようになった。

ドメインから離脱

セカンダリーDCで以下のコマンドを実行。

# samba-tool domain demote -U administrator
Using addc.hogeserver.hogeddns.jp as partner server for the demotion
Password for [HOGEDOMAIN\administrator]: ユーザーAdministraotrのパスワード[Enter]

これで、ドメインを離脱することができた。

強制的にドメインから外す

セカンダリーDCがドメインに参加した状態で、うっかりセカンダリーDCのコンテナとボリュームを削除してしまった。
この場合は、プライマリーDCから強制的にセカンダリーDCを削除する。

addc:~/samba$ sudo docker exec -it samba /bin/bash --login
# samba-tool domain demote --remove-other-dead-server=ADDC2

これで、セカンダリーDCの情報がすべて削除された。

リストアドDC

どこかのDCで行われた変更は、DC全体で同期されるので、誰かがどこかでデーターを壊せば、それが広がってしまう。
この場合、壊れたDCを停止し、バックアップからデーターを復元して(samba-tool domain backup restore)、他のDCは再度ドメインに参加する(samba-tool domain join dc)ことで、バックアップのデーターが同期される、ということのようだった。

なるほど…と、改めて、プライマリーDCとセカンダリーDCにできあがっているsmb.confを読んでみたところ、全く同じものになっていた。
同じ値が入るようにとoptionsを設定していたのは確かだけれど、それ以外の箇所もnetbios nameを除き、みんな同じ。
全く同じ設定で、全く同じ情報を持つ、そうか、そういうことだったのか。

そこから考えるリストアのシナリオは以下。

  • 新しいサーバーaddcrを立てて、リストアして待ち受ける。
  • addcからaddcrにjoin。
  • addc2からaddcにjoin。

実際にリストアしてみると、DNSにNSの登録がない。復元用のデーターを供給するためだけに立ち上がるようだ。

バックアップ

まずはこちらを確認。
Samba Wiki / Back up and Restoring a Samba AD DC

以前のバージョンでは、samba-toolに実装されているバックアップが使えず、バックアップ対象を調べて調べて…これだろう、というものを定期的にバックアップしている。
今回のバージョンでは標準機能が使えそうだ。

# samba --version
Version 4.15.9-Ubuntu

※4.9以降で標準機能が使える。ありがたい。

さて、プライマリーDCで、以下のコマンドを実行。
offlineはローカルのサーバーのバックアップを取ることを指し示しているだけで、稼働中でも問題なくバックアップが取れるとのこと。

# samba-tool domain backup offline --targetdir=/root/
# ll /root
-rw-r--r-- 1 root root 1738308 Aug 14 13:55 samba-backup-2022-08-14T13-55-02.655028.tar.bz2

できあがった、samba-backup-<datetime>.tar.bz2を保管しておけば良い。

作業のベースとなるイメージの作成

こちらも何度も試行錯誤しており、ベースイメージを作った。
プライマリーDCと同じものを使っている。

ファイル構成

ファイル構成はプライマリーDCとほぼ同じだけれど、docker-compose.ymlをリストアドDCの設定に変更する。
設定用のスクリプトはリストアドDC用に作り込んであり、packagesディレクトリに放り込んだバックアップファイルを使ってリストアする。

~/samba/
   ├ docker-compose.yml
   ├ Dockerfile
   ├ entrypoint.sh ← 実行権限を付ける
   └ packages/
       ├ config-restore.sh ← 実行権限を付ける
       ├ phpLDAPadmin-1.2.3.tar.gz ← あればphpLDAPadminが使えるようにする
       ├ samba-backup-YYYY-MM-DDTHH-MM-SS.nnnnnn.tar.bz2 ← 標準機能で作成したバックアップ
       └ cert ← プライマリーDCやセカンダリーDCで入れている場合には必要
           ├ ca.crt
           ├ server.crt
           ├ server.key ← アクセス権限 0600
           └ ca.crl

docker-compose.yml

プライマリーDC、セカンダリーDCとほとんど同じ。
IPアドレスとホスト名だけが変わっている。

~/samba/docker-compose.yml

version: "3.9"
services:

  samba:
   #image: ubuntu:jammy
    build: ./
    image: custom/samba:1.0.0
    container_name: samba
    restart: unless-stopped
    environment:
      TZ: Asia/Tokyo
      SMB_REALM: HOGESERVER.HOGEDDNS.JP
      SMB_DOMAIN: HOGEDOMAIN
      SMB_ADMINPASS: p@ssword123
      SMB_HOSTIP: 192.168.110.10
      SMB_RPC_PORTS: 49152-49200
      SMB_PURPOSE: "restore"
    volumes:
      - samba_etc:/etc/samba
      - samba_lib:/var/lib/samba
      - bind_etc:/etc/bind
      - bind_lib:/var/lib/bind
      - lam:/var/lib/ldap-account-manager
    networks:
      samba:
        ipv4_address: 172.26.0.103
    ports:
      - 192.168.110.10:53:53       #DNS
      - 192.168.110.10:53:53/udp   #DNS
      - 192.168.110.10:135:135     #End Point Mapper(WINS)
      - 192.168.110.10:137:137/udp #NetBIOS Name Service
      - 192.168.110.10:138:138/udp #NetBIOS Datagram
      - 192.168.110.10:139:139     #NetBIOS Session
      - 192.168.110.10:445:445     #SMB over TCP
      - 192.168.110.10:389:389     #LDAP
      - 192.168.110.10:389:389/udp #LDAP
      - 192.168.110.10:636:636     #LDAPS
      - 192.168.110.10:88:88       #Kerberos
      - 192.168.110.10:88:88/udp   #Kerberos
      - 192.168.110.10:464:464     #Kerberos kpasswd
      - 192.168.110.10:464:464/udp #Kerberos kpasswd
      - 192.168.110.10:3268:3268   #Global Catalog
      - 192.168.110.10:3269:3269   #Global Catalog SSL
                                   #RPC The same value as SMB_RPC_PORTS.
      - 192.168.110.10:49152-49200:49152-49200
      - 873:873 #rsync
      - 8081:80 #phpLDAPadmin & LDAP Account Manager
    hostname: addcr
    dns:
      - 192.168.110.1
    dns_search:
      - hogeserver.hogeddns.jp
    privileged: true
    devices:
      - /dev/net/tun
    cap_add:
      - NET_ADMIN

networks:
  samba:
    ipam:
      config:
        - subnet: 172.26.0.0/16
          gateway: 172.26.0.1

volumes:
  samba_etc:
  samba_lib:
  bind_etc:
  bind_lib:
  lam:

config-restore.sh

用意されたバックアップデータを復元して実行する。

本来のSambaの実装では、リストアされたディレクトリで上手く動く想定だけれども、せっかくのまっさら環境なので、復元したデーターを標準のディレクトリに移動し、復元時にsmb.confに入れられるディレクトリ指定を消して、標準的なディレクトリで起動させている。

証明書関係は、プライマリーDCやセカンダリーDCに入っている場合に、smb.confと共に復旧されるが、ca.crtだけは/usr/local/share/ca-certificatsに保管してあるのでバックアップ対象になっておらず、復旧できない。そのため、コンテナ起動時に与えてあげる必要がある。これは失敗だったなーと思うものの、CAの証明書なら持っているだろうし、なくしても作り直せば良いのだから、きっと困ることはないだろう。割り切りとする。

DNSバックエンドがBIND9だった場合、リストアされた環境でもBIND9で動かそうと考えたのだけれども、管理するゾーンのNSがないためエラーが発生して起動できなかった。NSはプライマリーが接続するときに登録されるようになっている訳なので、DNSバックエンドを内蔵のものに切り替える処理を入れている。

~/samba/packages/config-restore.sh

#!/bin/bash
echo "Restore domain controller settings."

#----------------------------------------------------------------------
# New volumes.
#----------------------------------------------------------------------
if [ -z "$(ls /var/lib/samba/private)" ]; then
    echo "New volumes."

    if [ $(ls /root/packages/samba-backup-* | wc -w) -ne 1 ]; then
        echo "There must be one backup file."
        exit 0
    fi

    samba-tool domain backup restore \
        --backup-file=$(ls /root/packages/samba-backup-*) \
        --newservername=$(hostname) \
        --targetdir=/root/packages/restore \
        --host-ip=$SMB_HOSTIP

    mv /root/packages/restore/etc/* /etc/samba/
    rmdir /root/packages/restore/etc

    mv /root/packages/restore/private/* /var/lib/samba/private/
    rmdir /root/packages/restore/private

    mv /root/packages/restore/state/sysvol /var/lib/samba/

    mv /root/packages/restore/state/bind-dns /var/lib/samba/

    mv /root/packages/restore/state/*.tdb /var/lib/samba/
    rmdir /root/packages/restore/state

    rm /root/packages/restore/gencache.tdb
    rm /root/packages/restore/backup.txt
    rmdir /root/packages/restore

    sed -i "/binddns dir/d" /etc/samba/smb.conf
    sed -i "/cache directory/d" /etc/samba/smb.conf
    sed -i "/lock directory/d" /etc/samba/smb.conf
    sed -i "/private dir/d" /etc/samba/smb.conf
    sed -i "/state directory/d" /etc/samba/smb.conf
    sed -i "s/--current-ip [0-9]\{1,3\}.[0-9]\{1,3\}.[0-9]\{1,3\}.[0-9]\{1,3\}/--current-ip $SMB_HOSTIP/" /etc/samba/smb.conf
    sed -i "s@/root/packages/restore/state/sysvol@/var/lib/samba/sysvol@g" /etc/samba/smb.conf

    # Change the DNS back end to internal.
    if [ -e /var/lib/samba/bind-dns/named.conf ]; then
        samba_upgradedns --dns-backend=samba_internal
        sed -i "/server services/d" /etc/samba/smb.conf
    fi
fi

#----------------------------------------------------------------------
# Volumes is left.
#----------------------------------------------------------------------
if [ ! -e /root/packages/configured ]; then
    echo "New container."

    # Register CA certificates.
    cp -a /root/packages/cert/ca.crt /usr/local/share/ca-certificates/ && \
        update-ca-certificates

    # Authentication sttings.
    sed -i "s/^\(passwd: \+\)[a-z ]\+$/\1compat winbind/" /etc/nsswitch.conf
    sed -i "s/^\(group: \+\)[a-z ]\+$/\1compat winbind/" /etc/nsswitch.conf

    # Copy krb5.conf
    mv --backup=numbered /etc/krb5.conf /etc/krb5.conf.bak
    cp /var/lib/samba/private/krb5.conf /etc/

    # Suppress apache warning.
    echo "ServerName localhost" | tee /etc/apache2/conf-available/fqdn.conf
    a2enconf fqdn

    # Setup phpLdapAdmin.
    if [ -e /root/packages/phpLDAPadmin-1.2.3.tar.gz ]; then
        a2dismod php8.1
        a2enmod php7.3
        if [ $(grep "ldap server require strong auth" /etc/samba/smb.conf -c) -ne 0 ]; then
            sed -i "/ldap server require strong auth/d" /etc/samba/smb.conf
        fi
        sed -i "/\[global\]/a \\\tldap server require strong auth = no" /etc/samba/smb.conf

        tar zxf /root/packages/phpLDAPadmin-1.2.3.tar.gz -C /var/www/
        mv /var/www/phpLDAPadmin-1.2.3 /var/www/phpldapadmin
        cp /etc/phpldapadmin/apache.conf /etc/phpldapadmin/apache.conf.bak
        sed -i "s@/usr/share/phpldapadmin/htdocs@/var/www/phpldapadmin@g" /etc/phpldapadmin/apache.conf
        cp /var/www/phpldapadmin/config/config.php.example /var/www/phpldapadmin//config/config.php
        sed -i "$ i\$servers->setValue('server','host','ldap://127.0.0.1');" /var/www/phpldapadmin/config/config.php
        sed -i "$ i\$servers->setValue('login','bind_id','administrator@${SMB_REALM,,}');" /var/www/phpldapadmin/config/config.php
        sed -i "$ i\$config->custom->appearance['hide_template_warning'] = true;" /var/www/phpldapadmin/config/config.php
        sed -i "s/\$servers->setValue('server','name','My LDAP Server');/\$servers->setValue('server','name','$SMB_DOMAIN');/" /var/www/phpldapadmin/config/config.php

        # Customize phpLDAPadmin
        # for PHP7.0
        sed -i "s/password_hash/password_hash_custom/g" /var/www/phpldapadmin/lib/*
        sed -i '2567d; 2568d; 2569i \\t\tforeach ($dn as $key => $rdn) {\n\t\t\t$a[$key] = preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return '\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $rdn\'');\n\t\t}' /var/www/phpldapadmin/lib/functions.php
        sed -i '2574c \\t\treturn  preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return'\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $dn);' /var/www/phpldapadmin/lib/functions.php
        sed -i '1119d; 1120d; 1121i \\t\t\tforeach ($dn as $key => $rdn) {\n\t\t\t\t$a[$key] = preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return '\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $rdn\'');\n\t\t\t}' /var/www/phpldapadmin/lib/ds_ldap.php
        sed -i '1126c \\t\t\treturn  preg_replace_callback('\''/\\\\\\([0-9A-Fa-f]{2})/'\'', function ($m) { return'\'\''.chr(hexdec('\''\\\\1'\'')).'\'\''; }, $dn);' /var/www/phpldapadmin/lib/ds_ldap.php
        # for PHP7.3
        sed -i '54c function my_autoload($className) {' /var/www/phpldapadmin/lib/functions.php
        sed -i '777c spl_autoload_register("my_autoload");' /var/www/phpldapadmin/lib/functions.php
        sed -i '1083c \\t\t$CACHE[$sortby] = __create_function('\''$a, $b'\'',$code);' /var/www/phpldapadmin/lib/functions.php
        sed -i '1091a function __create_function($arg, $body) {\n\tstatic $cache = array();\n\tstatic $maxCacheSize = 64;\n\tstatic $sorter;\n\n\tif ($sorter === NULL) {\n\t\t$sorter = function($a, $b) {\n\t\t\tif ($a->hits == $b->hits) {\n\t\t\t\treturn 0;\n\t\t\t}\n\n\t\t\treturn ($a->hits < $b->hits) ? 1 : -1;\n\t\t};\n\t}\n\n\t$crc = crc32($arg . "\\\\x00" . $body);\n\n\tif (isset($cache[$crc])) {\n\t\t++$cache[$crc][1];\n\t\treturn $cache[$crc][0];\n\t}\n\n\tif (sizeof($cache) >= $maxCacheSize) {\n\t\tuasort($cache, $sorter);\n\t\tarray_pop($cache);\n\t}\n\n\t$cache[$crc] = array($cb = eval('\''return function('\''.$arg.'\''){'\''.$body.'\''};'\''), 0);\n\treturn $cb;\n}\n' /var/www/phpldapadmin/lib/functions.php
    fi

    # Mark as configured.
    touch /root/packages/configured
fi

#----------------------------------------------------------------------
# Container and Volumes is left.
#----------------------------------------------------------------------
echo "Setting to do every time"

# Resolver settings.
cp /etc/resolv.conf /root/packages/resolv.conf
sed -i "s/nameserver 127.0.0.11/nameserver 127.0.0.1/" /root/packages/resolv.conf
cat /root/packages/resolv.conf > /etc/resolv.conf

ファイルを作ったら、実行権を付けておく。

$ chmod +x ~/samba/packages/config-restore.sh

その他のファイル

その他のファイルは、プライマリーDCと同じ。
プライマリーDCからコピーしてきて配置する。

コンテナの起動

ファイルの準備ができたら、ファイアウォールを設定する。

$ sudo ufw allow from 172.26.0.103 to 192.168.110.10 comment "From Container"

コンテナを起動する。

$ sudo docker compose up --build

スクリプトによりリストアされるので、どんな状態になっているのか確認してみたところ、ユーザーは復元していた。

$ sudo docker exec -it samba /bin/bash --login
# samba-tool user list
hogewife
ssoauth
Administrator
hoge
Guest
rohhie
krbtgt

コンピューターを確認すると、プライマリーDCとセカンダリーDCが存在しない。

# samba-tool computer list
ADDCR$
WORK$
WINTEMP$

DNSにはNSが登録されておらず、addcやaddc2に関する情報はない。
これこそが、復元用のDCたらしめている設定、ということのようだ(addcrはNSではなく、これからJoinするDCがNSになっていく)。

# samba-tool dns query localhost hogeserver.hogeddns.jp @ all -U administrator
Password for [HOGEDOMAIN\administrator]:
  Name=, Records=1, Children=0
    SOA: serial=33, refresh=900, retry=600, expire=86400, minttl=3600, ns=addcr.hogeserver.hogeddns.jp., email=hostmaster.hogeserver.hogeddns.jp. (flags=600000f0, serial=110, ttl=3600)
  Name=WINTEMP, Records=1, Children=0
    A: 192.168.110.21 (flags=f0, serial=110, ttl=1200)
  Name=work, Records=1, Children=0
    A: 192.168.110.3 (flags=f0, serial=110, ttl=3600)

プライマリーDCの復旧

ドメインに参加

リストアドDCに、プライマリーDCを参加させてみる。

addc:~/samba$ sudo docker exec -it samba /bin/bash --login
# samba-tool domain join HOGESERVER.HOGEDDNS.JP DC --server=192.168.110.10 -U administrator

Joinはできたようだ。
addcrでDNSの状態を見てみる。

# samba-tool dns query localhost hogeserver.hogeddns.jp @ all -U administrator
Password for [HOGEDOMAIN\administrator]:
  Name=, Records=1, Children=0
    SOA: serial=16, refresh=900, retry=600, expire=86400, minttl=3600, ns=addcr.hogeserver.hogeddns.jp., email=hostmaster.hogeserver.hogeddns.jp. (flags=600000f0, serial=16, ttl=3600)
  Name=addc, Records=1, Children=0
    A: 172.26.0.101 (flags=f0, serial=16, ttl=900)
  Name=WINTEMP, Records=1, Children=0
    A: 192.168.110.21 (flags=f0, serial=110, ttl=1200)
  Name=work, Records=1, Children=0
    A: 192.168.110.3 (flags=f0, serial=110, ttl=3600)

joinする際に、–host-ipを指定したかったが、そのようなオプションはなかったので、addcがコンテナの中で割り当てているIPアドレスが設定されている。

addcとaddcr双方でIPアドレスを修正する。

# samba-tool dns update localhost hogeserver.hogeddns.jp addc A 172.26.0.101 192.168.110.4 -U administrator
# samba-tool dns update 192.168.110.10 hogeserver.hogeddns.jp addc A 172.26.0.101 192.168.110.4 -U administrator
# exit

addcのコンテナを再起動する。

addc:~/samba$ sudo docker compose stop
addc:~/samba$ sudo docker compose up

他のコンソールで中に入り、DNSの状態を確認してみる。

addc:~/samba$ sudo docker exec -it samba /bin/bash --login
# samba-tool dns query localhost hogeserver.hogeddns.jp @ all -U administrator
Password for [HOGEDOMAIN\administrator]:
  Name=, Records=3, Children=0
    SOA: serial=34, refresh=900, retry=600, expire=86400, minttl=3600, ns=addcr.hogeserver.hogeddns.jp., email=hostmaster.hogeserver.hogeddns.jp. (flags=600000f0, serial=34, ttl=3600)
    NS: addc.hogeserver.hogeddns.jp. (flags=600000f0, serial=18, ttl=900)
    A: 192.168.110.4 (flags=600000f0, serial=19, ttl=900)
  Name=_sites, Records=0, Children=1
  Name=_tcp, Records=0, Children=4
  Name=_udp, Records=0, Children=2
  Name=addc, Records=1, Children=0
    A: 192.168.110.4 (flags=f0, serial=17, ttl=900)
  Name=DomainDnsZones, Records=0, Children=2
  Name=ForestDnsZones, Records=0, Children=2
  Name=WINTEMP, Records=1, Children=0
    A: 192.168.110.21 (flags=f0, serial=110, ttl=1200)
  Name=work, Records=1, Children=0
    A: 192.168.110.3 (flags=f0, serial=110, ttl=3600)

修正できた。
この修正は、今のコンテナの環境で起動しているから必要なのであって、smb.confでbind interface指定ができる環境なら、Joinするだけでこの状態になりそうだ。

IDマップの復旧

結論からすると、idmap.ldbを復旧すれば良いように見える。

とりあえず、見てみよう。
一旦、ユーザーとグループを列挙させる。

# getent passwd
# getent group

続いて、

# ldbsearch -H /var/lib/samba/private/sam.ldb '(sAMAccountName=*)' sAMAccountName objectSid > /tmp/sam.txt
# ldbsearch -H /var/lib/samba/private/idmap.ldb dn xidNumber > /tmp/idmap.txt

この段階で、sam.ldbが52レコード程、idmap.ldbで37行出力された。

思った通りにできず、かっこ悪いけれど、加工メモ。

# sed -zi "s/\n/\t/g" /tmp/sam.txt
# sed -i "s/#/\n#/g" /tmp/sam.txt

sam.ldbについて、復旧前後をExcelで適当に加工して比較してみたところ、完全に一致した。
idmap.ldbについても、同様の加工をして比較してみたところ、完全に一致した。

セカンダリーDCでJoinしたときと同様の手順を踏めば、問題なく復旧できるところが見えた。

復旧中のaddcで以下を実行。バックアップファイルの名前は適宜変更する。

addc:~/samba$ sudo docker exec -it samba mkdir /root/packages/work
addc:~/samba$ sudo docker cp samba-backup-YYYY-MM-DDTHH-MM-SS.nnnnnn.tar.bz2 samba:/root/packages/work/
addc:~/samba$ sudo docker exec -it samba /bin/bash --login
■プロセス停止
# pkill -SIGTERM ^samba$
■IDマップの上書き
# cd /root/packages/work/
# tar jxvf samba-backup-2022-08-20T13-28-16.775420.tar.bz2
# cp private/idmap.ldb /var/lib/samba/private/

sysvolの拡張属性の復旧

ここまできて、sysvolには拡張属性がセットされていることを思い出した。

拡張属性を見るコマンドはというと…
雑廉堂Wiki / Windows ACLs を利用して共有を設定する
Samba Wiki / Setting up a Share Using Windows ACLs

# samba-tool ntacl get /var/lib/samba/sysvol/hogeserver.hogeddns.jp/ --as-sddl

これを踏まえて、展開した上で、拡張属性を設定する。

# rm -r /var/lib/samba/sysvol/*
# tar -zxvf sysvol.tar.gz -C /var/lib/samba/sysvol/
# find /var/lib/samba/sysvol/ -name "*.NTACL" -exec bash -c 'TMP=$(dirname "{}")/$(basename "{}" .NTACL); samba-tool ntacl set $(cat "{}") "$TMP" && rm "{}"' \;
# net cache flush
# samba-tool ntacl sysvolreset
# exit

コンテナを再起動する。

addc:~/samba$ sudo docker compose stop
addc:~/samba$ sudo docker compose up

この後、Windowsでグループポリシーの管理を開いたところ、接続できない旨のエラーが表示された。
何度開き直しても、このメッセージが表示される。
そこで、このドメインを一度削除し、改めてhogeserver.hogeddns.jpを追加したところ、問題が解消した。

少しずつ調整は必要だが、以上でプライマリーDCの復旧が完了した。

FSMOロールの復旧

テスト環境でKopanoを起動し、LDAP認証させてみようとした
その過程で、スキーマの登録をしたところ、このようなエラーが出た。

# bash kopano_schema_add.sh DC=hogeserver,DC=hogeddns,DC=jp ./ \
 -H ldap://addc2.hogeserver.hogeddns.jp \
 -U Administrator%p@ssword123 \
 -writechanges
dos2unix: converting file kopano-ads.ldf.unix to Unix format...
ERR: (Unwilling to perform) "LDAP error 53 LDAP_UNWILLING_TO_PERFORM -  <00002035: schema_data_add: we are not master: reject add request
> <>" on DN CN=Kopano-Quota-Override,CN=Schema,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp at block before line 21
Modify failed after processing 0 records
Error: ldbmodify reported an error

FSMOロールの復旧が必要なことが分かった。
[Samba] We are not schema master on all DCs

# samba-tool fsmo show
SchemaMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
InfrastructureMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
RidAllocationMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
PdcEmulationMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
DomainNamingMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
DomainDnsZonesMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
ForestDnsZonesMasterRole owner: CN=NTDS Settings,CN=ADDCR,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp

以下のコマンドで復旧できる。

# samba-tool fsmo seize --role=all -U administrator
Attempting transfer...
Transfer unsuccessful, seizing...
Seizing rid FSMO role...
FSMO seize of 'rid' role successful
…
Attempting transfer...
Failed to connect to ldap URL 'ldap://d0764035-7f49-43fb-bf4e-d37cf7bb8f49._msdcs.hogeserver.hogeddns.jp' - LDAP client internal error: NT_STATUS_OBJECT_NAME_NOT_FOUND
Failed to connect to 'ldap://d0764035-7f49-43fb-bf4e-d37cf7bb8f49._msdcs.hogeserver.hogeddns.jp' with backend 'ldap': LDAP client internal error: NT_STATUS_OBJECT_NAME_NOT_FOUND
…

※途中でエラーが2つ出るけれども、成功の表示が7回。ロールは7つなので、恐らくは問題ないであろう。

復旧したようだ。

# samba-tool fsmo show
SchemaMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
InfrastructureMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
RidAllocationMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
PdcEmulationMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
DomainNamingMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
DomainDnsZonesMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp
ForestDnsZonesMasterRole owner: CN=NTDS Settings,CN=ADDC,CN=Servers,CN=Default-First-Site-Name,CN=Sites,CN=Configuration,DC=hogeserver,DC=hogeddns,DC=jp

セカンダリーDCの復旧

こちらは、一旦まっさらに戻して、復旧したaddcにjoinすることにした。

$ sudo docker compose down --volume

DNSをプライマリーDCに変更する。

docker-compose.yml

version: "3.9"
services:

  samba:
…
    dns:
    dns:
      - 192.168.110.4 #Used for domain to join
     #- 192.168.110.1 #Used for normal operation
…

コンテナを起動。

$ sudo docker compose up --build

この後に、IDマップを一致させるために、idmap.ldbをコピーして反映させ、DNSの設定を元に戻してコンテナを再起動。
プライマリーDCはrsyncの設定済みなので、Joinさえ上手くいけば復旧が完了する。

1回目は、addcのDNSにaddc2がNSとして登録されない問題が発生した。
もう一度まっさらにして、2回目に成功した。
2回目は、addcにaddc2のAレコードが登録されていたので、これが良かったようだ。
コンテナのIPアドレスが追加されてしまう問題がどうしても発生するが、その他の運用はかなり楽になるので、致し方ないところ。

以上で、セカンダリーDCの復旧が完了した。

広告

コメントはこちらから お気軽にどうぞ ~ 投稿に関するご意見・感想・他