跳到主內容

Samba AD 架設

  BUBU 因專案上接到 Samba AD 升級的需求,因此將整個安裝流程與相關設定記錄下來。Samba AD 可以視為 Linux 版的 Active Directory,能提供與 Windows Server 網域控制站相同的網域服務。

運行環境


  環境皆架設於「Proxmox VE」虛擬系統,預設以「LXC」模式為主,除非有特殊狀況才會改用「VM」模式。

  • 系統環境:Ubuntu 20.04、24.04
  • 網域名稱(Realm):abc.local
  • NetBIOS 名稱(Domain):abc
  • 第一台 DC:dc1、192.168.0.254
  • 第二台 DC:dc2、192.168.0.253
  • 防毒服務:ClamAV(套件庫版本,20.04 為 0.103.x、24.04 為 1.x,此版本差異會直接影響病毒碼能否更新,詳見「安裝防毒」章節)

前置需求


  • 網域名稱與 NetBIOS 名稱請先規劃確定,建置完成後要變更的代價極高。
  • 每台 DC 都必須使用固定 IP,且 DNS 改由 Samba 自行提供,不可再交由 DHCP 派發。
  • 主機名稱需事先確定,provision 完成後不建議再更動。
  • 各台 DC 之間的系統時間必須能正常同步,時間誤差過大會導致 Kerberos 驗證失敗。

管理者密碼必須符合複雜度要求:至少 8 個字元,且需包含大寫字母與數字。不符合時 provision 會直接失敗,這是初次架設最常見的卡關點。

安裝過程


環境設定

  • 設定本機名稱
sudo hostnamectl set-hostname dc1
  • 設定 Hosts vim /etc/hosts
192.168.0.254    dc1.abc.local   dc1
安裝 Samba AD 服務

  • 安裝 Samba AD 相關套件
sudo apt-get install -y acl attr samba samba-dsdb-modules samba-vfs-modules winbind libpam-winbind libnss-winbind libpam-krb5 krb5-config krb5-user dnsutils chrony net-tools
  • 安裝過程中系統會跳出三個設定視窗要求填入資訊,依序如下。
# 預設的 Kerberos 第五版領域(Realm):
abc.local

# 您的領域所使用的 Kerberos 伺服器:
dc1.abc.local

# 您的領域所使用的管理伺服器(Administrative server):
dc1.abc.local
  • 填入畫面如下

    • 設定 Kerberos 時系統會自動帶出您的網域名稱,不需修改,直接按 OK。

    debian-join-ucs-05.png

    • 輸入 Samba AD 主機名稱,例:dc1.abc.local

    debian-join-ucs-06.png

    • 一樣輸入 Samba AD 主機名稱,例:dc1.abc.local

    debian-join-ucs-07.png

  • 備份預設設定檔

sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.orig
sudo mv /etc/krb5.conf /etc/krb5.conf.orig
  • 設定 Samba AD 服務
sudo samba-tool domain provision --use-rfc2307 --interactive
  • 設定 AD 相關參數。執行上述指令後,系統會自動帶出剛才填入的資訊,方括號內為預設值,直接按 Enter 即可沿用。
    • Realm [abc.local]:直接按 Enter
    • Domain [abc]:直接按 Enter
    • Server Role (dc, member, standalone) [dc]:直接按 Enter
    • DNS backend (SAMBA_INTERNAL, BIND9_FLATFILE, BIND9_DLZ, NONE) [SAMBA_INTERNAL]:直接按 Enter
    • DNS forwarder IP address (write 'none' to disable forwarding) [127.0.0.53]:輸入 168.95.1.1
    • Administrator password:填入管理者的密碼
    • Retype password:再次填入管理者的密碼

DNS forwarder 只接受一組 IP 位址,不可填入多組。若需要多組上游 DNS,請於 provision 完成後在 smb.conf 的 [global] 區段以 dns forwarder 參數調整。

  • 最後會看到所設定的主機相關資訊

  • 複製設定檔
sudo cp /var/lib/samba/private/krb5.conf /etc/krb5.conf
  • 停止並停用 systemd-resolved 服務
sudo systemctl disable --now systemd-resolved
  • 移除符號連結檔案 /etc/resolv.conf
  • 建立一組新的 DNS 相關資訊 vim /etc/resolv.conf
# Samba 伺服器的 IP 位址
nameserver 192.168.0.254

# fallback resolver
nameserver 8.8.8.8
nameserver 1.1.1.1

# Samba 的主要網域
search abc.local
  • 鎖定 DNS 設定檔,避免被其他服務覆寫(chattr 說明:改變檔案的屬性(Change Attribute),+i 代表 immutable,設定後連 root 都無法直接修改或刪除)
sudo chattr +i /etc/resolv.conf
  • 如要異動 DNS 設定檔,要先把屬性解除才能修改
sudo chattr -i /etc/resolv.conf
  • 確認 Samba NTP 簽章位置
samba -b | grep 'NTP' | awk '{print $NF}'

samba -b 讀取的是編譯時的組態參數,不需要先啟動 Samba 服務即可查詢;ntp_signd 目錄在 provision 階段就已建立。

  • 執行後會出現 NTP 簽章位置

  • 設定 chrony 服務所需的目錄權限,供時間同步使用
sudo chown root:_chrony /var/lib/samba/ntp_signd/
sudo chmod 750 /var/lib/samba/ntp_signd/
  • 編輯 chrony 設定檔 vim /etc/chrony/chrony.conf,於設定檔最後新增以下三個參數
# 將 NTP 服務綁定到 Samba AD 的 IP 位址
bindaddress 192.168.0.254

# 允許網路內的用戶端連線到 Chrony NTP 伺服器
allow 192.168.0.0/24

# 指定 Samba AD 的 ntpsigndsocket 目錄位置,供 MS-SNTP 用戶端進行簽章驗證
ntpsigndsocket /var/lib/samba/ntp_signd

此處要用的是 bindaddress 而非 bindcmdaddress。bindaddress 綁定的是 NTP 服務本身,bindcmdaddress 綁定的則是 chronyc 使用的監控命令通道,預設僅監聽本機,不建議對外開放。

  • 重啟 chrony 服務
sudo systemctl restart chrony

在 Ubuntu 與 Debian 上服務名稱為 chrony,RHEL 系(Rocky、AlmaLinux 等)才是 chronyd,兩者不可混用。

  • 將 Samba 設定為以 systemd 服務執行。先停用並遮蔽(mask)smbd、nmbd、winbind,再解除遮蔽(unmask)並啟用 samba-ad-dc。
sudo systemctl stop smbd nmbd winbind
sudo systemctl disable smbd nmbd winbind
sudo systemctl mask smbd nmbd winbind
sudo systemctl unmask samba-ad-dc
sudo systemctl enable --now samba-ad-dc

在 AD DC 上,samba 服務會自動以子行程的方式啟動所需的 smbd 與 winbindd,切勿再另外手動啟動這三個服務,否則 Samba AD DC 將無法正常運作。

  • 重啟系統
sudo reboot
  • 確認 Samba AD 狀態
sudo systemctl status samba-ad-dc
  • 確認網域伺服器是否正常運行,可以透過 kinit 進行測試
kinit Administrator
  • 輸入密碼後,畫面會顯示該密碼將於幾天後到期

安裝第二台以上 Samba AD 服務

  第二台以後的 DC 是「加入」既有網域,而不是重新建立網域,因此不執行 provision,改為執行 domain join。以下先列出與第一台相同、可直接沿用的步驟,接著只詳述三處差異。

  • 與第一台相同的步驟,請直接參照上方章節操作,指令完全一致:

    • 環境設定(主機名稱改為 dc2,Hosts 改為 192.168.0.253 dc2.abc.local dc2
    • 安裝 Samba AD 相關套件(含安裝過程中的三個 Kerberos 設定視窗)
    • 備份預設設定檔
    • 停止並停用 systemd-resolved 服務、移除 /etc/resolv.conf 符號連結
  • 以下為可一次貼上執行的共通步驟

# 設定主機名稱
sudo hostnamectl set-hostname dc2

# 安裝相關套件
sudo apt-get install -y acl attr samba samba-dsdb-modules samba-vfs-modules winbind libpam-winbind libnss-winbind libpam-krb5 krb5-config krb5-user dnsutils chrony net-tools

# 備份預設設定檔
sudo mv /etc/samba/smb.conf /etc/samba/smb.conf.orig
sudo mv /etc/krb5.conf /etc/krb5.conf.orig

# 停用 systemd-resolved 並移除符號連結
sudo systemctl disable --now systemd-resolved
sudo unlink /etc/resolv.conf
  • 差異一:DNS 必須先指向第一台 DC。 加入網域時需要透過 DNS 找到既有網域,因此 vim /etc/resolv.conf 的 nameserver 要填第一台 DC 的位址,不可填自己。
# 第一台 Samba AD 伺服器的 IP 位址
nameserver 192.168.0.254

# fallback resolver
nameserver 8.8.8.8
nameserver 1.1.1.1

# Samba 的主要網域
search abc.local
  • 完成後同樣鎖定設定檔
sudo chattr +i /etc/resolv.conf
  • 差異二:以 domain join 加入網域,不執行 provision。 -W 指定的是 NetBIOS 網域名稱,必須與第一台 provision 時的 Domain 值一致(本文為 abc)。
sudo samba-tool domain join abc.local DC -W abc -U Administrator

  • 複製設定檔
sudo cp /var/lib/samba/private/krb5.conf /etc/krb5.conf
  • 接著的 chrony 與 systemd 服務設定同第一台,僅 bindaddress 改為本機 IP
# 確認 NTP 簽章位置
samba -b | grep 'NTP' | awk '{print $NF}'

# 設定 ntp_signd 目錄權限
sudo chown root:_chrony /var/lib/samba/ntp_signd/
sudo chmod 750 /var/lib/samba/ntp_signd/
  • 編輯 vim /etc/chrony/chrony.conf,於設定檔最後新增以下三個參數
# 將 NTP 服務綁定到本台 DC 的 IP 位址
bindaddress 192.168.0.253

# 允許網路內的用戶端連線到 Chrony NTP 伺服器
allow 192.168.0.0/24

# 指定 Samba AD 的 ntpsigndsocket 目錄位置
ntpsigndsocket /var/lib/samba/ntp_signd
  • 重啟 chrony,並將 Samba 設定為以 systemd 服務執行
sudo systemctl restart chrony

sudo systemctl stop smbd nmbd winbind
sudo systemctl disable smbd nmbd winbind
sudo systemctl mask smbd nmbd winbind
sudo systemctl unmask samba-ad-dc
sudo systemctl enable --now samba-ad-dc

sudo reboot
  • 重開機後確認服務狀態與網域驗證
sudo systemctl status samba-ad-dc
kinit Administrator
  • 差異三:檢查目錄資料庫正確性。 每次加入或移除 DC 後,建議執行以下指令確認複寫資料無誤。
samba-tool dbcheck --reindex
samba-tool dbcheck --fix
samba-tool dbcheck --cross-ncs --fix
憑證設定

  • 建立憑證存放位置。路徑可自行調整,但以下三處(存放目錄、權限設定、smb.conf 參數)必須完全一致。
sudo mkdir -p /root/ssl
  • privkey.pemcert.pemchain.pem 放入該目錄後,再設定權限
sudo chown root:root /root/ssl/*.pem
sudo chmod 600 /root/ssl/privkey.pem
sudo chmod 644 /root/ssl/cert.pem
sudo chmod 644 /root/ssl/chain.pem
  • 憑證設定 vim /etc/samba/smb.conf
tls enabled = yes
tls keyfile = /root/ssl/privkey.pem
tls certfile = /root/ssl/cert.pem
tls cafile = /root/ssl/chain.pem
  • 重啟服務
sudo systemctl restart samba-ad-dc

稽核記錄


  • Samba AD 啟用稽核記錄 vim /etc/samba/smb.conf
[global]
    # Samba AD DC 稽核記錄設定:
    # auth_json_audit:5            驗證成功/失敗、Kerberos、NTLM、來源 IP、授權身分
    # dsdb_json_audit:5            AD 物件及屬性的建立、修改與刪除
    # dsdb_password_json_audit:5   密碼變更與管理員重設密碼
    # dsdb_group_json_audit:5      群組成員新增與移除
    #
    # 註:auth 由 3 提升為 5,等級 3 僅有 Authentication,5 才含 Authorization
    # 註:已移除 dsdb_transaction_json_audit,三台複寫下量體暴增且訊噪比極差
    # 注意:任一 class 名稱拼錯會導致整串 log level 被拒絕,稽核完全無輸出
    #      修改後務必執行 smbcontrol all debuglevel 確認實際載入結果
    log level = 1 auth_json_audit:5@/var/log/samba/auth_json_audit.log dsdb_json_audit:5@/var/log/samba/dsdb_json_audit.log dsdb_password_json_audit:5@/var/log/samba/dsdb_password_json_audit.log dsdb_group_json_audit:5@/var/log/samba/dsdb_group_json_audit.log

    # 主記錄檔路徑,固定檔名,勿用 %m(依用戶端分檔會產生大量小檔)
    log file = /var/log/samba/log.samba

    # 停用 Samba 內建輪替,交由 logrotate 管理
    # 預設 5000(KB)只保留一份 .old,舊記錄會被直接覆蓋而產生稽核缺口
    max log size = 0

    # 時間戳記置於同一行前綴,每筆訊息為單行,省去 multiline 解析
    debug prefix timestamp = yes

    # 微秒級精度,跨三台 DC 事件排序時需要秒以下解析度
    debug hires timestamp = yes

    # 務必保持 no,設 yes 會將 SMB/LDAP 工作階段金鑰寫入記錄檔
    debug encryption = no

    # 保持 Samba 原生格式,避免與 rsyslog 前綴重複造成 JSON 解析失敗
    # 註:舊版無此參數,testparm 回報 Unknown parameter 時直接移除本行
    debug syslog format = no
  • 輪替保存 vim /etc/logrotate.d/samba-audit
# Samba AD DC 稽核記錄輪替設定:
# 本檔即為 180 天保存政策的技術實作,修改等同變更保存期限
# 異動會被 auditd 記錄(key: samba_rotate_change)

/var/log/samba/*_json_audit.log {

    # 每日輪替,與 maxsize 併用時單日可能輪替多次
    daily

    # 檔名改用日期後綴,取代預設的數字遞增(.1 .2 .3)
    dateext

    # 結尾 -%s(epoch 秒)不可省略:
    # 單日多次輪替時若檔名只到日期會發生碰撞,
    # logrotate 將直接跳過輪替,當日記錄持續累積直到寫爆磁碟
    dateformat -%Y%m%d-%s

    # 保留份數上限,純防呆用,非保存期限
    # 因 maxsize 存在,份數不等於天數,設太小會導致提前刪除
    rotate 900

    # 真正的保留期限:刪除 mtime 超過 190 天者
    # 目標 180 天,多給 10 天緩衝以涵蓋調閱作業與排程延遲
    # 唯一直接對應法遵要求的參數,變更前需經核准
    maxage 190

    # 單檔超過即提前輪替,避免密碼噴灑等異常產生單一巨大檔案
    maxsize 500M

    # 壓縮輪替檔,JSON 重複欄位多,壓縮效益極高
    compress
    compresscmd /usr/bin/zstd
    uncompresscmd /usr/bin/unzstd
    compressext .zst

    # -19 壓縮等級(保存期長,空間優先於耗時)
    # --rm 壓縮後刪除原始檔,-T2 限制執行緒避免排擠 samba
    # 註:未安裝 zstd 時將上述 compress* 四行整組移除即可退回 gzip
    compressoptions -19 --rm -T2

    # 延後一輪才壓縮,避免 samba 緩衝內容尚未落地就被壓縮而遺失
    delaycompress

    # 檔案不存在時不報錯(無對應事件時該 class 檔案不會被建立)
    missingok

    # 空檔案不輪替,避免產生大量 0 byte 歷史檔
    notifempty

    # 輪替後立即建立新檔,0640 僅 root 可寫、adm 可讀
    # 稽核記錄含帳號名稱與來源 IP,不可設為 0644
    create 0640 root adm

    # 目錄權限已收斂為 750 root:adm,未指定會因權限不足略過整組規則
    su root root

    # 比對到多個檔案時 postrotate 只執行一次
    sharedscripts

    postrotate
        # 通知 samba 重新開啟 file descriptor
        # 缺少此步驟,samba 會繼續寫入已改名的舊 inode,新檔案永遠是空的
        # 此為稽核記錄靜默失效最常見的原因
        /usr/bin/smbcontrol all reload-config > /dev/null 2>&1 || true
    endscript
}

安裝防毒


  本案的 Samba AD 只承擔 LDAP 帳號驗證功能,並未提供檔案共享等其他服務。若讓 ClamAV 以常駐模式(clamd)長期掛在記憶體中,反而會排擠網域驗證的效能,因此這裡改用「定期排程掃描」的作法:平時不留任何常駐程序,只在排定的時間更新病毒碼並掃描一次。

Ubuntu 20.04 請先確認病毒碼能否更新。ClamAV 官方已封鎖 0.105 及以下所有版本(含全部修訂版)的病毒碼下載,而 20.04 套件庫提供的是 0.103.x,執行 freshclam 會直接收到 403 Blocked by CDN 而取不到病毒碼。若必須在 20.04 上部署,需自行升級 ClamAV 或改指向內部鏡像,否則本章節的排程掃描等於空轉。

  • 安裝 ClamAV 服務
sudo apt update && sudo apt install -y clamav clamav-freshclam clamav-testfiles
  • 安裝後系統會自動啟動常駐服務,立即停用
sudo systemctl disable --now clamav-freshclam
sudo systemctl disable --now clamav-daemon 2>/dev/null || true

本案只安裝 clamav 與 clamav-freshclam,並未安裝 clamav-daemon 套件,因此停用指令的第二行刻意加上錯誤重導向與 || true,讓該服務不存在時不會中斷後續步驟。

  • 確認兩個服務都已停止
sudo systemctl is-active clamav-daemon clamav-freshclam
  • 備份 ClamAV 的預設設定檔
sudo cp /etc/clamav/freshclam.conf /etc/clamav/freshclam.conf.bak
  • 將原本的預設內容全部清除後,貼上以下參數 sudo vim /etc/clamav/freshclam.conf。此檔案屬 clamav 使用者,vim 可能提示唯讀,存檔時請以 :w! 強制寫入
# ClamAV 病毒碼更新設定 — Samba AD DC(純 LDAP 驗證用途)
# 架構:不使用常駐 daemon,由 systemd timer 於掃描前觸發單次更新
# 服務已停用:systemctl disable --now clamav-freshclam
# 最後修改:2026-07-26 / 負責人:XXX

# 更新程序執行身分,套件安裝時已建立此使用者
DatabaseOwner clamav

# 病毒碼儲存位置,約需 500 MB(更新期間會有暫存副本)
# 務必確認此路徑不在 /var/log 這個 LV 上,避免侵蝕 180 天稽核記錄的空間
DatabaseDirectory /var/lib/clamav

# 更新記錄檔路徑
UpdateLogFile /var/log/clamav/freshclam.log

# 每行加上時間戳記,便於判斷病毒碼年齡與更新失敗時間點
LogTime true

# 只記錄必要訊息,設 true 會產生大量除錯輸出
LogVerbose false

# 不寫入 syslog,避免與 Samba 稽核記錄混雜
LogSyslog false

# 停用內建輪替,交由 /etc/logrotate.d/clamav-custom 統一管理
# 與 smb.conf 的 max log size = 0 同一原則:避免兩套輪替機制互相打架
# 註:官方文件載明 LogRotate 在 LogFileMaxSize 啟用時會被強制開啟,
#     因此兩者必須成對設定,只設其中一項無法真正停用內建輪替
LogFileMaxSize 0
LogRotate false

# 更新來源。三台若同時對外更新,官方 CDN 節流機制可能暫時封鎖來源 IP
# 已由 timer 的 RandomizedDelaySec 錯開;若日後架設內部鏡像,改指向內部位址
DatabaseMirror database.clamav.net

# 版本查詢用的 DNS TXT 記錄,可在無需下載的情況下判斷是否有新版
DNSDatabaseInfo current.cvd.clamav.net

# 使用增量更新(.cdiff),大幅減少每次下載量與對外流量
# 副作用:daily 檔案會由 daily.cvd 變成 daily.cld,監控腳本兩者都要判斷
ScriptedUpdates yes

# 下載完成後驗證資料庫完整性再套用,避免半殘的病毒碼被載入
TestDatabases yes

# 一併下載 bytecode.cvd,內含針對較複雜惡意程式的偵測邏輯
# 註:在 freshclam.conf 中此參數控制的是「要不要下載」,預設即為 yes;
#     真正決定執不執行 bytecode 簽章的是 clamd.conf,兩者不同層
Bytecode true

# 連線逾時。DC 對外連線通常會經過 proxy 或防火牆,故將連線逾時放寬一倍
# 註:ConnectTimeout 官方預設 30 秒,此處 60 秒為實際放寬;
#     ReceiveTimeout 60 與 MaxAttempts 3 皆等同官方預設值,明列僅為日後便於調整
#     若對外線路不穩,官方範例值為 ReceiveTimeout 300、MaxAttempts 5
ConnectTimeout 60
ReceiveTimeout 60
MaxAttempts 3

# ── 以下為刻意停用的項目 ──────────────────────────────────────

# 通知 clamd 重新載入病毒碼
# 此參數接受的是 clamd.conf 的「路徑」,不是 true/false
# 寫成 NotifyClamd false 會讓 freshclam 把 false 當成檔名而報錯
# 本環境未執行 clamd,整行註解停用
#NotifyClamd /etc/clamav/clamd.conf

# 每日檢查次數,僅在 daemon 模式(freshclam -d)有效
# 本環境由 systemd timer 觸發單次執行,不需要此參數
#Checks 12

# PID 檔案,僅 daemon 模式需要
#PidFile /run/clamav/freshclam.pid

# 若 DC 需經 proxy 對外,取消註解並填入實際位址
#HTTPProxyServer proxy.aaa2.com
#HTTPProxyPort 3128

# 不要自訂 HTTPUserAgent,官方 CDN 會封鎖非標準 User-Agent
#HTTPUserAgent
  • 手動執行一次病毒碼更新
sudo freshclam
  • 確認病毒碼已下載
ls -lh /var/lib/clamav/*.c[vl]d

啟用 ScriptedUpdates 之後,daily 病毒碼的副檔名會由 .cvd 變成 .cld,因此上面刻意用 .c[vl]d 一次比對兩種副檔名,否則會誤判成沒有下載成功。

  • 用測試檔確認掃描器真的抓得到。前面安裝的 clamav-testfiles 會提供一組帶有測試特徵碼的檔案,掃描結果的 Infected files 應為非 0;若回報 0,代表病毒碼或掃描設定有問題,不要等排程上線後才發現
clamscan -r /usr/share/clamav-testfiles
  • 建立掃描腳本 sudo vim /usr/local/sbin/samba-dc-avscan.sh
#!/bin/bash
# Samba AD DC 防毒掃描:
# 每週執行一次,僅告警不處置,偵測結果由 LibreNMS 讀取狀態檔
# 刻意不使用 --remove / --move,誤刪 sam.ldb 或 GPO 的後果遠大於病毒本身

set -uo pipefail
# 註:不使用 -e,掃描發現病毒時 exit code 為 1,需自行判讀而非中止

LOG=/var/log/clamav/scan.log
STATE=/var/lib/clamav/last-scan.state      # 供監控讀取的狀態檔
SCAN_TARGETS="/var/lib/samba/sysvol /root /tmp /home /usr/local"

# 本次掃描的暫存輸出。告警內容只取本次結果,
# 若直接對 scan.log 過濾,會把前幾次掃描的舊紀錄一併寄出
TMPLOG=$(mktemp)
trap 'rm -f "$TMPLOG"' EXIT

# ── 掃描前更新病毒碼 ───────────────────────────────────────────
# 單次執行,完成即結束,不留常駐程序
freshclam --quiet || logger -t dc-avscan "WARN: freshclam update failed"

# ── 執行掃描 ───────────────────────────────────────────────────
# -r             遞迴子目錄
# --infected     只輸出感染項目,大幅減少記錄檔體積
# --stdout       錯誤訊息一併導向標準輸出,便於統一收集
# --cross-fs=no  不跨檔案系統(預設為 yes),避免誤掃到網路掛載或其他 LV
START=$(date +%s)
{
  echo "===== $(date '+%F %T') scan start ====="
  clamscan -r --infected --stdout --cross-fs=no $SCAN_TARGETS
  RC=$?
  echo "===== exit code: $RC ====="
} > "$TMPLOG" 2>&1
ELAPSED=$(( $(date +%s) - START ))

# 本次結果併入主記錄檔保存
cat "$TMPLOG" >> "$LOG"

# ── 記錄結果供監控使用 ─────────────────────────────────────────
# exit code:0=乾淨、1=發現病毒、2=掃描錯誤
echo "timestamp=$(date +%s) rc=${RC} elapsed=${ELAPSED}" > "$STATE"

case "$RC" in
  0) logger -t dc-avscan "scan clean (${ELAPSED}s)" ;;
  1) logger -t dc-avscan "ALERT: malware detected, see $LOG (manual review required)"
     # 僅告警。發現後由人工判斷,不自動處置
     # 只取本次掃描的 FOUND 行,避免混入歷史紀錄
     grep FOUND "$TMPLOG" | tail -20 | mail -s "[$(hostname -s)] ClamAV 偵測到威脅" [email protected] 2>/dev/null || true
     ;;
  *) logger -t dc-avscan "ERROR: scan failed with code ${RC}" ;;
esac

exit 0
# 註:一律回傳 0,避免 systemd 將發現病毒視為服務失敗
  • 設定掃描腳本權限,並建立記錄檔目錄
sudo chmod 750 /usr/local/sbin/samba-dc-avscan.sh
sudo mkdir -p /var/log/clamav
sudo chown clamav:adm /var/log/clamav
sudo chmod 750 /var/log/clamav

腳本中的告警信件依賴 mail 指令,Ubuntu 最小安裝並未內建,需另外安裝 mailutils 或 bsd-mailx 才會實際寄出。未安裝時腳本不會失敗,但信件會靜默地寄不出去,只剩 logger 寫入 syslog 的那一筆紀錄。

  • 建立掃毒的服務單元 sudo vim /etc/systemd/system/samba-dc-avscan.service
[Unit]
Description=Samba AD DC ClamAV scan

# 以下兩行必須成對出現。依 systemd 官方說明,network-online.target
# 屬於「由使用它的單元自行拉起」的類型,只寫 After= 並不會讓它被啟用
Wants=network-online.target
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/samba-dc-avscan.sh

# 資源限制:確保掃描作業永遠不會排擠 samba
# clamscan 載入完整病毒碼約需 1.5 GB,設 2G 保留裕度
MemoryMax=2G
MemorySwapMax=0

# 限制 CPU 使用率,避免掃描期間影響 LDAP 驗證回應時間
CPUQuota=40%
Nice=15

# I/O 排程設為 idle,只在系統閒置時使用磁碟
IOSchedulingClass=idle

# 記憶體吃緊時優先終止本程序,而非 samba
OOMScoreAdjust=800
  • 建立執行排程 sudo vim /etc/systemd/system/samba-dc-avscan.timer
[Unit]
Description=Weekly Samba AD DC ClamAV scan

[Timer]
# 每週日 04:00 執行,避開稽核記錄的 logrotate(00:00 前後)與歸檔作業(03:00)
OnCalendar=Sun *-*-* 04:00:00

# 隨機延遲最多 1 小時,避免三台同時對外更新病毒碼觸發 CDN 節流
RandomizedDelaySec=3600

# 主機關機而錯過排程時,開機後補執行
Persistent=true

[Install]
WantedBy=timers.target
  • 降低 samba 被 OOM Killer 終止的機率 sudo systemctl edit samba-ad-dc
[Service]
# 記憶體不足時,讓核心優先終止其他程序而非 samba
# 數值越低越不容易被 OOM Killer 選中
OOMScoreAdjust=-500

systemctl edit 建立的是 drop-in 覆寫檔,存放於 /etc/systemd/system/samba-ad-dc.service.d/override.conf,不會改動套件安裝的原始單元檔,因此套件升級時這項設定不會被覆蓋。此處與掃描服務的 OOMScoreAdjust=800 是一組的設計:一邊調高、一邊調低,才能確保記憶體吃緊時被犧牲的是掃描作業而不是網域服務。

  • 啟用服務
sudo systemctl daemon-reload
sudo systemctl enable --now samba-dc-avscan.timer
sudo systemctl list-timers samba-dc-avscan.timer
  • 設定記錄檔輪替 sudo vim /etc/logrotate.d/clamav-custom
# ClamAV 記錄檔輪替 — 保留 180 天,與 Samba 稽核記錄的保存政策一致
# /var/log 為稽核記錄專用 LV,ClamAV 記錄不得無限成長侵蝕其空間
# 前提:freshclam.conf 已設 LogRotate false + LogFileMaxSize 0,避免雙重輪替

/var/log/clamav/*.log {

    # 每週輪替。ClamAV 記錄量極小,不需要每日切檔
    weekly

    # 檔名改用日期後綴,便於稽核調閱時直接依日期定位
    dateext

    # 結尾 -%s(epoch 秒)不可省略:
    # maxsize 會造成單週多次輪替,檔名只到日期會發生碰撞,
    # logrotate 將直接跳過輪替而使記錄持續累積
    dateformat -%Y%m%d-%s

    # 保留份數上限,純防呆用,非保存期限
    # 週輪替 26 份即滿半年,設 60 提供 maxsize 額外輪替的裕度
    rotate 60

    # 真正的保留期限:刪除 mtime 超過 190 天者
    # 目標 180 天,多給 10 天緩衝以涵蓋調閱作業與排程延遲
    maxage 190

    # 單檔超過即提前輪替,防止病毒碼更新反覆失敗等異常造成暴增
    maxsize 20M

    # 壓縮輪替檔
    compress
    compresscmd /usr/bin/zstd
    uncompresscmd /usr/bin/unzstd
    compressext .zst
    compressoptions -19 --rm -T2

    # 延後一輪才壓縮,避免正在寫入的內容被壓縮而遺失
    delaycompress

    # 檔案不存在時不報錯(scan.log 在首次掃描前不存在)
    missingok

    # 空檔案不輪替
    notifempty

    # 輪替後立即建立新檔,擁有者與 freshclam 的 DatabaseOwner 一致
    create 0640 clamav adm

    # 目錄擁有者為 clamav 而非 root,未指定 su 會因權限檢查而略過整組規則
    su root root
}
  • 設定完成後,以測試模式確認輪替規則可被正確解析(-d 為模擬執行,不會實際輪替)
sudo logrotate -d /etc/logrotate.d/clamav-custom

補充說明


  • 如果有 Windows Server 要加入 Samba AD 網域控制站,記得在 vim /etc/samba/smb.conf 加入以下參數才能正常加入
dsdb:schema update allowed=true
  • 掃描結果的狀態檔 /var/lib/clamav/last-scan.state 內容為單行的 timestamprcelapsed 三組鍵值,可供 LibreNMS 等監控系統以自訂腳本讀取。要判斷「掃描是否已停擺」請比對 timestamp 與當前時間的差距,不要只看 rc:排程完全沒有執行時,rc 會一直停留在上一次的成功值,看起來與正常運作毫無差別。

疑難排解


  • 錯誤訊息: provision 階段出現密碼複雜度相關錯誤而中斷 解決方式: 管理者密碼至少 8 個字元,且需包含大寫字母與數字。provision 會重設網域的密碼原則,因此事前調整無效,請直接改用符合複雜度的密碼;若確有放寬需求,待建置完成後再以 samba-tool domain passwordsettings set --complexity=off 調整,但會明顯降低網域安全性。

  • 錯誤訊息: 稽核記錄檔完全沒有任何輸出 解決方式: log level 中只要有任一 class 名稱拼錯,整串設定都會被拒絕。請執行 smbcontrol all debuglevel 確認實際載入的結果是否與設定檔一致。

  • 錯誤訊息: 稽核記錄輪替後新檔案永遠是空的 解決方式: 輪替後必須通知 samba 重新開啟 file descriptor,否則會繼續寫入已改名的舊 inode。請確認 logrotate 設定中的 postrotate 區塊有執行 smbcontrol all reload-config

  • 錯誤訊息: 無法修改 /etc/resolv.conf,提示 Operation not permitted 解決方式: 該檔案已被設為 immutable,需先執行 sudo chattr -i /etc/resolv.conf 解除屬性,修改完成後再以 sudo chattr +i /etc/resolv.conf 鎖回。

  • 錯誤訊息: 第二台 DC 執行 samba-tool domain join 時找不到網域 解決方式: 加入網域前 /etc/resolv.conf 的 nameserver 必須指向第一台 DC,若指向自己或外部 DNS 將無法解析網域相關的 SRV 記錄。

  • 錯誤訊息: 執行 systemctl restart chronyd 提示找不到服務 解決方式: Ubuntu 與 Debian 的服務名稱為 chronychronyd 是 RHEL 系的命名,請改用 sudo systemctl restart chrony

  • 錯誤訊息: 執行 freshclam 出現 403 ForbiddenBlocked by CDN,完全取不到病毒碼 解決方式: ClamAV 官方已封鎖 0.105 及以下所有版本的病毒碼下載,請先以 clamscan --version 確認版本。Ubuntu 20.04 套件庫提供的 0.103.x 正落在封鎖範圍內,官方自 2025-09-14 起停止對其提供病毒碼。解法為升級 ClamAV、升級作業系統,或改以內部鏡像提供病毒碼,單純重試不會成功。

  • 錯誤訊息: 編輯 /etc/clamav/freshclam.conf 時 vim 提示唯讀而無法存檔 解決方式: 該檔案的擁有者為 clamav 使用者,請確認是以 sudo 開啟,並於存檔時以 :w! 強制寫入。

  • 錯誤訊息: freshclam 執行失敗,並抱怨找不到名為 false 的檔案 解決方式: NotifyClamd 接受的是 clamd.conf 的路徑,不是 truefalse,寫成 NotifyClamd false 會讓 freshclam 把 false 當成設定檔路徑。本案未執行 clamd,請將整行註解停用。

  • 現象: 排程確實有執行,但掃描結果永遠是乾淨的 解決方式: 先以 clamscan -r /usr/share/clamav-testfiles 確認掃描器抓得到測試特徵碼。若連測試檔都回報 0,代表病毒碼並未成功下載(最常見的原因即為前述的 CDN 封鎖),而不是系統真的乾淨。

  • 現象: 掃描已發現病毒,但 systemctl status samba-dc-avscan 仍顯示執行成功 解決方式: 這是刻意的設計。掃描腳本最後一律 exit 0,避免 systemd 將「發現病毒」判定為服務失敗。實際結果請改看 /var/lib/clamav/last-scan.state 中的 rc 值(0 為乾淨、1 為發現病毒、2 為掃描錯誤),或 syslog 中標籤為 dc-avscan 的紀錄。

備註





參考相關網頁