15.6 Jail 的應用

15.6.1 Service Jails

Contributed by Daniel Gerzo.

本節主要以 Simon L. Nielsen 寫的 http://simon.nitro.dk/service-jails.html 為主,加上 Ken Tom 所更新的文章。 本節介紹如何設定 FreeBSD 以 jail(8) 功能來增加額外的安全層面。 這部分假設您系統跑的是 RELENG_6_0 或更新的版本, 並且對本章先前部分均能理解。

15.6.1.1 Design

Jail 的主要問題之一在於如何對其進行更新、升級和管理。 由於每個 jail 都是從頭重新編譯,對於單一 jail 而言, 升級也許還不是很嚴重的問題,因為更新、升級並不會太麻煩。 但對於一堆 jail 而言,升級不僅會耗費太多時間,並相當枯燥乏味。

Warning: 這些設定的前提是您對 FreeBSD 使用、功能運用上有相當的經驗, 若下面的設定對您來說太過複雜,建議您該考慮用較簡易的系統,像是 sysutils/ezjail,其提供更簡單的 FreeBSD jail 管理方式。

基本的想法是在不同的 jail 中儘量以安全的方式來共用資源 —— 採用唯讀的 mount_nullfs(8) 掛載,來讓升級更簡單, 並把各個 service 放到不同的 jail 的作法會更加可行。 此外, 其也提供對於如何增加、刪除、升級 jail 的簡便方式。

Note: service 常見的例子包括: HTTP server、DNS server、SMTP server 等等。

本節介紹的設定目的在於:

  • 建立簡易且容易理解的 jail 架構。 也就是說 不必為每個 jail 都執行完整的 installworld 。

  • 讓 jail 的新增、移除更簡單。

  • 讓 jail 的更新、升級更輕鬆。

  • 可以跑自行打造的 FreeBSD 分支。

  • 對安全有更偏執狂的追求,儘可能降低被攻陷的可能。

  • 儘量節省空間與 inode。

如同先前所提到的,這設計主要是靠把唯讀的主要模版 (也就是大家所熟知的 nullfs)掛載到每個 jail,並且讓每個 jail 有個可讀、寫的設備,這設備可以是獨立實體硬碟、 、分割區、或以 vnode 為後端的 md(4) 設備。 在本例當中, 我們採用可讀寫的 nullfs 掛載。

下面的表則介紹檔案系統的配置:

  • 每個 jail 都會掛載到 /home/j 底下的其中一個目錄。

  • /home/j/mroot 則是每個 jail 共用的模版,並對於所有 jail 而言都是唯讀。

  • 每個 jail 在 /home/j 底下都有一個相對應的空目錄。

  • 每個 jail 都會有 /s 目錄, 該目錄會連到系統的可讀寫部分。

  • 每個 jail 都會在 /home/j/skel 目錄建立自屬的可讀寫空間 。

  • 每個 jailspace (各 jail 可讀寫的部分) 都建在 /home/js>。

Note: 這邊假設所有 jail 都放在 /home 分割區。 當然, 也可以依自身需求更改,但接下來的例子中, 也要記得修改相對應的地方。

15.6.1.2 建立模版

本節將逐步介紹如何建立 jail 要用的唯讀主模版。

建議先把 FreeBSD 系統升級到最新的 -RELEASE 分支,至於如何做請參閱 Handbook 的 相關章節。 當更新完成之後,就要進行 buildworld 程序,此外還要裝 sysutils/cpdup 套件。 我們將用 portsnap(8) 來下載 FreeBSD Ports Collection, 在 Handbook 中對 Portsnap 章節 中有相關介紹,初學者可以看看。

  1. 首先,先建立唯讀的目錄結構給 jail 放 FreeBSD binary, 接著到 FreeBSD source tree 目錄,並安裝 jail 模版:

    # mkdir -p /home/j/mroot
    # cd /usr/src
    # make installworld DESTDIR=/home/j/mroot
    
  2. 接著跟 FreeBSD source tree 一樣,也把 FreeBSD Ports Collection 放一份供 jail 使用,以備 mergemaster

    # cd /home/j/mroot
    # mkdir usr/ports
    # portsnap -p /home/j/mroot/usr/ports fetch extract
    # cpdup /usr/src /home/j/mroot/usr/src
    
  3. 建立可讀寫部分的骨架:

    # mkdir /home/j/skel /home/j/skel/home /home/j/skel/usr-X11R6 /home/j/skel/distfiles
    # mv etc /home/j/skel
    # mv usr/local /home/j/skel/usr-local
    # mv tmp /home/j/skel
    # mv var /home/j/skel
    # mv root /home/j/skel
    
  4. mergemaster 來裝漏掉的設定檔。 接下來刪除 mergemaster 所建立的多餘目錄:

    # mergemaster -t /home/j/skel/var/tmp/temproot -D /home/j/skel -i
    # cd /home/j/skel
    # rm -R bin boot lib libexec mnt proc rescue sbin sys usr dev
    
  5. 現在把可讀寫的檔案系統以 symlink 方式連到唯讀的檔案系統。 請確認 symbolic link 是否有正確連到 s/ 目錄,若目錄建立方式不對, 或指向位置不對,可能會導致安裝失敗。

    # cd /home/j/mroot
    # mkdir s
    # ln -s s/etc etc
    # ln -s s/home home
    # ln -s s/root root
    # ln -s ../s/usr-local usr/local
    # ln -s ../s/usr-X11R6 usr/X11R6
    # ln -s ../../s/distfiles usr/ports/distfiles
    # ln -s s/tmp tmp
    # ln -s s/var var
    
  6. 最後則是新增 /home/j/skel/etc/make.conf ,並填入以下內容:

    WRKDIRPREFIX?=  /s/portbuild
    

    要設定 WRKDIRPREFIX 才可以讓各 jail 得以順利編譯 FreeBSD ports。請記住 ports 目錄是屬唯讀檔案系統。 而搭配自訂的 WRKDIRPREFIX 才可以讓各 jail 在可讀寫空間進行編譯。

15.6.1.3 建立 Jail

現在已經有完整的 FreeBSD jail 模版,可以在 /etc/rc.conf 內做相關設定。 下面這例子則示範如何建立 3 個 jail:“NS”、 “MAIL”、“WWW”。

  1. /etc/fstab 加上下列設定, 以便讓系統自動掛載各 jail 所需的唯讀模版與讀寫空間:

    /home/j/mroot   /home/j/ns     nullfs  ro  0   0
    /home/j/mroot   /home/j/mail   nullfs  ro  0   0
    /home/j/mroot   /home/j/www    nullfs  ro  0   0
    /home/js/ns     /home/j/ns/s   nullfs  rw  0   0
    /home/js/mail   /home/j/mail/s nullfs  rw  0   0
    /home/js/www    /home/j/www/s  nullfs  rw  0   0
    

    Note: 分割區的 pass number 標示為 0 就不會在開機時做 fsck(8) 檢查;而分割區的 dump number 標示為 0 則不會被 dump(8) 所備份。 我們並不希望 fsck 檢查 nullfs 的掛載,或者讓 dump 備份 jail 內唯讀的 nullfs 掛載。 這也就是為何上述 fstab 每行設定後面都有兩欄為 “0 0”。

  2. /etc/rc.conf 內設定 jail:

    jail_enable="YES"
    jail_set_hostname_allow="NO"
    jail_list="ns mail www"
    jail_ns_hostname="ns.example.org"
    jail_ns_ip="192.168.3.17"
    jail_ns_rootdir="/usr/home/j/ns"
    jail_ns_devfs_enable="YES"
    jail_mail_hostname="mail.example.org"
    jail_mail_ip="192.168.3.18"
    jail_mail_rootdir="/usr/home/j/mail"
    jail_mail_devfs_enable="YES"
    jail_www_hostname="www.example.org"
    jail_www_ip="62.123.43.14"
    jail_www_rootdir="/usr/home/j/www"
    jail_www_devfs_enable="YES"
    

    Warning: 之所以要把 jail_name_rootdir/home 改為 /usr/home 的原因在於 FreeBSD 預設安裝的 /home 目錄其實只是指向 /usr/home 的 symbolic link。 而 jail_name_rootdir 變數須為 實體目錄 而非 symbolic link, 否則 jail 會拒絕啟動。 可以用 realpath(1) 來決定該變數。 詳情請參閱 FreeBSD-SA-07:01.jail 安全通告。

  3. 替每個 jail 建立必須的唯讀檔案系統掛載點:

    # mkdir /home/j/ns /home/j/mail /home/j/www
    
  4. 為每個 jail 安裝可讀寫的模版。 請注意這時要用 sysutils/cpdup ,它能確保每個目錄都有正確複製。

    # mkdir /home/js
    # cpdup /home/j/skel /home/js/ns
    # cpdup /home/j/skel /home/js/mail
    # cpdup /home/j/skel /home/js/www
    
  5. 如此一來就已完成 jail 環境建立,可以準備好要用了。 請先為各 jail 掛載所須的檔案系統,再用 /etc/rc.d/jail script 來啟動:

    # mount -a
    # /etc/rc.d/jail start
    

現在 jail 應該就會啟動了。 若要檢查是否有正常啟動,可以用 jls(8) 指令來看,該指令的執行結果應該類似下面:

# jls
   JID  IP Address      Hostname                      Path
     3  192.168.3.17    ns.example.org                /home/j/ns
     2  192.168.3.18    mail.example.org              /home/j/mail
     1  62.123.43.14    www.example.org               /home/j/www

此時就可以登入各 jail 並新增帳號與設定相關 service 要用的 daemon 。 上面的 JID 欄代表正在運作中的 jail 編號。 可用下列指令以在 JID 編號 3 的 jail 執行管理工作:

# jexec 3 tcsh

15.6.1.4 升級

有時由於安全問題或者 jail 內要用新功能,而需要把 FreeBSD 系統升級到更新。 這種安裝設計方式讓既有的 jail 升級變得更加容易。 jail 也可以把 service 停機時間(downtime)降到最低,因為 jail 只需在最後關鍵才需要重開。 此外,萬一新版有問題的話, 它也提供輕鬆回溯到舊版的功能。

  1. 首先是照一般方式來升級 host system,再新增臨時的唯讀模版 /home/j/mroot2

    # mkdir /home/j/mroot2
    # cd /usr/src
    # make installworld DESTDIR=/home/j/mroot2
    # cd /home/j/mroot2
    # cpdup /usr/src usr/src
    # mkdir s
    

    同樣地,在執行 installworld 時會建立一些用不著的目錄,請把這些砍掉:

    # chflags -R 0 var
    # rm -R etc var root usr/local tmp
    
  2. 重新建立到主系統的可讀寫空間 symlink:

    # ln -s s/etc etc
    # ln -s s/root root
    # ln -s s/home home
    # ln -s ../s/usr-local usr/local
    # ln -s ../s/usr-X11R6 usr/X11R6
    # ln -s s/tmp tmp
    # ln -s s/var var
    
  3. 現在可以關閉 jail:

    # /etc/rc.d/jail stop
    
  4. 卸載原先的檔案系統:

    # umount /home/j/ns/s
    # umount /home/j/ns
    # umount /home/j/mail/s
    # umount /home/j/mail
    # umount /home/j/www/s
    # umount /home/j/www
    

    Note: 可讀寫空間(/s 是掛載在唯讀檔案系統底下,故要先卸載。

  5. 把舊的唯讀系統搬走,換成新的。 如此一來, 可同時保留先前系統的備份,以備萬一升級後有問題可回復。 這邊的命名方式採新唯讀檔案系統的建立時間,此外原先 FreeBSD Ports Collection 直接搬到新的檔案系統,以節省硬碟空間與 inode :

    # cd /home/j
    # mv mroot mroot.20060601
    # mv mroot2 mroot
    # mv mroot.20060601/usr/ports mroot/usr
    
  6. 現在新的唯讀模版準備好了,只剩下重新掛載以及啟動 jail:

    # mount -a
    # /etc/rc.d/jail start
    

最後以 jls(8) 來檢查 jail 是否均正常啟動。 別忘了要在各 jail 內執行 mergemaster,還有相關設定檔以及 rc.d scripts 均要更新。

本文及其他文件,可由此下載:ftp://ftp.FreeBSD.org/pub/FreeBSD/doc/

若有 FreeBSD 方面疑問,請先閱讀 FreeBSD 相關文件,如不能解決的話,再洽詢 <questions@FreeBSD.org>。
關於本文件的問題,請洽詢 <doc@FreeBSD.org>。