【Docker】docker-composeを使ってコンテナ間ssh通信

何番煎じか分かりませんが、せっかく勉強したのでコンテナ間ssh通信ができるシンプルな環境を構築してみました。


# 方針
・`docker compose up`するだけで簡単にssh通信を検証できる仕組みにしたい
・ホストマシンの~/.sshとかを汚さず検証したい
・sshなのでセキュアな感じで検証したい

こんなイメージを元に以下のような環境を構築してみようと思います。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3537959/8cc5a050-a02c-0f08-56e0-37fa8af23576.png)

# 実装
* ディレクトリ構成
```shell
% tree
.
├── compose.yml
├── local-volume
│   ├── local_config
│   ├── local_id_rsa
│   ├── local_id_rsa.pub
│   └── local_known_hosts
├── ssh-client
│   └── Dockerfile
└── ssh-server
    └── Dockerfile
```

* Compose.yml
```yml
# networksについて
# bridgeならデフォルトで設定されるため省略でも問題ないが、subnet適用の挙動確認のため記載している

# version表記は現在は非推奨の模様
# version: "3"

services:
  ssh_server:
    build: 
      context: .
      dockerfile: ./ssh-server/Dockerfile
    container_name: ssh_server
    hostname: master
    privileged: true # /sbin/initを特権モード(Dockerデーモンへのアクセス)で実行するために必要
    command: /sbin/init
    volumes:
      - ./local-volume/local_id_rsa.pub:/home/ssh-man01/.ssh/authorized_keys
    expose:
      - "2222" # コンテナ間通信のみ実現させたいのportsではなくexposeでOK
    networks:
      ssh_network:
        ipv4_address: 192.168.10.2

  ssh_client:
    build: 
      context: .
      dockerfile: ./ssh-client/Dockerfile
    container_name: ssh_client
    hostname: slave
    tty: true # コンテナの起動状態を継続させる
    volumes:
      - ./local-volume/local_known_hosts:/root/.ssh/known_hosts
      - ./local-volume/local_id_rsa:/root/.ssh/id_rsa
      - ./local-volume/local_config:/root/.ssh/config
    networks:
      ssh_network:
        ipv4_address: 192.168.10.3

networks:
  ssh_network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.10.0/24
```

* Dockerfile(ssh-client)
```Dockerfile
FROM ubuntu:20.04

RUN apt -y update && \
    apt clean all && \
    apt -y install openssh-client && \
    mkdir -m 700 ~/.ssh
```

* Dockerfile(ssh-server)
```Dockerfile
FROM ubuntu:20.04

RUN apt -y update && \
    apt clean all && \
    apt -y install openssh-server && \
    sed -ri 's/^#PasswordAuthentication yes/PasswordAuthentication no/' /etc/ssh/sshd_config && \
    sed -ri 's/^#Port 22/Port 2222/' /etc/ssh/sshd_config && \
    useradd -m ssh-man01 && \
    install -m 700 -o ssh-man01 -d /home/ssh-man01/.ssh
```

* local_config
```shell
Host master
    HostName master
    User ssh-man01
    Port 2222
    IdentityFile ~/.ssh/id_rsa
    ServerAliveInterval 60
    ServerAliveCountMax 5
```

# 検証
#### 事前準備
`ssh-keygen -t rsa`で鍵生成して公開鍵/秘密鍵をlocal-volumeディレクトリに配置し、それぞれ**権限600**に変更
```shell
% ls -l local-volume 
total 32
-rw-r--r--@ 1 user  staff   148  9 20 12:44 local_config
-rw-------@ 1 user  staff  2602  9 19 20:30 local_id_rsa
-rw-------@ 1 user  staff   564  9 19 20:15 local_id_rsa.pub
-rw-r--r--@ 1 user  staff   444  9 20 14:40 local_known_hosts
```

#### コンテナ起動
```shell
% docker compose up -d                                  
[+] Running 3/3
 ✔ Network ssh_network   Created                                                                                                                                                                       0.0s 
 ✔ Container ssh_client  Started                                                                                                                                                                       0.3s 
 ✔ Container ssh_server  Started
```

#### network構成確認
・「指定したnetwork名・sbunetで構築できていること」を確認
・「指定したipv4アドレスが各コンテナに割り当てられ、指定のネットワークに属していること」を確認
```json
% docker network inspect ssh_network 
[
    {
        "Name": "ssh_network",
        "Id": "e21a0a50a33a5c6b69f752f4f6114adaac471df40447b329ce80b9135cfefc4e",
        "Created": "2023-09-20T05:58:58.725362953Z",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": null,
            "Config": [
                {
                    "Subnet": "192.168.10.0/24"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "7be6980344c041e01f0b8fb64d77b86ff8e02836710e16cab91f4765f6f0340a": {
                "Name": "ssh_server",
                "EndpointID": "9fe9aaa47cd88b9dbd895a9da08f98af9b9bfdbcc0c6b2c3dbfc42ebea7847f4",
                "MacAddress": "02:42:c0:a8:0a:02",
                "IPv4Address": "192.168.10.2/24",
                "IPv6Address": ""
            },
            "9b517b7fb1130382bc11b392a792a76294b49025c4cfd9e6e4a4e3850eed16b1": {
                "Name": "ssh_client",
                "EndpointID": "e93a15cf99024c764bc0e7104acb2a49d2507989f4652cab77aa5b5a6788e5d8",
                "MacAddress": "02:42:c0:a8:0a:03",
                "IPv4Address": "192.168.10.3/24",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {
            "com.docker.compose.network": "ssh_network",
            "com.docker.compose.project": "docker-compose-test",
            "com.docker.compose.version": "2.20.2"
        }
    }
]
```

#### slave側のコンテナへログイン
```shell
% docker exec -it ssh_client bash
root@slave:/#
```

#### master側へssh
「ssh-man01というユーザ名でmaster側へsshログインできること」を確認
```shell
# ~/.ssh/configを設定しているため下記コマンドで公開鍵認証が可能です
root@slave:/# ssh master
The authenticity of host '[master]:2222 ([192.168.10.2]:2222)' can\'t be established.
ECDSA key fingerprint is SHA256:DQSQdUdNphmtnOPpzKDEl/XMNCO+KN+Pz5sIEBzVGwo.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes

-- (中略) --

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

$ whoami
ssh-man01
$ 
```


#### ホストマシンからssh
「ホストマシンからはsshサーバのホストが解決できないこと」を確認
```shell
% ssh -i ./local-volume/local_id_rsa -oPort=2222 ssh-man01@192.168.10.2

ssh: connect to host 192.168.10.2 port 2222: Operation timed out
```

<br />
ということで、当初の構想通り環境を構築することができました！



# まとめ
* DockerよりもLinuxサーバーやネットワークの勉強した方がよさそう...（Dockerはここらへんの基礎がしっかりあっての応用という感じがした）
* とはいえ、あまり複雑な構成でなければ割とハードル低く使っていけそう
* 今回はローカルマシン汚さずにssh検証することができたので、Dockerの良さを少し感じることができた

# 余談
さらっとできたように書きましたが、めちゃくちゃ時間かかってます...(._.)
そもそもSSHサーバー起動ってどうやるの？って感じでした。
sshd頑張って起動しようとしてみたり、Dockerhubのopenssh-serverなど使ってみようとしてみたり、、全然うまくいかず、試行錯誤の末以下の記事に助けられました。

https://zenn.dev/akhmgc/articles/52c90fe6100eaf

`privileged`は
・Dockerデーモン実行したい時
・ホストのハードウェアに直接アクセスしたい時
に使う特権モードのようです。（今回のケースは前者ですかね）
ホストコンピュータのルート権限を持つことと同義のようで、セキュリティ的な危険性が発生するようです。
今回はローカルで閉じた環境なので気にしなくていいですが、業務で使う際は十分気をつけないといけないですね。

# 参考
* https://zenn.dev/akhmgc/articles/52c90fe6100eaf
* https://docs.docker.jp/compose/toc.html
* https://www.udemy.com/share/109eIi3@V90_P314cpKcCxH5Tm1w78iWeugFigCOir2n73wrMli62ZMwkR4I9ylXZWRV6KVFCA==/
* https://josysnavi.jp/2020/tmsb_21jan20_docker-in-docker
* https://utouto97.hatenablog.com/entry/2022/03/15/092924
* https://qiita.com/mountcedar/items/43157ff1225c56500655