I want to share data with different friends, but I don’t really want to create new users on my system. Because in most Linux distributions, the default umask is 022, which means that other users can access my home directory and read files at will, which means no privacy and no security. If my friend’s account/password is accidentally leaked, it will also endanger all the data in my system, I can’t imagine.

So I’m going to start a sshd service inside my Docker container. This way, I can use Linux namespace to isolate the file system, network and other resources, and cgroups to limit the quota of users. Although Linux namespace and cgroups are not absolutely secure and isolated, it is safer and less expensive than creating users directly.

Container Mirroring

There are already GitHub users who have made very simple and easy to use sshd images that can be used with a simple configuration. Of course, there are some concepts about ssh and sshd that should be clear (more on that later).

This image was made very simple: the sshd service was initialized and run directly based on the Alpine Linux installation.

Here is its 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"]

Configuration-related focus is all in this entry.sh, which contains various initialization scripts, such as initializing key pairs, creating users, etc.

Configuration files

sshd will load the /etc/ssh/sshd_config configuration file (also visible from Dockerfile) when it starts. If you have your own configuration file, you can mount it directly. If you don’t, it’s a simpler solution to use the environment variables provided by the image author, whose script will automatically create this configuration file for us with some of these environment variables.

Environment variables

The following is a non-exhaustive list of common environment variables, a full list and updates can be found earlier on the GitHub project home page.

name description example
SSH_USERS Initialize the user list. The format is: username:user ID:group ID. entry.sh creates these users during initialization. If you don’t need to create users other than root, you can leave them unset. SSH_USERS=www:48:48,admin:1000:1000
SSH_ENABLE_ROOT Whether to enable the root account. Not enabled by default. SSH_ENABLE_ROOT=false
SSH_ENABLE_PASSWORD_AUTH Whether to enable password login. If not, you can only use the public key to log in. SSH_ENABLE_PASSWORD_AUTH=true

Note: Environment variables are set in different ways in different environments. For example, in bash, in docker run parameters, in docker-compose files, etc. Readers should distinguish between them.

Server Key Pairs (Host Keys)

When an ssh client communicates with an ssh server (i.e. sshd), it needs to confirm each other’s identity. Both communicating parties need to provide credentials (public keys) and keep them in each party’s local configuration file after the first communication is manually confirmed, and subsequently do not need to confirm them again as long as the credentials remain the same. Otherwise, it will warn of credential changes, possible man-in-the-middle attacks, and require manual reconfirmation.

The author’s entry.sh script will automatically create these key pairs for us (if they don’t exist), but, as mentioned above, these key pairs should not change often or they will re-confirm the communication. So we should mount the directory where the key pair is located inside the container to prevent it from being recreated every time.

The path of this directory inside the container is: /etc/ssh/keys/ .

The directory after a successful run generally has generated various types of key pairs.

 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

The private keys without extensions and the public keys with .pub extensions are 4 pairs in total.

These files are also set with the correct permissions to prevent unauthorized access.

Creating users

Set the above environment variable SSH_USERS directly. Multiple users are separated by commas.

You can also add users inside the container using the useradd command, which is not discussed in this article.

If you don’t need to create users other than root, you don’t need this environment variable.

Public key login

After the ssh client is authenticated with the ssh server public key, the server writes this credential to .ssh/authorized_keys in the user’s home directory. You can mount your public key to this file (if you are the only one using the account) or append to it (multiple users using the same account). This will allow password-free login.

Special note: this public key file inside the container must be consistent with the uid/gid of the corresponding user inside the container, otherwise it is invalid (because it is not secure).

Password Login

Note: Logging in with a public key is a more secure and simpler way than logging in with a password and should be used in preference.

To enable password login, the environment variable SSH_ENABLE_PASSWORD_AUTH should be set to true. This step is executed in the entry.sh script before the sshd service is started. The new user will be added to the /etc/passwd file and the user password will be encrypted and saved in the /etc/shadow file.

Set password

Passwords need to be encrypted and then written to the /etc/shadow file. There are two ways to encrypt passwords, either with the mkpasswd command or with the web-side tool provided by the mirror author.

  • mkpasswd runs the following command.
1
$ docker run --rm -it --entrypoint mkpasswd panubo/sshd

After running, you will be prompted for a password, enter and the encrypted password will be displayed.

  • The web tool address is here: https://trnubo.github.io/passwd.html The author has declared that this is only a browser-side script and no data will be sent to any server. After entering the password and confirming it, you will get the encrypted password.

Once the password for the username is generated, it should be saved as an executable script file (e.g. set-passwd.sh) in the following format.

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

Finally, mount this script in the /etc/entrypoint.d/ directory inside the container, and all custom scripts in this directory will be executed when entry.sh is executed before sshd starts.

Example docker-compose.yml file

 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

Then docker-compose up will start it.

I won’t write the commands to run directly with docker for now, they can be easily converted from the docker-compose file. If you need help, you can comment after the article.

You can download it directly: sshd.tgz.

Login example

1
2
3
$ ssh -p 2222 root@localhost

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

Conclusion

The above process may seem like a lot of work, but the security and convenience it provides is much more rewarding than this small amount of work.

It’s also fun to go through the whole process and learn how our everyday ssh works!