在容器内运行 sshd 服务

陪她去流浪 桃子 2021年01月24日 编辑 阅读次数:3181

本文介绍在容器内提供 sshd 服务以与其他用户安全地共享数据。

背景

想要与不同的朋友共享数据,但是我又不太愿意在我的系统里面创建新的用户。 因为在大多数 Linux 发行版里面,用户默认的 umask 是 022,即其他用户可以任意进入我的家目录读文件,这样即没有隐私,也不安全。 万一朋友的账号/密码不小心泄露了,还连带危害到我系统内的所有数据,不敢想像。

所以我打算在 Docker 容器内启动一个 sshd 服务。 这样可以借助 Linux 的 namespace 来隔离文件系统、网络等资源,还可以借助 cgroups 来限制用户的配额。 虽然 Linux 的 namespace 和 cgroups 并非绝对安全隔离,但是相对于直接创建用户来说,还是安全、省心得多。

容器镜像

已经有 GitHub 用户制作了非常简单好用的 sshd 镜像,简单配置一下即可使用。 当然,关于 ssh 和 sshd 的一些概念是要明确的(后面会讲到)。

这个镜像制作得很简单:直接基于 Alpine Linux 安装 sshd 服务初始化并运行。

以下是其 Dockerfile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
FROM alpine:3.12

RUN apk update && \
    apk add bash git openssh rsync augeas shadow rssh && \
    deluser $(getent passwd 33 | cut -d: -f1) && \
    delgroup $(getent group 33 | cut -d: -f1) 2>/dev/null || true && \
    mkdir -p ~root/.ssh /etc/authorized_keys && chmod 700 ~root/.ssh/ && \
    augtool 'set /files/etc/ssh/sshd_config/AuthorizedKeysFile ".ssh/authorized_keys /etc/authorized_keys/%u"' && \
    echo -e "Port 22\n" >> /etc/ssh/sshd_config && \
    cp -a /etc/ssh /etc/ssh.cache && \
    rm -rf /var/cache/apk/*

EXPOSE 22

COPY entry.sh /entry.sh

ENTRYPOINT ["/entry.sh"]

CMD ["/usr/sbin/sshd", "-D", "-e", "-f", "/etc/ssh/sshd_config"]

配置相关的重点全部在这个 entry.sh 里面,其包含了各种初始化脚本,比如:初始化钥匙对、创建用户等。

配置文件

sshd 在启动的时候会加载 /etc/ssh/sshd_config 这个配置文件(从 Dockerfile 也可以看到)。 如果你有自己的配置文件,可以直接挂载进去。如果没有的话,直接使用镜像作者提供的环境变量是更简单的方案,作者的脚本会自动以这些环境变量中的一些为我们创建这个配置文件。

环境变量

以下是一些不完整的常用环境变量列表,完整的列表及更新可以见前面的 GitHub 项目主页

名字 说明 示例
SSH_USERS 初始化用户列表。
格式为:用户名:用户ID:组ID
entry.sh 在初始化的时候会创建这些用户。

如果不需要创建 root 以外的其他用户,可以不用设置。

SSH_USERS=www:48:48,admin:1000:1000
SSH_ENABLE_ROOT 是否启用 root 帐户。
默认不启用。
SSH_ENABLE_ROOT=false
SSH_ENABLE_PASSWORD_AUTH 是否启用密码登录。
不启用则只能使用公钥登录。
SSH_ENABLE_PASSWORD_AUTH=true

注意:环境变量的设置在不同的环境下有不同的设置方式。比如 bash 中、docker run 的参数中、docker-compose 的文件中等等,读者应自行区分。

服务器钥匙对( Host Keys)

ssh 客户端在与 ssh 服务端(即 sshd)通信时,需要确认彼此的身份,通信的双方都需要提供凭证(公钥),并且在第一次通信手动确认后将其保持在各方的本地配置文件中,后续只要凭证不变,则不需要再次确认。否则会警告凭证变更,可能受到中间人攻击,需要手动再次确认。

作者的 entry.sh 脚本会自动为我们创建这些钥匙对(如果不存在的话),但是,如上面所说,这些钥匙对不应该经常变化,不然会重新确认通信。所以我们应该把钥匙对所在的目录挂载到容器内,以防止每次都重新创建。

这个目录在容器内的路径为:/etc/ssh/keys/

成功运行后的目录一般有生成各种类型的钥匙对:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
$ tree -pug /etc/ssh/keys
/etc/ssh/keys
├── [-rw------- root root]  ssh_host_dsa_key
├── [-rw-r--r-- root root]  ssh_host_dsa_key.pub
├── [-rw------- root root]  ssh_host_ecdsa_key
├── [-rw-r--r-- root root]  ssh_host_ecdsa_key.pub
├── [-rw------- root root]  ssh_host_ed25519_key
├── [-rw-r--r-- root root]  ssh_host_ed25519_key.pub
├── [-rw------- root root]  ssh_host_rsa_key
└── [-rw-r--r-- root root]  ssh_host_rsa_key.pub

0 directories, 8 files

不带扩展名的是私钥,带 .pub 扩展名的是公钥,一共 4 对。

这些文件同样也都会被正确地设置权限以防止被非法读取。

创建用户

直接设置上述环境变量 SSH_USERS 即可。多个用户用逗号分隔。

也可以在容器内使用 useradd 命令添加用户,本文暂不讨论。

如果不需要创建 root 以外的用户,则不用此环境变量。

公钥登录

ssh 客户端在被 ssh 服务器公钥认证后,服务端会把这个凭证写入到用户家目录下的 .ssh/authorized_keys 中。你可以把你的公钥挂载成这个文件(如果只有你一个人使用这个帐号的话)或者追加到这个文件中(多个用户使用同一个帐号)。这样就可以免密码登录了。

特别注意:容器里面的这个公钥文件必须与容器内对应用户的 uid/gid 一致,否则无效(因为不安全)。

密码登录

注:使用公钥登录是比使用密码登录更安全也更简单的方式,应该优先使用。

为了启用密码登录,环境变量 SSH_ENABLE_PASSWORD_AUTH 应设置为 true。 然后需要为创建用户和设置用户密码,这个步骤是在 sshd 服务启动之前在 entry.sh 脚本中执行的。 新用户会添加到 /etc/passwd 文件中,用户密码会加密后保存在 /etc/shadow 文件中。

设置密码

密码需要先被加密然后写入 /etc/shadow 文件中。 密码加密有两种方式,一种是使用 mkpasswd 命令,一种是使用镜像作者提供的网页端工具。

  • mkpasswd

    运行下述命令即可:

    1
    
    $ docker run --rm -it --entrypoint mkpasswd panubo/sshd
    

    运行后,会提示你输入密码,回车后会显示加密后的密码。

  • 网页工具

    地址在这里:https://trnubo.github.io/passwd.html,作者已声明这仅是一个浏览器侧的脚本,不会有任何的数据发往任何服务器。

    输入密码及确认输入密码后,即可得到加密后的密码。

用户名对应的密码生成后,应以如下格式保存成一个可执行的脚本文件(比如:set-passwd.sh):

1
2
3
4
5
6
7
8
9
#!/usr/bin/env bash

set -e

echo '用户名1:加密后的密码1' | chpasswd --encrypted
echo '用户名2:加密后的密码2' | chpasswd --encrypted

# 示例
echo 'tao:$6$lAkdPbeeZR7YJiE3$ohWgU/hEZ2VOVKvxD...' | chpasswd --encrypted

最后,把这个脚本挂载进容器内的 /etc/entrypoint.d/ 目录内,sshd 启动前 entry.sh 执行的时候会执行这个目录内的所有自定义脚本。

示例 docker-compose.yml 文件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
version: "2"
services:
  sshd:
    image: panubo/sshd
    environment:
      - 'SSH_ENABLE_ROOT=true'
      - 'SSH_USERS=tao:1000:1000'
      - 'SSH_ENABLE_PASSWORD_AUTH=true'
    volumes:
      # 挂载目录以保存服务器钥匙对
      - ./config/keys:/etc/ssh/keys
      # 我的公钥,用于免密码登录 root 用户。
      # 可以直接用你现在的,比如:~/.ssh/id_rsa.pub
      - ./config/id_ed25519.pub:/root/.ssh/authorized_keys:ro
      # 设置用户密码的脚本(如果没有,请删除这行)
      - ./config/set-passwd.sh:/etc/entrypoint.d/set-passwd.sh:ro
      # 非 root 用户的家目录的父目录
      - ./home:/home
      # root 用户的家目录
      - ./home/root:/root
    ports:
      ## 别忘了把 sshd 服务暴露出来哦
      - '2222:22'
    restart: unless-stopped
    # 一些配额设置(非必须)
    mem_limit: 512m
    cpu_quota: 50000

然后 docker-compose up 即可启动。

直接使用 docker 运行的命令我暂时不写了,可以很容易地从 docker-compose 文件转换过去。如果读者有需要帮忙可以在文章后面评论。

可以直接下载:sshd.tgz

登录示例

1
2
3
$ ssh -p 2222 root@localhost

$ ssh -p 2222 -i config/id_ed25519 root@localhost

结语

上述过程看起来挺麻烦的,但相对于这一小点麻烦来说,其提供的安全与便捷收益更高。

完整地走完这个流程还可以学习到我们日常使用的 ssh 是如何工作的,何其乐也!

标签:ssh · docker