# 使用 FreeRADIUS 搭配 Google 認證服務進行雙重驗證
###### tags: `MFA` `freeradius` `gauth`
協助同事處理 MFA 驗證問題,之前也沒玩過,順便紀錄一下過程。這種架構應該是蠻適合用 **Docker** 建立的,有機會再說囉!
[toc]
# 執行環境
- Ubuntu 18.04.6 LTS
- FreeRADIUS 3.0
- [google-authenticator](https://github.com/google/google-authenticator)
# 執行步驟
:::warning
- 執行步驟應可以使用腳本檔執行達成目標,這樣一來只要**做一次一勞永逸**。
- 使用容器完成應該會更好。
:::
## 修改主機名稱
使用 **`hostnamectl`** 可不用重啟系統調整主機名稱,但記得要**重新登入**。
- **`hostnamectl set-hostname authServer`**

- **`hostnamectl`**

## 確認 NTP 狀態
Ubuntu 預設使用 **systemd-timesycd** 作為 NTP 校時服務。
使用 **`timedatectl`** 確認系統時間相關資訊。
- **`timedatectl [status]`**

以上資訊可以得知:
- **`System clock synchronized: yes`** 該系統啟用系統網路校時服務。
:::info
- **開啟 NTP 功能**: `timedatectl set-ntp true`
- **關閉 NTP 功能**: `timedatectl set-ntp false`
:::
- **`systemd-timesyncd.service active: yes`** 該系統目前使用 **`systemd-timesyncd`** 服務進行網路校時作業。
:::info
- 組態檔 **`/etc/systemd/timesyncd.conf`**。
- 可在組態檔中指定校時 NTP 伺服器,若有多組以**空白**區隔。
```conf
[Time]
NTP=time1.google.com time2.google.com time3.google.com time4.google.com
FallbackNTP=ntp.ubuntu.com
```
- 使用 **`systemctl restart systemd-timesyncd`** 重啟服務。
:::

## 修改時區
同樣使用 **`timedatectl`** 列出可用時區及調整時區。
- **`timedatectl list-timezones`**

指定 **`Asia/Taipei`** 作為系統時區。
- **`timedatectl set-timezone Asia/Taipei`**
再次使用 **`timedatectl`** 確認系統時間相關設定。
- **`timedatectl [status]`**

:::danger
**注意**
**RTC 時間** 請使用 **UTC 時間**,而不要儲存**本地時間(Local Time)**。
:::
## 確認 DNS 解析
一般 Linux 作業系統關於 DNS 解析都可以檢視 **`/etc/resolv.conf`** 並修改其中 **`nameserver`** 參數。
不過,目前似乎各發行版本都改由 **`systemd-resolved`** 服務處理。該組態檔可以檢視
- **`/run/systemd/resolve/resolv.conf`**: 基本上可以**直接修改此組態檔,再重啟服務**。
- **`/run/systemd/resolve/stub-resolv.conf`**


當然也可以使用不同 Linux 發行平台所提供的工具。
- Ubuntu: **netplan**
- 組態檔: **`/etc/netplan/<network_config>.yaml`**。編輯網路組態後,使用 **`netplan apply`** 套用設定。

- 使用 **`systemd-resolve --status | grep -A2 'DNS Servers'`** 查詢設定。

:::info
[**Netplan configuration examples**](https://netplan.io/examples)
:::
- Fedora: **nmcli**
- 使用 **`nmcli connection modify <device> ipv4.dns "8.8.8.8 8.8.4.4"`**

- 使用 **`resolvectl status | grep 'DNS Servers'`** 查詢設定。

:::info
[**Configuring IP networking with nmcli**](https://docs.fedoraproject.org/en-US/quick-docs/configuring-ip-networking-with-nmcli/)
:::
使用 **`dig`** 確認名稱解析。

## 更新系統套件

## 安裝 FreeRADIUS 和 PAM 相關套件
```bash
$ sudo apt-get install build-essential libpam0g-dev freeradius git libqrencode3
```
## 安裝 Google Authenticator 套件
```bash
$ sudo apt-get install -y libpam-google-authenticator
```
:::info
**參考**
以上系統更新及套件安裝動作,可以使用腳本檔一鍵執行完成,可以免去重複打字或輸入錯誤的問題或使用 Ansible 自動佈署達成。
- **update_install.sh**
```bash!
#!/bin/bash
#
passport="${HOME}/.passport"
if [ -f ${passport} ]; then
pass=$(cat ${passport})
else
echo -e "<!> ${passport} NOT FOUND!<!>\n"
exit
fi
##
echo -e "\n[TASK] Update the System\n"
echo ${pass} | sudo -S apt update && sudo -S apt -y upgrade
echo -e "\n[TASK] Install FreeRadius & PAM packages\n"
echo -e " - build-essential
- libpam0g-dev
- freeradius
- freeradius-utils
- git
- libqrencode3"
echo
echo ${pass} | sudo -S apt install -y build-essential libpam0g-dev freeradius freeradius-utils git libqrencode3
echo -e "\n[TASK] Install Google Authenticator packages\n"
echo -e " - libpam-google-authenticator"
echo
echo ${pass} | sudo -S apt install -y libpam-google-authenticator
```
- 執行結果

:::
## 組態 FreeRADIUS 使用 Google Authenticator 進行 MFA
:::warning
**注意**
- freeRADIUS 有可能會因為**版本不同**造成**服務組態檔位置不同**,請根據實際安裝版本檢視並修正對應組態檔。
- 使用 **`freeradius -v`** 可檢視安裝版本。

- 下列以 **版本 3.0.16** 進行操作。
- 主要 freeRADIUS 組態檔所在目錄為 **`/etc/freeradius/3.0/`**。
:::
### 建立暫時禁用權限群組
為了不刪除既有使用者,建立一個暫時禁用權限的群組 **`radius-disabled`**。
```bash
sudo addgroup radius-disabled
```
### 編輯 `/etc/freeradius/3.0/radiusd.conf`
:::danger
**警告** 在編輯任一個服務組態檔時,務必先備份原始檔案後變動。
:::
:::info
將會變動以下列出組態檔內容:
- /etc/freeradius/3.0/radiusd.conf
- /etc/freeradius/3.0/users
- /etc/freeradius/3.0/sites-enabled/default
- /etc/freeradius/3.0/clients.conf
- /etc/pam.d/radiusd
:::
編輯組態檔 **`/etc/freeradius/3.0/radiusd.conf`**,進行以下變更。
:::success
**註解以下內容**
```
#user = freerad
#group = freeead
```
**增加以下內容**
```
user = root
group = root
```
:::


### 編輯 `/etc/freeradius/3.0/users`
請在 **# Deny access for a group of users.** 這個區塊加入以下內容。
:::danger
請**務必**按照以下內容的**格式對齊**,否則將會出現**無法正確啟用 FreeRADIUS 應用服務**。
:::
:::success
```
DEFAULT Group == "radius-disabled", Auth-Type := Reject
Reply-Message = "Your account has been disabled."
DEFAULT Auth-Type := PAM
```
:::

### 編輯 `/etc/freeradius/3.0/sites-enabled/default`
請在 **Pluggable Authentication Modules.** 這個區塊加入註解掉 **`pam`** 內容。

### 編輯 `/etc/pam.d/radiusd`
配置 PAM 以便透過**組合本機用戶密碼和 Google Authenticator 產生 PIN** 作為驗證方式。
:::success
**以下註解**
```
#@include common-auth
#@include common-account
#@include common-password
#@include common-session
```
**增加以下設定**
```
auth requisite pam_google_authenticator.so forward_pass
auth required pam_unix.so use_first_pass
```
:::

### 重新開機
完成以上設定,請將**系統重開**,並確認服務運作正常。
```bash
$ reboot
$ sudo systemctl status freeradius
```

## 設定 FreeRADIUS 用戶端
用戶端就是設定可以向 FreeRADIUS 要求進行用戶身份驗證的主機。若以防火牆整合驗證需求來看,防火牆 就可視為一個用戶端,而設定的 IP 位址就是要與 FreeRADIUS 溝通並進行驗證行為的。
### 編輯 `/etc/freeradius/3.0/clients.conf`
以下是用戶端設定範例。
:::success
```
client Gateway {
ipaddr = 10.2.2.1
secret = SECRET
}
client 10.2.2.1 {
secret = SECRET
shortname = Gateway
}
```
:::
我們也可以參考設定檔中 **`localhost`** 和 **`localhost_ipv6`** 的設定。

:::info
- **secret** 是 FreeRADIUS 與用戶端進行驗證的**密碼**。
- 若只是簡易測試,使用 **Localhost** 即可。
:::
## 設定 FreeRADIUS 使用者資料庫
根據以下流程建立使用者並進行 Goolge Authenticator 生成。
### 建立使用者
可以使用以下簡單的腳本檔建立測試使用者帳號及密碼。
```bash!
#!/bin/bash
passport="${HOME}/.passport"
pass=$(cat ${passport})
username="${1:-user01}"
password='VMware1!'
userid='1500'
if id -u ${username} &>/dev/null; then
echo -e "<!> User [${username}] is existed\n"
exit
fi
echo -e "\n[TASK] Create a user for Google Authenticator"
echo ${pass} | sudo -S useradd -s /bin/bash -d "/home/${username}" -u ${userid} -m ${username}
echo "${username}:${password}" | sudo chpasswd
echo -e "\n[RESULT] User Infomation:"
id ${username}
```

:::warning
使用 **`sudo userdel -r {user_name}`** 命令可以刪除使用者相關資訊。
:::
### 使用新建立使用者登入
使用新建立的使用者帳號及密碼登入系統進行測試。

### 創建 Google Authenticator 驗證碼
在該使用者家目錄下直接執行 **`google-authenticator`** 程式。
> 在終端機中產生的 QR-Code 尺寸過大,所以加入 **`-Q UTF8`** 可縮小一些,要不然就要手動調整終端機配置了!
由於我們使用的是 **Time-based One-Time Password(TOTP)**,所以第一個選項請回答 **`y`**。
```
Do you want authentication tokens to be time-based (y/n) y
```
接著系統便會產生相關資訊:
- **QR Code 網頁顯示 URL**: 可以將此 URL 寄給使用者,以完成 Google 兩步驟驗證功能設定。
- **QR Code**
- **密鑰(Secret Key)**
- **驗證碼(verification code)**
- **備用碼(emergency scratch code)**

:::info
稍微研究一下顯示 QR Code URL。程式產生的 URL 如下:
```
https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&chl=otpauth://totp/user02@authServer%3Fsecret%3DRAP7KZ4CPRCRDTU6WRIKSEB2W4%26issuer%3DauthServer
```
其中帶了一些 urlencode 碼 [[**參考**]](https://www.w3schools.com/tags/ref_urlencode.ASP),可以透過 **`urlencode -d`** 轉換成可識別字符。

所以 QR Code 顯示 URL 的結構可以解析成 (1) + (2)。其中 (1) 部份為固定, (2) 部份其中 **{variable}** 則為變數。
> (1) https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&
> (2) chl=otpauth://totp/**{USERNAME}**@**{HOSTNAME}**?secret=**{SECRET_KEY}**&issuer=**{HOSTNAME}**
所以此 QR Code 顯示的 URL,可以取得相關 **{變數}** 後**自行產生建立**。
有關 **Key URI** 詳細資訊可以參考 [[**連結**]](https://github.com/google/google-authenticator/wiki/Key-Uri-Format)。
:::
參考以下步驟繼續完成 Goolge Authenticator Token 建立程序。

完成上述設定程序,會將相關資訊儲存在該使用者家目錄中的 **`.google-authenticator`** 檔案。

### Google-Authenticator 探索
其實可以研究一下 **`google-authenticator`** 程式執行的參數,讓這樣的設定程序可以自動化一點。
根據上述的問答流程,大致可以知道需要的配置如下:
- 使用 TOTP 驗證
- 自行產出密鑰
- 生成備用碼 5 組並更新原有設定檔(.google_authenticator)
- 單一驗證碼不允許重複使用
- 驗證碼 30 秒刷新(預設)
- 時間同步窗口 (3, 預設)
- 限制每 30 秒內不超過 3 次驗證流量
- 不顯示 QR Code
```bash
$ google-authenticator --quiet --force \
--time-based \
--disallow-reuse \
--window-size=3 \
--rate-limit=3 --rate-time=30 \
--step-size=30 \ ## 可省略,會自帶
--emergency-codes=5 ## 可省略,會自帶
```
可簡寫為以下命令:
```bash
$ google-authenticator -qftd -w 3 -r 3 -R 30 -S 30 -e 5
```
透過上述命令可以查看**執行結果**,省去產生 QR Code 及問答流程,可直接產生組態檔。

至於 QR Code URL 透過以下簡單的腳本程式就可以取得!
- **`getCodeUrl.sh`**
```bash!
#!/bin/bash
#
username="${1}"
user_home=$(grep "${username}" /etc/passwd | cut -d: -f6)
mfa_config="${user_home}/.google_authenticator"
echo -e "\n[TASK] Check the configuration of Google Authenticator"
if [ ! -f "${mfa_config}" ]; then
echo -e " > Not found!! Please run \"google-authenticator\" first"
exit
fi
secretkey=$(head -n1 ${mfa_config})
hostname=$(hostname)
baseUrl='https://www.google.com/chart?chs=200x200&chld=M|0&cht=qr&'
keyUri="chl=otpauth://totp/${username}@${hostname}?secret=${secretkey}&issuer=${hostname}"
qrCodeUrl="${baseUrl}${keyUri}"
echo -e "\n[TASK] QR Code URL for \"${username}@${hostname}\""
echo -e "\n${qrCodeUrl}\n"
```

將上述產生的 QR Code URL 貼於瀏覽器確認狀態。

若要取得該使用者的驗證備用碼也很容易!
```bash!
$ cat .google_authenticator | sed -n '/^[0-9].*[0-9]$/p'
```

透過簡單的 **mailx | mutt** 程式,可以模擬將驗證碼資訊寄發給使用者的應用情境。信件內文的訊息就是透過以上動作整合完成的。

這樣一來,串連以上相關的步驟,應該就可以**自動化產生驗證碼並寄發使用者相關資訊**了!
:::info
若要透過 Linux 主機的郵件服務寄送至外部郵件伺服器,則須注意是否有被 Anti-SPAM 功能阻擋囉。
公司採用的是 Office365,可以透過 **反垃圾郵件 IP 取消清單網站** [[連結]](https://sender.office.com/) 將 IP 位址列入驗證要求清單。完成設定,可能需要等待**一段時間**再進行測試吧。

重新寄送驗證碼資訊到公司郵箱,模擬使用者收到驗證碼設定的郵件,看起來沒有問題!

:::
### 重啟 FreeRADIUS 服務
<font color=red>**注意: 請務必確認以下連結是否存在,否則二次驗證在 FreeRADIUS 中無法成功啟用。**</font>

完成所有配置程序,將 FreeRADIUS 服務重啟。
```bash
$ sudo systemctl restart freeradius
```
:::warning
若有需要進行手機 App 測試,請取得 QR Code URL 後,以手機 App 操作掃描完成相關設定即可。
:::
## 功能驗證
為了簡化驗證測試,就先使用 **`localhost`** 本機進行驗證程序。使用 **`radtest`** 命令,採用使用者 **`user02`**,驗證密碼為 **`{使用者密碼}{Google Authenticator 驗證碼}`**。使用以下命令進行測試。
```bash!
$ sudo radtest {username} {password} localhost 18120 testing123
```
**執行結果**

:::info
驗證碼也採用**備用碼**以簡化測試過程。
另外檢視使用者的組態檔 **`.google_authenticator`**,也可發現使用的備用碼也從該組態檔中刪去。

可查看 **`/var/log/auth.log`** 檔檢視驗證紀錄。

:::
:::success
- 以上大致就是使用 FreeRADIUS 搭配 Google Authenticator 作 MFA 的紀錄。
- 至於後續與其他服務或網路設備整合,大致上只要將驗證主機指向 FreeRADIUS 服務應該就可以達成。
:::
:::warning
**待解問題**
- 雖然可以按照步驟完成 freeRADIUS 與 Google 認證產生 QRCode。但是整個**執行流程**似乎沒有**前端**可以提供給終端用戶。但可以**透過腳本程式達成一點點自動化的目標**。
- 若客戶**已有 AD 的使用者資料庫**,似乎 freeRADIUS 還要另外**與 AD 整合使用者資料庫**,否則將使造成管理者管理帳戶的負擔。
- 不清楚 AD 是否可以直接與 Google 認證結合?還是可以改用 **Microsoft Azure**?
:::
---
# 參考
- [SSLVPN Two-factor Authentication with Google Authenticator](https://kb.hillstonenet.com/en/wp-content/uploads/2019/09/SSLVPN-Two-factor-Authentication-with-Google-Authenticator.pdf)
- [FreeRADIUS Google Dual Factor Authenticator](https://developer.aliyun.com/article/449710)
- [Setup FreeRADIUS on Kali Linux for 802.1X Authentication](https://www.youtube.com/watch?v=AwkIUw8mS_c)
- [Quickly Install FreeRadius on CentOS 7 and Do a Basic Configuration](https://youtu.be/CXG51efc5oc)