背景介绍
最近,笔者接手公司的一项任务:建造服务器的堡垒跳板机。
关于跳板机的实现,其实简单版本网上一大堆,甚至更有开源堡垒机Jumpserver可供选择,方案很多。接下来会就我的实现方案,整理出几篇文章来做概要描述。
覆盖功能
正所谓兵马未动,粮草先行,在设计之前,先整理出我们一期中堡垒机要覆盖的基本功能点:
-
服务器统一账号权限管理,包括哪些用户可以对哪些服务器进行login,哪些用户有sudo权限;
-
用户行为记录,可在必要时回看审查;
-
用户登录校验审查;
现在初期的目标是将所有的linux服务器通过堡垒机进行管理把控,将来扩展下,同样可以通过ssh协议对 交换机、路由器、甚至是 Windows进行管理(目前windows已经可以实现通过ssh登录,不过这种方式就没有图形界面了且只能通过powershell来进行管理)。
架构设计
基于以上几点功能点,设计架构如下:
下面对这个架构图做下说明:
整体分为三层,总体来说,
第一层 校验用户是否有登录堡垒机的权限;
第二层真正为用户分配权限,同时判断经过第一层的用户是否有对目标机器操作的权限;
第三层则是真正登录/操作服务器的方式,在这里我将服务器的auth+sudo权限通过ldap来进行分布式动态管理,稍后会有专门的说明;
第一层:
登录入口,凡是有堡垒机使用权限的均可以由此入口处登录成功。
涉及主要服务: user login shell。
服务主要功能:
-
读取用户信息,判断是否有登录权限;
-
调用动态Token服务,验证用户passwd;
-
调用动态token服务,实现二维码扫码快速登录;
-
调用第二层中的授权服务api,获取&判断用户的login权限;
-
记录用户操作日志;
关联服务:
-
动态Token服务,类似于google auth,每个人的动态码均不一样,每分钟update一次,以此做登录堡垒机的校验,当然如果想简单,单独分配一个静态密码也可以;
第二层:
授权服务管理,获取登录用户当前的权限ip列表,判断用户的操作是否符合预授权。
涉及主要服务:授权管理服务
服务主要功能:
-
设置用户/team的 权限列表;
-
将权限数据下发至第三层的ldap集群;
-
提供api获取用户的权限list;
关联服务:
-
CMDB,以cmdb中的服务树为基本单位做授权,同时在cmdb中判断授权的服务器对象是否有效;
-
OA,以oa中的用户组为基本单位做权限授予,同时基于oa来判断用户是否有效;
第三层:
登录实体服务器&执行命令;
将所有目标服务器的ssh登录体系对接ldap集群,通过在ldap中设置用户的publickey & sudo等信息,来统一控制用户的登录权限&sudo权限。
目标规模:
使用两台服务器做ldap主从集群,所有实体服务器对接此集群,从而统一进行auth验证。
未完待续
整体的架构说明就简单这样,接下来对就每一层的具体实现在分别和大家分享。
堡垒跳板机实现——架构实现
总述
这是关于堡垒机实现的第二篇文章,主要阐述三层架构分别如何实现,包括第一层&第二层的设计与实现,即用户登录堡垒机的入口 & 授权验证, 第三层,如何通过ldap来统一管理服务器账号权限。
关于堡垒机三层架构可以参见前一篇blog:堡垒跳板机实现——整体架构
登录入口
先说第一层,这层的主要功能为检测用户是否有使用堡垒机的权限。
这个很好理解,总不能来个匿名用户就可以让他随意使用堡垒机,虽说在二层授权验证这里可以有效的抵挡,但是,既然可以在第一层有效的对无效用户做快捷的拦截,为什么要放后呢?
验证的方式可以有很多种,比如,如果用linux用作第一层架构中的服务器,那么可以天然的使用linux的user auth作为检验机制,单纯为有使用权限的用户在服务器上adduser
,单独创建一个唯一的32位的密码。
服务器定制
我们在这层的做法是 将用户验证与我们内部的动态Token服务相结合(类似google authenticator
),同时还要提供友好的登录shell界面,这样的话单纯的使用user auth就不太能够达到我们的目的,这里,我们对一层服务器做了ssh登录all permit定制,修改/etc/pam.d/sshd
:
auth required pam_permit.so
account required pam_permit.so
password required pam_permit.so
session required pam_permit.so
开启sshd的pam认证,修改sshd_config
:
UsePAM yes
然后重启sshd:
/etc/ini.d/sshd restart
同时将用户对应的默认login shell改为我们自定义的。
代码结构
放上我们的代码结构,主要结构如下:
├── gateway-shell
├── login-shell
├── mshell
└── sdshell
功能描述
接下来对上述文件做下功能描述:
-
login-shell: 用户的login shell,调用sdshell验证用户的login passwd,初始化用户的监控日志路径,并开启监控。
-
sdshell:读取用户的login passwd并判断验证结果,以exit code来对表示验证结果,0表示验证成功,1表示验证失败,2表示root用户登录并且验证成功。
-
mshell:验证用户passwd成功能,循环调用gateway-shell来读取用户的action操作选择,并执行。
-
gateway-shell:提供给用户操作cli界面,供用户选择操作。
调用顺序为:
login_shell(入口) -> sdshell(判断PIN+TOKEN) -> mshell -> gateway-shell(判断/获取 用户选项)
代码解读
login_shell:
当用户登录时首先进入到这里进行验证
1 #!/bin/bash 2 3 WORKDIR=`dirname $0` 4 source $WORKDIR/sentry.env 5 BINDIR="$WORKDIR" 6 #LOGDIR="/data0/logdir" 7 8 #1, 验证token 9 $BINDIR/sdshell 10 sdstats=$? 11 #sdstats=0 12 13 # 当sdshell返回为0时,表示当前用户登录成功 14 # 当sdshell返回为1时,表示当前用户验证失败 15 # 当sdshell返回为2时,表示root登录成功 16 if [[ $sdstats -eq 1 ]];then 17 exit 1 18 elif [[ $sdstats -eq 2 ]];then 19 /bin/bash 20 exit 0 21 fi 22 23 #2, gen_log_dir 24 user=`/usr/bin/whoami` 25 if [ ! -d $LOGDIR/$user ];then 26 mkdir -p $LOGDIR/$user 27 chown -R $user $LOGDIR/$user 28 chmod a+w $LOGDIR/$user 29 fi 30 31 #3, param 32 lip=`/usr/bin/env|grep SSH_CONNECTION|cut -f 2 -d =|cut -f 1 -d \' \'` 33 now=`date \'+%F-%k:%M:%S\'|tr -d " "` 34 user=`/usr/bin/whoami` 35 sip=`/usr/bin/env|grep SSH_CONNECTION|cut -f 2 -d =|cut -f 3 -d \' \'` 36 37 #4, begin script 38 export TMOUT=10 39 export SHELL=$BINDIR/mshell 40 /usr/bin/script -q -t 2>$LOGDIR/$user/$user-$lip-$sip-$now.time -f $LOGDIR/$user/$user-$lip-$sip-$now.txt
其中,3-5行做基本的环境变量初始化,第9行调用 sdshell 验证用户输入的动态码的验证结果,即,用户在这时进入到 sdshell 做验证,此时,就可以随意对 sdshell 做功能定制了,我们实现的功能定制界面类如:
是不是比较酷炫~~
当一切都验证完毕,我们进入到login_shell第24-35行,初始化用户行为日志记录的初始化。
我们这里使用linux自带的script命令做用户的记录操作,简单有效到没有朋友啊有木有!!
最后39-40两行,将用户的shell指向 mshell ,同时,开始记录用户行为。
mshell
现在用户的行为已经进入到这里,说明这个用户通过了第一层校验,现在需要他进行相关action操作。
1 #!/bin/bash 2 3 WORKDIR=`dirname $0` 4 source $WORKDIR/sentry.env 5 user=`/usr/bin/whoami` 6 GWDIR=$WORKDIR 7 privatekey=$RSAFILE 8 mkdir /home/$user/.ssh 2>/dev/null 9 cat $privatekey > /home/$user/.ssh/id_rsa 10 chmod 600 /home/$user/.ssh/id_rsa 11 12 while true 13 do 14 clear 15 $GWDIR/gateway-shell 16 gsstats=$? 17 18 # 返回值说明: 19 # 0 表示正常,要登录了; 20 # 1 表示立即quit 21 # 2 表示因为超时而quit 22 # 3 表示登录本地服务器 23 # 其他未异常 24 case $gsstats in 25 0) 26 ssh -2 -i ~/.ssh/id_rsa -l $user `cat $ACTIONDIR/$user.action` 27 ;; 28 1) 29 exit 30 ;; 31 2) 32 echo "action timeout" 33 exit 34 ;; 35 3) 36 /bin/bash 37 ;; 38 *) 39 echo "error" 40 sleep 2 41 ;; 42 esac 43 done
3-10行环境初始化,然后开启循环监听用户输入的action,为用户展示可使用的action list以及判断用户是否有对应操作的权限则都是第15行的 mshell 来进行操作,之后同样根据exit code来执行相关的动作。
对于 mshell ,同样可以自行设计相关的功能,我们设计的shell操作界面如下:
同样酷炫有木有!
一层架构总结
此时,一层架构中的流程实现基本完成,剩下的就是具体控制登录服务器的技术管理工作,即第三层的工作了,稍后为大家分享。
再次总结下一层架构中的调用关系:
login_shell(入口) -> sdshell(判断PIN+TOKEN) -> mshell -> gateway-shell(判断/获取 用户选项)
咦,好像缺少第二层的说明?
其实第二层主要做action行为授权&校验工作,还记得 gateway-shell 的作用吗:
“为用户展示可使用的action list以及判断用户是否有对应操作的权限 ”
在这里 gateway-shell 通过api与第二层做交互,对用户的行为及授权范围作了操作,具体交互的功能包括:
-
搜索服务器信息;
-
获取用户有权限的服务器list;
-
获取用户有权限的服务器Group list;
-
获取服务器Group下的server list;
-
获取当前所有有权限的user list
具体怎么实现? 相信1000个工程师有1001中不同的做法,这里就不在阐述~
如何统一管理服务器的登录账号
接入我们有1000台服务器,有100个人,需要统一管理 哪些账号 可以登录 哪些服务器,哪些账号 在 哪些服务器 上可以 sudo哪些命令 。
问题抛出来了,下面就是如何解决它。
单刀直入的方案,我为每个服务器根据需要创建对应的人的账号,在分别配置sudoer的权限。
缺点就不说了,改个配置能累死人。。。。。
所以,我们最好能有个地方能够统一来配置:
-
创建一个人的账号后,那么这个人就能登录所有的服务器;
-
管理这个人的登录服务器的范围;
-
为这个人分配统一的sudo权限,及在哪些目标服务器上有执行哪些命令的sudo权限;
上述的需求可以通过服务器接入ldap,通过ldap管理在实现上述的需求。
具体的过程这里不再累赘,可以参见网上的博文,还是挺多的,比如:
使用 OpenLDAP 集中管理用户帐号
基于 PAM 的 LDAP 用户身份验证
这里只贴上完成后类似的ldap结构:
[root@testldap openldap]# ldapsearch -x -LLL -D "cn=admin,dc=lianjia,dc=com" uid=test -W Enter LDAP Password: dn: uid=test,ou=people,dc=lianjia,dc=com objectClass: posixAccount objectClass: shadowAccount objectClass: person objectClass: inetOrgPerson objectClass: ldapPublicKey cn: System sn: li givenName: test displayName: test uid: test userPassword:: testpasswd uidNumber: 1001 gidNumber: 1001 gecos: System Manager homeDirectory: /home/test shadowLastChange: 16020 shadowMin: 0 shadowMax: 999999 shadowWarning: 7 shadowExpire: -1 employeeNumber: 20248353 mobile: 18519199234 mail: 654306390@qq.com postalAddress: beijing initials: test loginShell: /bin/bash sshPublicKey: ssh-rsa Axxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx32x9jF5cFDqktiTQIdD9 PMq8b86v2p8Es4us7OTzo7XomcjEPpfP/Realy9BOuteohA4JzezrAyFQhJui6BdovkzhnVRyFERJ uTA/19biQkCZB91XrWxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxJXH+f0 VOvx5FiF0bV3IfJt32cdmI8O7hNI+ttPCQ4V1R8vr0wIhCmUcKzD5vOx+0H9B1EY4d/imSxFHIebe 4l//rthyAr3x0XmNvuFD9khqfDK7bmXnHu26s++O8A1SDJ5beuu4xXl/mN8mc5WPmoQQSjIzruWPa jLx8m6HF root@channel.lianjia.com
这样,对于此用户,就可以通过password 或者 publickey的方式,登录服务器,同理,也可以对sudo进行类似的管理,还是真心强大的。
堡垒跳板机实现——ldap配置
这是关于堡垒跳板机的第三篇文章,之前介绍了实现堡垒跳板机的整体三层架构 和 设计实现:
堡垒跳板机整体架构
堡垒跳板机架构实现
本篇介绍整个体系中的一个补充,对第三层ldap架构的实现的具体操作进行下记录。
整体目标
后端服务器数量日益增加,账号的数量也在不断增加,账号的统一管理变得尤为重要。结合堡垒机,主要针对服务器账号体系接入LDAP管理做如下主要工作:
-
ldap server主从的搭建,ldap主从考虑用同步复制(syncrepl)实现,大致为slave到master以拉的模式同步目录树,master负责读写,slave只读。另外主从都需接入负载均衡提供读服务;
-
服务器账号接入ldap,客户端可以ssh远程连接服务器用户名和密码登录;
-
ldap管理客户端的公钥,使客户端可以ssh服务器免密码登录;
-
ldap管理服务器用户的sudo权限
ldap主从
syncrepl同步机制说明
openldap有自带的几种同步模式,也可以采用inotify+rsync
或者rsync
加入定时任务等方法。这里采用的是syncrepl
同步机制,syncrepl
是OpenLDAP服务器之间同步所使用的协议, 且属于slapd
的一个模块。在它基本的refreshOnly
同步模式下, 提供者使用基于拉模式的同步, 在拉模式同步下, 消费者定期拉取提供者( master) 服务器的内容来更新本地目录树并应用。
从库写入数据将出现此提示:
Enter LDAP Password: adding new entry "uid=test4,ou=people,dc=lianjia,dc=com" ldap_add: Server is unwilling to perform (53)
OpenLDAP的同步模式要求
1) OpenLDAP服务器之间需要保持时间同步。
2) OpenLDAP软件包版本保持一致。
3) OpenLDAP节点之间域名可以互相解析。
4) 配置OpenLDAP同步复制, 需要提供完全一样的配置及目录树信息。
5) 数据条目保持一致。
6) 额外的schema文件保持一致。
syncrepl同步机制的优缺点
此种模式的优势就是简单,仅slave
定期从master
拉取数据就行,并且能够完全同步,缺点是面对上万条的大量数据,并且写操作较多时,压力就会产生,鉴于ldap
本身的优势就是提供读的服务为主,可能一天的写进数据量都不会太大,此种模式理论上是可行的。
在slapd.conf中的配置
主master:
modulepath /usr/lib/openldap modulepath /usr/lib64/openldap moduleload syncprov.la #以上为开启同步模块 index entryCSN,entryUUID eq #添加索引,提高性能,此索引主要用于提高同步速度,同步需要使用到这二个字段 overlay syncprov #后端工作在overlay模式(不明,缺少此参数同步不能进行) syncprov-checkpoint 100 10 #当满足修改100个条目或者10分钟的条件时主动以推的方式执行 syncprov-sessionlog 100 #同步时的会话日志量
从slave:
modulepath /usr/lib/openldap modulepath /usr/lib64/openldap moduleload syncprov.la index entryCSN,entryUUID eq syncrepl rid=002 #节点ID必须在整个OpenLDAP集群中是唯一的, serverID与MySQL复制中的server-id是同等的概念 provider=ldap://admin.lianjia.com:389/ #主OpenLDAP服务器IP地址及端口 type=refreshOnly #设定模式为拉 retry="60 10 600 +" #在开始的10次每60秒重试一次,在10次以后每600秒重试一次 interval=00:00:00:10 #同步的时间间隔,interval格式day:hour:minitus:second) searchbase="dc=lianjia,dc=com" #从另外一台OpenLDAP服务器即master的根域同步条目 scope=sub #sub代表同步根域lianjia.com下的所有条目 schemachecking=off #不启用schema语法检测 bindmethod=simple #采用的同步验证模式为简单 binddn="cn=admin,dc=lianjia,dc=com" #使用admin.lianjia.com即管理员读取目录树信息 attrs="*,+" #同步所有属性信息 credentials=***************** #密码
Ldapclient(部署于服务器端)配置变更
相关package
-
openldap
-
oddjob
-
oddjob-mkhomedir
-
nss-pam-ldapd
-
openldap-clients
-
authconfig
-
openssh-ldap
开机自启动
-
messagebus
-
oddjobd
-
nslcd
-
sshd
服务器接入ldap的配置命令
authconfig --enablemkhomedir --enableldap --enableldapauth --ldapserver=ldap://admin.lianjia.com --ldapbasedn="dc=lianjia,dc=com" --enableforcelegacy --disablesssd --disablesssdauth --update --enablemkhomedir #创建家目录并配置相关用户家目录的环境变量,建议都采用oddjob软件的mkhomedir模块 --enableldap --enableldapauth --ldapserver=ldap://admin.lianjia.com --ldapbasedn="dc=lianjia,dc=com" #接入ldapserver --enableforcelegacy --disablesssd --disablesssdauth #不采用sssd验证的方式,默认会采用。 --update #对相关配置进行更新
服务参数配置
/etc/sudo-ldap.conf:
uri ldap://admin.lianjia.com/\' #ldap服务端地址 SUDOERS_BASE ou=sudoers,dc=lianjia,dc=com\' #sudo的规则都在组织单元sudoers下,服务端需先存在组织单元ou=sudoers和sudo相关的schema文件
/etc/nsswitch.conf:
#认证的顺序配置 原配置为: passwd: files shadow: files group: files netgroup: nisplus automount: files nisplus 现有配置为: passwd: files ldap shadow: files ldap group: files ldap netgroup: files ldap automount: files ldap sudoers: files ldap #配置ldap管理sudo规则时新增的
/etc/ssh/sshd_config:
PubkeyAuthentication yes #允许sshpublickey验证 AuthorizedKeysCommand /usr/libexec/openssh/ssh-ldap-wrapper #抓取ldapserver的用户配置的公钥的脚本 AuthorizedKeysCommandRunAs root #运行脚本命令用户为root
/etc/ssh/ldap.conf:
URI ldap://admin.lianjia.com BASE dc=lianjia,dc=com #服务端需先配置导入sshpublickey相关的schema文件 port 389 ssl off
/etc/nslcd.conf:
#配置客户端与后端的ldap认证服务器进行交互,新增如下 uid nslcd #默认 gid ldap #默认 uri ldap://admin.lianjia.com base dc=lianjia,dc=com ssl no tls_cacertdir /etc/openldap/cacerts
/etc/pam_ldap.conf:
#配置c/s交互中走pam的认证 uri ldap://admin.lianjia.com ssl no tls_cacertdir /etc/openldap/cacerts pam_password md5
/etc/pam.d/system-auth:
#系统认证走ldap,如su - user(ldap),debug打印相关日志信息 auth required pam_env.so debug auth sufficient pam_unix.so nullok try_first_pass debug auth requisite pam_succeed_if.so uid >= 500 quiet auth sufficient pam_ldap.so use_first_pass debug #新增行 auth required pam_deny.so account required pam_unix.so broken_shadow debug #新增broken_shadow参数,在作为account使用时,该参数用来忽略对影子密码的读错误 account sufficient pam_localuser.so account sufficient pam_succeed_if.so uid < 500 quiet account [default=bad success=ok user_unknown=ignore] pam_ldap.so debug #新增行 account required pam_permit.so password requisite pam_cracklib.so try_first_pass debug retry=3 type= password sufficient pam_unix.so sha512 shadow nullok try_first_pass use_authtok debug password sufficient pam_ldap.so use_authtok debug #新增行 password required pam_deny.so session optional pam_keyinit.so revoke session required pam_limits.so debug session optional pam_oddjob_mkhomedir.so umask=0022 debug #新增行,authconfig自动配置时,家目录umas为0077,手动改的0022 session [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid session required pam_unix.so debug session optional pam_ldap.so debug
/etc/sysconfig/authconfig:
#authconfig软件安装后所带的配置文件,配置文件用来跟踪LDAP身份认证机制是否正确启用,比如里面就配置了关闭sssd认证采用ldap认证,并配置家目录等等。 修改后的内容如下:(no-->yes) USESHADOW=yes USELDAPAUTH=yes USELOCAUTHORIZE=yes USELDAP=yes FORCELEGACY为yes
/etc/openldap/ldap.conf:
#主要用于客户端查询OpenLDAP服务器条目信息。 新增如下: TLS_CACERTDIR /etc/openldap/cacerts URI ldap://admin.lianjia.com BASE dc=lianjia,dc=com #以上内容除了/etc/nsswitch.conf配置文件的新增行sudoers: files ldap外,全部通过authconfig命令实现批量配置,操作前做了初始化的备份,不指定备份,authconfig也将上一次的配置备份。备份目录在/var/lib/authconfig/。
涉及的配置文件
变更过程中涉及到的配置文件有:
/etc/nsswitch.conf #配置认证顺序,比如认证时先走file还是ldap还是nis等等 /etc/nslcd.conf #这里是通过nslcd方式与OpenLDAP服务端进行交互。nslcd进程由nss-pam-ldapd软件包提供, 根据nslcd.conf配置信息, 与后端的认证服务器进行交互。 /etc/pam_ldap.conf #实现ldapclient与ldapserver的交互 /etc/openldap/ldap.conf #主要用于查询OpenLDAP服务器所有条目信息。 /etc/pam.d/system-auth #加入了pam_ldap.so的模块,主要用于系统本身的一些认证如:su - user /etc/pam.d/password-auth #各种密码的认证走的一个配置文件,其中sshd会include这个模块,所以sshd认证的相关参数配置在此 /etc/sysconfig/authconfig #authconfig软件安装后所带的配置文件,配置文件用来跟踪LDAP身份认证机制是否正确启用,比如里面就配置了关闭sssd认证采用ldap认证,并配置家目录等等。
问题总结
未自动创建nslcd的账户
有测试过程中出现安装完nslcd软件(nslcd用于与后端的ldap认证服务器进行交互),但是却没有自动创建nslcd的账户,原本应创建如下两个账户:
nscd:x:28:28:NSCD Daemon:/:/sbin/nologin
nslcd:x:65:55:LDAP Client User:/:/sbin/nologin
解决方案:yum reinstall -y nss-pam-ldapd
重新安装此软件
服务端slapd与客户端nslcd连接问题
问题描述:
ldap初始化的机器以及两台ldap服务端登陆缓慢,将近一分钟才能登陆上,watch -n 1 \'netstat -apn | grep slapd\'
发现ldap服务器的slapd
与客户端nslcd
建立的ESTABLISHED
非常之多,并且连接数一直在增长。
问题原因:
连接数过高时候(超过默认1024),slapd
就会hang死,并且此时打开的文件数也异常之高。服务端一直保持与客户端建立长连接,不主动释放连接,同时客户端默认与服务端是紧耦合的连接方式,会一直尝试连接服务端。
问题解决:
更改客户端与服务端的ESTABLISH POLICY
,将客户端/etc/pam_ldap.conf
中bind_policy hard
改为bind_policy soft
。/etc/pam_ldap.conf
与/etc/nslcd.conf
中的idle_timelimit timelimit bind_timelimit
(分别为不需要连接服务端时的连接断开时间,查询服务器数据的超时断开时间,连接服务器超时的连接断开时间)调整为5秒。将服务端idletimeout
调整为5秒,这样客户端在不需要ldap认证时,服务端会在5秒断开连接。
这样,服务端只在客户端请求ldap认证时才与客户端建立连接,并且闲置时间为5秒即断开连接,这样连接数不会太高,并且打开的文件数也不会太高,同时客户端也不会因为服务端不响应而一直尝试连接服务端。