解决 SELinux 导致 SSH 无法登录的问题

.

.

.

.

.

偶然发现一台主机使用 ssh 登录不上去了,报 Permission denied

$ ssh -i .ssh/id_ed25519_dy 10.200.0.2
dybai@10.200.0.2: Permission denied (publickey,gssapi-keyex,gssapi-with-mic).

因为我是用智能卡(Yubikey)登录的,所以一开始以为是智能卡的配置问题,后来改用密钥文件仍然无法登录,报同样的错误,而且用 ssh -vvv 也没有看到详细的报错。

后来在 Google 上搜索的时候,偶然发现一篇文章提到可能是 SELinux 导致登录失败,于是尝试关闭 SELinux,发现果然可以登录成功了,那么就可以确认确实是 SELinux 导致无法登录了。

>$ sudo setenforce 0
>$ getenforce
Permissive

继续搜索 SELinux 导致 ssh 登录失败的情况要如何解决,但网上给出的解决方案基本都是把 SELinux 给关掉,我并不希望关掉它,经过一番搜索和实验后,终于找到了解决方案。

先说结论:

由于前段时间家目录所在的分区经常出现逻辑错误,于是我把家目录的数据备份了一份,将家目录的分区格式化了一下,然后再把数据恢复回来,结果 ${HOME}/.ssh 文件夹以及其下面的所有文件的Security context(安全上下文)属性都变成了默认属性,而 .ssh 文件夹的安全上下文属性是要 sshd 进程有权限访问才可以的,因此刷新一下 .ssh 文件夹的安全上下文属性就可以了。

>$ restorecon -Rv ${HOME}/.ssh

 

下面借助这个例子来说明如何为进程配置文件的安全上下文属性。

ssh 登录失败的情况下,先使用 audit2why 命令分析一下 SELinux 的日志,将其转换为人类容易读的格式。-d 参数表示从 dmesg 命令的输出中分析,也就是从 kernel 的日志中分析 SELinux 的日志。

>$ audit2why -d
[  151.733050] audit: type=1400 audit(1665716442.778:379): avc:  denied  { read } for  pid=3359 comm="sshd" name="authorized_keys" dev="sda1" ino=19030695 scontext=system_u:system_r:sshd_t:s0-s0:c0.c1023 tcontext=unconfined_u:object_r:unlabeled_t:s0 tclass=file permissive=0
    Was caused by:
        Missing type enforcement (TE) allow rule.

        You can use audit2allow to generate a loadable module to allow this access.

从上面的内容不难看出来,sshd 进程尝试从 /dev/sda1(也就是我要登录的用户的家目录所在的分区)读取({ read }) authorized_keys 文件时被拒绝了,permissive=0 表示操作被拒绝。

限于篇幅,有关 SELinux 的基础知识本文就不介绍了,需要了解的同学请移步至文末的参考文献链接。

每个进程和文件都有自身的安全上下文属性,在 MAC(强制访问控制) 模式下,进程的安全上下文和文件的安全上下文必须对应(不是必须一样)才能对文件进行访问。

所以首先我们分别看一下文件的安全上下文和进程的安全上下文都是什么,通过 ls -Z 和 pz -Z 即可查看。

>$ ls -Z .ssh/authorized_keys
user           role       type          sensitivity    category
unconfined_u : object_r : unlabeled_t : s0                         .ssh/authorized_keys
>$ ps -Z | grep sshd
system_u     : system_r : sshd_t      : s0-s0        : c0.c1023    sshd

安全上下文由 user、role、type、sensitivity、category 五个部分组成,每个部分用冒号隔开。

最关键的就是 type 字段,从上面的结果可以看到,sshd 进程的 type 是 sshd_t;.ssh/authorized_keys 文件的 type 是 unlabeled_t。因此我们需要看下 sshd_t 进程能够读取的文件类型是否包含 unlabeled_t

$ sesearch -A -s sshd_t | grep unlabeled_t
allow corenet_unlabeled_type unlabeled_t:association { recvfrom sendto };
allow corenet_unlabeled_type unlabeled_t:dccp_socket recvfrom;
allow corenet_unlabeled_type unlabeled_t:peer recv;
allow corenet_unlabeled_type unlabeled_t:rawip_socket recvfrom;
allow corenet_unlabeled_type unlabeled_t:tcp_socket recvfrom;
allow corenet_unlabeled_type unlabeled_t:udp_socket recvfrom;
allow domain unlabeled_t:packet { recv send };

 sesearch -A -s <进程type> 命令可以查询<type类型的进程>能够读取的<文件type>。

如果找不到 sesearch 命令,参见文末彩蛋。

由上面的结果可以看到, sshd_t 类型的进程并不能读取 unlabeled_t 类型的文件。

那么接下来的解决办法就是,找到一种 sshd_t 类型的进程可以读取的文件类型,并将该类型添加到 .ssh/authorized_keys 文件的安全上下文中,这样就能实现让 sshd 读取 authorized_keys 文件了。

>$ sesearch -A -s sshd_t
# ... 忽略不相关内容 ...
allow login_pgm ssh_home_t:dir { getattr ioctl lock open read search };
allow login_pgm ssh_home_t:file { getattr ioctl lock open read };
allow login_pgm ssh_home_t:lnk_file { getattr read };
allow ssh_server ssh_home_t:dir { add_name create link remove_name rename reparent rmdir setattr unlink watch watch_reads write };
allow ssh_server ssh_home_t:file { append create link rename setattr unlink watch watch_reads write };
# ... 忽略不相关内容 ...

在结果中找了以下,allow login_pgm ssh_home_t:file { getattr ioctl lock open read }; 这个 type 就是比较适合给.ssh/authorized_keys 文件的,于是将此 type 添加到 authorized_keys 文件的安全上下文中。

>$ chcon -t ssh_home_t .ssh/authorized_keys
>$ ls -Z .ssh/authorized_keys
unconfined_u:object_r:ssh_home_t:s0 .ssh/authorized_keys
>$ restorecon -v .ssh/authorized_keys

chcon 命令用于动修改文件的安全上下文,-R选项可以递归修改目录,-v选项可以显示修改前后的结果

chcon -t     # 修改文件的context的type字段

chcon -u     # 修改文件的context的user字段

chcon -r     # 修改role字段

chcon -l     # 修改安全级别

chcon -h     # 针对软链接文件的修改,不加-h则会修改软链接对应的原文件

chcon --reference=file # 以file的context为模板修改

修改完文件的安全上下文后,还需要使用 restorecon 命令修改 context 为期望 context,格式:restorecon 选项 目标文件。

restorecon -R    # 递归修改目录

restorecon -v    # 显示修改的过程

 

修改完成之后,再次尝试 ssh 登录,终于可以成功了。

 

== 彩蛋 ====================

在查找资料的过程中,发现了一个有趣的网站,域名叫做 command-not-found.com,这个名字相当直白了,只要你有找不到的命令,就可以在这个网站上查询到在各个系统中的安装命令是什么。

只需要将需要查询的命令跟在域名后面的路径中就可以了。比如查询 sesearch 这个命令,只需要在浏览器的地址栏中输入 https://command-not-found.com/sestatus 即可。

 

 

参考文献:

SELinux 基础知识

Install sesearch

posted on 2022-10-14 14:14  0xCAFEBABE  阅读(1528)  评论(0编辑  收藏  举报

导航