標籤:Ubuntu

在 Ubuntu 裡面啓用 cgroups v2

因為最近跳了 Podman[1] 的坑,所以就想說順便玩玩 rootless container 好了,結果發現如果要用 rootless container 的話,Podman 在 cgroups v2 裡面才可以做各種資源限制,但 Ubuntu 預設只使用了 cgroups v1,所以下面就來看看要怎麼把它切到 v2 囉。

TL;DR

#!/bin/bash

echo 'GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT} systemd.unified_cgroup_hierarchy=1"' | sudo tee /etc/default/grub.d/70-cgroup-unified.cfg
sudo update-grub

Kernel parameters

想要在 Systemd enabled Linux 裡面啓用 cgroups v2 的話可以直接在 kernel parameters 裡面加上 systemd.unified_cgroup_hierarchy=1[2] 即可在開機後使用 cgroups v2。

Grub

雖然在開機的時候可以手動修改 parameters,但總不可能每次開機的時候都手動調整,所以我們要靠修改 grub 開機選單來幫我們解決這個問題。

我們可以在 /etc/default/grub 裡面直接添加 GRUB_CMDLINE_LINUX_DEFAULT="systemd.unified_cgroup_hierarchy=1" 或是在 /etc/default/grub.d/ 下面新增一個檔案來載入設定,我自己會選擇後者[3],避免被其他設定檔覆蓋(例如: 50-curtin-settings.cfg 會覆寫 GRUB_CMDLINE_LINUX_DEFAULT):

# /etc/default/grub.d/70-cgroup-unified.cfg
GRUB_CMDLINE_LINUX_DEFAULT="${GRUB_CMDLINE_LINUX_DEFAULT} systemd.unified_cgroup_hierarchy=1"

修改完畢後執行 sudo update-grub 即可套用我們剛剛新增的設定,亦可從 /boot/grub/grub.cfg 檢查設定是否有誤,都沒問題後就可以直接重開機了!

檢查結果

我們可以在重開機之後看看系統是不是真的有把 cgroups v2 啓用,可以從 mount list 檢查:

$ mount | grep cgroup2
cgroup2 on /sys/fs/cgroup type cgroup2 (rw,nosuid,nodev,noexec,relatime)

也可以手動 mount 看看 cgroups2:

$ sudo mkdir -p /test-cgroups2
$ sudo mount -t cgroup2 none /test-cgroups2
$ ls /test-cgroups2
cgroup.controllers  cgroup.max.descendants  cgroup.stat             cgroup.threads
cgroup.max.depth    cgroup.procs            cgroup.subtree_control  ...
$ sudo umount /test-cgroups2
$ sudo rmdir /test-cgroups2

如果可以成功存取 cgroups2 的資源,基本上就是成功了!


  1. Podman, a Pod Manager tool. ↩︎

  2. cgroups - ArchWiki ↩︎

  3. 關於 /etc/default/grub.d 是如何被載入的,可以參考 /usr/sbin/grub-mkconfig 裡面的實作 ↩︎

如何升級 EoL 的 Ubuntu 版本

最近有從 Ubuntu 18.10 升級的需求,但因為 18.10 早就在 2019/07/18 EoL 了,因此沒辦法直接使用 do-release-upgrade 升級,這個時候該怎麼辦呢?

TL;DR

  • 如果還在 Lifespan 的版本:直接 do-release-upgrade [-d]
  • 如果已經 EoL 的版本:
    • 選擇下一個版本
      • LTS:最近的 LTS (16.04 xenial -> 18.04 bionic
      • 非 LTS:最近的版本 (18.10 cosmic -> 19.04 disco
    #!/bin/bash
    set -euo pipefail
    
    NEXT_CODE='disco' # 下一個版本的 codename
    
    # 把 apt 源切到 `http://old-releases.ubuntu.com/ubuntu/`
    sudo sed -i -re 's/([a-z]{2}\.)?archive.ubuntu.com|security.ubuntu.com/old-releases.ubuntu.com/g' /etc/apt/sources.list
    sudo apt update
    sudo apt upgrade
    
    pushd `mktemp -d`
    curl http://old-releases.ubuntu.com/ubuntu/dists/${NEXT_CODE}-updates/main/dist-upgrader-all/current/${NEXT_CODE}.tar.gz | tar -zxf -
    sudo python3 ./dist-upgrade.py
    

可以使用 do-release-upgrade 的場景

Ubuntu 提供 do-release-upgrade[1] 方便使用者更新到最新的發行版本,但其更新路線是有限制的。
一般來說 LTS 可以從上一個 LTS 或普通版本更新,而普通版本只能從上一個版本更新,例如 20.04 可以從 18.0419.10 更新上去,但 19.04 只能從 18.10 更新,更多資訊可以參考 Ubuntu wiki 的版本升級說明[2]

do-release-upgrade 決定下一個版本的方式可以從 /etc/update-manager/release-upgrades 修改,可以指定到最新普通版本還是最新 LTS 版本。
如果目前使用的版本可以直接更新到最新的版本的話那就直接用 do-release-upgrade [-d] 升級就可以了。

無法使用 do-release-upgrade 的場景

如果目前的版本已經不適用 do-release-upgrade 升級到最新版本或不打算使用的話,可以考慮從下一個版本的軟體源中下載升級工具:(把 ${NEXT_CODE} 換成下一個版本的 codename)

# 還在 Lifespan 的版本
http://archive.ubuntu.com/ubuntu/dists/${NEXT_CODE}-updates/main/dist-upgrader-all/current/${NEXT_CODE}.tar.gz
# 已經 EoL 的版本
http://old-releases.ubuntu.com/ubuntu/dists/${NEXT_CODE}-updates/main/dist-upgrader-all/current/${NEXT_CODE}.tar.gz

下載回來之後可以用 tar -zxf 解壓縮,然後執行 sudo python3 ./dist-upgrade.py 就可以進行更新。

EoL 版本無法 apt update 的解法

對於已經 EoL 的版本,Ubuntu 會將其軟體源移動到 http://old-releases.ubuntu.com/ubuntu[3] 來保存,大家可以直接把源切到這邊繼續使用,但因為已經 EoL 了的關係,還是建議趕快升級發行版吧 XDDD


  1. 大部分情況下會預載,或是可以 apt install update-manager-core 取得 ↩︎

  2. 19.04 Disco 可以從 18.10 升級;而 20.04 focal 則可以從 19.10/18.04 LTS 升級 ↩︎

  3. Old Ubuntu Releases ↩︎

在 WSL2 中使用 Ubuntu 桌面環境

雖然微軟在 BUILD 2020 上已經宣佈,未來會讓 WSL2 可以執行 GUI 應用程式[1],但不知道什麼時候才會正式支援這個功能,對於想體驗看看效果到底如何的我呢,就打算先在 Windows 端啓動一個 X Window Server 來嚐鮮看看。

事前準備

首先,對於一個習慣 Ubuntu 的我來說,如果可以體驗到完整的 Ubuntu 桌面是再好不過了,換句話說,我們需要在上面可以跑一個完整的 Gnome Shell 環境,並加上 Ubuntu 的 Extension 們。

取得完整 systemd 環境

Gnome Shell 從 3.34 版開始,就已經跟 systemd 整合[2]了,也就是說,我們必須要先有一個完整的 systemd 環境才能順利的執行 Gnome Shell。由於 WSL2 的實作機制其實是啓動一個 Tiny Linux VM,然後利用 Linux Namespace 機制來執行並隔離各個發行版,並且由微軟實作了一個 init (PID 1) 來辦到快速啓動以及作為與 windows 溝通的橋樑[3]

也因此,在 WSL2 的系統裡面,其實是沒有啓用 systemd 的,我們可以簡單的透過下面的方式來檢查看看:

$ systemctl
System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down
$ ps u -q 1
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.0  0.0    908   592 ?        Sl   10:31   0:00 /init

可以很明顯的看到,當我們執行 systemctl 的時候,會顯示出我們的 init system (PID 1) 並非 systemd,而是微軟提供的 /init

那我們如果想要擁有一個 systemd 環境的話,該怎麼辦呢?

由於 systemd 必須以 PID 1 的方式執行,所以直接執行 systemd 是沒有用的,但多虧了 Linux Namespace 我們可以在 WSL2 中建立新的 Namespace 並把 systemd 作為 PID 1 來執行,也就是在 WSL2 中再多加一層 PID Namespace,使得我們可以建築一個 systemd 的環境並跳進這個新的 Namespace 中。

所幸,我們不需要自己來研究這部分該如何操作,GitHub 上已經有幾個專案可以直接拿來參考並使用:

我們下面會使用 DamionGans/ubuntu-wsl2-systemd-script 來當作範例,只要按照說明操作就可以了:

$ git clone https://github.com/DamionGans/ubuntu-wsl2-systemd-script.git
Cloning into 'ubuntu-wsl2-systemd-script'...
remote: Enumerating objects: 76, done.
remote: Counting objects: 100% (76/76), done.
remote: Compressing objects: 100% (55/55), done.
remote: Total 76 (delta 40), reused 41 (delta 21), pack-reused 0
Unpacking objects: 100% (76/76), 19.46 KiB | 996.00 KiB/s, done.
$ cd ubuntu-wsl2-systemd-script/
ubuntu-wsl2-systemd-script $ bash ubuntu-wsl2-systemd-script.sh
[sudo] password for davy:
Hit:1 http://security.ubuntu.com/ubuntu focal-security InRelease
Hit:2 http://archive.ubuntu.com/ubuntu focal InRelease
Hit:3 http://archive.ubuntu.com/ubuntu focal-updates InRelease
Hit:4 http://archive.ubuntu.com/ubuntu focal-backports InRelease
Reading package lists... Done
(Reading database ... 31836 files and directories currently installed.)
Preparing to unpack .../0-dbus-user-session_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking dbus-user-session (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Preparing to unpack .../1-dbus-x11_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking dbus-x11 (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Preparing to unpack .../2-dbus_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking dbus (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Preparing to unpack .../3-libdbus-1-3_1.12.16-2ubuntu2.1_amd64.deb ...
Unpacking libdbus-1-3:amd64 (1.12.16-2ubuntu2.1) over (1.12.16-2ubuntu2) ...
Selecting previously unselected package daemonize.
Preparing to unpack .../4-daemonize_1.7.8-1_amd64.deb ...
Unpacking daemonize (1.7.8-1) ...
Selecting previously unselected package fontconfig.
Preparing to unpack .../5-fontconfig_2.13.1-2ubuntu3_amd64.deb ...
Unpacking fontconfig (2.13.1-2ubuntu3) ...
Setting up fontconfig (2.13.1-2ubuntu3) ...
Regenerating fonts cache... done.
Setting up libdbus-1-3:amd64 (1.12.16-2ubuntu2.1) ...
Setting up dbus (1.12.16-2ubuntu2.1) ...
Setting up daemonize (1.7.8-1) ...
Setting up dbus-x11 (1.12.16-2ubuntu2.1) ...
Setting up dbus-user-session (1.12.16-2ubuntu2.1) ...
Processing triggers for systemd (245.4-4ubuntu3) ...
Processing triggers for man-db (2.9.1-1) ...
Processing triggers for libc-bin (2.31-0ubuntu9) ...
rm: cannot remove '/lib/systemd/system/sysinit.target.wants/proc-sys-fs-binfmt_misc.mount': No such file or directory
'\\wsl$\Ubuntu-20.04\home\davy\ubuntu-wsl2-systemd-script'
是目前用來啟動 CMD.EXE 的目錄路徑。不支援 UNC 路徑。
預設目錄是 Windows 目錄。

成功: 已經儲存指定的值。
'\\wsl$\Ubuntu-20.04\home\davy\ubuntu-wsl2-systemd-script'
是目前用來啟動 CMD.EXE 的目錄路徑。不支援 UNC 路徑。
預設目錄是 Windows 目錄。

成功: 已經儲存指定的值。
ubuntu-wsl2-systemd-script $

安裝完畢後我們需要重啓整個 WSL2,假定我們的 WSL 名稱是 ubuntu-20.04,在命令提示字元(CMD)中執行下列指令以關閉 WSL2:

> wsl.exe -t ubuntu-20.04

接者使用一般使用者啓動 WSL2,就可以發現有不一樣的地方了(多了一個啓動 systemd 的提示),透過檢查 PID 1 也可以發現整個環境已經是由 systemd 掌握了:

Sleeping for 1 second to let systemd settle
Welcome to Ubuntu 20.04 LTS (GNU/Linux 4.19.104-microsoft-standard x86_64)

$ ps u -q 1
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  2.8  0.0 175224 12920 ?        Ss   21:48   0:06 /lib/systemd/systemd --system-unit=basic.target
$

移除用不到的 snap(可選)

由於 Ubuntu 安裝 systemd 時,會連 snap 也一併啓用,如果大家用不到的話可以把 snap 從系統中移除,這麼一來也可以提升啓動速度,我們可以看到系統啓動時也連著一些 snap 的元件一起啓動了(而且還不少 Processes):

$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  1.3  0.0 108788 11360 ?        Ss   22:03   0:00 /lib/systemd/systemd --system-unit=basic.targ
root          43  2.1  0.1  45248 14600 ?        S<s  22:03   0:00 /lib/systemd/systemd-journald
root          61  0.4  0.0  21592  8496 ?        Ss   22:03   0:00 /lib/systemd/systemd-udevd
systemd+      63  1.2  0.0  18548  7896 ?        Ss   22:03   0:00 /lib/systemd/systemd-networkd
root         152  0.0  0.0  10572  4588 pts/0    S    22:03   0:00 /bin/login -p -f      'HOSTTYPE=x86_64' 'PWD=
root         219  1.2  0.0   3608  1792 ?        Ss   22:03   0:00 snapfuse /var/lib/snapd/snaps/core18_1705.sna
root         220  0.3  0.0   3660  1476 ?        Ss   22:03   0:00 snapfuse /var/lib/snapd/snaps/lxd_14804.snap
root         221  0.1  0.0   3488  1536 ?        Ss   22:03   0:00 snapfuse /var/lib/snapd/snaps/snapd_7264.snap
systemd+     228  1.1  0.0  24116 12592 ?        Ss   22:03   0:00 /lib/systemd/systemd-resolved
root         231  0.1  0.0 241020  9280 ?        Ssl  22:03   0:00 /usr/lib/accountsservice/accounts-daemon
message+     232  0.2  0.0   7428  4640 ?        Ss   22:03   0:00 /usr/bin/dbus-daemon --system --address=syste
root         235  0.3  0.1  29216 17788 ?        Ss   22:03   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher
syslog       236  0.1  0.0 224328  4296 ?        Ssl  22:03   0:00 /usr/sbin/rsyslogd -n -iNONE
root         238  2.1  0.2 1457156 35432 ?       Ssl  22:03   0:00 /usr/lib/snapd/snapd
root         240  1.1  0.0  16852  7728 ?        Ss   22:03   0:00 /lib/systemd/systemd-logind
root         261  0.0  0.0 236408  9084 ?        Ssl  22:03   0:00 /usr/lib/policykit-1/polkitd --no-debug
root         302  0.0  0.0   8540  2764 ?        Ss   22:03   0:00 /usr/sbin/cron -f
root         307  0.4  0.1 108036 20512 ?        Ssl  22:03   0:00 /usr/bin/python3 /usr/share/unattended-upgrad
daemon       308  0.0  0.0   3796  2200 ?        Ss   22:03   0:00 /usr/sbin/atd -f
root         318  0.0  0.0   7356  2172 tty1     Ss+  22:03   0:00 /sbin/agetty -o -p -- \u --noclear --keep-bau
root         326  0.0  0.0   5832  1744 ?        Ss   22:03   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
davy         376  0.2  0.0  18444  9612 ?        Ss   22:03   0:00 /lib/systemd/systemd --user
davy         377  0.0  0.0 110132  3188 ?        S    22:03   0:00 (sd-pam)
davy         387  0.2  0.0  10048  4992 pts/0    S    22:03   0:00 -bash
davy         431  0.0  0.0  10604  3224 pts/0    R+   22:04   0:00 ps aux
$

首先我們先將所有 snap 都列出後,一個一個移除:

$ snap list
Name    Version   Rev    Tracking         Publisher   Notes
core18  20200311  1705   latest/stable    canonical✓  base
lxd     4.0.1     14804  latest/stable/…  canonical✓  -
snapd   2.44.3    7264   latest/stable    canonical✓  snapd
# snap remove lxd
# snap remove core18
# snap remove snapd
$ snap list
No snaps are installed yet. Try 'snap install hello-world'.
# apt purge snapd
$ ps aux
USER         PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root           1  0.6  0.0 109692 12692 ?        Ss   22:03   0:03 /lib/systemd/systemd --system-unit=basic.targ
root          43  0.1  0.1  53444 15456 ?        S<s  22:03   0:00 /lib/systemd/systemd-journald
root          61  0.0  0.0  21592  8496 ?        Ss   22:03   0:00 /lib/systemd/systemd-udevd
systemd+      63  0.1  0.0  18548  7896 ?        Ss   22:03   0:00 /lib/systemd/systemd-networkd
root         152  0.0  0.0  10572  4588 pts/0    S    22:03   0:00 /bin/login -p -f      'HOSTTYPE=x86_64' 'PWD=
systemd+     228  0.0  0.0  24116 12592 ?        Ss   22:03   0:00 /lib/systemd/systemd-resolved
root         231  0.0  0.0 241020  9280 ?        Ssl  22:03   0:00 /usr/lib/accountsservice/accounts-daemon
message+     232  0.0  0.0   7428  4640 ?        Ss   22:03   0:00 /usr/bin/dbus-daemon --system --address=syste
root         235  0.0  0.1  29216 17788 ?        Ss   22:03   0:00 /usr/bin/python3 /usr/bin/networkd-dispatcher
syslog       236  0.0  0.0 224328  4296 ?        Ssl  22:03   0:00 /usr/sbin/rsyslogd -n -iNONE
root         240  0.1  0.0  16852  7728 ?        Ss   22:03   0:00 /lib/systemd/systemd-logind
root         261  0.0  0.0 236408  9084 ?        Ssl  22:03   0:00 /usr/lib/policykit-1/polkitd --no-debug
root         302  0.0  0.0   8540  2764 ?        Ss   22:03   0:00 /usr/sbin/cron -f
root         307  0.0  0.1 108036 20512 ?        Ssl  22:03   0:00 /usr/bin/python3 /usr/share/unattended-upgrad
daemon       308  0.0  0.0   3796  2200 ?        Ss   22:03   0:00 /usr/sbin/atd -f
root         318  0.0  0.0   7356  2172 tty1     Ss+  22:03   0:00 /sbin/agetty -o -p -- \u --noclear --keep-bau
root         326  0.0  0.0   5832  1744 ?        Ss   22:03   0:00 /sbin/agetty -o -p -- \u --noclear tty1 linux
davy         376  0.0  0.0  18444  9708 ?        Ss   22:03   0:00 /lib/systemd/systemd --user
davy         377  0.0  0.0 110132  3188 ?        S    22:03   0:00 (sd-pam)
davy         387  0.0  0.0  10180  5252 pts/0    S    22:03   0:00 -bash
root        1168  0.0  0.1 283780 15888 ?        Ssl  22:07   0:00 /usr/lib/packagekit/packagekitd
davy        1273  0.0  0.0  10604  3316 pts/0    R+   22:11   0:00 ps aux

結束後我們就可以發現 Processes 數量減少了許多,讓我們的環境又稍微輕量了些。

在 Windows 準備 X Window Server

這邊有很多選擇,我選擇了 X410[4](付費),大家也可以選擇 VcXsrv[5] 之類的解決方案,這邊就不多贅述了,在這裡的範例中會需要將 X Window Server 開在 Windows 的 6000 port 上。

安裝 Ubuntu 桌面

接著我們就可以來安裝 Ubuntu 的預設桌面了,這裡會需要比較多的硬碟空間:

# apt install ubuntu-desktop
...
10 upgraded, 1077 newly installed, 0 to remove and 64 not upgraded.
Need to get 605 MB of archives.
After this operation, 2260 MB of additional disk space will be used.
Do you want to continue? [Y/n] y
...

安裝結束後,我們需要先取得 Windows 的 IP 位置[6],並嘗試對 X Window Server 連線看看:

$ cat /etc/resolv.conf
# This file was automatically generated by WSL. To stop automatic generation of this file, add the following entry to /etc/wsl.conf:
# [network]
# generateResolvConf = false
nameserver 172.17.160.1
$ nc -v 172.17.160.1 6000
Connection to 172.17.160.1 6000 port [tcp/x11] succeeded!
^C
$

成功連線後,我們可以撰寫一個啓動腳本,將 DISPLAY 指向 Windows 上的 X Window Server 中並且加上一些 Ubuntu 桌面的設定後執行 Gnome Shell:

$ cat - > gnome.sh <<'EOF'
#!/bin/bash
export DISPLAY=$(cat /etc/resolv.conf | grep nameserver | awk '{print $2}'):0.0;
export XDG_SESSION_TYPE="x11"
export XDG_RUNTIME_DIR=~/.cache/xdg
export XDG_SESSION_CLASS="user"
export XDG_SESSION_DESKTOP=ubuntu
export XDG_CURRENT_DESKTOP=ubuntu:GNOME
export DESKTOP_SESSION=ubuntu
export GDMSESSION=ubuntu
export GNOME_SHELL_SESSION_MODE=ubuntu

gnome-session "$@"
EOF
$ chmod +x gnome.sh
$ ./gnome.sh

Ubuntu Desktop (Gnome Shell) in WSL2

當然,如果想要在這個桌面中執行 Windows CMD 也是沒有問題的,而系統也可以正確的偵測到 Virtualization 是 WSL 呢。

Run CMD in Desktop

關閉 systemd-resolved

這裡是一個可選的項目,由於筆者在透過 /etc/resolv.conf 取得 Windows IP 時有遇到 resolv.conf 被 systemd-resolved 替換成 127.0.0.53 的問題,這邊提供一個方式將這個東西停用,操作完重啓 WSL2 即可:

# systemctl stop systemd-resolved
# systemctl disable systemd-resolved
Removed /etc/systemd/system/multi-user.target.wants/systemd-resolved.service.
Removed /etc/systemd/system/dbus-org.freedesktop.resolve1.service.

後記

由於目前的 WSL2 還沒有 GPGPU 加速,所以拿來執行 GUI 程式的話可能還是會有點 lag,微軟提出的最終方案是透過 RDP 來執行 GUI 應用程式也許會透過 RemoteApp 來將顯卡加速做在 Windows 端,但作為嚐鮮一下,目前的結果已經算是還可以的了XD


  1. 微軟在 BUILD 2020 上宣佈,將會讓 WSL2 可以執行 GUI 應用程式,並使用 Wayland 配合 RDP Protocol 來實作這個功能,詳見微軟部落格〈The Windows Subsystem for Linux BUILD 2020 Summary〉一文 ↩︎

  2. 關於 Gnome Shell 的這個變化,可以參考 Gnome Blog 的〈GNOME 3.34 is now managed using systemd〉一文。 ↩︎

  3. 對於 WSL2 的詳細架構,可以參考 BUILD 2019 的〈The new Windows subsystem for Linux architecture: a deep dive - BRK3068〉議程。 ↩︎

  4. https://x410.dev/ ↩︎

  5. https://sourceforge.net/projects/vcxsrv/ ↩︎

  6. https://docs.microsoft.com/zh-tw/windows/wsl/compare-versions#accessing-network-applications ↩︎

如何自己編譯 PPA 上的 source

由於最近在玩 FreeIPA[1] 的關係,就一併研究要怎麼整合到 Homelab 裡,其中一個支援的功能就是讓 Samba[2] 加入 FreeIPA 中[3],雖然官方宣稱這只有在 RHEL 8.1 上才提供的實驗性功能,但該版本使用的 FreeIPA 版本與 Ubuntu 19.10 上提供的 FreeIPA 一樣新,所以我想應該也可以吧。

結果,代誌不是憨人想的這麼簡單[4],FreeIPA 整合的 Kerberos 是 MIT 實作,但 Ubuntu 上的 Samba 整合的卻是 Heimdal 實作[5],這導致 Samba 看不懂 MIT Kerberos 的 ticket 進而無法進行後續的驗證步驟。

不過既然 RHEL 有支援 MIT Kerberos 的 Samba,那麼我也來安裝支援 MIT krb 的 Samba 就好啦,不過翻找了一下網路資源之後發現竟然沒有現成的 ppa[6] 可以用,這下好了,只好自己編譯了,於是我們就來談談,如何從 ppa 中下載 source 並編譯出 deb 包吧。

事前準備

在正式編譯 ppa 上的 source 之前,我們需要做一些準備,所謂「工欲善其事,必先利其器」,我們需要一些工具來下載原始碼以及打包工具:

將 ppa 的 source 加入 APT

我們可以透過把 ppa 加入 APT[7] 的 sources.list 中,來讓 APT 可以協助我們取得原始碼,以 samba 為例,我們先找出目標套件的 APT Source

$ apt show samba
Package: samba
Version: 2:4.10.7+dfsg-0ubuntu2.4
Priority: optional
Section: net
Origin: Ubuntu
Maintainer: Ubuntu Developers <[email protected]>
Original-Maintainer: Debian Samba Maintainers <[email protected]>
Bugs: https://bugs.launchpad.net/ubuntu/+filebug
Installed-Size: 16.4 MB
Pre-Depends: dpkg (>= 1.15.6~)
Depends: ...
Recommends: ...
Enhances: bind9, ntp
Homepage: http://www.samba.org
Task: samba-server, ubuntu-budgie-desktop
Supported: 9m
Download-Size: 976 kB
APT-Sources: http://archive.ubuntu.com/ubuntu eoan-updates/main amd64 Packages
Description: SMB/CIFS file, print, and login server for Unix

N: There is 1 additional record. Please use the '-a' switch to see it
$

我們可以看到 samba 是來自於 http://archive.ubuntu.com/ubuntu eoan-updates/main[8],接下來我們就可以把 source 加到 APT 的 source list 中,這裡有幾個方式可以進行:

透過 add-apt-repository

相對簡單的方式就是透過 add-apt-repository 來協助添加 source list,這個工具有三種用法,下面在範例中介紹:

## 使用完整 deb source 語句
# add-apt-repository -s "deb http://myserver/path/to/repo stable myrepo"
## 加入 ppa
# add-apt-repository -s ppa:user/repository
## 指定 distro component
# add-apt-repository -s multiverse

其中,-s 表示要一併加入 source repo,如此一來就可以透過 apt 下載 source。

若需要安裝 add-apt-repository 的話可以透過 apt install software-properties-common 取得。

但這裡我選擇手動添加,因為 samba 是位於預設套件庫的套件,Ubuntu 會在 APT source list 中加入預設套件庫的位置,並把 source 註解起來,也就是說,我們只要手動打開就可以了。

手動添加 source list

編輯 /etc/apt/source.list,找到剛剛的 eoan-updates main,將 deb-src 取消註解:

...

## Major bug fix updates produced after the final release of the
## distribution.
deb http://archive.ubuntu.com/ubuntu/ eoan-updates main restricted
deb-src http://archive.ubuntu.com/ubuntu/ eoan-updates main restricted

...

完成之後記得使用 apt update 來更新 cache。

Debian package development tools

這個 dpkg suite 可以在稍後協助我們建立 deb 包,安裝方式很簡單,透過 apt 取得即可:

# apt install dpkg-dev

安裝建置用依賴套件

接下來我們還需要在編譯建置套件之前準備好依賴套件,每個套件需要的依賴項都有所不同,我們一樣可以透過 apt 來快速取得建置依賴套件:

# apt build-dep samba

如此一來,所有的前置準備就都做足了,接下來我們來看看要怎麼建立完整的 deb 包吧。

下載 source

下載 source 的部分在前置作業結束之後就變得簡單許多了,只需要靠 APT 就能輕鬆辦到:

$ apt source samba
Reading package lists... Done
NOTICE: 'samba' packaging is maintained in the 'Git' version control system at:
https://salsa.debian.org/samba-team/samba.git
Please use:
git clone https://salsa.debian.org/samba-team/samba.git
to retrieve the latest (possibly unreleased) updates to the package.
Need to get 11.9 MB of source archives.
Get:1 http://archive.ubuntu.com/ubuntu eoan-updates/main samba 2:4.10.7+dfsg-0ubuntu2.4 (dsc) [4215 B]
Get:2 http://archive.ubuntu.com/ubuntu eoan-updates/main samba 2:4.10.7+dfsg-0ubuntu2.4 (tar) [11.6 MB]
Get:3 http://archive.ubuntu.com/ubuntu eoan-updates/main samba 2:4.10.7+dfsg-0ubuntu2.4 (diff) [265 kB]
Fetched 11.9 MB in 4s (3206 kB/s)
dpkg-source: info: extracting samba in samba-4.10.7+dfsg
dpkg-source: info: unpacking samba_4.10.7+dfsg.orig.tar.xz
dpkg-source: info: unpacking samba_4.10.7+dfsg-0ubuntu2.4.debian.tar.xz
dpkg-source: info: using patch list from debian/patches/series
dpkg-source: info: applying 07_private_lib
dpkg-source: info: applying bug_221618_precise-64bit-prototype.patch
...
dpkg-source: info: applying CVE-2019-19344.patch
$

apt source 除了會將原始碼下載回來之外,還會一併將指定的 patch 套用至原始碼中,結束後應該就會得到一個以該套件名與版本號命名的資料夾,這裡是 samba-4.10.7+dfsg,同時還會留下原始碼包以及 deb 包資訊檔(.dsc)。

這裡如果是用 root 身份或 sudo 進行操作的話,APT 會警告無法使用 _apt 使用者進行操作,原因是在下載原始碼包之後,APT 會嘗試切換到 _apt 這個使用者來解開原始碼及套用 patch,為了避免這個操作有負作用所以使用獨立的使用者來進行。

建立 deb 包

在有了原始碼之後,我們可以看到目錄大概是這樣的結構

samba-4.10.7+dfsg/
 |-- debian/
 |    |-- control
 |    |-- rules
 |    | ...
 | ...

其中 debian/ 目錄的功能就是存放與 deb 相關的內容,其中 control 檔表示的就是與 deb 包相關的資訊,包含依賴項目、套件說明等等;而 rules 則是一 Makefile 格式的檔案,裡面描述了打包時詳細需要進行什麼動作,例如編譯方式、需要打包哪些檔案等等。

至於要如何才能建置 deb 包呢? 還記得剛剛準備的 Debian package development tools 嗎,這就是為了這個時候而存在的,步驟也很簡單:

$ cd samba-4.10.7+dfsg/
$ dpkg-buildpackage
...
$ cd ..
$ ls *.deb
ctdb_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libnss-winbind_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libpam-winbind_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libparse-pidl-perl_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libsmbclient-dev_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libsmbclient_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libwbclient-dev_4.10.7+dfsg-0ubuntu2.4_amd64.deb
libwbclient0_4.10.7+dfsg-0ubuntu2.4_amd64.deb
python3-samba_4.10.7+dfsg-0ubuntu2.4_amd64.deb
registry-tools_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba-common-bin_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba-common_4.10.7+dfsg-0ubuntu2.4_all.deb
samba-dev_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba-dsdb-modules_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba-libs_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba-testsuite_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba-vfs-modules_4.10.7+dfsg-0ubuntu2.4_amd64.deb
samba_4.10.7+dfsg-0ubuntu2.4_amd64.deb
smbclient_4.10.7+dfsg-0ubuntu2.4_amd64.deb
winbind_4.10.7+dfsg-0ubuntu2.4_amd64.deb
$

如果沒有發生錯誤的話就可以在上層目錄看到打包好的 deb 檔們了,如此一來我們就完成了這次的目標 —— 從 PPA 上的 source 打包成 deb 了。

dh_missing --fail-missing 錯誤

其實筆者在嘗試的時候有遇到這個錯誤,dh_missing 會檢查 fakeroot[9] 中的檔案是否都已經被包入 deb 中,如果有遺漏的檔案則會拋出錯誤或提醒,如果是不打算包入 deb 的檔案可以在 debian/not-installed 中列出,或是可以透過修改 debian/rules 來改變檢查的方式:

override_dh_missing:
	# dh_missing --fail-missing
	dh_missing --list-missing

不過這個方式表示會跳過檢查,不建議在正式環境中使用,但對於在測試時查看有哪些檔案被遺漏了是很有用的。

安裝 deb 包

安裝其實也是很簡單,使用 dpkg 就可以安裝了:

# dpkg -i *.deb

結語

以上就是從 PPA 中編譯 source 的方式,應該還算是簡單。
之後會再另外撰寫有關於如何透過修改建置 deb 包相關設定來達到我們想要自行編譯支援 MIT Kerberos 版本的 samba,以及如何將這個修改結果上傳到 Launchpad 中讓其他人也可以使用我們修改好的 ppa。
還有或許 FreeIPA 玩出心得之後也許也會再發幾篇文章跟大家分享看看。


  1. 由 Red Hat 支援的一個整合性資訊系統,包含 389 Directory、MIT Kerberos、NTP server、BIND9、Dogtag 等,並提供網頁及 CLI 管理工具。 詳見官網說明↩︎

  2. 為一個開源的 SMB 實作,同時支援了 CIFS/Active Directory 等功能。 ↩︎

  3. 此處指的是 Red Hat 在 RHEL 8.1 中加入的實驗性功能,可以讓 IdM member 以 Samba 提供服務並整合帳號資料到 IdM 中,詳見官方文件↩︎

  4. 應為「代誌毋是憨人想的遐爾簡單」,為一臺灣閩南語經典臺詞,意為事件的發展並不是單純如表面上進行而有了重大的轉折,出自〈鐵獅玉玲瓏〉。 ↩︎

  5. 目前兩大主流 Kerberos 實作為 Heimdal 及 MIT,兩者在支援度及實作細節上互有不同,導致兩者並不能混用。Debian 及 SUSE 系的發行版採用了較為老牌的 Heimdal,而 RH 系的發行版則是投入 MIT 陣營,並為此將與 Heimdal 高度耦合的 Samba 改寫為支援 MIT 的版本。 ↩︎

  6. Ubuntu 的套件管理中,支援個人自行發行套件(Personal Package Archive),其大宗 hosting service 便是 Launchpad↩︎

  7. Advanced Packaging Tools,為 Debian 及其衍生發行版本的軟體包管理器。 ↩︎

  8. 眼尖的讀者可能會發現 apt 其實還有找到其他的記錄,這句 "N: There is 1 additional record. Please use the '-a' switch to see it" 表示同名的套件中還有其他的版本可以列出,只需要使用 apt show -a samba 即可列出所有 samba 的版本及其資訊。 ↩︎

  9. 由於我們並沒有要將編譯好的套件直接安裝到系統中,而是將其安裝到一個假的系統根目錄中,方便我們在打包時可以先以該套件原始的安裝方式釐清檔案們會被安裝到何處,並且只要在打包時維持這些檔案的路徑及權限即可讓 deb 重現這個安裝過程。 ↩︎

修改 PAM 讓特定 IP 透過 SSH 登入時不需要輸入密碼

我有兩臺電腦,一臺是外出用的 MacBook Air (13" later)、另一臺是自己組裝的 Windows 桌機,但平時工作都是在 Windows 上面開 Linux(當然是我最愛的 Ubuntu)的 VM 再從兩臺電腦 SSH 進去 VM 裡面。但從 Windows SSH 進去 VM 的時候就很想跳過輸入密碼的步驟,當然用 SSH Key 也可以辦到,但我覺得他們根本就是同一個電腦啊,還要加上 SSH Key 也太累了吧!要如何僅依靠來源 IP 就決定要不要輸入密碼呢?

!!警告!! 跳過密碼就可以登入是非常危險的,看看蘋果今天才緊急更新的免密碼登入 root 漏洞,即使是本機也是非常危險的呢!
https://support.apple.com/en-us/HT208315

PAM (Pluggable Authentication Modules)

首先,想跟各位介紹一個叫做 PAM (Pluggable Authentication Modules) 的模組,PAM 提供了一套驗證的 API 讓應用程式可以透過 PAM 做使用者驗證。

我們即是要對 PAM 來下手,而 PAM 的設定檔們都存放在 /etc/pam.d/ 下,sshd 會有自己專屬的設定檔可以使用就叫做 /etc/pam.d/sshd

我們把設定檔 cat 出來看一下會看到類似以下的內容:

# PAM configuration for the Secure Shell service

# Standard Un*x authentication.
@include common-auth

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so

# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so

# Standard Un*x authorization.
@include common-account

# SELinux needs to be the first session rule.  This ensures that any
# lingering context has been cleared.  Without this it is possible that a
# module could execute code in the wrong domain.
session [success=ok ignore=ignore module_unknown=ignore default=bad]        pam_selinux.so close

# Set the loginuid process attribute.
session    required     pam_loginuid.so

# Create a new session keyring.
session    optional     pam_keyinit.so force revoke

# Standard Un*x session setup and teardown.
@include common-session

# Print the message of the day upon successful login.
# This includes a dynamically generated part from /run/motd.dynamic
# and a static (admin-editable) part from /etc/motd.
session    optional     pam_motd.so  motd=/run/motd.dynamic
session    optional     pam_motd.so noupdate

# ......

# Standard Un*x password updating.
@include common-password

我們可以很清楚的看到,每一條項目都有以下格式:

[驗證類別] [控制標記] [檔名] [參數]

驗證類別 (Type)

驗證類別分成四種:account(帳號)、auth(驗證)、session(執行期間)、password(更新驗證資訊)。

舉例來說,我們可以清楚的看到 account 中有一個用來阻擋 nologin 的模組:

# Disallow non-root logins when /etc/nologin exists.
account    required     pam_nologin.so

表示這個模組會在使用者登入時,輸入帳號後,檢查是否為 nologin,來決定這個帳號是否合法。

控制標記 (Control Flag)

常見的控制標記有 4 種:

  • required
    若驗證成功則帶有 success 的標誌;
    若驗證失敗則帶有 failure 的標誌。
    但不論成功或失敗都會繼續後續的驗證流程。

  • requisite
    若驗證失敗則立刻回報 failure,並終止後續的驗證流程;
    若驗證成功則帶有 success 的標誌並繼續後續的驗證流程。
    required 最大的差異就在於失敗時就會終止驗證流程。

  • sufficient
    若驗證成功則立刻回報 success,並終止後續的驗證流程;
    若驗證失敗則帶有 failure 標誌並繼續後續的驗證流程。
    requisits 剛好相反。

  • optional
    不論成功或失敗都不回報且不影響後續的流程。

但這些都只是常見的預設標記,PAM 還提供了自己撰寫驗證流程規則的標記,可以自己寫要不要回報,以及要不要中斷流程。可以看看 pam.d(5) 的 man page。

觀察可用的 PAM 模組

我們可以注意到預設的 sshd 裡面有一個令人有興趣的設定是:

# Uncomment and edit /etc/security/access.conf if you need to set complex
# access limits that are hard to express in sshd_config.
# account  required     pam_access.so

如果我把註解拿掉的話,他就會在帳號檢查階段(account),去檢查這個帳號是不是可以被存取,這個不是跟我們想要的東西很像嗎?
只是我們要檢查的是「這個密碼需不需要被輸入」而已,很像嘛! (迷之音:真的很像嗎?)

實做

PAM 設定

以下是我使用的設定,插在 common-auth 之前,來保持我的語句比其他人早驗證:

# PAM configuration for the Secure Shell service

auth [success=done default=ignore] pam_access.so accessfile=/etc/security/sshd-access-local.conf # <<<<<<<
# Standard Un*x authentication.
@include common-auth

# ...

我在這裡使用了 auth,讓我需要被驗證的時候執行檢查;
而在控制標記的部分我則是自己撰寫了規則 [success=done default=ignore],表示在 success 的時候就直接回報 success 並結束驗證流程,但在非 success 的情況下就不回報也不結束流程(ignore)。
而使用的當然是剛剛觀察到的 pam_access.so 模組,他可以接受一個名為 accessfile 的參數,來提供他判斷的條件,這裡我先命名為 /etc/security/access-local.conf
其他參數大家可以參考 pam_access(8) 的 man page。

pam_access.so accessfile

接下來我們來看看這個 accessfile 裡面到底長怎樣,一樣大家可以參考 access.conf(5) 的 man page,先列出我自己的 config:

+ : davy : 192.168.3.0/24
+ : davy : 10.8.0.0/24
- : ALL : ALL

相對於 PAM 的設定,access.conf 的格式更加地簡單了:

permission : users/groups : origins

其中 permission 只有兩種值: +- 分別對應到 granteddenied,表示符合條件的狀況會不會通過測驗;
user/groups 則是指定套用規則的使用者或群組,可以用 ALL 表示全部使用者皆套用;
origins 指定的是來源,可以是 TTY 名稱、IP address、domain name 等,其中有兩個特別的值是 ALLLOCAL,分別表示全部適用以及套用到本機服務或本機 TTY。

設定檔前兩條規則,帳號都限定在我個人的帳號(davy);
而第一條是只有我才連得上的 VPN subnet、第二條是 VM 網路的 subnet。
最後還要加上一條 deny 全部其他的規則,到這裡我們已經大功告成了。

測試

只要我從符合上述條件的網路登入指定帳號,就可以不需要密碼就能夠登入了!

以下是從 Windows 用 pietty 登入的測試結果,可以看到我輸入完帳號之後就進入系統了:

最後我在這裡還是要在提醒大家一次:跳過密碼就可以登入是非常危險的,除非你的環境十分值得信賴,不然千萬不要忽略驗證!