一步步学KubeVirt CI (3) - gosu在容器中的使用

2022-11-25 16:16:42 浏览数 (1)

gosu在容器中的使用

容器中使用gosu的起源来自安全问题,容器中运行的进程,如果以root身份运行的会有安全隐患,该进程拥有容器内的全部权限,更可怕的是如果有数据卷映射到宿主机,那么通过该容器就能操作宿主机的文件夹了,一旦该容器的进程有漏洞被外部利用后果是很严重的。因此,容器内使用非root账号运行进程才是安全的方式。gosu类似linux中的su和sudo命令。但是既然有了su和sudo为何还要做出一个gosu来。因为:

  1. gosu启动命令时只有一个进程,所以docker容器启动时使用gosu,那么该进程可以做到PID等于1;
  2. sudo启动命令时先创建sudo进程,然后该进程作为父进程去创建子进程,1号PID被sudo进程占据;

拿经典的redis镜像举例

首先得了解RUN CMD ENTRYPOINT区别

三者的共同点是:都是执行命令;都有两种格式Shell格式和Exec格式。

不同点是:RUN命令执行命令并创建新的镜像层,通常用于安装软件包。CMD ENTRYPOINT是设置容器启动后默认执行的命令其参数且他们的组合官网有个说明。

No ENTRYPOINT

ENTRYPOINT exec_entry p1_entry

ENTRYPOINT [“exec_entry”, “p1_entry”]

No CMD

error, not allowed

/bin/sh -c exec_entry p1_entry

exec_entry p1_entry

CMD [“exec_cmd”, “p1_cmd”]

exec_cmd p1_cmd

/bin/sh -c exec_entry p1_entry

exec_entry p1_entry exec_cmd p1_cmd

CMD [“p1_cmd”, “p2_cmd”]

p1_cmd p2_cmd

/bin/sh -c exec_entry p1_entry

exec_entry p1_entry p1_cmd p2_cmd

CMD exec_cmd p1_cmd

/bin/sh -c exec_cmd p1_cmd

/bin/sh -c exec_entry p1_entry

exec_entry p1_entry /bin/sh -c exec_cmd p1_cmd

所以下面的Dockfile,docker-entrypoint.sh是0,redis-server是1

Dockfile

代码语言:javascript复制
FROM alpine:3.4...RUN addgroup -S redis && adduser -S -G redis redis...ENTRYPOINT ["docker-entrypoint.sh"]EXPOSE 6379CMD [ "redis-server" ]
代码语言:javascript复制
...RUN addgroup -S redis && adduser -S -G redis redis...

上面的需要root处理的步骤放这一段,后面启动redis-server用了非root用户启动。原因是下面的docker-entrypoint.sh脚本:

entrypoint.sh

代码语言:javascript复制
#!/bin/sh...# allow the container to be started with `--user`if [ "$1" = 'redis-server' -a "$(id -u)" = '0' ]; then
	find . ! -user redis -exec chown redis '{}'  	exec gosu redis "$0" "$@"fiexec "$@"

上面的代码有一层递归。

检测到root用户启动redis命令redis-server,就会做两件事:

  1. 找到当前目录的所有非redis用户文件并将找出的全部文件改成redis所有,find . ! -user redis是找出当前目录的所有非redis用户文件,-exec chown redis '{}' 是将找出的文件修改成redis用户所有。
  2. exec gosu redis "

第二次执行CMD ENTRYPOINT,因为是redis用户执行的,所以不进入if语句,直接exec "

该脚本的内容就是根据 CMD 的内容来判断,如果是 redis-server 的话,则切换到 redis 用户身份启动服务器,否则依旧使用 root 身份执行。比如:

代码语言:javascript复制
$ docker run -it redis iduid=0(root) gid=0(root) groups=0(root)

附 为何要用gosu代替sudo

sudo as it has unpredictable TTY and signal-forwarding behavior that can cause problems

上面是docker官方best practices提到的 https://docs.docker.com/develop/develop-images/dockerfile_best-practices/#user

gosu官方仓库有一段回复 https://github.com/tianon/gosu#gosu

su & gosu

代码语言:javascript复制
$ docker run -it --rm ubuntu:trusty su -c 'exec ps aux'USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0  46636  2688 ?        Ss   02:22   0:00 su -c exec ps a
root         6  0.0  0.0  15576  2220 ?        Rs   02:22   0:00 ps aux
$ docker run -it --rm ubuntu:trusty sudo ps auxUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  3.0  0.0  46020  3144 ?        Ss   02:22   0:00 sudo ps aux
root         7  0.0  0.0  15576  2172 ?        R    02:22   0:00 ps aux
$ docker run -it --rm -v $PWD/gosu-amd64:/usr/local/bin/gosu:ro ubuntu:trusty gosu root ps auxUSER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   7140   768 ?        Rs   02:22   0:00 ps aux

sudo: Why does sudo call fork() and exec() rather than just exec()?

If sudo merely called exec, then sudo couldn’t do things like run any cleanup tasks when the exec’d code completed. Take pam_open_session and pam_close_session for example.

https://linux.die.net/man/3/pam_open_session https://linux.die.net/man/3/pam_close_session

0 人点赞