You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Ubuntu下如何实现基于外部身份提供商的非本地用户Linux PAM认证以搭建SFTP服务器

Ubuntu下如何实现基于外部身份提供商的非本地用户Linux PAM认证以搭建SFTP服务器

你遇到的这个问题我之前帮朋友踩过坑,核心原因其实是SSH服务在触发PAM认证之前,就已经通过系统的getpwnam()函数检查用户是否存在了——也就是先查本地的/etc/passwd或者NSS(名称服务切换)配置的用户源。你之前在PAM脚本里动态创建用户的逻辑,是在认证阶段才执行的,这时候SSH已经完成了用户存在性校验,所以第一次登录必然失败,第二次因为用户已经创建在本地了,getpwnam()能查到,就正常通过了。

要实现完全依赖外部身份提供商(IDP)的用户认证,不用预创建本地用户,得同时搞定两个核心环节:一是让PAM通过IDP的REST API验证密码,二是让系统能从IDP获取用户的元数据(比如UID、GID、家目录这些),不然系统不知道怎么给用户创建会话。下面是具体的实现步骤:

一、调整SSH配置,让它完全依赖PAM和NSS做用户校验

默认情况下SSH会自己先做用户存在性检查,我们得把这个逻辑交给PAM和NSS来处理。编辑/etc/ssh/sshd_config,确保以下配置:

UsePAM yes
PasswordAuthentication yes
ChallengeResponseAuthentication yes
# 如果之前有AllowUsers限制,改成允许所有或按需调整
AllowUsers *

修改完后重启SSH服务生效:

sudo systemctl restart sshd

二、用NSS模块动态提供用户元数据

系统要给用户创建会话,必须知道用户的UID、GID、家目录这些属性,所以得让getpwnam()这类函数能从你的IDP获取这些信息。这里推荐用nss-python来快速实现(如果Ubuntu仓库没有,可能需要源码编译安装):

  1. 编写NSS脚本,比如/usr/lib/nss_python.py,用来调用IDP API获取用户信息:
import requests

def getpwnam(name):
    # 调用你的IDP API获取用户详情
    response = requests.get(f"https://你的IDP地址/api/users/{name}", headers={"X-API-Key": "你的API密钥"})
    if response.status_code != 200:
        return None
    user_data = response.json()
    # 返回符合NSS要求的用户元数据格式:(用户名, 占位密码, UID, GID, 用户描述, 家目录, 登录shell)
    return (
        user_data['username'],
        'x',  # 密码字段用x,因为PAM会单独处理密码验证
        user_data['uid'],  # 可以从IDP获取,也可以统一分配(比如从10000开始递增)
        1001,  # 这里用你之前创建的sftp组的GID
        user_data['full_name'],
        f"/home/{user_data['username']}",
        "/usr/sbin/nologin"  # SFTP用户推荐用nologin,禁止登录shell
    )

# 按需实现其他NSS函数,比如getpwuid(通过UID查用户),如果不需要可以返回None
def getpwuid(uid):
    return None
  1. 修改/etc/nsswitch.conf,让系统在查询用户时优先(或同时)使用我们的Python模块:
passwd:         compat python
group:          compat
shadow:         compat

三、优化PAM认证脚本,去掉本地用户创建逻辑

现在NSS已经能提供用户元数据了,就不用再在PAM里创建本地用户了,直接专注于密码验证即可。修改你的PAM脚本:

import requests

def pam_sm_authenticate(pamh, flags, argv):
    try:
        user = pamh.get_user(None)
        # 获取用户输入的密码
        password = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "Password:")).resp
    except pamh.exception as e:
        return e.pam_result

    # 调用IDP的认证API验证密码
    auth_url = "https://你的IDP地址/api/auth"
    payload = {"username": user, "password": password}
    headers = {"X-API-Key": "你的API密钥"}
    response = requests.post(auth_url, json=payload, headers=headers)
    
    if response.status_code == 200 and response.json().get('success'):
        # 验证成功后,确保用户家目录存在并设置正确权限(SFTP要求)
        home_dir = f"/home/{user}"
        if not os.path.exists(home_dir):
            import os
            os.makedirs(home_dir)
            # 从NSS获取用户的UID和GID,或者直接从IDP API返回的结果里拿
            uid = response.json().get('uid', 10000)
            gid = 1001  # sftp组的GID
            os.chown(home_dir, uid, gid)
            os.chmod(home_dir, 0700)
        return pamh.PAM_SUCCESS
    else:
        return pamh.PAM_AUTH_ERR

# 以下是PAM要求必须实现的空函数,直接返回成功即可
def pam_sm_setcred(pamh, flags, argv):
    return pamh.PAM_SUCCESS

def pam_sm_acct_mgmt(pamh, flags, argv):
    return pamh.PAM_SUCCESS

def pam_sm_open_session(pamh, flags, argv):
    return pamh.PAM_SUCCESS

def pam_sm_close_session(pamh, flags, argv):
    return pamh.PAM_SUCCESS

def pam_sm_chauthtok(pamh, flags, argv):
    return pamh.PAM_SUCCESS

然后修改/etc/pam.d/sshd,替换原有的认证模块为我们的Python脚本:

# 注释掉原来的common-auth引用,或者把我们的模块放在最前面
# @include common-auth
auth    required    pam_python.so /path/to/你的PAM脚本.py
account required    pam_python.so /path/to/你的PAM脚本.py
session required    pam_python.so /path/to/你的PAM脚本.py

四、(可选但推荐)配置SFTP Chroot环境

如果要限制用户只能访问自己的家目录,防止越权访问,可以在sshd_config里添加以下配置:

Subsystem sftp internal-sftp

Match Group sftp
    ChrootDirectory /home/%u
    ForceCommand internal-sftp
    X11Forwarding no
    AllowTcpForwarding no
    PermitTunnel no
    AllowAgentForwarding no

确保sftp组已经创建:

sudo groupadd sftp

关键注意事项

  • 权限与安全:PAM和NSS脚本需要有调用外部API的权限,但不要给过高权限,避免安全风险;调用API一定要用HTTPS,同时给API加身份验证(比如API密钥),防止恶意调用。
  • API可靠性:如果IDP API不稳定,用户可能无法登录,可以考虑添加本地缓存机制,比如把用户信息临时存在本地文件里,减少API调用频率。
  • 测试验证:先在测试环境反复验证,不要直接在生产环境修改,避免不小心锁自己出系统。

备注:内容来源于stack exchange,提问作者Jonny Marotte

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.22 08:59:31