修改 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 登入的測試結果,可以看到我輸入完帳號之後就進入系統了:

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