准备工作搭建CentOS7,地址为:192.168.51.104,192.168.51.105
1.配置pam
# 192.168.51.104 /etc/pam.d/sshd
#%PAM-1.0
auth sufficient pam_python.so auth.py #新增内容
auth required pam_sepermit.so
auth substack password-auth
auth include postlogin
# Used with polkit to reauthorize users in remote sessions
-auth optional pam_reauthorize.so prepare
account required pam_nologin.so
account include password-auth
password include password-auth
# pam_selinux.so close should be the first session rule
session required pam_selinux.so close
session required pam_loginuid.so
# pam_selinux.so open should only be followed by sessions to be executed in the user context
session required pam_selinux.so open env_params
session required pam_namespace.so
session optional pam_keyinit.so force revoke
session include password-auth
session include postlogin
# Used with polkit to reauthorize users in remote sessions
-session optional pam_reauthorize.so prepare
2.pam_python.so制作
# 192.168.51.104
yum install pam pam-devel -y #安装编译需要的依赖
wget -O pam-python_1.0.8.tar.gz https://sourceforge.net/projects/pam-python/files/latest/download?source=files --no-check-certificate #下载pam_python模块
tar xvfpam-python_1.0.8.tar.gz
cd pam-python_1.0.8
make lib
cp src/build/lib.linux-x86_64-2.7/pam_python.so /usr/lib64/security/
3.编写auth.py
# 192.168.51.104
import syslog
import base64
import sys
import json
try:
sys.path.append('/usr/local/lib/python2.7/site-packages')
sys.path.append('/usr/local/lib64/python2.7/site-packages')
except Exception as e:
raise Exception(str(e))
import requests
EXCLUDE_USER = ["root"]
# Notice: Auth use 127.0.0.1, requests set header X_FORWARDED_FOR to send real IP
AUTH_URL = 'http://127.0.0.1/auth' # 认证服务地址
def otp_log(msg):
syslog.openlog(facility=syslog.LOG_AUTH)
syslog.syslog("otp auth log: "+msg)
syslog.closelog()
def do_auth(url, username, password, host):
username = username
password = password
headers = {'X-FORWARDED-FOR': host}
try:
resp = requests.post(url, json={'username': username, 'password': password}, headers=headers)
except Exception as e:
raise Exception(str(e))
else:
if resp.status_code == 200:
return True
else:
return False
def pam_sm_authenticate(pamh, flags, argv):
try:
user = pamh.get_user(None)
except pamh.exception as e:
return e.pam_result
try:
if user in EXCLUDE_USER:
return pamh.PAM_AUTH_ERR
msg = pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, "Password:")
resp = pamh.conversation(msg)
otp_log("[resp] %s" % resp.resp)
result = do_auth(AUTH_URL, pamh.user, resp.resp, pamh.rhost)
otp_log("[result] %s" % result)
if result:
return pamh.PAM_SUCCESS
else:
return pamh.PAM_AUTH_ERR
except Exception as e:
raise Exception(str(e))
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
auth.py添加到pam库
# 192.168.51.104
cp auth.py /usr/lib/security/
4.配置sshd
# 192.168.51.104 /etc/ssh/sshd_config
HostKey /etc/ssh/ssh_host_rsa_key
HostKey /etc/ssh/ssh_host_ecdsa_key
HostKey /etc/ssh/ssh_host_ed25519_key
SyslogFacility AUTHPRIV
AuthorizedKeysFile .ssh/authorized_keys
PasswordAuthentication yes
ChallengeResponseAuthentication no
GSSAPIAuthentication yes
GSSAPICleanupCredentials no
UsePAM yes
X11Forwarding yes
AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY LC_MESSAGES
AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT
AcceptEnv LC_IDENTIFICATION LC_ALL LANGUAGE
AcceptEnv XMODIFIERS
重新启动sshd服务
# 192.168.51.104
service sshd restart
5.验证
1.安装flask并启动web服务
# 192.168.51.104
pip install flask
2.编写服务端用于验证pam功能
新建pam_server.py
# 192.168.51.104
from flask import Flask, request, Response
app = Flask(__name__)
USERNAME = 'Test'
PASSWORD = 'Password'
@app.route('/auth', methods=['POST', 'GET'])
def auth():
if request.method == 'POST':
print(request.json)
if request.json.get('username') == USERNAME and request.form.get('password') == PASSWORD:
return Response(status=200)
return Response(status=401)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=8088, debug=True)
3.执行pam_server.py
# 192.168.51.104
[root@host ~]# python pam_server.py
* Serving Flask app 'pam_server' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on all addresses.
WARNING: This is a development server. Do not use it in a production deployment.
* Running on http://192.168.51.104:8088/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 849-362-694
4.本机添加用户
# 192.168.51.104
useradd Test
5.ssh登录验证,可以成功登录
# 192.168.51.105
[root@host2 ~]$ ssh Test@192.168.51.104
The authenticity of host '192.168.51.104 (192.168.51.104)' can't be established.
ECDSA key fingerprint is SHA256:c2HeAq0o66TI9MO8AmMK0eZlTc5zvDCud0cBIg6+FGA.
ECDSA key fingerprint is MD5:65:cb:3f:6e:68:59:5c:d4:ad:7a:a2:6e:20:a4:59:46.
Are you sure you want to continue connecting (yes/no)? yes
Warning: Permanently added '192.168.51.104' (ECDSA) to the list of known hosts.
Test@192.168.51.104's password:
Last login: Fri Mar 25 16:13:50 2022 from 192.168.51.106
[Test@host2 ~]$
6.登录成功后查看pam server调试信息
# 192.168.51.104
[root@host ~]# python pam_server.py
* Serving Flask app 'pam_server' (lazy loading)
* Environment: production
WARNING: This is a development server. Do not use it in a production deployment.
Use a production WSGI server instead.
* Debug mode: on
* Running on all addresses.
WARNING: This is a development server. Do not use it in a production deployment.
* Running on http://192.168.51.105:8088/ (Press CTRL+C to quit)
* Restarting with stat
* Debugger is active!
* Debugger PIN: 849-362-694
{'username': 'Test', 'password': 'Password'}
192.168.51.104 - - [25/Mar/2022 16:55:57] "POST /auth HTTP/1.1" 200 -