起因
线上生产环境服务器我们一般是用work账户,使用work账户启动线上服务。同时为方便操作,开发人员一般也可以使用work登录服务器进行操作, 比如查看日志,问题排查打印JVM堆栈,查看GC等。
但方便的同时,往往也意味着会有一些代价,比如误操作kill导致java进程被干掉。 误操作rm命令导致核心服务文件被删掉等。实际线上也发生过类似的问题,开发人员误操作了rm命令导致服务宕机,也有误操作rm命令把数据库删除的。
诉求比较简单:
- 对服务部署: 需要有单独的帐号用于服务部署,且权限范围有限。
- 对开发人员: 需能登录服务器,并做基本操作。 比如固定目录下文件操作,java进程的排查等等。
对应方案也较简单:
- work权限仅对部署机器生效,开发人员无work权限。
- 提供readonly权限给开发人员,提供readonly用户读写目录。 因为java进程启动是使用work权限启动的,所以需要能让readonly权限可以对java进程执行常用的排查命令,如jstack/jmap等等。 而这块的实现,可以通过su提权的方式实现。
实操
首先先创建两个帐号,work 和 readonly
# 添加work帐号
useradd -d /home/work -m work
# 设置密码
passwd work
# 添加readonly帐号
useradd -d /home/readonly -m readonly
# 设置密码
passwd readonly
使用work启动java进程
java -jar com.dutycode.api.weixin-0.0.1-SNAPSHOT.jar
查看当前的java进程号,可以看到进程是使用work账户启动
切换至readonly账户(或者使用readonly账户登录),当我们使用jstack命令去查看18121这个java进程的时候,会被提示18121: Operation not permitted
, 也就是权限受限。
我们需要使用root帐号,对readonly帐号进行提权
# 编辑sudoers
vim /etc/sudoers
# 在文件中新增:
readonly ALL=(work) NOPASSWD:/usr/java/default/bin
# 格式为:
# 格式: 用户 主机名=(目标用户) 命令,!命令(表示此命令不能使用)
# 上面的表示,对readonly,在ALL主机上可以使用work的权限,无需密码去执行/usr/java/default/bin下的命令。
增加提权之后,我们可以使用sudo 来对work用户下的java进程执行jstack命令
sudo -u work /usr/java/default/bin/jstack 18121
便可以在readonly用户下使用jstack对work用户的java进程操作了。
后记
操作提权本身不复杂。 有意思的是,一个公司的发展过程中,在服务器权限上走了很多路。 最开始的work权限,需要帐号密码,到后来的使用kerberos授权方式隐藏掉work密码,再到后来的readonly账户用于登录,经历了很多过程,也见证了公司的业务发展。
从前期的口头约定,变成了现在的系统约束,逐步降低了风险。