我想阻止用户使用 ssh-copy-id 访问服务器
在我的 sshd_config 中我已经设置
AuthorizedKeysFile /etc/ssh/global_authorized_keys
我确认已阅读sshd -T | grep -i authorizedkey
authorizedkeysfile /etc/ssh/global_authorized_keys
我创建了一个只读文件来匹配
ls -l global_authorized_keys
-r--r--r-- 1 root root 0 Sep 15 10:38 global_authorized_keys
问题是,当 sshd 服务重新启动后,客户端仍然可以使用
ssh-copy-id client@server
/usr/bin/ssh-copy-id: INFO: attempting to log in with the new key(s), to filter out any that are already installed
/usr/bin/ssh-copy-id: INFO: 2 key(s) remain to be installed -- if you are prompted now it is to install the new keys
Number of key(s) added: 2
Now try logging into the machine, with: "ssh 'client@server'"
and check to make sure that only the key(s) you wanted were added.
当我检查客户端 .ssh 目录时,我看到
ls -l authorized_keys
-rw------- 1 client client 1312 Sep 15 10:42 authorized_key
那么看起来 sshd 忽略了 AuthorizedKeysFile 指令?
我的服务器版本是
OpenSSH_8.4p1 Debian-5+deb11u1, OpenSSL 1.1.1n 15 Mar 2022
也许还相关的是我使用 LDAP 作为用户公钥的提供者
AuthenticationMethods publickey keyboard-interactive
AuthorizedKeysCommand /usr/local/bin/ldap-ssh-keys.sh %u
AuthorizedKeysCommandUser nobody
cat /usr/local/bin/ldap-ssh-keys.sh
#!/bin/bash
ldap_server="ldaps://server.example.com"
ldap_base="dc=example,dc=com"
#log_file="/var/log/ldap-ssh-keys.log"
# Get the public key from LDAP
search_output=$(ldapsearch -x -H $ldap_server -b "$ldap_base" -LLL "(uid=$1)" -o ldif-wrap=no sshPublicKey 2>&1)
#echo "$search_output" >> "$log_file"
public_key=$(echo "$search_output" | grep sshPublicKey:: | cut -d' ' -f2- | tr -d '\r\n' | base64 -d 2>&1)
#echo "LDAP returned SSH public key: $public_key" >> "$log_file"
logger -t ldap-ssh-keys "LDAP public key search for $1 at $ldap_base on $ldap_server found: $public_key"
# Output the public key if found, or nothing if not
echo "$public_key"
除此之外,我已将个人用户授权密钥更改为指向只读的全局集的链接
lrwxrwxrwx 1 root root 31 Sep 15 11:15 authorized_keys -> /etc/ssh/global_authorized_keys
我已经将 LDAP 脚本的更改实现为 Python,并进行了少量修改
#!/usr/bin/env python3
import argparse
import ldap
import ldap.filter
import syslog
def main():
parser = argparse.ArgumentParser()
parser.add_argument("username")
args = parser.parse_args()
# Use filter_format() to avoid LDAP filter injection
filter_str = ldap.filter.filter_format("(&(objectClass=posixAccount)(uid=%s))",
[args.username])
conn = ldap.initialize("ldaps://ldap.example.com")
try:
conn.simple_bind_s()
res = conn.search_s("dc=example,dc=com",
ldap.SCOPE_SUBTREE,
filter_str,
["sshPublicKey"])
if len(res) == 0:
syslog.syslog(syslog.LOG_WARNING, f"No LDAP entry found for {args.username}")
exit(2)
elif len(res) > 1:
syslog.syslog(syslog.LOG_ALERT, f"More than one LDAP entry for {args.username}")
exit(1)
for dn, attrs in res:
keys = attrs.get("sshPublicKey", [])
syslog.syslog(syslog.LOG_INFO, f"Found {len(keys)} keys for {args.username}")
for val in keys:
syslog.syslog(syslog.LOG_DEBUG, f"Found key: {val}")
print(val.decode().strip())
except ldap.LDAPError as e:
syslog.syslog(syslog.LOG_ERR, f"LDAP error: {e}")
exit(3)
finally:
conn.unbind_s()
if __name__ == "__main__":
main()
我还有一个问题,即每个连接调用两次 LDAP 脚本
Sep 16 06:58:16 egde ldap-ssh-keys.py: Found 1 keys for jeremy
Sep 16 06:58:18 egde ldap-ssh-keys.py: Found 1 keys for jeremy
Sep 16 06:58:18 egde systemd[1]: Started Session 742 of user jeremy.
Sep 16 06:58:21 egde systemd[1]: session-742.scope: Succeeded.
答案1
问题是,当 sshd 服务重新启动后,客户端仍然可以使用
ssh-copy-id
ssh-copy-id 只是一个脚本,手动在远程系统上创建文件并向其添加一行 – 它不使用任何特殊的 sshd 工具来执行此操作。(甚至没有找出要添加的正确路径,因为这sshd -T
是最近才添加的)。
因此客户端可以以与客户端相同的方式使用 ssh-copy-id,vim ~/.ssh/authorized_keys
而 sshd 无法对此产生任何影响。
该选项唯一改变的是 sshd 是否会读该文件是否被上传或是否被完全忽略。您的用户可以随意使用 ssh-copy-id,SSH 服务器根本不会接受使用他们上传的密钥的登录。
此外,该 LDAP 脚本很糟糕。它假设只有一个密钥(“sshPublicKey”属性是多值的),并且假设 ldapsearch 将始终以 Base64 编码输出其值(它才不是,除非密钥的注释中恰好包含一些非 ASCII 文本!)。请正确执行。
#!/usr/bin/env python3
import argparse
import ldap
import ldap.filter
import syslog
parser = argparse.ArgumentParser()
parser.add_argument("username")
args = parser.parse_args()
# Use filter_format() or escape_filter_chars() to avoid LDAP filter injection
filter = ldap.filter.filter_format("(&(objectClass=posixAccount)(uid=%s))",
[args.username])
conn = ldap.initialize("ldaps://ldap.example.com")
conn.simple_bind_s()
res = conn.search_s("o=Example",
ldap.SCOPE_SUBTREE,
filter,
["sshPublicKey"])
if len(res) > 1:
syslog.syslog(syslog.LOG_ALERT, f"More than one LDAP entry for {filter}")
exit(1)
for dn, attrs in res:
keys = attrs.get("sshPublicKey", [])
syslog.syslog(syslog.LOG_INFO, f"Found {len(keys)} keys for {args.username}")
for val in keys:
syslog.syslog(syslog.LOG_DEBUG, f"Found key: {val}")
print(val.decode().strip())