Skip to content

Instantly share code, notes, and snippets.

@zoonderkins
Last active December 25, 2025 12:51
Show Gist options
  • Select an option

  • Save zoonderkins/14a044e32fd078de422bfb463241f4da to your computer and use it in GitHub Desktop.

Select an option

Save zoonderkins/14a044e32fd078de422bfb463241f4da to your computer and use it in GitHub Desktop.
cisco-duo-install on debian 13 to protect ssh

Duo SSH 2FA 安全登入整合

適用 Linux 伺服器:Debian / Ubuntu / Proxmox / Standard VPS
支援 密碼 + Duo / 公鑰 + Duo / Root Key + Duo,使用者密碼 + Duo


🚀 功能特色

功能 支援
自動安裝 duo-unix PAM 模組 ✔️
自動設定 /etc/pam.d/sshd ✔️
自動備份所有被修改檔案 ✔️
自動調整 /etc/ssh/sshd_config 登入模式 ✔️
多台伺服器共用一個 Duo Application ✔️
Root 與一般使用者登入方式分流 ✔️
OTP / Duo Push / 自動推送 (autopush) ✔️
密碼登入模式保留 PAM fallback ✔️

📦 安裝方式

chmod +x install-duo-interactive.sh
sudo ./install-duo-interactive.sh

執行後依照指示輸入:

Duo ikey: DIWEXXXXX
Duo skey: Po0oItWHuVXXXX
Duo api host: api-xxxx.duosecurity.com

🔐 登入模式選擇

安裝腳本提供 3 種模式:

模式編號 模式 使用情境
1 密碼 + Duo 多人 / 混合登入 / VPS
2 Key + Duo 全面禁止密碼登入
3 Root → Key + Duo / 其他 → 密碼 + Duo 管理員安全 + 使用者體驗

🔑 登入流程示意

1️⃣ 密碼 + Duo

ssh user@server
↓
Linux 密碼
↓
Duo Push / OTP
↓
登入成功

2️⃣ Key + Duo

ssh -i id_ed25519 user@server
↓
公鑰驗證
↓
Duo Push / OTP
↓
登入成功

3️⃣ Root Key + Duo / User 密碼 + Duo

root → 公鑰 + Duo
一般使用者 → 密碼 + Duo

🧩 關鍵設定檔

/etc/pam.d/sshd

auth    required     pam_nologin.so
@include common-auth
auth    required     /usr/lib64/security/pam_duo.so

account required     pam_nologin.so
@include common-account

session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
session required     pam_loginuid.so
session optional     pam_keyinit.so force revoke
@include common-session
session optional     pam_motd.so motd=/run/motd.dynamic
session optional     pam_motd.so noupdate
session optional     pam_mail.so standard noenv
session required     pam_limits.so
session required     pam_env.so
session required     pam_env.so user_readenv=1 envfile=/etc/default/locale
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open

@include common-password

SSH 設定重點

模式 1 — 密碼 + Duo

PasswordAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods keyboard-interactive
UsePAM yes

模式 2 — Key + Duo

PasswordAuthentication no
PubkeyAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes

模式 3 — Root Key + Duo、普通密碼 + Duo

PasswordAuthentication yes
PubkeyAuthentication yes
KbdInteractiveAuthentication yes
UsePAM yes
AuthenticationMethods keyboard-interactive

Match User root
    PasswordAuthentication no
    AuthenticationMethods publickey,keyboard-interactive

/etc/duo/pam_duo.conf

[duo]
ikey = DIWEXXXXX
skey = Po0oItWHuVXXXX
host = api-xxxx.duosecurity.com
failmode = safe
pushinfo = yes
autopush = yes

🔍 檢查 / 除錯

journalctl -xeu ssh -f
sshd -t

🛠️ 常見問題

問題 原因 解決
Permission denied (publickey) 認證鏈要求公鑰但沒提供 keyboard-interactive
Your account does not have access Duo 後台沒該 user 新增 user
pam_duo.so not found duo-unix 未安裝成功 apt install duo-unix
sudo: unable to resolve host hostname/hosts 不符 修正 /etc/hosts

修正 sudo:

echo "127.0.1.1 $(hostname)" | sudo tee -a /etc/hosts

📁 備份位置

/root/duo-backup-YYYYMMDD-HHMM/

🎯 推薦策略

root → 公鑰 + Duo
一般用戶 → 密碼 + Duo

📌 License

MIT — 可自由使用與調整

#!/usr/bin/env bash
set -euo pipefail
echo "=== Duo SSH (pam_duo) 互動式安裝腳本 ==="
########################################
# 0. 權限與必備工具檢查
########################################
if [[ "$EUID" -ne 0 ]]; then
if ! command -v sudo >/dev/null 2>&1; then
echo "❌ 需要 root 或 sudo,但系統沒有 sudo"
echo " 請先執行:apt update && apt install -y sudo"
exit 1
fi
SUDO="sudo"
else
SUDO=""
fi
REQUIRED_CMDS=(curl wget gpg)
MISSING=()
for cmd in "${REQUIRED_CMDS[@]}"; do
if ! command -v "$cmd" >/dev/null 2>&1; then
MISSING+=("$cmd")
fi
done
if ((${#MISSING[@]} > 0)); then
echo "⚠️ 缺少必要工具: ${MISSING[*]}"
echo "📦 自動安裝中 ..."
$SUDO apt-get update -y
$SUDO apt-get install -y "${MISSING[@]}"
else
echo "✔️ curl / wget / gpg 已安裝"
fi
echo "----------------------------------------"
########################################
# 1. 互動輸入資訊
########################################
read -rp "請輸入 Duo integration key (ikey): " IKEY
read -rp "請輸入 Duo secret key (skey): " SKEY
read -rp "請輸入 Duo API host (例如 api-xxxx.duosecurity.com): " HOST
read -rp "SSH Port (預設 22): " PORT
PORT="${PORT:-22}"
echo
echo "是否啟用 autopush?"
echo " y = 自動推送 Duo Push,不顯示 passcode 輸入框"
echo " n = 顯示選單,可輸入 Duo OTP / passcode(建議)"
read -rp "[y/N]: " AUTOPUSH_ANS
AUTOPUSH_VALUE=$( [[ "${AUTOPUSH_ANS:-n}" =~ ^[Yy]$ ]] && echo "yes" || echo "no" )
echo
echo "登入模式選擇:"
echo " 1) 所有使用者:密碼 + Duo (keyboard-interactive → PAM 密碼 + pam_duo)"
echo " 2) 所有使用者:Key + Duo (publickey + pam_duo,不問密碼)"
echo " 3) root:Key + Duo;其他:密碼 + Duo"
read -rp "請選擇 1/2/3(預設 1): " MODE
MODE="${MODE:-1}"
echo
echo "設定確認:"
echo " ikey = $IKEY"
echo " host = $HOST"
echo " SSH Port = $PORT"
echo " autopush = $AUTOPUSH_VALUE"
echo " 模式 = $MODE"
read -rp "確認開始安裝?(y/N): " CONFIRM
CONFIRM="${CONFIRM:-n}"
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
echo "🚫 已取消。"
exit 0
fi
########################################
# 2. 自動備份現有設定
########################################
BACKUP_DIR="/root/duo-backup-$(date +%F_%H-%M-%S)"
$SUDO mkdir -p "$BACKUP_DIR"
FILES_TO_BACKUP=(
/etc/duo/pam_duo.conf
/etc/pam.d/sshd
/etc/ssh/sshd_config
/etc/apt/sources.list.d/duosecurity.list
)
echo
echo "📁 備份檔案到:$BACKUP_DIR"
for f in "${FILES_TO_BACKUP[@]}"; do
if [[ -f "$f" ]]; then
$SUDO cp "$f" "$BACKUP_DIR"/
echo " ✔ 已備份 $f"
fi
done
########################################
# 3. 設定 Duo APT repository(無 apt-key)
########################################
echo
echo "🔑 設定 Duo APT repo..."
$SUDO mkdir -p /usr/share/keyrings
curl -fsSL https://duo.com/DUO-GPG-PUBLIC-KEY.asc \
| $SUDO gpg --dearmor -o /usr/share/keyrings/duo-archive-keyring.gpg
CODENAME=$($SUDO bash -lc 'lsb_release -cs 2>/dev/null || echo "stable"')
cat <<EOF | $SUDO tee /etc/apt/sources.list.d/duosecurity.list >/dev/null
deb [signed-by=/usr/share/keyrings/duo-archive-keyring.gpg] https://pkg.duosecurity.com/Debian ${CODENAME} main
EOF
echo " ✔ 已寫入 /etc/apt/sources.list.d/duosecurity.list"
########################################
# 4. 安裝 duo-unix
########################################
echo
echo "📦 安裝 duo-unix..."
$SUDO apt-get update -y
$SUDO apt-get install -y duo-unix
########################################
# 5. 找 pam_duo.so 路徑
########################################
echo
echo "🔍 搜尋 pam_duo.so 路徑..."
PAM_DUO=$($SUDO bash -lc 'find / -name pam_duo.so 2>/dev/null | head -n 1 || true')
if [[ -z "$PAM_DUO" ]]; then
echo "❌ 找不到 pam_duo.so,可能 duo-unix 安裝失敗。"
echo " 備份位置:$BACKUP_DIR"
exit 1
fi
echo " ✔ 使用 pam_duo.so: $PAM_DUO"
########################################
# 6. 寫入 /etc/duo/pam_duo.conf
########################################
echo
echo "📝 寫入 /etc/duo/pam_duo.conf..."
cat <<EOF | $SUDO tee /etc/duo/pam_duo.conf >/dev/null
[duo]
ikey = ${IKEY}
skey = ${SKEY}
host = ${HOST}
failmode = safe
autopush = ${AUTOPUSH_VALUE}
pushinfo = yes
EOF
$SUDO chown root:root /etc/duo/pam_duo.conf
$SUDO chmod 600 /etc/duo/pam_duo.conf
echo " ✔ pam_duo.conf 權限設定為 root:root 600"
########################################
# 7. 依模式產生 /etc/pam.d/sshd
########################################
echo
echo "🔐 產生 /etc/pam.d/sshd ..."
if [[ "$MODE" == "1" || "$MODE" == "3" ]]; then
# 模式 1 & 3:需要「密碼 + Duo」
cat <<EOF | $SUDO tee /etc/pam.d/sshd >/dev/null
# PAM configuration for the Secure Shell service
auth required pam_nologin.so
@include common-auth
auth required ${PAM_DUO}
account required pam_nologin.so
@include common-account
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
session required pam_loginuid.so
session optional pam_keyinit.so force revoke
@include common-session
session optional pam_motd.so motd=/run/motd.dynamic
session optional pam_motd.so noupdate
session optional pam_mail.so standard noenv
session required pam_limits.so
session required pam_env.so
session required pam_env.so user_readenv=1 envfile=/etc/default/locale
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
@include common-password
EOF
else
# 模式 2:只要 Duo,不要系統密碼(公鑰 + Duo)
cat <<EOF | $SUDO tee /etc/pam.d/sshd >/dev/null
# PAM configuration for the Secure Shell service
auth required pam_nologin.so
auth sufficient ${PAM_DUO}
auth required pam_deny.so
account required pam_nologin.so
@include common-account
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so close
session required pam_loginuid.so
session optional pam_keyinit.so force revoke
@include common-session
session optional pam_motd.so motd=/run/motd.dynamic
session optional pam_motd.so noupdate
session optional pam_mail.so standard noenv
session required pam_limits.so
session required pam_env.so
session required pam_env.so user_readenv=1 envfile=/etc/default/locale
session [success=ok ignore=ignore module_unknown=ignore default=bad] pam_selinux.so open
EOF
fi
echo " ✔ /etc/pam.d/sshd 已更新"
########################################
# 8. 依模式產生 /etc/ssh/sshd_config
########################################
echo
echo "⚙️ sshd_config 設定模式:"
echo " 1) 覆蓋為精簡配置(新主機 / 想重置設定時建議)"
echo " 2) 在現有 sshd_config 末尾追加 Duo 設定"
read -rp "請選擇 1/2(預設 1): " SSHD_MODE
SSHD_MODE="${SSHD_MODE:-1}"
if [[ "$MODE" == "1" ]]; then
# 所有人:密碼 + Duo (keyboard-interactive → PAM)
if [[ "$SSHD_MODE" == "1" ]]; then
cat <<EOF | $SUDO tee /etc/ssh/sshd_config >/dev/null
Port ${PORT}
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods keyboard-interactive
UsePAM yes
UseDNS no
MaxAuthTries 10
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
EOF
else
cat <<EOF | $SUDO tee -a /etc/ssh/sshd_config >/dev/null
# Duo SSH settings (mode 1: password + Duo)
Port ${PORT}
PasswordAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods keyboard-interactive
UsePAM yes
EOF
fi
elif [[ "$MODE" == "2" ]]; then
# 所有人:Key + Duo (publickey + keyboard-interactive → pam_duo)
if [[ "$SSHD_MODE" == "1" ]]; then
cat <<EOF | $SUDO tee /etc/ssh/sshd_config >/dev/null
Port ${PORT}
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication no
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes
UseDNS no
MaxAuthTries 10
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
EOF
else
cat <<EOF | $SUDO tee -a /etc/ssh/sshd_config >/dev/null
# Duo SSH settings (mode 2: key + Duo)
Port ${PORT}
PasswordAuthentication no
KbdInteractiveAuthentication yes
AuthenticationMethods publickey,keyboard-interactive
UsePAM yes
EOF
fi
else
# MODE 3: root = key + Duo, 其他 = 密碼 + Duo
if [[ "$SSHD_MODE" == "1" ]]; then
cat <<EOF | $SUDO tee /etc/ssh/sshd_config >/dev/null
Port ${PORT}
PermitRootLogin yes
PubkeyAuthentication yes
PasswordAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods keyboard-interactive
UsePAM yes
UseDNS no
MaxAuthTries 10
AcceptEnv LANG LC_*
Subsystem sftp /usr/lib/openssh/sftp-server
Match User root
PasswordAuthentication no
AuthenticationMethods publickey,keyboard-interactive
EOF
else
cat <<EOF | $SUDO tee -a /etc/ssh/sshd_config >/dev/null
# Duo SSH settings (mode 3: root key+duo, others password+duo)
Port ${PORT}
PasswordAuthentication yes
KbdInteractiveAuthentication yes
AuthenticationMethods keyboard-interactive
UsePAM yes
Match User root
PasswordAuthentication no
AuthenticationMethods publickey,keyboard-interactive
EOF
fi
fi
########################################
# 9. 驗證 & 重啟 SSH
########################################
echo
echo "🧪 驗證 sshd 語法..."
if $SUDO sshd -t 2>/tmp/sshd_check.log; then
echo "🚀 語法 OK,重啟 ssh ..."
$SUDO systemctl restart ssh
echo "🎉 Duo 已成功啟用"
else
echo "❌ sshd 語法錯誤,ssh 未重啟。"
echo "🔎 詳細錯誤:"
cat /tmp/sshd_check.log
echo "📁 備份檔案在:$BACKUP_DIR"
exit 1
fi
echo
echo "✅ 完成!備份位置:$BACKUP_DIR"
echo
echo "👉 模式 1(密碼 + Duo):直接 ssh 登入,會先問密碼,再跳 Duo"
echo "👉 模式 2(Key + Duo):必須先有公鑰登入成功,再跑 Duo"
echo "👉 模式 3:root 只能 Key + Duo;其他帳號用 密碼 + Duo"
echo
echo "🍻 Done."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment