Ubuntu

Ubuntu22.04 DockerでSamba-ad-dc

現在稼働中のSamba ad dcはUbuntu 18.04で構築したもの。
OSの更改が必要な時期なので、DockerでSamba ad dcを立ち上げて移行することにした。



広告


Samba ad dcの更新だけを考えると、

  • OSをアップグレードして、Sambaのバージョンも上げて使い続ける。
  • 今とは別のSamba ad dcを立てて、稼働中のSamba ad dcにJoinして、稼働中のSamba ad dcを引退させる。
  • 稼働中のSamba ad dcのバックアップをとり、OSを差し替えてリストアする。

等々色々な方法があるが、Samba ad dcのDockerイメージ(プライマリーDC、セカンダリーDC、リストアドDC)を作ることができたので、どの方法でも移行ができるようになった。

[追記 2022/10/15]
ネットワークモードをhostに変更し、DCのIPアドレスの取り扱いを簡素化。
本記事を複数ページ構成にして、旧バージョンはそちらに移動。
ソースはこちらからダウンロードできるようにしました。
※旧バージョンで稼働していたSamba ad dcは、リストアドDCを立てて動作することを確認後、プライマリーDCをまっさらから立ち上げ直してJoinして、新バージョンに移行しました。復元の手順も整理しておいて良かった。

目指す構成

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

ホストコンテナのホスト名IP Address説明
classc192.168.110.10Ubuntu 22.04で運用するホームラボのルーター、兼、DNS。
mirroraddc192.168.110.4ネットワークに向けてプライマリーDCとして振る舞うように設定する。
nanashiaddc2192.168.110.34ネットワークに向けてセカンダリーDCとして振る舞うように設定する。
設置は任意。
partyaddcr192.168.110.12リストアドDCとして振る舞うように設定する。
リストアが必要な時に設置。
work192.168.110.3Ubuntu 20.04のサーバー。ドメインに参加するテスト用。
WinTemp192.168.110.21Windows 10。ドメインに参加するテスト用。

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

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

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

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

プライマリー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

ベースとなるイメージを作るスクリプトを作成。

~/samba/baseimage/mkbaseimage.sh

#!/bin/bash
cd $(dirname ${0})
docker build -t custom/samba:0.0.1 -f $PWD/Dockerfile .

ベースイメージを作成。

$ chmod +x ~/samba/baseimage/mkbaseimage.sh
$ sudo ~/samba/baseimage/mkbaseimage.sh

ファイル構成

ファイル構成は以下の通り。ダウンロードはこちら

~/samba/
   ├ .env
   ├ docker-compose.yml
   ├ Dockerfile
   ├ setufw.sh ← 実行権限を付ける
   ├ 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

.env

プライマリーDCとフォワード先を定義しておく。
docker-compose.ymlで使用する。

.env

PRIMARYIP=192.168.110.4
SECONDARYIP=192.168.110.34
RESTOREDIP=192.168.110.12
FORWARDERIP=192.168.110.10

※SECONDARYIPとRESTOREDIPは必要になってから設定する、で問題はない。

docker-compose.yml

架空のレルム EXAMPLE.NET (HOGEDOMAIN) を運営する。

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

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

SMB_FORWARDでは、Samba ad dcのフォワード先を指定できる。

SMB_USEBIND9では、バックエンドDNSを内蔵のものにするか、BIND9にするか指定できる。
ここでは false を指定して内蔵のものを利用している。

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

~/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: EXAMPLE.NET
      SMB_DOMAIN: HOGEDOMAIN
      SMB_ADMINPASS: p@ssword123
      SMB_HOSTIP: ${PRIMARYIP}
      SMB_FORWARD: ${FORWARDERIP}
      SMB_RPC_PORTS: 49152-49200
      SMB_WEB_PORTS: 8081
      SMB_PURPOSE: "primary"
      SMB_USEBIND9: "false"
     #RSY_SECONDARY: ${SECONDARYIP}
     #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
    network_mode: "host"
    hostname: addc
    dns:
      - ${PRIMARYIP}
    dns_search:
      - example.net
    privileged: true

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ありありDNSバックエンドの設定を見て、必要に応じて内部とBIND9を切り替える。

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

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

今回は、ネットワークモードをhostにしたので、
 コンテナの中のプログラム → ホストのIPアドレス(=Samba ad dc) → ホストの名前解決(127.0.0.53)
で行われるようにした。

~/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 = $SMB_FORWARD\"
        --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\"
        --option=\"bind interfaces only = yes\"
        --option=\"interfaces = $SMB_HOSTIP 127.0.0.1\"
        --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

    # Sets the port used by apache.
    sed -i "s/Listen 80/Listen $SMB_WEB_PORTS/" /etc/apache2/ports.conf

    # 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"

# 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 { $SMB_FORWARD; };\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)。

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

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

ファイアウォール

サービスを提供するのに必要なポートを解放する。

setufw.sh

#!/bin/bash
ufw $1 allow to any port   53 proto any from any comment "DNS"
ufw $1 allow to any port  135 proto tcp from any comment "End Point Mapper(WINS)"
ufw $1 allow to any port  137 proto udp from any comment "NetBIOS Name Service"
ufw $1 allow to any port  138 proto udp from any comment "NetBIOS Datagram"
ufw $1 allow to any port  139 proto tcp from any comment "NetBIOS Session"
ufw $1 allow to any port  445 proto tcp from any comment "SMB over TCP"
ufw $1 allow to any port  389 proto any from any comment "LDAP"
ufw $1 allow to any port  636 proto tcp from any comment "LDAPS"
ufw $1 allow to any port   88 proto any from any comment "Kerberos"
ufw $1 allow to any port  464 proto any from any comment "Kerberos kpasswd"
ufw $1 allow to any port 3268 proto tcp from any comment "Global Catalog"
ufw $1 allow to any port 3269 proto tcp from any comment "Global Catalog SSL"
ufw $1 allow to any port 49152:49200 \
                              proto tcp from any comment "RPC The same value as SMB_RPC_PORTS."
# for Primary
ufw $1 allow to any port  873 proto tcp from any comment "rsync"

実行権限を付けておく。

$ chmod +x setufw.sh

ファイアウォールを設定する。

$ sudo ./setufw.sh

もし、何らかの理由でファイアウォールの設定を削除する場合には以下を実行。

$ sudo ./setufw.sh delete

起動と確認

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

コンテナを起動する。

$ sudo docker compose up --build

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

$ sudo docker exec -it samba 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  Fri Oct 14 06:50:39 2022
  ..                                  D        0  Fri Oct 14 06:50:42 2022

                19948144 blocks of size 1024. 8855556 blocks available

チケットの発行。

# kinit administrator
Password for administrator@EXAMPLE.NET: ユーザーadministratorのパスワード[Enter]

# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: administrator@EXAMPLE.NET

Valid starting       Expires              Service principal
10/14/2022 06:53:17  10/14/2022 16:53:17  krbtgt/EXAMPLE.NET@EXAMPLE.NET
        renew until 10/15/2022 06:53:14

# kdestroy

名前解決。

# dig @127.0.0.1 addc.example.net +short
192.168.110.4

# dig addc.example.net +norec
…
;; ANSWER SECTION:
addc.example.net.       900     IN      A       192.168.110.4

;; AUTHORITY SECTION:
example.net.            3600    IN      SOA     addc.example.net. hostmaster.example.net. 1 900 600 86400 3600

;; Query time: 0 msec
;; SERVER: 192.168.110.4#53(192.168.110.4) (UDP)
;; WHEN: Fri Oct 14 06:56:08 JST 2022
;; MSG SIZE  rcvd: 97

hostsとresolv.confを確認してみた。

# cat /etc/hosts
127.0.0.1 localhost
127.0.1.1 mirror
…(ホストのhostsが表示された)

# cat /etc/resolv.conf
search example.net
nameserver 192.168.110.4
options edns0 trust-ad

ゾーン情報。

# samba-tool dns query localhost example.net @ 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.example.net., email=hostmaster.example.net. (flags=600000f0, serial=1, ttl=3600)
    NS: addc.example.net. (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@example.net
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@example.net
# samba-tool user add hoge p@ssword123 --given-name=User --surname=Hoge --mail-address=hoge@example.net
# samba-tool user add hogewife p@ssword123 --given-name=User --surname=Wife --mail-address=hogewife@example.net

グループを作ってみる。

# samba-tool group add parent --mail-address=parent@example.net
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を稼働させることができた。

ホストのDNS設定

コンテナを動作させているホストが名前解決をするときに、Samba ad dcを参照させる。

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

network:
  ethernets:
    ens33:
      addresses:
      - 192.168.110.4/24
      routes:
        - to: default
          via: 192.168.110.10
      nameservers:
        addresses:
        - 192.168.110.4
        search:
        - example.net
#        addresses:
#        - 192.168.110.10
#        search:
#        - hogeserver.hogeddns.jp
…

反映。

$ sudo netplan apply

動作を確かめる。

$ dig example.net +short
192.168.110.4

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

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

$ dig mirror.example.net +short
192.168.110.4

時刻サーバー

ドメインで皆が「同じ時間」を過ごせるように、時刻サーバーを動かす。

コンテナの中でできるかもしれないけれど、システム時間の設定と考えると、設定がやたらと大変になりそうだなと思った。
そこで、ホストにChronyをインストールすることにした。

$ sudo apt install chrony

設定を日本向けに設定。
あわせてアクセス可能なネットワークを指定している。この指定がないと、NTPサーバーとして動作しないので注意。

/etc/chrony/chrony.conf

…
pool ntp.nict.jp          iburst
pool ntp1.jst.mfeed.ad.jp iburst
pool ntp2.jst.mfeed.ad.jp iburst
pool ntp3.jst.mfeed.ad.jp iburst
…
# Access control.
allow 192.168.110.0/24

設定の反映。

$ sudo systemctl restart chrony

ポートの開放。

$ sudo ufw allow to any port 123 proto udp from any comment "NTP"

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

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

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

ドメインへの参加

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

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

network:
  ethernets:
    ens33:
      addresses:
      - 192.168.110.3/24
      routes:
        - to: default
          via: 192.168.110.10
      nameservers:
        addresses:
        - 192.168.110.4
        search:
        - example.net

反映。

$ sudo netplan apply

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

hostsファイルを修正。

/etc/hosts

127.0.0.1 localhost
#127.0.1.1 work
192.168.110.3 work.example.net work

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

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

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

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

※DeepL先生の翻訳

Sambaの設定。

/etc/samba/smb.conf

[global]
    realm = EXAMPLE.NET
    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がインストールしてあり、それぞれのコンテナでしか使えない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 'example.net'

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

設定を反映。

$ sudo systemctl restart nmbd winbind

さて、チケットの発行には、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 = EXAMPLE.NET

※作成するだけでOK。

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

$ dig @example.net work.example.net

…
;; ANSWER SECTION:
work.example.net.       3600    IN      A       192.168.110.3

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

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

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

$ dig addc.example.net
…
;; ANSWER SECTION:
addc.example.net.       675     IN      A       192.168.110.4

;; Query time: 0 msec
;; SERVER: 127.0.0.53#53(127.0.0.53)
;; WHEN: Fri Oct 14 07:47:01 JST 2022
;; MSG SIZE  rcvd: 61

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

$ getent passwd
…
HOGEDOMAIN\administrator:*:1000500:1000513::/home/HOGEDOMAIN/administrator:/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@EXAMPLE.NET: ユーザーrohhieのパスワード[Enter]

$ klist
Ticket cache: FILE:/tmp/krb5cc_1000
Default principal: rohhie@EXAMPLE.NET

Valid starting       Expires              Service principal
10/14/2022 08:12:24  10/14/2022 18:12:24  krbtgt/EXAMPLE.NET@EXAMPLE.NET
        renew until 10/15/2022 08:12:22

$ 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 Oct 14 08:15 ./
drwxr-xr-x 3 root            root                    4096 Oct 14 08:15 ../
-rw-r--r-- 1 HOGEDOMAIN\hoge HOGEDOMAIN\domain users  220 Oct 14 08:15 .bash_logout
-rw-r--r-- 1 HOGEDOMAIN\hoge HOGEDOMAIN\domain users 3771 Oct 14 08:15 .bashrc
-rw-r--r-- 1 HOGEDOMAIN\hoge HOGEDOMAIN\domain users  807 Oct 14 08:15 .profile

$ exit

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

$ sudo rm -r /home/HOGEDOMAIN/

$ smbclient //localhost/hoge -U hoge -c 'ls'
Password for [HOGEDOMAIN\hoge]: hogeのパスワード[Enter]
  .                                   D        0  Fri Oct 14 08:17:10 2022
  ..                                  D        0  Fri Oct 14 08:17:10 2022
  .profile                            H      807  Fri Oct 14 08:17:10 2022
  .bash_logout                        H      220  Fri Oct 14 08:17:10 2022
  .bashrc                             H     3771  Fri Oct 14 08:17:10 2022

                20463184 blocks of size 1024. 9739388 blocks available

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

ドメインからの離脱

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

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

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

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

…
      nameservers:
        addresses:
        - 192.168.110.10
        search:
        - hogeserver.hogeddns.jp

反映。

$ sudo netplan apply

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

/etc/nsswitch.conf

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

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

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

addc:~$ sudo docker exec -it samba bash --login
# samba-tool dns delete localhost example.net 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からもエントリーが消える。…はずだけれども、一応確認して、残っていたら消す。

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

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

ドメインへの参加

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

実際に参加してみる。

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

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

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

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

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

参加後の動作確認

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

C:\Users\rohhie.HOGEDOMAIN>nslookup wintemp
サーバー:  mirror
Address:  192.168.110.4

名前:    wintemp.example.net
Address:  192.168.110.21

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

C:\Users\rohhie.HOGEDOMAIN>klist

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

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

#0>     クライアント: rohhie @ EXAMPLE.NET
        サーバー: krbtgt/EXAMPLE.NET @ EXAMPLE.NET
        Kerberos チケットの暗号化の種類: AES-256-CTS-HMAC-SHA1-96
        チケットのフラグ 0x60ac0000 -> forwardable forwarded renewable pre_authent ok_as_delegate 0x80000
        開始時刻: 10/15/2022 6:50:25 (ローカル)
        終了時刻: 10/15/2022 16:50:24 (ローカル)
        更新期限: 10/22/2022 6:50:24 (ローカル)
        セッション キーの種類: AES-256-CTS-HMAC-SHA1-96
        キャッシュ フラグ: 0x2 -> DELEGATION
        呼び出された Kdc: addc.example.net
…

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

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

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

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

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

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

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

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

\\example.net\netlogon\test.bat

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

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

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

ドメインからの離脱

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

ドメインからの離脱は、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 example.net 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 example.net 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の設定

コンテナのApacheをポート8081で動作させているので、そこへProxyする。
Apacheはインストール済みのホストだったので、そこへ設定を追加している。

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

<VirtualHost *:80>
…
<VirtualHost *:443>
    ServerAdmin webmaster@example.net
    ServerName addc.example.net
    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/example.net.crt
    SSLCertificateKeyFile /etc/ssl/private/example.net.key
</VirtualHost>

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

設定を有効化する。

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

phpLDAPadmin

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

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

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

LDAP Acount Managerの初期設定

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

まず、サイトにアクセスする。
https://example.net/lam

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

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

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

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

サーバー証明書・秘密鍵を用意している場合、Server addressには証明書の発行先(Subject Alternative Name)にあったものにしておく。
今回用意したのは、SANにexample.net, *.example.netが設定されているワイルドカードな証明書だったので、example.netを設定した。
証明書の設定と違った名前でアクセスすると、「(-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"

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

これまでexample.netとかやってきたが、ここで、復元の対象となる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 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 packages/cert/ca.crl /var/lib/samba/private/tls/

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

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

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

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

$ sudo docker exec -it samba bash --login
# cd /var/lib/samba
# sudo bash sysvol/NTACL
# sudo rm sysvol/NTACL
# exit

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

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

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

$ sudo docker exec -it samba 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/
   ├ .env
   ├ docker-compose.yml
   ├ Dockerfile
   ├ setufw.sh ← 実行権限を付ける
   ├ 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

.env

セカンダリーDCとフォワード先を定義しておく。
docker-compose.ymlで使用する。

.env

PRIMARYIP=192.168.110.4
SECONDARYIP=192.168.110.34
RESTOREDIP=192.168.110.12
FORWARDERIP=192.168.110.10

docker-compose.yml

架空のレルム EXAMPLE.NET(HOGEDOMAIN) に参加する。

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

SMB_HOSTIPでは、このコンテナを動かすホストのIPアドレスを指定しており、Join時に使う。

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

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

~/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: EXAMPLE.NET
      SMB_DOMAIN: HOGEDOMAIN
      SMB_ADMINPASS: p@ssword123
      SMB_HOSTIP: ${SECONDARYIP}
      SMB_FORWARD: ${FORWARDERIP}
      SMB_RPC_PORTS: 49152-49200
      SMB_WEB_PORTS: 8081
      SMB_PURPOSE: "secondary"
      SMB_USEBIND9: "false"
      RSY_PRIMARY: ${PRIMARYIP}
      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
    network_mode: "host"
    hostname: addc2
    dns:
      - ${SECONDARYIP}
    dns_search:
      - example.net
    privileged: true

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ありありDNSバックエンドの設定を見て、必要に応じて内部とBIND9を切り替える。

プライマリー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="
        --server=${RSY_PRIMARY}
        --username=administrator
        --password=$SMB_ADMINPASS
        --realm=$SMB_REALM
        --option=\"dns forwarder = ${SMB_FORWARD}\"
        --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\"
        --option=\"bind interfaces only = yes\"
        --option=\"interfaces = $SMB_HOSTIP 127.0.0.1\"
    "
    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
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

    # Sets the port used by apache.
    sed -i "s/Listen 80/Listen $SMB_WEB_PORTS/" /etc/apache2/ports.conf

    # 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"

# 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 { ${SMB_FORWARD}; };\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が起動する。

ファイアウォール

プライマリーDCと同じスクリプトで設定。

$ sudo ./setufw.sh

コンテナの起動

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

$ sudo docker compose up --build

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

コンテナが起動した後、プライマリー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=2c33ed57-f019-46aa-b03b-6a633ec4ebc2._msdcs.example.net,target_principal=GC/addc2.example.net/example.net,abstract_syntax=e3514235-4b06-11d1-ab04-00c04fc2dcd2/0x00000004,localaddress=192.168.110.4] 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:*:3000016:100::/home/HOGEDOMAIN/krbtgt:/bin/bash
HOGEDOMAIN\rohhie:*:3000017:100::/home/HOGEDOMAIN/rohhie:/bin/bash
HOGEDOMAIN\ssoauth:*:3000018:100::/home/HOGEDOMAIN/ssoauth:/bin/bash
HOGEDOMAIN\hoge:*:3000019:100::/home/HOGEDOMAIN/hoge:/bin/bash
HOGEDOMAIN\hogewife:*:3000020:100::/home/HOGEDOMAIN/hogewife:/bin/bash

# getent group
…
BUILTIN\cryptographic operators:x:3000035:
BUILTIN\event log readers:x:3000036:
BUILTIN\certificate service dcom access:x:3000037:
HOGEDOMAIN\cert publishers:x:3000038:
HOGEDOMAIN\ras and ias servers:x:3000039:
HOGEDOMAIN\allowed rodc password replication group:x:3000040:
…

ホストのDNS設定

コンテナを動作させているホストが名前解決をするときに、Samba ad dcを参照させる。

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

network:
  ethernets:
    ens33:
      #dhcp4: true
      addresses:
      - 192.168.110.34/24
      routes:
        - to: default
          via: 192.168.110.10
      nameservers:
        addresses:
        - 192.168.110.34
        search:
        - example.net
#        addresses:
#        - 192.168.110.10
#        search:
#        - hogeserver.hogeddns.jp

反映。

$ sudo netplan apply

動作を確かめる。

$ dig example.net +short
192.168.110.4
192.168.110.34

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

$ sudo docker exec -it samba samba-tool dns add localhost example.net nanashi A 192.168.110.34 -U administrator
Password for [HOGEDOMAIN\administrator]: ユーザーadministratorのパスワード[Enter]
Record added successfully

$ dig nanashi.example.net +short
192.168.110.34

ドメインから離脱

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

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

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

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

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

addc:~/samba$ sudo docker exec -it samba 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 1641003 Oct 15 11:22 samba-backup-2022-10-15T11-22-41.389738.tar.bz2

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

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

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

ファイル構成

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

~/samba/
   ├ .env
   ├ docker-compose.yml
   ├ Dockerfile
   ├ setufw.sh ← 実行権限を付ける
   ├ 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

.env

リストアドDCとフォワード先を定義しておく。
docker-compose.ymlで使用する。

.env

PRIMARYIP=192.168.110.4
SECONDARYIP=192.168.110.34
RESTOREDIP=192.168.110.12
FORWARDERIP=192.168.110.10

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: EXAMPLE.NET
      SMB_DOMAIN: HOGEDOMAIN
      SMB_ADMINPASS: p@ssword123
      SMB_HOSTIP: ${RESTOREDIP}
      SMB_FORWARD: ${FORWARDERIP}
      SMB_RPC_PORTS: 49152-49200
      SMB_WEB_PORTS: 8081
      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
    network_mode: "host"
    hostname: addcr
    dns:
      - ${RESTOREDIP}
    dns_search:
      - example.net
    privileged: true

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 \
        --option="dns forwarder = $SMB_FORWARD" \
        --option="bind interfaces only = yes" \
        --option="interfaces = $SMB_HOSTIP 127.0.0.1"

    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

    # Sets the port used by apache.
    sed -i "s/Listen 80/Listen $SMB_WEB_PORTS/" /etc/apache2/ports.conf

    # 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"

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

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

その他のファイル

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

ファイアウォール

一時的に立てるサーバーなので、ファイアウォールの設定はしないかもしれない。
設定する場合は、プライマリーDCと同じスクリプトで設定。

$ sudo ./setufw.sh

コンテナの起動

$ 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 bash --login
# samba-tool user list
hogewife
ssoauth
Administrator
hoge
Guest
rohhie
krbtgt

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

# samba-tool computer list
ADDCR$

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

# samba-tool dns query localhost example.net @ all -U administrator
Password for [HOGEDOMAIN\administrator]:
  Name=, Records=3, Children=0
    SOA: serial=22, refresh=900, retry=600, expire=86400, minttl=3600, ns=addcr.example.net., email=hostmaster.example.net. (flags=600000f0, serial=22, ttl=3600)
    NS: addcr.example.net. (flags=600000f0, serial=6, ttl=900)
    A: 192.168.110.12 (flags=600000f0, serial=7, ttl=900)
  Name=_sites, Records=0, Children=1
  Name=_tcp, Records=0, Children=4
  Name=_udp, Records=0, Children=2
  Name=addcr, Records=1, Children=0
    A: 192.168.110.12 (flags=f0, serial=5, ttl=900)
  Name=DomainDnsZones, Records=0, Children=2
  Name=ForestDnsZones, Records=0, Children=2
  Name=mirror, Records=1, Children=0
    A: 192.168.110.4 (flags=f0, serial=2, ttl=900)
  Name=nanashi, Records=1, Children=0
    A: 192.168.110.34 (flags=f0, serial=4, ttl=900)

プライマリーDCの復旧

ドメインに参加

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

addc:~/samba$ sudo docker exec -it samba bash --login
# samba-tool domain join EXAMPLE.NET DC --server=192.168.110.12 -U administrator

Joinはできたようだ。
addcrでDNSの状態を見てみる。addcでも結果は同じだった。

# samba-tool dns query localhost example.net @ all -U administrator
  Name=, Records=3, Children=0
    SOA: serial=23, refresh=900, retry=600, expire=86400, minttl=3600, ns=addcr.example.net., email=hostmaster.example.net. (flags=600000f0, serial=23, ttl=3600)
    NS: addcr.example.net. (flags=600000f0, serial=6, ttl=900)
    A: 192.168.110.12 (flags=600000f0, serial=7, 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=23, ttl=900)
  Name=addcr, Records=1, Children=0
    A: 192.168.110.12 (flags=f0, serial=5, ttl=900)
  Name=DomainDnsZones, Records=0, Children=2
  Name=ForestDnsZones, Records=0, Children=2
  Name=mirror, Records=1, Children=0
    A: 192.168.110.4 (flags=f0, serial=2, ttl=900)
  Name=nanashi, Records=1, Children=0
    A: 192.168.110.34 (flags=f0, serial=4, ttl=900)

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

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

もう一度確認してみると、NSとしてaddcが追加されている。

addc:~/samba$ sudo docker exec -it samba bash --login
# samba-tool dns query localhost example.net @ all -U administrator
Password for [HOGEDOMAIN\administrator]:
  Name=, Records=5, Children=0
    SOA: serial=40, refresh=900, retry=600, expire=86400, minttl=3600, ns=addcr.example.net., email=hostmaster.example.net. (flags=600000f0, serial=40, ttl=3600)
    NS: addcr.example.net. (flags=600000f0, serial=6, ttl=900)
    A: 192.168.110.12 (flags=600000f0, serial=7, ttl=900)
    NS: addc.example.net. (flags=600000f0, serial=24, ttl=900)
    A: 192.168.110.4 (flags=600000f0, serial=25, 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=23, ttl=900)
  Name=addcr, Records=1, Children=0
    A: 192.168.110.12 (flags=f0, serial=5, ttl=900)
  Name=DomainDnsZones, Records=0, Children=2
  Name=ForestDnsZones, Records=0, Children=2
  Name=mirror, Records=1, Children=0
    A: 192.168.110.4 (flags=f0, serial=2, ttl=900)
  Name=nanashi, Records=1, Children=0
    A: 192.168.110.34 (flags=f0, serial=4, ttl=900)

addcrを停止する。

$ sudo docker compose stop

addcで、addcrを強制削除する。

addc:~/samba$ sudo docker exec -it samba samba-tool domain demote --remove-other-dead-server=ADDCR

これで、ドメインコントローラーとして動作するようになった。

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 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/example.net/ --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でグループポリシーの管理を開いたところ、接続できない旨のエラーが表示された。
何度開き直しても、このメッセージが表示される。
そこで、このドメインを一度削除し、改めてexample.netを追加したところ、問題が解消した。

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

セカンダリーDCの復旧

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

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

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

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

やったこと

IDマッピング

現在、本番稼働しているDCとメンバーサーバーは、それぞれ使えてはいるのだけれど、それぞれのサーバーでUID/GIDが変わってしまっている。
今回は少し学習して、こんなIDマッピングをさせることにした。

  • DCでは、UID/GIDの管理はtdbで管理。
    セカンダリーDCは、ドメイン参加時にプライマリーDCのidmap.ldbを反映させて、UID/GIDを一致させる。
  • メンバーサーバーはridで管理。
    DCとは違うIDになるが、すべてのメンバーサーバーが同じIDを使う。

Dockerで動かすDCは、ファイルサーバーとして動作させるつもりがないし、telnet接続させることもないので、メンバーサーバーとIDが違っていても良いだろうと考えている。
ただし、DCをRFC2307を使用する形で構築しているので、IDマッピングのバックエンドをadに移行することはできる想定。

IDマッピングのバックエンドについて、ざっくりと整理したものがこちら。

バックエンド機能
tdbデフォルト。SIDとUID/GIDのマッピングテーブルをidmap.ldbに保存する。
ldapSIDとUID/GIDのマッピングテーブルをLDAPに保存する。
ridSIDとUID/GIDを計算でマッピングする。
読み取り専用のため、DCでは使用できない。
メンバーサーバーがこれを使用すると、一意のUID/GIDが計算される。
hash非推奨。
autoridSIDとUID/GIDを計算でマッピングする。
フォレストの各ドメインで使われるレンジを自動的に設定する。
adRFC2307スキーマ拡張が使えることが前提。
uidNumber/gidNumberが設定されているユーザーのみをマップする。
利用しているグループにはgidNumberを割り当てることが推奨される。
nssUID/GIDをWindowsのアカウントに名前でマッピングする。
rfc2307UID/GIDをWindowsのアカウントに名前でマッピングする。
マッピングにLDAPを使用する。

DCではridが使えないということを知らなかったので、どんなに設定してもUID/GIDが3000000番台になることについて色々と調べてみている。

まず、xidNumber、uidNumber、gidNumberはこういった内容だった。

属性名内容
xidNumber3000000から始まるIDで、グループやユーザーなどに自動で割り付けられる。ただし、例外あり。
idmap.ldbで管理されており、そこでグループやユーザーのobjectSidと、xidNumberが紐付けられている。
uidNumberSambaには自動設定の機能はなく、管理者が設定する。
winbindでは、IDマップのバックエンドがadの時に使われる。
gidNumberSambaには自動設定の機能はなく、管理者が設定する。

xidNumberの例外はこちら。

タイプ名前SIDUID/GID
ユーザーAdministratorS-1-5-21-3516392134-2671769076-675773320-5000
グループDomain UsersS-1-5-21-3516392134-2671769076-675773320-513100
ユーザーS-1-5-765534
xidNumberの例外

DCの具体的な動きはこちら。

まず、ユーザーを作ると、/var/lib/samba/private/sam.ldb にデーターが作られる。

# ldbedit -H /var/lib/samba/private/sam.ldb -b CN=rohhie,CN=Users,DC=hogeserver,DC=hogeddns,DC=jp   ← 編集
# ldbsearch -H /var/lib/samba/private/sam.ldb -b CN=rohhie,CN=Users,DC=hogeserver,DC=hogeddns,DC=jp ← 表示
…
objectSid: S-1-5-21-3532290644-531026872-1514797162-1103

このユーザーのidmapは、/var/lib/samba/private/idmap.ldbに作られる。

# ldbsearch -H /var/lib/samba/private/idmap.ldb -b CN=S-1-5-21-3532290644-531026872-1514797162-1103
# record 1
dn: CN=S-1-5-21-3532290644-531026872-1514797162-1103
cn: S-1-5-21-3532290644-531026872-1514797162-1103
objectClass: sidMap
objectSid: S-1-5-21-3532290644-531026872-1514797162-1103
type: ID_TYPE_BOTH
xidNumber: 3000043
distinguishedName: CN=S-1-5-21-3532290644-531026872-1514797162-1103

getent passwd や getent group で表示されていたUID/GIDは、このxidNumberだった。
DCのデフォルトのIDマップは、バックエンドがtdb、範囲が3000000-4999999(変更できない)となっているので、3000000番台のxidNumberを割り付けており、この番号が表示されていた。
これがidmap.ldbに保管されているので、各DCが同じidmap.ldbを使えば、同じUID/GIDになるというわけ。

xidNumberは、何らかの理由(getentで呼び出されたとか、ログインされたとか)でwinbindがユーザー情報を引っ張ってきたとき、つまり新しいSIDに遭遇したときに作られるのではないかと想像している。だから、idmap.ldbって時々同期しないといけないんじゃ?と思うのだけれども、現時点で答えは持っていない。不具合が起きてから対応するというのでも間に合うかなーとも思っている。

ちなみに、色々やっている中で、winbindd_idmap.tdbというファイルが作られることがあった。
中身が気になったので確認してみたところ、4レコードしかなかった。

# dbwrap_tool --persistent /var/lib/samba/winbindd_idmap.tdb listkeys
DN=@BASEINFO\00
USER HWM\00
GROUP HWM\00
IDMAP_VERSION\00

最終的にできあがった構成では、winbindd_idmap.tdbは作られないので、この記事としては意味がなさそうだけれども、データーの読み方の1つとしてメモしておく。

ドメイン参加時のhostsへの登録順序

ドメインに参加する。

$ 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'
No DNS domain configured for work. Unable to perform DNS Update.
DNS update failed: NT_STATUS_INVALID_PARAMETER

おっと…DNSの登録に失敗してる…
原因は、hostsの設定にあった。
h-otterの備忘録 / WinbindでDNSの動的更新

どういうことかと確認してみると、ホスト名の登録順序によって逆引きしたときの表示順序が変わることが分かった。

$ dig @127.0.0.53 -x 192.168.110.3
…
;; ANSWER SECTION:
3.110.168.192.in-addr.arpa. 0   IN      PTR     work.hogeserver.hogeddns.jp.
3.110.168.192.in-addr.arpa. 0   IN      PTR     work.
…

そのせいか、ping workとしたときも、表示のされ方が違う。

/etc/hostsの指定
192.168.110.3 work.hogeserver.hogeddns.jp work
$ ping work
PING work.hogeserver.hogeddns.jp (192.168.110.3) 56(84) bytes of data.
…
$ ping work.hogeserver.hogeddns.jp
PING work.hogeserver.hogeddns.jp (192.168.110.3) 56(84) bytes of data.
…

/etc/hostsの指定
192.168.110.3 work work.hogeserver.hogeddns.jp
$ ping work
PING work (192.168.110.3) 56(84) bytes of data.
…
$ ping work.hogeserver.hogeddns.jp
PING work (192.168.110.3) 56(84) bytes of data.
…

この後、色々とログを確認する中で、IPアドレスからの逆引きを行っているような処理が見受けられた。
先にFQDNが取り出せないと具合が悪い、ということのようだ。

プロセスの停止

プロセスの停止にpkillを使っている。
正直なところ、プロセス番号の取り扱いが分からないからはじめたことだったが、そのまま突き進んでいる。
この場合、余計なプロセスにシグナルを送ることがないようにしたいので、やり方を確認してみた。
@IT / 【 pkill 】コマンド――名前を指定してプロセスを終了させる

Sambaのプロセスはこんな風に見えている。

# ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0   7408  3648 ?        Ss   18:40   0:00 /bin/bash /entrypoint.sh
root          11  1.1  1.0 132164 42268 ?        S    18:40   0:00 samba: root process
root          25  0.0  0.6 132168 24528 ?        S    18:40   0:00 samba: tfork waiter process(26)
root          26  0.0  0.6 132168 27640 ?        S    18:40   0:00 samba: task[s3fs] pre-fork master
root          36  1.1  1.4 144176 58644 ?        Ss   18:40   0:00 /usr/sbin/smbd -D --option=server role check:inhibit=
root          54  1.1  1.3 136176 53320 ?        Ss   18:40   0:00 /usr/sbin/winbindd -D --option=server role check:inhi
root          77  0.0  0.9 142728 36880 ?        S    18:40   0:00 /usr/sbin/smbd -D --option=server role check:inhibit=
root          78  0.0  0.8 142728 34280 ?        S    18:40   0:00 /usr/sbin/smbd -D --option=server role check:inhibit=
root          79  0.0  0.9 136680 36412 ?        S    18:40   0:00 winbindd: domain child [HOGEDOMAIN]
root          81  1.2  1.3 143580 54500 ?        S    18:40   0:00 /usr/lib/x86_64-linux-gnu/samba/samba-bgqd --ready-si

※いくつかを抜粋。

pkillは、正規表現を使って、プロセス名を指定するのだそうだ。

# pgrep samba
11
81

# ps auc 11 81
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root          11  0.0  1.0 132164 42268 ?        S    18:40   0:00 samba
root          81  0.0  1.3 143580 55184 ?        S    18:40   0:00 samba-bgqd
root          97  0.0  0.1   7672  4160 pts/0    Ss   18:40   0:00 bash
root        1606  0.0  0.0  10108  3192 pts/0    R+   19:04   0:00 ps

ということならば…

# pgrep ^samba$
11

これで、間違いなくsambaのルートプロセスにシグナルを送ることができる。

nmbdは起動しなくて良いのか

全然意識していなかったけれど、nmbdって動かしておく必要があるの?ということで、確認。
ポート137, 138にバインドする、ということは、Samba ad dcが掴んでいるなら、動かすことができない。
日本 Samba ユーザー会 Web サイト / smb.conf — Samba の設定ファイル

というか、その機能を含めてSamba ad dcだよなぁ、ということで念押し確認。
ポートの使用状況を確認する

# ss -p -A tcp,udp,raw state listening state unconnected -n | egrep \(:137\|:138\)
udp   UNCONN 0      0        172.26.0.101:137        0.0.0.0:*    users:(("nbt[master]",pid=30,fd=27))                  
udp   UNCONN 0      0      172.26.255.255:137        0.0.0.0:*    users:(("nbt[master]",pid=30,fd=26))                  
udp   UNCONN 0      0             0.0.0.0:137        0.0.0.0:*    users:(("nbt[master]",pid=30,fd=20))                  
udp   UNCONN 0      0        172.26.0.101:138        0.0.0.0:*    users:(("nbt[master]",pid=30,fd=29))                  
udp   UNCONN 0      0      172.26.255.255:138        0.0.0.0:*    users:(("nbt[master]",pid=30,fd=28))                  
udp   UNCONN 0      0             0.0.0.0:138        0.0.0.0:*    users:(("nbt[master]",pid=30,fd=24))

やはり、既にここをコントロールするプロセスが起動していた。
Samba ad dcを起動しているサーバーでは、nmbdは起動しなくて大丈夫だ。

いつの間にかdcのIPアドレスが追加されている

定期的にsamba_dnsupdateコマンドが実行され、コンテナのIPアドレスで更新されているようだ。
試しに、以下を実行してみたところ、コンテナのIPアドレスが表示された。

# samba_dnsupdate --verbose
IPs: ['172.30.0.2']
…

これは何かと都合が悪いので、IPアドレスをホストのIPアドレスに固定する方法はないかと考えていたら、–current-ipというパラメーターが使えることが分かった。

# samba_dnsupdate --current-ip=192.168.110.4 --verbose
IPs: ['192.168.110.4']
…

定期的に実行されるコマンドは、smb.confで設定できるようだった。

# testparm -sv | grep "dns update command"
Load smb config files from /etc/samba/smb.conf
Loaded services file OK.
Weak crypto is allowed

Server role: ROLE_ACTIVE_DIRECTORY_DC

        dns update command = /usr/sbin/samba_dnsupdat

dns update command に–current-ipでホストのIPアドレスを入れて、この問題は解消した。

kinitでエラー

各DCで、コンテナからの接続を許可するようにファイアウォールを開けているのは、これが理由。

セカンダリーDCを立てて、kinitを実行したら、問題なくチケットが発行できた。
そういえばプライマリーDCで試していなかったな…

# kinit
kinit: Cannot find KDC for realm "HOGESERVER.HOGEDDNS.JP" while getting initial credentials

えっ!?

短時間でこれが表示されたタイミングでは、Samba ad dcが起動しておらず、名前解決ができない状態だった。
タイムアウトしたであろうタイミングで表示されたときには、以下の状態であった。

コンテナの中で実行したコマンド結果考察
dig hogeserver.hogeddns.jp @127.0.0.1回答ありSamba ad dcに聞きに行き、DNSが持っている情報が戻された。
dig hogeserver.hogeddns.jp @127.0.0.11回答ありdocker-compose.ymlのdns:で指定したDNSに聞きに行った。
dig hogeserver.hogeddns.jp @192.168.110.4タイムアウトコンテナ→ゲートウェイ→ホストに聞きに行った。

他のホストからの問い合わせには応答できるので、間違いなくDockerによりポートは開けられている。
pingは問題なく通る…

ファイアウォールのログを見てみたところ、ここで引っかかっていた。
/var/log/ufw.log

Aug  9 08:00:35 mirror kernel: [ 3042.338102] [UFW BLOCK] IN=br-aaf26b9c7f1b OUT= PHYSIN=veth53b2727 MAC=nn:nn:nn:nn:nn:nn:nn:nn:nn:nn:nn:nn:nn:nn SRC=172.18.0.2 DST=192.168.110.4 LEN=91 TOS=0x00 PREC=0x00 TTL=64 ID=2224 PROTO=UDP SPT=56803 DPT=53 LEN=71

ens33から入ってきたパケットはDNATして通してくれるけれど、コンテナの中からens33に向かうパケットはケアされていない。普通はそんな接続はしないので、当然といえば当然の措置。
とりあえず、コンテナから入ってきたパケットを通過させれば、接続は成功する。

$ sudo ufw allow from 172.18.0.2 to 192.168.110.4 comment "From Container"

Dockerのネットワークの設定は、しないで済むならそれがいい、と思っていた。
とはいえ、この接続はできないと色々と困りそうなので、ネットワーク範囲を固定して対応することにした。

パスワードポリシーのデフォルト値

チケット発行時にパスワードの有効期間に対するワーニングが出力された。

# kinit administrator
Password for administrator@HOGESERVER.HOGEDDNS.JP:
Warning: Your password will expire in 41 days on Tue 20 Sep 2022 01:40:04 AM JST

インターネットに向けて開けているサービスも、認証はSamba ad dcが司っている。
パスワードの管理は確かに大切なんだけれども、定期的な変更には意味がないと思っている。

過去にポリシーの設定を整理していて、やっぱりユルユルに設定していた。
今回の記事では、どーんとポリシーを緩めることにした。

もしかしたら必要になるかもしれないので、デフォルト値を示しておく。

# samba-tool domain passwordsettings show
Password information for domain 'DC=hogeserver,DC=hogeddns,DC=jp'

Password complexity: on
Store plaintext passwords: off
Password history length: 24
Minimum password length: 7
Minimum password age (days): 1
Maximum password age (days): 42
Account lockout duration (mins): 30
Account lockout threshold (attempts): 0
Reset account lockout after (mins): 30

日本語表示できない

コンテナの中で、日本語のコメントが入ったsmb.confを開いてみたら、文字化けしてしまった。
Dockerfileでこれを実行しているけれども、ロケールを設定できていない。

RUN locale-gen en_US.UTF-8 && \
    update-locale LANG=en_US.utf8

# locale
LANG=
LANGUAGE=
LC_CTYPE="POSIX"
LC_NUMERIC="POSIX"
LC_TIME="POSIX"
LC_COLLATE="POSIX"
LC_MONETARY="POSIX"
LC_MESSAGES="POSIX"
LC_PAPER="POSIX"
LC_NAME="POSIX"
LC_ADDRESS="POSIX"
LC_TELEPHONE="POSIX"
LC_MEASUREMENT="POSIX"
LC_IDENTIFICATION="POSIX"
LC_ALL=

こちらで解決策を教えてくれた。Dockerfileで環境変数を設定しておけば良いようだ。
Qiita / Docker: コンテナのlocaleを設定したい

ENV LANG en_US.UTF-8
ENV LANGUAGE en_US:en
ENV LC_ALL en_US.UTF-8

update-localeコマンドを実行する必要はないらしい。

phpLDAPadmin+PHP7.4

phpLDAPadminの1.2.6.3は、PHP7の時期のリリースのようなので、PHP7.4で動作させてみることにした。
様々なバージョンのPHPを提供してくれているリポジトリを利用させていただく。
https://launchpad.net/~ondrej/+archive/ubuntu/php

まず、リポジトリを追加。

# add-apt-repository ppa:ondrej/php
bash: add-apt-repository: command not found

コマンドがないのか…ベースイメージでインストールすべきパッケージを探してみる。

# apt install apt-file
# apt-file update
# apt-file search apt-add-repository
software-properties-common: /usr/bin/apt-add-repository
software-properties-common: /usr/share/bash-completion/completions/apt-add-repository
software-properties-common: /usr/share/man/man1/apt-add-repository.1.gz

パッケージをインストールしてみよう。

# apt install software-properties-common
…
0 upgraded, 54 newly installed, 0 to remove and 0 not upgraded.
Need to get 13.2 MB of archives.
After this operation, 58.3 MB of additional disk space will be used.
Do you want to continue? [Y/n] n

リポジトリを追加するためだけにこれだけのパッケージが必要なのか…と躊躇して、他の方法でリポジトリを追加することにした。

# cat <<EOF > /etc/apt/sources.list.d/ondrej-ubuntu-php-jammy.list
deb https://ppa.launchpadcontent.net/ondrej/php/ubuntu/ jammy main
# deb-src https://ppa.launchpadcontent.net/ondrej/php/ubuntu/ jammy main
EOF

GPGキーが必要らしいのだけれど、どうやってインストールすれば良いか…色々と教えてくれたのはこちら。
gpg: can’t import key: “new key but contains no user ID – skipped”
Solve: Legacy trusted.gpg keyring – APT Key ‘apt-key’ Deprecation on Ubuntu 

先程のリポジトリについての説明ページで、Technical details about this PPAをクリックすると、Signing keyという項目が表示される。
このリンクをクリックすると詳細情報が表示される。このキーサーバーから、sigを使ってGPGキーをダウンロードして登録する。

# gpg --keyserver hkps://keyserver.ubuntu.com --recv-key 4F4EA0AAE5267A6C
# gpg -a --export 4F4EA0AAE5267A6C | gpg --dearmour -o /etc/apt/trusted.gpg.d/ondrej.gpg

これでリポジトリが登録できたので、PHP7.4をインストールする。

# apt update
# apt install php7.4 php7.4-ldap php7.4-xml php7.4-xml php7.4-imagick php7.4-mbstring php7.4-gmp php7.4-zip

通常使用するPHPのバージョンを7.4に変更してみたが、これはApacheには効果がなかった。

# update-alternatives --config php

Apacheでは、この方法で利用するphpのバージョンが指定できた。

# a2dismod php8.1
# a2enmod php7.4

# apachectl stop
# apachectl start ← restartだと、アクセスした途端にCPU100%で応答なし。

これでどうにか動作させることはできたけれど、ログインしても、Logged in as: Anonymousとなって、ツリー情報が見られなかった。

指定したユーザーでログインして、情報が閲覧できるバージョンはどれか…と探していって、その中で一番新しいのは1.2.3であることを確認したので、このバージョンを利用させていただくことにした。

DNSバックエンドをBIND9に変更する

こちらで簡単に切り替えられると教えてくれていた。
chaperone / samba/DNS切替

こちらを見ながら変更してみる。
Samba wiki / Changing the DNS Back End of a Samba AD DC

BIND9をインストールする。

# apt install bind9
# named -v
BIND 9.18.1-1ubuntu1.1-Ubuntu (Stable Release) <id:>

DNSバックエンドを切り替えると、このようなメッセージが表示される。

# samba_upgradedns --dns-backend=BIND9_DLZ
Reading domain information
DNS accounts already exist
No zone file /var/lib/samba/bind-dns/dns/HOGESERVER.HOGEDDNS.JP.zone (normal)
DNS partitions already exist
Adding dns-addc account
check_spn_alias_collision: trying to add SPN 'DNS/addc.hogeserver.hogeddns.jp' on 'CN=dns-addc,CN=Users,DC=hogeserver,DC=hogeddns,DC=jp' when 'host/addc.hogeserver.hogeddns.jp' is on 'CN=ADDC,OU=Domain Controllers,DC=hogeserver,DC=hogeddns,DC=jp'
See /var/lib/samba/bind-dns/named.conf for an example configuration include file for BIND
and /var/lib/samba/bind-dns/named.txt for further documentation required for secure DNS updates
Finished upgrading DNS
You have switched to using BIND9_DLZ as your dns backend, but still have the internal dns starting. Please make sure you add '-dns' to your server services line in your smb.conf.

/var/lib/samba/bind-dns/named.confは、/etc/bind/named.confでインクルードして使う。
中身は、BINDのバージョンによって使用するライブラリのバージョンが違うらしく、それを示している。

/etc/bind/named.conf

include "/var/lib/samba/bind-dns/named.conf";

※最後に追加。

/var/lib/samba/bind-dns/named.txtに従って作業を進める。
ステップ1、BIND9のバージョンが違うので飛ばしていたが、このステップをやっておかないと、SambaがBIND9の情報を更新できなく待ってしまうことが分かった。
named.conf.optionsファイルに、以下の2行を追加した。

tkey-gssapi-keytab "/var/lib/samba/bind-dns/dns.keytab";
minimal-responses yes;

ステップ2、SELinuxが有効な場合に必要とのこと。
UbuntuはAppArmorだけど、コンテナの中では動いていないので飛ばす。

ステップ3、BIND9をchrootで動かしていないので、これも飛ばす。

3. BIND の chroot サポートを無効にする。
BINDはしばしばchrootで動作するように設定されるが、これはデータベースへの アクセスと更新が必要とするdns/sam.ldbファイルへのアクセスとは互換性がない。 さらに、DLZプラグインは多数のSamba共有ライブラリにリンクされ、additonalプラグインをロードする。

DeepL先生の翻訳

ステップ3(2つ目)、jnlファイルといわれているけれども、動かした結果で探してみると、Ubuntu環境の場合は /var/cache/bind/ が該当するようで、恐らくは問題ないと思われる。

3. 動的に更新されるBINDゾーン・ファイルが、BINDデーモンが書き込み可能なディレクトリにあることを確認する。
BINDが動的更新を行う場合、ゾーンファイル自体を更新するだけでなく、動的更新を追跡するためにジャーナル(.jnl)ファイルを作成する必要があります。
Fedora 9 では、/var/named ディレクトリは、「named」ユーザーによって書き込むことができません。
しかし、/var/named/dynamicディレクトリは書き込みアクセスが可能です。
したがって、ゾーンファイルは/var/named/dynamicディレクトリの下に配置されました。
このファイルの冒頭にある両方のゾーンステートメントの例のファイルディレクティブ は、ディレクトリ “dynamic/”を先頭に付けて変更されている。

# ll /var/cache/bind/
total 20
drwxrwxr-x 1 root bind 4096 Sep 15 06:02 ./
drwxr-xr-x 1 root root 4096 Sep 11 19:32 ../
-rw-r--r-- 1 bind bind  821 Sep 15 06:02 managed-keys.bind
-rw-r--r-- 1 bind bind 3200 Sep 15 06:02 managed-keys.bind.jnl

最後に、Samba ad dcのDNS機能が動かないようにする。

/etc/samba/smb.conf

[global]
…
    server role = active directory domain controller
    server services = -dns

コマンド実行時に書かれていたことは、これですべて実行できた。

さて、このSamba ad dcでは、dns forwarderとして127.0.0.11を指定していた。これにより、コンテナが利用するリゾルバー(docker-compose.ymlで指定可能、無指定ならばホストのリゾルバーが使われる)が使われる。
これと同等の設定をBIND9に追加する。併せて、ゾーン転送を拒否する設定も入れておいた。

/etc/bind/named.conf.options

…
    forwarders {
        127.0.0.11;
    };
    allow-transfer {
        none;
    };
};

この設定を終えたら、Sambaのプロセスを停止→再度起動し、BINDを起動して、動作することが確認できた。

ドメイン参加したUbuntuにログインすること

最初の設定で手を抜いていたため、ドメイン参加していてユーザーが見えているのに、ユーザーの切り替えができなかった。

$ su - hogedomain\\hoge
Password:
su: warning: cannot change directory to /home/HOGEDOMAIN/hoge: No such file or directory

一見、ディレクトリがないからログインできないのかと思ったけれど、ディレクトリを作っておいてもログインできなかった。
何故かというと、シェルが/bin/falseになっているからだった。

$ getent passwd | grep hoge
HOGEDOMAIN\hogewife:*:1001106:1000513::/home/HOGEDOMAIN/hogewife:/bin/false
HOGEDOMAIN\hoge:*:1001105:1000513:User Hoge:/home/HOGEDOMAIN/hoge:/bin/false

これは、以下の設定でbashに変更できる。

/etc/samba/smb.conf

[global]
    template homedir = /home/%D/%U
    template shell = /bin/bash

この設定はwinbindに影響するので、winbindを再起動。

$ sudo systemctl restart winbind

ログインすると、ユーザーが切り替わったが、やっぱりホームディレクトリがなかった。

$ su - hogedomain\\hoge
Password:
su: warning: cannot change directory to /home/HOGEDOMAIN/hoge: No such file or directory
HOGEDOMAIN\hoge@work:/home/rohhie$

ディレクトリの作成…簡単にできたように記憶していたのに、なんでできないんだっけ。
技術評論社 / SUAのNIS機能による認証統合[2]
Cugel’s Saga / ホームディレクトリを自動で作成したい

pam_winbind.soというのがあり、PAMのsessionブロックであれば、mkhomedirというオプションが利用でき、on-the-flyでホームディレクトリが作られるとのこと。

/etc/pam.d/common-session

session optional                        pam_winbind.so mkhomedir

あるいは、pam_winbind.confというファイルでホームディレクトリを作成するようになる。

$ sudo cp /usr/share/doc/libpam-winbind/examples/pam_winbind/pam_winbind.conf /etc/security/

/etc/security/pam_winbind.conf

mkhomedir = yes

設定はどちらか一方で大丈夫。
これらは、サービスの再起動などを必要とせず、設定したらすぐに反映されていた。

$ su - hogedomain\\hoge
Password:
Creating directory: /home/HOGEDOMAIN/piyo failed: No such file or directory
HOGEDOMAIN\piyo@work:~$ ls -la
total 8
drwx------ 2 HOGEDOMAIN\piyo HOGEDOMAIN\domain users 4096 Sep 18 21:49 .
drwxr-xr-x 3 root            rohhie                  4096 Sep 18 21:49 ..

結果として、ディレクトリは作られるが、/home/HOGEDOMAINのオーナーがrohhieになってしまった。
これは、sudoしてこのコマンドを実行するか、できあがったディレクトリのオーナーを変更すれば修正できる。

問題は、/etc/skelのファイルがコピーされていないことで、この方向では問題解消できそうもない。
と調べていって、pam-auth-updateコマンドを思い出した。

この確認で、

  • 共有への接続でsmbdがホームディレクトリを作る場合はroot preexecを使う。
  • 認証の流れの中でホームディレクトリを作る場合はPAMを使う。

と整理できたので、本編に反映した。

起きたこと

testparmでエラー

範囲が重なるわけもないのに、エラーが起きている。

# testparm
ERROR: Do not use the 'rid:HOGEDOMAIN=3000000-4999999' backend as the default idmap backend!

でも、DCではsamba-toolを使うべきだそう。

# samba-tool testparm
# samba-tool testparm -v ← 未設定でデフォルト値が使われている項目も表示する

この後で、DCでないSambaで、メンバーサーバーとしてJoin使用とするとき、testparmを使用した。
これは、IDの範囲を問題なく認識していたので、使い分けが必要であることが分かった。

ドメイン参加時にDNSを更新できない(断念)

Ubuntuサーバーをドメインに参加させては外し、参加させては外し…を繰り返していたところ、参加する際にDNSの更新ができなくなった。

色々とやってみたけれども、もうモードとも呼ぶべき2つの状態、

  • 上手くいくモード
  • 上手くいかないモード

があって、上手くいくモードでもJoinでDNSレコードを追加・更新できない場合がある。
Leave→Joinした後にそうなって、一度レコードを消すと追加できるようになったりする。

まず、DNSの更新テストをしてみた。
雑廉堂Wiki / 動的DNS更新のテスト

# /usr/sbin/samba_dnsupdate --current-ip 192.168.110.4 --verbose --all-names

※IPアドレスの指定を入れておかないと、コンテナ内部のIPアドレスで処理してしまうので注意。

結果として、いくつもエラーが表示されたのだが、これは既にレコードがあるからアップデートできませんでした、ということらしい。
ArchLinux / samba dynamic updates – TSIG error

; TSIG error with server: tsig verify failure
Failed nsupdate: 2
Failed update of 29 entries

実際に、–use-samba-toolを付けて実行してみたところ、その様子が分かったので、このエラーは気にしなくて良さそうだ。
とすると、このテストの意味…

上手くいかないモードに入ったら、何をどういう風にしてもJoinではDNSレコードを登録ができない。
で、半日?一日?経ったりすると復旧したりする。
発生契機はLeaveだろうと思われるけれど、具体的な条件や、状態の確かめ方が分からない。

$ 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'
DNS Update for work.hogeserver.hogeddns.jp failed: ERROR_DNS_UPDATE_FAILED
DNS update failed: NT_STATUS_UNSUCCESSFUL

ドメインには参加した状態なので、別の方法で登録してみた。

$ net ads dns register -U administrator
Enter administrator's password:
create_local_private_krb5_conf_for_domain: smb_mkstemp failed, for file /run/samba/smb_tmp_krb5.QWuzew. Errno Permission denied
Successfully registered hostname with DNS

これで、DNSへの登録はできたのだが、エラーが出ている。sudoを忘れていた…
Server Fault / Centos joined to domain successfully, can’t create keytab?

これでkeytabという概念を意識したが、このことには関係しないようだった。
一応、こんなコマンドもありますよ、ということでメモしておく。

$ sudo net ads keytab list

Warning: "kerberos method" must be set to a keytab method to use keytab functions.
ads_keytab_open: Invalid kerberos method set (0)

Sambaとは直接は関係しないかなと思いつつも、kinitについて確認してみた。
テストでkinitを実行しており、その後でドメインから離脱するとこの問題が発生するのではないかと考えて、以下のパターンを試した。

  • sudo klist してドメイン参加。
  • sudo kdestroyしてドメイン参加。

しかし、結果は変わらなかった。

次に、Samba ad dc側でクライアントを削除するのと、クライアント側から離脱するのとで、何かが変わるかもしれないと考えて試した。
しかし、上手くいかないモードでは、どんなパターンでも上手くいかない。

ドメイン参加
サーバー側
DNS登録ドメイン参加
クライアント側
DNS更新結果
register
なしなしなし×すべてをクリアにした状態。
なしありなし×参加 → クライアント側から離脱した状態。
なしなしあり×ドメイン参加した状態で、Samba ad dcでコンピューター削除。
ありなしあり×

参加時のログを詳細に出力するために、以下を実行してみた。
上手くいくモードと、上手くいかないモードの差分は黄色文字の箇所のみ。
この件に関する詳細な情報は、クライアント側では何も表示されない、といって良さそうだ。

$ sudo net ads join -U administrator -d 10
…
Joined 'WORK' to dns domain 'hogeserver.hogeddns.jp'
kerberos_kinit_password_ext: as WORK$@HOGESERVER.HOGEDDNS.JP using [MEMORY:net_ads] as ccache and config [/run/samba/smb_krb5/krb5.conf.HOGEDOMAIN]
kerberos_kinit_password_ext: WORK$@HOGESERVER.HOGEDDNS.JP mapped to WORK$@HOGESERVER.HOGEDDNS.JP
name_to_fqdn: lookup for WORK -> work.hogeserver.hogeddns.jp.
added interface ens33 ip=192.168.110.3 bcast=192.168.110.255 netmask=255.255.255.0
added interface lo ip=127.0.0.1 bcast=127.255.255.255 netmask=255.0.0.0
dns_lookup_send_next: Sending DNS request #0 to 127.0.0.53
dns_cli_request_send: Asking 127.0.0.53 for hogeserver.hogeddns.jp/1/2 via UDP
[0000] 75 B1 01 00 00 01 00 00   00 00 00 00 0A 68 6F 67   u....... .....hog
[0010] 65 73 65 72 76 65 72 08   68 6F 67 65 64 64 6E 73   eserver. hogeddns
[0020] 02 6A 70 00 00 02 00 01                             .jp.....
dns_lookup_send_next: cancelling wait_subreq
[0000] 75 B1 81 80 00 01 00 01   00 00 00 00 0A 68 6F 67   u....... .....hog
[0010] 65 73 65 72 76 65 72 08   68 6F 67 65 64 64 6E 73   eserver. hogeddns
[0020] 02 6A 70 00 00 02 00 01   C0 0C 00 02 00 01 00 00   .jp..... ........
[0030] 03 26 00 07 04 61 64 64   63 C0 0C                  .&...add c..
dns_cli_request_udp_done: Got op=8180 1/1/0/0 recs
DoDNSUpdate called with flags: 0x0000003d
DoDNSUpdate: signed update failed
DNS Update for work.hogeserver.hogeddns.jp failed: ERROR_DNS_UPDATE_FAILED
DNS update failed: NT_STATUS_UNSUCCESSFUL
return code = 0

この問題が発生する時、サーバー側のログレベルを10にして採取したログはこちら。
変更する権限がない、といっているようなのだけれど、JoinできてDNSを変更できないということがあるのだろうか。

samba  | DSDB Change [Modify] at [Fri, 12 Aug 2022 08:30:56.928644 JST] status [insufficient access rights] remote host [Unknown] SID [S-1-5-21-1365095260-1365749210-611595881-1167] DN [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] attributes [replace: dnsRecord {CAAAAAUAAABuAAAAAAAAAAAAAAAAAAAAArAjaNqt2AE=} replace: dNSTombstoned [TRUE]]
samba  | {"timestamp": "2022-08-12T08:30:56.928739+0900", "type": "dsdbChange", "dsdbChange": {"version": {"major": 1, "minor": 0}, "statusCode": 50, "status": "insufficient access rights", "operation": "Modify", "remoteAddress": null, "performedAsSystem": false, "userSid": "S-1-5-21-1365095260-1365749210-611595881-1167", "dn": "DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp", "transactionId": "a8419456-749f-46d6-8c5d-8d0bbee9605e", "sessionId": "13c73136-4ca7-4064-bccf-468ec96662c6", "attributes": {"dnsRecord": {"actions": [{"action": "replace", "values": [{"base64": true, "value": "CAAAAAUAAABuAAAAAAAAAAAAAAAAAAAAArAjaNqt2AE="}]}]}, "dNSTombstoned": {"actions": [{"action": "replace", "values": [{"value": "TRUE"}]}]}}}}
samba  | dns_common_replace: DNS timing: result: [WERR_ACCESS_DENIED] duration: (21457) zone: [] name: [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] data: []
samba  | DSDB Transaction [rollback] at [Fri, 12 Aug 2022 08:30:56.928815 JST] duration [22317]
samba  | {"timestamp": "2022-08-12T08:30:56.928822+0900", "type": "dsdbTransaction", "dsdbTransaction": {"version": {"major": 1, "minor": 0}, "action": "rollback", "transactionId": "a8419456-749f-46d6-8c5d-8d0bbee9605e", "duration": 22317}}
samba  | No mapping exists for WERR_ACCESS_DENIED
samba  | UnbindRequest

ドメインに参加しているけれども、DNSが登録されていない場合。
を試してみようと思ったら…いやー、これが成功するんだなー。モード切替の契機がよく分からない。

samba  | dns_common_lookup: DNS timing: result: [WERR_OK] duration: (138) zone: [] name: [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] data: []
samba  | ldb:acl_modify: dnsRecord
samba  | DSDB Change [Modify] at [Fri, 12 Aug 2022 09:25:02.354069 JST] status [Success] remote host [Unknown] SID [S-1-5-21-1365095260-1365749210-611595881-1168] DN [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] attributes [replace: dnsRecord {BAABAAXwAABuAAAAAAAOEAAAAACQZDgArBEAAQ==} {BAABAAXwAABuAAAAAAAOEAAAAACQZDgArBBuAw==}]
samba  | {"timestamp": "2022-08-12T09:25:02.354746+0900", "type": "dsdbChange", "dsdbChange": {"version": {"major": 1, "minor": 0}, "statusCode": 0, "status": "Success", "operation": "Modify", "remoteAddress": null, "performedAsSystem": false, "userSid": "S-1-5-21-1365095260-1365749210-611595881-1168", "dn": "DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp", "transactionId": "6c4ca1ac-b88c-416c-aefe-0f9f86b8d02a", "sessionId": "004560b5-3fc3-4a74-9986-40f2a17e8261", "attributes": {"dnsRecord": {"actions": [{"action": "replace", "values": [{"base64": true, "value": "BAABAAXwAABuAAAAAAAOEAAAAACQZDgArBEAAQ=="}, {"base64": true, "value": "BAABAAXwAABuAAAAAAAOEAAAAACQZDgArBBuAw=="}]}]}}}}
samba  | dns_common_replace: DNS timing: result: [WERR_OK] duration: (2289) zone: [] name: [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] data: []
samba  | DSDB Transaction [commit] at [Fri, 12 Aug 2022 09:25:02.360005 JST] duration [46467]
samba  | {"timestamp": "2022-08-12T09:25:02.360196+0900", "type": "dsdbTransaction", "dsdbTransaction": {"version": {"major": 1, "minor": 0}, "action": "commit", "transactionId": "6c4ca1ac-b88c-416c-aefe-0f9f86b8d02a", "duration": 46467}}
samba  |      &state->out_packet: struct dns_name_packet
samba  |         id                       : 0x4873 (18547)
samba  |         operation                : 0xa880 (43136)
…

一旦成功した後、leaveしてjoin。エラーが出た。

samba  | dns_common_lookup: DNS timing: result: [WERR_OK] duration: (76) zone: [] name: [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] data: []
samba  | ldb:acl_modify: dnsRecord
samba  | Access on DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp deniedSecurity context:     : struct security_token
samba  |         num_sids                 : 0x00000007 (7)
samba  |         sids: ARRAY(7)
samba  |             sids                     : S-1-5-21-1365095260-1365749210-611595881-1175
…
samba  | DSDB Change [Modify] at [Fri, 12 Aug 2022 10:36:57.849743 JST] status [insufficient access rights] remote host [Unknown] SID [S-1-5-21-1365095260-1365749210-611595881-1175] DN [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] attributes [replace: dnsRecord {CAAAAAUAAABuAAAAAAAAAAAAAAAAAAAAGEDMAuyt2AE=} replace: dNSTombstoned [TRUE]]
samba  | {"timestamp": "2022-08-12T10:36:57.849819+0900", "type": "dsdbChange", "dsdbChange": {"version": {"major": 1, "minor": 0}, "statusCode": 50, "status": "insufficient access rights", "operation": "Modify", "remoteAddress": null, "performedAsSystem": false, "userSid": "S-1-5-21-1365095260-1365749210-611595881-1175", "dn": "DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp", "transactionId": "aa6c6025-5a5e-49cb-944a-0dbb39dccdd3", "sessionId": "189c3b1d-f370-4dca-bd88-6d89e4c8b491", "attributes": {"dnsRecord": {"actions": [{"action": "replace", "values": [{"base64": true, "value": "CAAAAAUAAABuAAAAAAAAAAAAAAAAAAAAGEDMAuyt2AE="}]}]}, "dNSTombstoned": {"actions": [{"action": "replace", "values": [{"value": "TRUE"}]}]}}}}
samba  | dns_common_replace: DNS timing: result: [WERR_ACCESS_DENIED] duration: (24309) zone: [] name: [DC=work,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,DC=hogeserver,DC=hogeddns,DC=jp] data: []
samba  | DSDB Transaction [rollback] at [Fri, 12 Aug 2022 10:36:57.849883 JST] duration [25055]
samba  | {"timestamp": "2022-08-12T10:36:57.849888+0900", "type": "dsdbTransaction", "dsdbTransaction": {"version": {"major": 1, "minor": 0}, "action": "rollback", "transactionId": "aa6c6025-5a5e-49cb-944a-0dbb39dccdd3", "duration": 25055}}
samba  | No mapping exists for WERR_ACCESS_DENIED
samba  | stream_terminate_connection: Terminating connection - 'dns_tcp_call_loop: tstream_read_pdu_blob_recv() - NT_STATUS_CONNECTION_DISCONNECTED'
samba  | msg_dgm_ref_destructor: refs=0x55e41c2189b0
samba  | UnbindRequest
samba  | stream_terminate_connection: Terminating connection - 'ldapsrv_call_wait_done: call->wait_recv() - NT_STATUS_LOCAL_DISCONNECT'
samba  | msg_dgm_ref_destructor: refs=0x55e41c196170

成功したときはDNSレコードがAddで、失敗したときはUpdateなのでちょっと違っている。

このあたりで、どんなデーター構成になっているのか確認したくなり、以下を実行。
この時点ではphpLDAPadminが動かせなかったので、コマンドで探すことに…トホホ。

# ldapsearch -H ldaps://addc.hogeserver.hogeddns.jp:636 \
-b "DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,dc=hogeserver,dc=hogeddns,dc=jp" \
-s sub -D rohhie@hogeserver.hogeddns.jp -W
Enter LDAP Password:
# extended LDIF
#
# LDAPv3
# base <DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZones,dc=hogeserver,dc=hogeddns,dc=jp> with scope subtree
# filter: (objectclass=*)
# requesting: ALL
#

# _kerberos._tcp, hogeserver.hogeddns.jp, MicrosoftDNS, DomainDnsZones.hogeserv
 er.hogeddns.jp
dn: DC=_kerberos._tcp,DC=hogeserver.hogeddns.jp,CN=MicrosoftDNS,DC=DomainDnsZo
 nes,DC=hogeserver,DC=hogeddns,DC=jp
objectClass: top
…

このあたりで、もう、ちょっと無理だな…と分析を断念した。

パスワードが変更できない

以下のエラーが発生。

# samba-tool user password -U administrator --no-pass
New Password:
Retype Password:
ERROR: Failed to change password : (-1073741643, "Connection to SAMR pipe of PDC of domain 'HOGEDOMAIN' failed: NT_STATUS_IO_TIMEOUT")

これ、最終的には、ネットワークドライバーをhostにしても解決できなかった。
※hostは、コンテナがホストのNICを使う設定。
色々と試してみたけれども、最終的にはパスワード変更でコアを吐いていた。

で、以下を試したところ、パスワードの変更ができた。

# smbpasswd -U administrator
New SMB password:
Retype new SMB password:

だいぶ深追いして結果が出なかったので、これを解決策とした。

RealmのIPアドレス

IPアドレスの指定方法

Samba ad dcでRealmの名前解決をすると、そのサーバーのIPアドレスが返ってくる。

# dig hogeserver.hogeddns.jp +short
172.27.0.2

コンテナは個別にIPアドレスを持っているけれど、他のサーバーやPCからはホストのIPアドレスに見えている。一致しないのは問題がありそうだ。
環境変数 SMB_HOSTIP にホストのIPアドレスを設定し、以下を実行してみた。

samba-tool domain provision --use-rfc2307 \
 --realm=$SMB_REALM \
…
 --host-ip=$SMB_HOSTIP

/etc/samba/smb.confに変化はない…んー。

# dig @localhost hogeserver.hogeddns.jp +short
192.168.110.34

お!そういうこと!?
DNSに–host-ipの値をセットするのか。

# samba-tool dns zonelist localhost -U administrator
Password for [MYHOME\administrator]:
  2 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                 : _msdcs.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_FOREST_DEFAULT DNS_DP_ENLISTED
  pszDpFqdn                   : ForestDnsZones.hogeserver.hogeddns.jp

# samba-tool dns query localhost hogeserver.hogeddns.jp @ ALL -U administrator
Password for [MYHOME\administrator]:
  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.34 (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=DomainDnsZones, Records=0, Children=2
  Name=ForestDnsZones, Records=0, Children=2
  Name=addc, Records=1, Children=0
    A: 192.168.110.34 (flags=f0, serial=1, ttl=900)

# samba-tool dns query localhost _msdcs.hogeserver.hogeddns.jp @ ALL -U administrator
Password for [MYHOME\administrator]:
  Name=, Records=2, 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)
  Name=9168f820-56ce-4049-a7fa-a7072ceadb48, Records=1, Children=0
    CNAME: addc.hogeserver.hogeddns.jp. (flags=f0, serial=1, ttl=900)
  Name=dc, Records=0, Children=2
  Name=domains, Records=0, Children=1
  Name=gc, Records=0, Children=2
  Name=pdc, Records=0, Children=1

ホストの中で、ホスト名でIPアドレスを聞くと、コンテナのIPアドレスが戻ってくる。
でも、他のホストから聞くときには、FQDNで聞くことになるのだから問題はない。

ということで、色々な操作でFQDNを設定することを解決策とした。

IPアドレスの修正方法

もし、–host-ipの設定を間違えた場合、どのように修正ができるのか試してみた。
バックアップからデーターを復元し、–host-ip指定と同様の変更ができるか…

# samba-tool dns update localhost hogeserver.hogeddns.jp hogeserver.hogeddns.jp A 192.168.110.5 192.168.110.34 -U rohhie
# samba-tool dns update localhost hogeserver.hogeddns.jp addc A 192.168.110.5 192.168.110.34 -U rohhie

できた。

# dig @localhost hogeserver.hogeddns.jp +short
192.168.110.34

コンテナを再起動して確かめてみたが、問題なく変更ができていた。

起動時のスクリプトがエラー終了しない

スクリプトの中でset -eを指定しているにもかかわらず、起動時に各種設定をするコマンドがエラー終了しても、その先の処理が行われていた。

原因は、

  • restart: unless-stoppedを設定している。
  • bashの set -e 指定のため、コマンドが失敗するとエラーを戻す。
    あるいは、exit -1 等と書いてエラーを戻していた。

の両方が重なっていたから。

実際には、スクリプトはエラー終了しているが、unless-stoppedの指定により再度呼び出されていたのだった。
作っていたスクリプトは、初期設定が済んでいたらスキップ、という処理が入っていたため、何度か呼び出されるうちに正常終了するルートを走行していた。

今回、エラーが発生したらコンテナが落ちる、という普通の作りにすると、問題発生時に原因が特定しづらい。
set -eを止めて、exit -1はexit 0に変更して、思ったときに思ったように終了するようにして、スクリプトの実行を本当に止めたいときには止められるようにした。

時間表示がUTCのまま

TZ=Asia/Tokyoを設定していたが、時間表示がUTCのままだった。

# date
Fri Aug  5 22:05:24 Asia 2022

コンテナの中でtzdataをインストールしたところ、表示を変えることができた。

# apt install tzdata
…
Configuring tzdata
------------------

Please select the geographic area in which you live. Subsequent configuration questions will narrow this down by
presenting a list of cities, representing the time zones in which they are located.

  1. Africa   3. Antarctica  5. Arctic  7. Atlantic  9. Indian    11. US
  2. America  4. Australia   6. Asia    8. Europe    10. Pacific  12. Etc
Geographic area: 6[Enter]

Please select the city or region corresponding to your time zone.

…
  3. Amman     18. Choibalsan   33. Irkutsk      48. Kuwait        63. Qostanay       78. Thimphu
  4. Anadyr    19. Chongqing    34. Istanbul     49. Macau         64. Qyzylorda      79. Tokyo
  5. Aqtau     20. Colombo      35. Jakarta      50. Magadan       65. Rangoon        80. Tomsk
…
Time zone: 79[Enter]


Current default time zone: 'Asia/Tokyo'
Local time is now:      Sat Aug  6 07:14:13 JST 2022.
Universal Time is now:  Fri Aug  5 22:14:13 UTC 2022.
Run 'dpkg-reconfigure tzdata' if you wish to change it.

Dockerfileでtzdataをインストールしてみたところ、表示がJSTになった。

rootで認証されない

とりあえず動いているかどうかを確認しようと、ゾーン情報を見てみることにした。

# samba-tool dns zonelist localhost
Password for [MYHOME\root]: p@ssword123[Enter]
Failed to bind to uuid 50abc2a4-574d-40b3-9d66-ee4fd5fba076 for ncacn_ip_tcp:127.0.0.1[49153,sign,abstract_syntax=50abc2a4-574d-40b3-9d66-ee4fd5fba076/0x00000005,localaddress=127.0.0.1] NT_STATUS_LOGON_FAILURE
ERROR: Connecting to DNS RPC server 127.0.0.1 failed with (3221225581, 'The attempted logon is invalid. This is either due to a bad username or authentication information.')

ちょっと悩んだけれど…そうか、rootなんて人はいない。

# samba-tool dns zonelist localhost -U administrator
Password for [MYHOME\administrator]: p@ssword123[Enter]
  2 zone(s) found
…

administratorでゾーンの情報を見ることができた。

で、これは、administratorのuidNumberとgidNumberを変更することで解消できることもあったりする。
Ubuntuが管理しているユーザーと同じであることを示すことができる。

Dockerでネットワークが見つからない

コンテナを起動しようとして、以下のメッセージが表示された。

Error response from daemon: network a4dd037147e18680673c49bd87c7eab293b74925852436eb96fff09bae535dda not found

これは作り直しが必要になる模様。
Stack Overflow / Docker Network not Found

$ sudo docker compose up --force-recreate

この結果、消える範囲がどれだけなのか…色々試している最中で、消えても良い状態だから良いのだけれど…。

DNSバックエンドを切り替えられない

DNSバックエンドは簡単に切り替えられるように見えたが、最初は上手くいかなかった。
切り替えに際してハードリンクを作成しており、違うデバイスにそれは作れないというエラーだった。

# samba_upgradedns --dns-backend=BIND9_DLZ
Reading domain information
DNS accounts already exist
No zone file /var/lib/samba/bind-dns/dns/HOGESERVER.HOGEDDNS.JP.zone (normal)
DNS partitions already exist
Adding dns-addc account
check_spn_alias_collision: trying to add SPN 'DNS/addc.hogeserver.hogeddns.jp' on 'CN=dns-addc,CN=Users,DC=hogeserver,DC=hogeddns,DC=jp' when 'host/addc.hogeserver.hogeddns.jp' is on 'CN=ADDC,OU=Domain Controllers,DC=hogeserver,DC=hogeddns,DC=jp'
Failed to create link /var/lib/samba/private/dns.keytab -> /var/lib/samba/bind-dns/dns.keytab: Invalid cross-device link
Failed to chown /var/lib/samba/bind-dns/dns.keytab to bind gid 107
Failed to setup database for BIND, AD based DNS cannot be used
Traceback (most recent call last):
  File "/usr/sbin/samba_upgradedns", line 508, in <module>
    create_samdb_copy(ldbs.sam, logger, paths, names, domainsid,
  File "/usr/lib/python3/dist-packages/samba/provision/sambadns.py", line 902, in create_samdb_copy
    os.link(os.path.join(samldb_dir, metadata_file),
OSError: [Errno 18] Invalid cross-device link: '/var/lib/samba/private/sam.ldb.d/metadata.tdb' -> '/var/lib/samba/bind-dns/dns/sam.ldb.d/metadata.tdb'

この時の、ボリューム指定は以下だった。

docker-compose.yml

…
    volumes:
      - lam:/var/lib/ldap-account-manager
      - etc:/etc/samba
      - private:/var/lib/samba/private
      - sysvol:/var/lib/samba/sysvol
:::

ちょっと構造を見てみると、確かに違う場所のようだ。
/var/lib/samba/bind-dnsも同じようにvolume指定してみたが、やはり別デバイス扱いになる。

# mount
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/F5KSYOLIFOSIDHXICMZGV27Z56:…
…
/dev/sda2 on /etc/samba type ext4 (rw,relatime)
/dev/sda2 on /etc/hosts type ext4 (rw,relatime)
/dev/sda2 on /etc/resolv.conf type ext4 (rw,relatime)
/dev/sda2 on /etc/hostname type ext4 (rw,relatime)
/dev/sda2 on /var/lib/ldap-account-manager type ext4 (rw,relatime)
/dev/sda2 on /var/lib/samba/private type ext4 (rw,relatime)
/dev/sda2 on /var/lib/samba/sysvol type ext4 (rw,relatime)

このこともあって、/var/lib/sambaをボリューム指定することにした。

リストアドDCでBIND9が起動しない

リストアドDCでもBIND9が動作するように環境を整備したのだけれど、上手く動かない。

まず、dns.keytabがなかった。

# ll /var/lib/samba/bind-dns/dns.keytab
ls: cannot access '/var/lib/samba/bind-dns/dns.keytab': No such file or directory

DNSバックエンドをBIND9に設定してみる。

$ dnssamba_upgradedns --dns-backend=BIND9_DLZ

これでdns.keytabができあがったので、BIND9を起動してみると、以下のエラーが表示された。

# /usr/sbin/named -u bind -g
…
10-Sep-2022 09:30:33.906 Loading 'AD DNS Zone' using driver dlopen
10-Sep-2022 09:30:34.046 samba_dlz: started for DN DC=hogeserver,DC=hogeddns,DC=jp
10-Sep-2022 09:30:34.046 samba_dlz: starting configure
10-Sep-2022 09:30:34.050 zone hogeserver.hogeddns.jp/NONE: has no NS records
10-Sep-2022 09:30:34.050 samba_dlz: Failed to configure zone 'hogeserver.hogeddns.jp'
10-Sep-2022 09:30:34.054 loading configuration: bad zone
10-Sep-2022 09:30:34.054 exiting (due to fatal error)

BIND9を起動するためにはNSレコードの登録が必要ということになるが、それをするとリストア環境がリストア環境ではなくなってしまう。
(この後、復元したいサーバーからJoinすると、NSが登録されていく流れになるから)

以上のことから、バックアップを実施した環境のDNSバックエンドがBIND9だったとしても、復元環境のDNSバックエンドは内蔵のものにしている。

phpLDAPadmin+PHP8.1

Ubuntu 22.04で提供されているphpLDAPadminのパッケージは1.2.6.3、phpは8.1。
調べてみている限り、phpLDAPadminは1.2.6.4でphp8.1に対応したように見える。

phpLDAPadminをインストールしてアクセスしてみたら、以下のエラーが表示されている。

Unrecognized error number: 8192: trim(): Passing null to parameter #1 ($string) of type string is deprecated

PHP8からNULLがから文字に変換されなくなったとか。
stackoverflow / Migration to PHP 8.1 – how to fix Deprecated Passing null to parameter error – rename build in functions

ここで教えてくれているcustom_trim関数を作り、それを呼び出す形にして解決。

次は、メモリ不足の警告が表示されている。

Your php memory limit is low – currently 128M, you should increase it to atleast 24. This is normally controlled in /etc/php.ini.

うーん…よく意味が分からないけれど、以下の設定で回避。

/etc/php/8.1/apache2/php.ini

; Maximum amount of memory a script may consume
; https://php.net/memory-limit
#memory_limit = 128M
memory_limit = 240M

念のため、239Mでメッセージが表示されることを確認したので、最初のメッセージは24ではなく、240Mと表示したかったのだろうと思われる。

次、phpLDAPadminでログイン操作をしたところ、型変換のエラーが発生して、ログインができない。

Unrecognized error number: 8192: Implicit conversion from float 1.6604392750966098E+19 to int loses precision

動かない。

ということで、ソースコードも見てみたけれども、ちょっと簡単に修正できそうな気がしない。
探してみると、このようなやりとりを発見。
Github / leenooks / phpLDAPadmin / Not working with PHP 8.0 #133

この情報を発見する前に方向転換、PHPの7.4で動かす古いバージョンを動かすことにしていた。
最終的に、1.2.6.4は試していない。

さいごに

やりたいことは概ね実現できたものの、まだ課題が残っている状態で記事をリリースする。
日頃はあまりやらないことだけれど、仕事で使いたいようなものも一部あったりするので、例外的にそうすることにした。

だいたい、volumeの扱いが雑すぎた。
ダイレクトにバックアップを取る記事をいくつか書いちゃってるけれども、この操作はやってはいけないことだったりすることが分かり、ホームラボのあれこれを修正する必要が出てきたし、記事も修正しなければ。課題がてんこ盛りのまま、夏休みが終了する。

夏休みに片付けようと思っていた部屋の一角もそのまま。上手くいかないことの調査に時間を掛けすぎた。
でも、分からないんだから仕方がない、わかるまで調べておきたい。

やることは色々あるけど、1つ1つ整理していけば、いつの日かきれいになる。少しずつやっていくのみ。

広告

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