589 lines
19 KiB
Bash
589 lines
19 KiB
Bash
#!/usr/bin/env bash
|
||
# =============================================================================
|
||
# setup-cf-browser-ssh.sh
|
||
# Cloudflare Browser-rendered SSH + Short-lived Certificate 服务器端一键配置
|
||
# 支持系统:Ubuntu / Debian
|
||
#
|
||
# 功能:
|
||
# 1. 安装 cloudflared(如未安装)
|
||
# 2. 可选:用 Tunnel Token 注册 cloudflared 系统服务
|
||
# 3. 写入 Cloudflare CA 公钥到 /etc/ssh/ca.pub
|
||
# 4. 配置 sshd_config 信任 Cloudflare CA
|
||
# 5. 配置用户名映射(支持 root 共享登录)
|
||
# 6. 校验 sshd 配置并 reload
|
||
#
|
||
# 用法:
|
||
# 交互式(推荐):
|
||
# sudo bash setup-cf-browser-ssh.sh
|
||
#
|
||
# 非交互式(全部参数传入):
|
||
# sudo bash setup-cf-browser-ssh.sh \
|
||
# --ca-pubkey "ecdsa-sha2-nistp256 AAAA... open-ssh-ca@cloudflareaccess.org" \
|
||
# --sso-email admin@example.com \
|
||
# --tunnel-token "eyJhIjoiNz..."
|
||
#
|
||
# 选项:
|
||
# --ca-pubkey <string|filepath> Cloudflare short-lived CA 公钥(内容或文件路径)
|
||
# --sso-email <email> Cloudflare SSO 登录邮箱(用于提取用户名)
|
||
# --login-user <username> 浏览器终端登录的 Linux 用户(默认:root)
|
||
# --tunnel-token <token> cloudflared Tunnel Token(可选,不传则跳过服务注册)
|
||
# --skip-cloudflared 跳过 cloudflared 安装
|
||
# --allow-all-principals 允许任意 Access 用户登录 --login-user(默认开启 root 时自动启用)
|
||
# --dry-run 只打印将要执行的操作,不实际修改
|
||
# -h, --help 显示帮助
|
||
#
|
||
# 回滚:
|
||
# 脚本会在修改前备份 sshd_config,备份文件名如 sshd_config.bak.20260412_120000
|
||
# 如果 sshd -t 校验失败,脚本会自动还原备份并拒绝 reload。
|
||
# =============================================================================
|
||
set -euo pipefail
|
||
|
||
# ----- 颜色输出 -----
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
CYAN='\033[0;36m'
|
||
NC='\033[0m' # No Color
|
||
|
||
info() { echo -e "${GREEN}[INFO]${NC} $*"; }
|
||
warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
|
||
err() { echo -e "${RED}[ERROR]${NC} $*" >&2; }
|
||
step() { echo -e "\n${CYAN}===> $*${NC}"; }
|
||
|
||
# ----- 默认值 -----
|
||
CA_PUBKEY=""
|
||
SSO_EMAIL=""
|
||
LOGIN_USER=""
|
||
TUNNEL_TOKEN=""
|
||
SKIP_CLOUDFLARED=false
|
||
ALLOW_ALL_PRINCIPALS=false
|
||
DRY_RUN=false
|
||
|
||
# 从完整命令或纯 token 中提取 tunnel token
|
||
# 支持输入: "sudo cloudflared service install eyJ..." 或直接 "eyJ..."
|
||
extract_tunnel_token() {
|
||
local input="$1"
|
||
# 去掉首尾空白
|
||
input="$(echo "$input" | xargs)"
|
||
# 如果包含 "install",取最后一个参数作为 token
|
||
if [[ "$input" == *"install "* ]]; then
|
||
input="${input##*install }"
|
||
input="$(echo "$input" | awk '{print $1}')"
|
||
fi
|
||
echo "$input"
|
||
}
|
||
|
||
SSHD_CONFIG="/etc/ssh/sshd_config"
|
||
CA_PUB_FILE="/etc/ssh/ca.pub"
|
||
BACKUP_SUFFIX="bak.$(date +%Y%m%d_%H%M%S)"
|
||
|
||
# ----- 参数解析 -----
|
||
while [[ $# -gt 0 ]]; do
|
||
case "$1" in
|
||
--ca-pubkey) CA_PUBKEY="$2"; shift 2 ;;
|
||
--sso-email) SSO_EMAIL="$2"; shift 2 ;;
|
||
--login-user) LOGIN_USER="$2"; shift 2 ;;
|
||
--tunnel-token) TUNNEL_TOKEN="$(extract_tunnel_token "$2")"; shift 2 ;;
|
||
--skip-cloudflared) SKIP_CLOUDFLARED=true; shift ;;
|
||
--allow-all-principals) ALLOW_ALL_PRINCIPALS=true; shift ;;
|
||
--dry-run) DRY_RUN=true; shift ;;
|
||
-h|--help)
|
||
awk '/^# =====/{ if(n++) exit } n{ sub(/^# ?/,""); print }' "$0"
|
||
exit 0
|
||
;;
|
||
*) err "未知参数: $1"; exit 1 ;;
|
||
esac
|
||
done
|
||
|
||
# ----- 前置检查 -----
|
||
check_root() {
|
||
if [[ $EUID -ne 0 ]]; then
|
||
err "请使用 root 或 sudo 执行此脚本"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
check_os() {
|
||
if [[ ! -f /etc/os-release ]]; then
|
||
err "无法检测操作系统,此脚本仅支持 Ubuntu/Debian"
|
||
exit 1
|
||
fi
|
||
# shellcheck source=/dev/null
|
||
source /etc/os-release
|
||
case "$ID" in
|
||
ubuntu|debian) info "检测到系统: $PRETTY_NAME" ;;
|
||
*)
|
||
err "此脚本仅支持 Ubuntu/Debian,当前系统: $ID"
|
||
exit 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
# ----- 交互式收集缺失参数 -----
|
||
prompt_missing_params() {
|
||
# CA 公钥
|
||
if [[ -z "$CA_PUBKEY" ]]; then
|
||
echo ""
|
||
warn "未提供 Cloudflare CA 公钥。"
|
||
echo " 获取方式:Cloudflare One → Access controls → Service credentials → SSH"
|
||
echo " → 选择对应 Application → 复制 CA public key"
|
||
echo ""
|
||
read -rp "请粘贴 CA 公钥(一行,以 ecdsa-sha2 或 ssh-rsa 开头),或输入本地文件路径: " CA_PUBKEY
|
||
fi
|
||
|
||
# 如果是文件路径,读取内容
|
||
if [[ -f "$CA_PUBKEY" ]]; then
|
||
info "从文件读取 CA 公钥: $CA_PUBKEY"
|
||
CA_PUBKEY="$(cat "$CA_PUBKEY")"
|
||
fi
|
||
|
||
# 校验公钥格式
|
||
if [[ ! "$CA_PUBKEY" =~ ^(ecdsa-sha2-nistp256|ssh-rsa|ssh-ed25519)[[:space:]] ]]; then
|
||
err "CA 公钥格式不正确,应以 ecdsa-sha2-nistp256 / ssh-rsa / ssh-ed25519 开头"
|
||
exit 1
|
||
fi
|
||
|
||
# SSO 邮箱 → 登录用户名
|
||
if [[ -z "$LOGIN_USER" ]]; then
|
||
if [[ -z "$SSO_EMAIL" ]]; then
|
||
echo ""
|
||
echo -e " ${YELLOW}⚠️ 重要:Cloudflare 浏览器 SSH 强制使用 SSO 邮箱前缀作为用户名${NC}"
|
||
echo " 例如:admin@moe.tips → 用户名自动变为 admin"
|
||
echo " 你无法在浏览器终端里选择用户名,它由邮箱前缀决定。"
|
||
echo ""
|
||
read -rp "请输入你的 Cloudflare SSO 登录邮箱: " SSO_EMAIL
|
||
fi
|
||
if [[ -n "$SSO_EMAIL" ]]; then
|
||
LOGIN_USER="${SSO_EMAIL%%@*}"
|
||
info "从邮箱 '$SSO_EMAIL' 提取的登录用户名: $LOGIN_USER"
|
||
else
|
||
LOGIN_USER="root"
|
||
warn "未提供邮箱,默认使用 root(仅当你的邮箱前缀是 root 时才有效)"
|
||
fi
|
||
fi
|
||
|
||
# root 模式自动启用 allow-all-principals
|
||
if [[ "$LOGIN_USER" == "root" ]]; then
|
||
ALLOW_ALL_PRINCIPALS=true
|
||
fi
|
||
|
||
# Tunnel Token
|
||
if [[ -z "$TUNNEL_TOKEN" && "$SKIP_CLOUDFLARED" != true ]]; then
|
||
echo ""
|
||
echo -e " ${CYAN}Tunnel Token 获取方式:${NC}"
|
||
echo " Cloudflare One → Networks → Tunnels → 选择/创建 Tunnel → Install connector"
|
||
echo ""
|
||
echo " 直接把控制台给你的整条命令粘贴进来,脚本会自动提取 Token:"
|
||
echo -e " ${GREEN}sudo cloudflared service install eyJhIjoiZDZjN2MwNT...${NC}"
|
||
echo ""
|
||
echo " (也可以只粘贴 eyJ... Token 部分;留空则跳过隧道服务注册)"
|
||
echo ""
|
||
read -rp "请粘贴命令或 Token: " TUNNEL_TOKEN_RAW
|
||
TUNNEL_TOKEN="$(extract_tunnel_token "$TUNNEL_TOKEN_RAW")"
|
||
fi
|
||
}
|
||
|
||
# ----- 安装 cloudflared -----
|
||
install_cloudflared() {
|
||
step "安装 cloudflared"
|
||
|
||
if command -v cloudflared &>/dev/null; then
|
||
local ver
|
||
ver=$(cloudflared --version 2>&1 | head -1)
|
||
info "cloudflared 已安装: $ver(跳过安装)"
|
||
return 0
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" == true ]]; then
|
||
info "[DRY-RUN] 将安装 cloudflared(apt 方式)"
|
||
return 0
|
||
fi
|
||
|
||
info "添加 Cloudflare GPG key 和 apt 源..."
|
||
mkdir -p --mode=0755 /usr/share/keyrings
|
||
curl -fsSL https://pkg.cloudflare.com/cloudflare-public-v2.gpg \
|
||
| tee /usr/share/keyrings/cloudflare-public-v2.gpg >/dev/null
|
||
|
||
echo "deb [signed-by=/usr/share/keyrings/cloudflare-public-v2.gpg] https://pkg.cloudflare.com/cloudflared any main" \
|
||
| tee /etc/apt/sources.list.d/cloudflared.list >/dev/null
|
||
|
||
info "正在安装 cloudflared(全程无交互)..."
|
||
export DEBIAN_FRONTEND=noninteractive
|
||
apt-get update -qq
|
||
apt-get install -y -qq cloudflared
|
||
|
||
if command -v cloudflared &>/dev/null; then
|
||
info "cloudflared 安装成功: $(cloudflared --version 2>&1 | head -1)"
|
||
else
|
||
err "cloudflared 安装失败"
|
||
exit 1
|
||
fi
|
||
}
|
||
|
||
# ----- 注册 cloudflared 系统服务 -----
|
||
register_cloudflared_service() {
|
||
step "注册 cloudflared 系统服务"
|
||
|
||
if [[ -z "$TUNNEL_TOKEN" ]]; then
|
||
warn "未提供 Tunnel Token,跳过服务注册"
|
||
echo ""
|
||
echo " 后续手动注册命令:"
|
||
echo " sudo cloudflared service install <YOUR_TUNNEL_TOKEN>"
|
||
echo ""
|
||
return 0
|
||
fi
|
||
|
||
if systemctl is-active --quiet cloudflared 2>/dev/null; then
|
||
info "cloudflared 服务已在运行(跳过)"
|
||
return 0
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" == true ]]; then
|
||
info "[DRY-RUN] 将执行: cloudflared service install <TOKEN>"
|
||
return 0
|
||
fi
|
||
|
||
info "执行 cloudflared service install ..."
|
||
cloudflared service install "$TUNNEL_TOKEN"
|
||
|
||
if systemctl is-active --quiet cloudflared 2>/dev/null; then
|
||
info "cloudflared 服务已启动"
|
||
else
|
||
warn "cloudflared 服务注册完成但未自动启动,尝试启动..."
|
||
systemctl start cloudflared
|
||
systemctl enable cloudflared
|
||
fi
|
||
}
|
||
|
||
# ----- 写入 CA 公钥 -----
|
||
write_ca_pubkey() {
|
||
step "写入 Cloudflare CA 公钥 → $CA_PUB_FILE"
|
||
|
||
if [[ "$DRY_RUN" == true ]]; then
|
||
info "[DRY-RUN] 将写入 CA 公钥到 $CA_PUB_FILE"
|
||
return 0
|
||
fi
|
||
|
||
# 幂等:检查是否已包含相同公钥
|
||
if [[ -f "$CA_PUB_FILE" ]]; then
|
||
if grep -qF "$CA_PUBKEY" "$CA_PUB_FILE" 2>/dev/null; then
|
||
info "CA 公钥已存在于 $CA_PUB_FILE(跳过)"
|
||
return 0
|
||
fi
|
||
# 文件存在但内容不同,追加
|
||
info "$CA_PUB_FILE 已存在,追加新公钥"
|
||
echo "$CA_PUBKEY" >> "$CA_PUB_FILE"
|
||
else
|
||
echo "$CA_PUBKEY" > "$CA_PUB_FILE"
|
||
fi
|
||
|
||
chmod 644 "$CA_PUB_FILE"
|
||
info "CA 公钥已写入"
|
||
}
|
||
|
||
# ----- 配置 sshd_config -----
|
||
configure_sshd() {
|
||
step "配置 $SSHD_CONFIG"
|
||
|
||
if [[ ! -f "$SSHD_CONFIG" ]]; then
|
||
err "$SSHD_CONFIG 不存在"
|
||
exit 1
|
||
fi
|
||
|
||
# 备份
|
||
local backup="${SSHD_CONFIG}.${BACKUP_SUFFIX}"
|
||
cp "$SSHD_CONFIG" "$backup"
|
||
info "已备份: $backup"
|
||
|
||
if [[ "$DRY_RUN" == true ]]; then
|
||
info "[DRY-RUN] 将修改 $SSHD_CONFIG"
|
||
info "[DRY-RUN] PubkeyAuthentication yes"
|
||
info "[DRY-RUN] TrustedUserCAKeys $CA_PUB_FILE"
|
||
if [[ "$ALLOW_ALL_PRINCIPALS" == true ]]; then
|
||
info "[DRY-RUN] AuthorizedPrincipalsCommand (allow all for $LOGIN_USER)"
|
||
fi
|
||
return 0
|
||
fi
|
||
|
||
local changed=false
|
||
|
||
# --- PubkeyAuthentication yes ---
|
||
if grep -qE '^\s*PubkeyAuthentication\s' "$SSHD_CONFIG"; then
|
||
# 如果存在但不是 yes,替换
|
||
if ! grep -qE '^\s*PubkeyAuthentication\s+yes' "$SSHD_CONFIG"; then
|
||
sed -i 's/^\s*PubkeyAuthentication\s.*/PubkeyAuthentication yes/' "$SSHD_CONFIG"
|
||
info "已修改 PubkeyAuthentication → yes"
|
||
changed=true
|
||
else
|
||
info "PubkeyAuthentication yes 已存在"
|
||
fi
|
||
else
|
||
# 不存在,添加到文件顶部(跳过注释)
|
||
sed -i '1i PubkeyAuthentication yes' "$SSHD_CONFIG"
|
||
info "已添加 PubkeyAuthentication yes"
|
||
changed=true
|
||
fi
|
||
|
||
# --- TrustedUserCAKeys ---
|
||
local ca_directive="TrustedUserCAKeys $CA_PUB_FILE"
|
||
if grep -qE '^\s*TrustedUserCAKeys\s' "$SSHD_CONFIG"; then
|
||
if ! grep -qF "$ca_directive" "$SSHD_CONFIG"; then
|
||
sed -i "s|^\s*TrustedUserCAKeys\s.*|$ca_directive|" "$SSHD_CONFIG"
|
||
info "已修改 TrustedUserCAKeys → $CA_PUB_FILE"
|
||
changed=true
|
||
else
|
||
info "TrustedUserCAKeys 已正确配置"
|
||
fi
|
||
else
|
||
# 添加到 PubkeyAuthentication 之后
|
||
sed -i "/^PubkeyAuthentication/a $ca_directive" "$SSHD_CONFIG"
|
||
info "已添加 $ca_directive"
|
||
changed=true
|
||
fi
|
||
|
||
# --- 用户名映射(root / 共享账号模式)---
|
||
if [[ "$ALLOW_ALL_PRINCIPALS" == true ]]; then
|
||
configure_principals "$LOGIN_USER"
|
||
fi
|
||
|
||
# --- 确保 PermitRootLogin 允许公钥登录(仅 root 模式)---
|
||
if [[ "$LOGIN_USER" == "root" ]]; then
|
||
ensure_root_login
|
||
fi
|
||
|
||
if [[ "$changed" == true ]]; then
|
||
info "sshd_config 已修改"
|
||
else
|
||
info "sshd_config 无需修改"
|
||
fi
|
||
|
||
# --- 校验 ---
|
||
validate_and_reload "$backup"
|
||
}
|
||
|
||
# 配置 AuthorizedPrincipalsCommand,让任意 Access 用户都可以登录指定用户
|
||
configure_principals() {
|
||
local user="$1"
|
||
|
||
# 标记行,用于幂等检查
|
||
local marker="# --- Cloudflare Access short-lived cert: $user ---"
|
||
|
||
if grep -qF "$marker" "$SSHD_CONFIG" 2>/dev/null; then
|
||
info "AuthorizedPrincipalsCommand($user)已配置(跳过)"
|
||
return 0
|
||
fi
|
||
|
||
warn "⚠️ 将允许任意通过 Cloudflare Access 认证的用户以 '$user' 身份登录"
|
||
warn "⚠️ 请确保你的 Access Policy 已正确限制可访问人员!"
|
||
|
||
# 对于 root,使用全局 AuthorizedPrincipalsCommand
|
||
# 对于其他用户,使用 Match User 块
|
||
if [[ "$user" == "root" ]]; then
|
||
cat >> "$SSHD_CONFIG" << EOF
|
||
|
||
$marker
|
||
# 允许任意 Cloudflare Access 短期证书用户以 root 登录
|
||
# 安全性完全依赖 Cloudflare Access Policy,请务必正确配置!
|
||
Match User root
|
||
AuthorizedPrincipalsCommand /bin/bash -c "echo '%t %k' | ssh-keygen -L -f - | grep -A1 Principals"
|
||
AuthorizedPrincipalsCommandUser nobody
|
||
EOF
|
||
else
|
||
cat >> "$SSHD_CONFIG" << EOF
|
||
|
||
$marker
|
||
Match User $user
|
||
AuthorizedPrincipalsCommand /bin/bash -c "echo '%t %k' | ssh-keygen -L -f - | grep -A1 Principals"
|
||
AuthorizedPrincipalsCommandUser nobody
|
||
EOF
|
||
fi
|
||
|
||
info "已添加 AuthorizedPrincipalsCommand($user)"
|
||
}
|
||
|
||
# 确保 root 可以通过公钥登录
|
||
ensure_root_login() {
|
||
# PermitRootLogin 需要为 yes 或 prohibit-password 或 without-password
|
||
if grep -qE '^\s*PermitRootLogin\s+(no|forced-commands-only)\b' "$SSHD_CONFIG"; then
|
||
sed -i 's/^\s*PermitRootLogin\s.*/PermitRootLogin prohibit-password/' "$SSHD_CONFIG"
|
||
info "已修改 PermitRootLogin → prohibit-password(允许证书登录,禁止密码)"
|
||
elif grep -qE '^\s*PermitRootLogin\s' "$SSHD_CONFIG"; then
|
||
info "PermitRootLogin 当前设置允许公钥登录"
|
||
else
|
||
echo "PermitRootLogin prohibit-password" >> "$SSHD_CONFIG"
|
||
info "已添加 PermitRootLogin prohibit-password"
|
||
fi
|
||
}
|
||
|
||
# ----- 校验并 reload -----
|
||
validate_and_reload() {
|
||
local backup="$1"
|
||
|
||
step "校验 sshd 配置"
|
||
|
||
if sshd -t 2>&1; then
|
||
info "sshd -t 校验通过 ✓"
|
||
else
|
||
err "sshd -t 校验失败!正在回滚..."
|
||
cp "$backup" "$SSHD_CONFIG"
|
||
info "已回滚到备份: $backup"
|
||
err "请手动检查 $SSHD_CONFIG 后重试"
|
||
exit 1
|
||
fi
|
||
|
||
step "重新加载 SSH 服务"
|
||
if systemctl reload ssh 2>/dev/null || systemctl reload sshd 2>/dev/null; then
|
||
info "SSH 服务已 reload ✓"
|
||
else
|
||
warn "systemctl reload 失败,尝试 service ssh reload..."
|
||
service ssh reload 2>/dev/null || service sshd reload 2>/dev/null || {
|
||
err "SSH 服务 reload 失败,请手动执行: systemctl reload ssh"
|
||
exit 1
|
||
}
|
||
fi
|
||
}
|
||
|
||
# ----- 创建登录用户(如不存在) -----
|
||
create_login_user() {
|
||
local user="$1"
|
||
|
||
# root 不需要创建
|
||
if [[ "$user" == "root" ]]; then
|
||
return 0
|
||
fi
|
||
|
||
step "检查 Linux 用户: $user"
|
||
|
||
if id "$user" &>/dev/null; then
|
||
info "用户 '$user' 已存在"
|
||
return 0
|
||
fi
|
||
|
||
if [[ "$DRY_RUN" == true ]]; then
|
||
info "[DRY-RUN] 将创建用户 '$user' 并加入 sudo 组"
|
||
return 0
|
||
fi
|
||
|
||
info "创建用户 '$user'(无密码,仅证书登录)..."
|
||
adduser --disabled-password --gecos "" --allow-bad-names "$user"
|
||
|
||
# 配置免密 sudo(证书用户没有密码,普通 sudo 组会要求输入密码)
|
||
local sudoers_file="/etc/sudoers.d/${user//[^a-zA-Z0-9_-]/-}"
|
||
echo "$user ALL=(ALL) NOPASSWD:ALL" > "$sudoers_file"
|
||
chmod 440 "$sudoers_file"
|
||
info "用户 '$user' 已创建,免密 sudo 已配置 ✓"
|
||
}
|
||
|
||
# ----- 打印后续手工步骤 -----
|
||
print_next_steps() {
|
||
echo ""
|
||
echo -e "${CYAN}============================================================${NC}"
|
||
echo -e "${CYAN} 服务器端配置完成!以下是你需要在 Cloudflare 控制台完成的步骤:${NC}"
|
||
echo -e "${CYAN}============================================================${NC}"
|
||
echo ""
|
||
echo " 1. 创建 / 确认 Self-hosted Application"
|
||
echo " → Cloudflare One → Access controls → Applications"
|
||
echo " → 添加 Self-hosted Application,域名指向你的 SSH 服务"
|
||
echo ""
|
||
echo " 2. 开启 Browser Rendering"
|
||
echo " → 在 Application 设置中,Browser rendering → 选择 SSH"
|
||
echo ""
|
||
echo " 3. 生成 Short-lived Certificate(如果你还没生成 CA 公钥)"
|
||
echo " → Access controls → Service credentials → SSH"
|
||
echo " → Add a certificate → 选择你的 Application → Generate"
|
||
echo ""
|
||
echo " 4. 配置 Access Policy"
|
||
echo " → 在 Application 中配置谁可以访问(邮箱、域名、组等)"
|
||
echo ""
|
||
echo " 5. 测试"
|
||
echo " → 浏览器访问你配置的域名"
|
||
echo " → 完成 SSO 登录后,应直接进入网页 SSH 终端"
|
||
echo ""
|
||
echo -e " ${YELLOW}ℹ️ 浏览器终端强制使用 SSO 邮箱前缀作为用户名${NC}"
|
||
echo " 你必须用邮箱前缀为 '${LOGIN_USER}' 的账号登录 SSO"
|
||
if [[ -n "$SSO_EMAIL" ]]; then
|
||
echo " 即使用: ${SSO_EMAIL}"
|
||
fi
|
||
echo ""
|
||
|
||
if [[ "$LOGIN_USER" == "root" ]]; then
|
||
echo -e " ${YELLOW}⚠️ 重要提醒:${NC}"
|
||
echo -e " ${YELLOW} 你配置的是 root 登录模式。${NC}"
|
||
echo -e " ${YELLOW} 只有邮箱前缀为 'root' 的 SSO 账号才能通过浏览器 SSH 登录!${NC}"
|
||
echo -e " ${YELLOW} 服务器安全性完全依赖 Access Policy,确保只允许受信任的用户访问!${NC}"
|
||
echo ""
|
||
else
|
||
echo -e " ${GREEN}✅ 服务器已配置用户 '${LOGIN_USER}'(带 sudo 权限)${NC}"
|
||
echo " 登录后可用 sudo -i 获取 root shell"
|
||
echo ""
|
||
fi
|
||
|
||
if [[ -z "$TUNNEL_TOKEN" && "$SKIP_CLOUDFLARED" != true ]]; then
|
||
echo " ⓘ cloudflared 已安装但 Tunnel 服务未注册。"
|
||
echo " 如需注册,请在控制台获取 Token 后执行:"
|
||
echo " sudo cloudflared service install <YOUR_TUNNEL_TOKEN>"
|
||
echo ""
|
||
fi
|
||
|
||
echo -e "${GREEN} 配置文件位置:${NC}"
|
||
echo " CA 公钥: $CA_PUB_FILE"
|
||
echo " sshd_config: $SSHD_CONFIG"
|
||
echo " sshd 备份: ${SSHD_CONFIG}.${BACKUP_SUFFIX}"
|
||
echo ""
|
||
}
|
||
|
||
# =============================================================================
|
||
# 主流程
|
||
# =============================================================================
|
||
main() {
|
||
echo ""
|
||
echo -e "${CYAN}╔══════════════════════════════════════════════════════════════╗${NC}"
|
||
echo -e "${CYAN}║ Cloudflare Browser SSH + Short-lived Certificates Setup ║${NC}"
|
||
echo -e "${CYAN}╚══════════════════════════════════════════════════════════════╝${NC}"
|
||
echo ""
|
||
|
||
check_root
|
||
check_os
|
||
prompt_missing_params
|
||
|
||
echo ""
|
||
info "配置摘要:"
|
||
info " SSO 邮箱: ${SSO_EMAIL:-(未提供)}"
|
||
info " 登录用户: $LOGIN_USER"
|
||
info " 允许任意 principal: $ALLOW_ALL_PRINCIPALS"
|
||
info " 安装 cloudflared: $( [[ "$SKIP_CLOUDFLARED" == true ]] && echo '跳过' || echo '是' )"
|
||
info " Tunnel Token: $( [[ -n "$TUNNEL_TOKEN" ]] && echo '已提供' || echo '未提供' )"
|
||
info " Dry Run: $DRY_RUN"
|
||
echo ""
|
||
|
||
if [[ "$DRY_RUN" != true ]]; then
|
||
read -rp "确认以上配置并继续?[Y/n] " confirm
|
||
if [[ "${confirm,,}" =~ ^n ]]; then
|
||
info "已取消"
|
||
exit 0
|
||
fi
|
||
fi
|
||
|
||
# 1. cloudflared
|
||
if [[ "$SKIP_CLOUDFLARED" != true ]]; then
|
||
install_cloudflared
|
||
register_cloudflared_service
|
||
else
|
||
info "跳过 cloudflared 安装(--skip-cloudflared)"
|
||
fi
|
||
|
||
# 2. CA 公钥
|
||
write_ca_pubkey
|
||
|
||
# 3. 创建登录用户
|
||
create_login_user "$LOGIN_USER"
|
||
|
||
# 4. sshd 配置
|
||
configure_sshd
|
||
|
||
# 4. 后续步骤
|
||
print_next_steps
|
||
}
|
||
|
||
main "$@"
|