Dockerコンテナの実行ユーザーと権限の関係

- 最近、Dockerのコンテナの実行ユーザーと権限の関係について調べ直したので、内容をまとめました。
- 検証対象のDockerのバージョンは`20.10.12`です。
- 何かお気づきの点があればコメントいただけるとありがたいです

# 前提知識：Linuxのアクセス権の確認方法

- この記事では`ls -l`を用いてファイルやディレクトリのアクセス権を確認する。`ls -l`を実行結果は以下の通り。
  ```
  -rw-rw-r-- 1 user1 user1 210 4月 10 07:53 docker-compose.yml
  ```
- この見方がわからないと、この後の記事を理解するのが難しいため、ここで説明しておく

### アクセス権の見方

- 表示結果は以下のように分割して見る

  | オブジェクトのアクセス権 | ハードリンク数 | 所有者 | 所有グループ | ファイルサイズ | 更新日 | ファイル名 |
  | -- | -- | -- | -- | -- | -- | -- |
  | -rw-rw-r-- | 1 | user1 | user1 | 210 | 4月 10 07:53 | docker-compose.yml |

- 最初の10文字がアクセス権。上記の例では`-rw-rw-r--`。以下のように分割して見る

  | オブジェクトの種類(1文字目) | 所有者の権限(2～4文字目) | 所有グループの権限(5～7文字目) | その他グループの権限(8～10文字目) |
  | -- | -- | -- | -- |
  | - | rw- | rw- | r-- |

#### オブジェクトの種類(1文字目)

- 対象オブジェクトの種類を表す

  | 値 | 意味 |
  | -- | -- |
  | - | ファイル |
  | d | ディレクトリ |
  | l | シンボリックリンク |

#### 所有者の権限(2～4文字目)、所有グループの権限(5～7文字目)、その他グループの権限(8～10文字目)

- 各主体(所有者、所有グループ、その他)の権限情報を表す

  | 値 | 意味 | サンプル |
  | -- | -- | -- |
  | 2～4文字目 | 所有者の権限 | rw- |
  | 5～7文字目 | 所有グループの権限 | rw- |
  | 8～10文字目 | その他の権限 | r-- |

#### 各主体の権限の見方

- 各主体(所有者、グループ、その他)の権限の見方は以下の通り

  | モード | 説明 |
  | -- | -- |
  | - | 主体に権限がないということ |
  | r | 主体に読み取り権があるということ |
  | w | 主体に書き込み権があるということ |
  | x | 主体に実行権限があるということ |

以上がアクセス権の基本的な見方。以下、記事の本題に移る。

# コンテナの実行ユーザーと権限について

- 以下の流れでコンテナの実行ユーザーと権限について説明する
  - 実行ユーザーを指定しない場合
  - ホストのユーザーを、コンテナの実行ユーザーとして指定する場合
  - コンテナの実行ユーザーをホストのユーザーにマッピングする場合
  - RootlessモードでDockerを実行する場合

## コンテナの実行ユーザーを指定しない場合

- コンテナの実行ユーザーを指定しない場合、実行ユーザーは`root`となる
- これはセキュリティ的に危険。コンテナが乗っ取られた場合、ホストに危険な操作が実行される恐れがある
- コンテナから作成されたファイルの所有者も`root`となるため、ホスト側からの取り扱いが難しくなる

### 動作確認
- ホストの`/sample`ディレクトリをマウントし、`/bin/bash`を実行するDockerfileとdocker-composeを作成する
  ```docker:Dockerfile
  FROM ubuntu:latest
  RUN mkdir /sample
  CMD ["/bin/bash"]
  ```
  ```docker:docker-compose.yml
  version: "3.3"
  services:
    sample:
      build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - /sample:/sample
  ```
- 次に、`docker-compose up --build` → `docker-compose run sample`を実行。
- アクセス後、コンテナ側で`id`を実行し、ログインユーザーを確認する
  ```
  uid=0(root) gid=0(root) groups=0(root)
  ```
- 結果は上記の通り。実行ユーザーが`root`であることがわかる
- 次に、コンテナからファイル`sample03`を作成する

  ```
  echo "Hello" > /sample/sample03.txt
  ```

- その後、`sample03.txt`のアクセス権をホストから確認する。ホストの`/sample`ディレクトリに移動し、`ls -l`を実行

  ```
  -rw-r--r-- 1 root     root     6  5月  8 17:35 sample03.txt
  ```

- 結果は上記の通りで`sample03.txt`の所有者が`root`になっている

## コンテナの実行ユーザーを指定する場合

- `docker`コマンドの`--user`オプションや、`docker-compose`の`user`で実行ユーザーを指定する方法が一般的
- この時、コンテナのユーザーとホストのユーザーのuid/gidを一致させる方が望ましいが、工夫が必要。具体的には以下のいずれかの作業を行う
  ① ホストの`/etc/passwd`と`/etc/group`をマウントし、コンテナ実行時にホストの実行ユーザーを指定する
  ② ホストの実行ユーザーと同じUID/GUDでコンテナにユーザーを作成し、このユーザーでコンテナを実行する
  ③ (イメージに実行ユーザーが作成されている場合)、コンテナの実行ユーザーのUID/GUIを、ホストの実行ユーザーのUID/GIDで変更する

- 以下、①~③の方法を確認ます。

### 事前準備：ホスト側に実行ユーザーを作成する

- ホストに実行ユーザーを作成する。今回は`dockeruser`という名前のユーザーを作成する

```
useradd -m dockeruser
```

- 作成したユーザーのUID/GUIDを確認する。`id dockeruser`を実行

```
uid=1001(dockeruser) gid=1001(dockeruser) groups=1001(dockeruser)
```

- また、ホストのマウント先ディレクトリ`/sample`に対して、`dockeruser`がファイルを作成できるように権限を変更しておく

### ① `/etc/passwd`と`/etc/group`をマウントし、コンテナ実行時にホストの実行ユーザーを指定する

- 先ほどのdocker-composeを下記のように修正する。`/etc/passwd`と`/etc/group`をマウントし、実行ユーザー`dockeruser`を指定する
- `/etc/passwd`と`/etc/group`は読み取り専用でマウントしておく

  ```diff_docker:docker-compose.yml
  version: "3.3"
  services:
    sample:
      build:
      context: .
      dockerfile: Dockerfile
  + user:1001:1001
    volumes:
      - /sample:/sample
  +   - /etc/passwd:/etc/passwd:ro
  +   - /etc/group:/etc/group:ro
  ```

- Dockerfileは変更しない

  ```docker:Dockerfile
  FROM ubuntu:latest
  RUN mkdir /sample
  CMD ["/bin/bash"]
  ```

- `docker-compose up --build` → `docker-compose run sample`を実行。
- コンテナにアクセスし、`id`コマンドでログインユーザーを確認する
  ```
  uid=1001(dockeruser) gid=1001(dockeruser) groups=1001(dockeruser)
  ```
- 結果は上記の通りで、指定した実行ユーザー(uid=1001)となっている
- 次に、コンテナからファイルを作成する

  ```
  echo "Hello" > /sample/sample03-1.txt
  ```

- 作成されたファイル`sample03-1.txt`のアクセス権をホストから確認する。ホストの`/sample`ディレクトリに移動し、`ls -l`を実行する

  ```
  -rw-r--r-- 1 dockeruser dockeruser 6  5月  8 18:11 sample03-1.txt
  ```

- 結果は上記の通りで、作成したファイル`sample03-1.txt`の所有者が指定した実行ユーザー`dockeruser`となっている

### ② ホストの実行ユーザーと同じUID/GUDでコンテナにユーザーを作成し、このユーザーでコンテナを実行する

- コンテナ実行時に、実行ユーザーを作成するスクリプト(createuser.sh)が実行され、かつ、作成された実行ユーザーでコンテナが開始されるようにする
- 先ほどのDockerfileを下記のように修正

```diff_docker:Dockerfile
FROM ubuntu:latest
RUN mkdir /sample
+ RUN apt-get update && apt-get -y install gosu
+ COPY createuser.sh /usr/bin/createuser.sh
+ RUN chmod +x /usr/bin/createuser.sh
+ ENTRYPOINT ["/usr/bin/createuser.sh"]
CMD ["/bin/bash"]
```

- 実行ユーザーを作成するcreateuser.shは以下。

```shell:createuser.sh
#!/bin/bash
USERID=${USER_ID}
GROUPID=${GROUP_ID}
echo "Create User = $USERID. Group = $GROUPID"
groupadd -g $GROUPID dockeruser
useradd -m -s /bin/bash -u $USERID -g $GROUPID dockeruser
exec /usr/sbin/gosu dockeruser "$@"
```
- docker-composeは以下のように修正。コンテナ実行時の環境変数に実行ユーザー`dockeruser`のUID (1001)とGIUD (1001)を指定し、`createuser.sh`に渡るようにする

```diff_docker:docker-compose.yml
version: "3.3"
services:
  sample:
    build:
    context: .
    dockerfile: Dockerfile
+ environment:
+   - USER_ID=1001
+   - GROUP_ID=1001
  volumes:
    - /sample:/sample
```

- `docker-compose up --build` → `docker-compose run sample`を実行
- ① と同様に、コンテナ側でマウント先のディレクトリ`/sample`にファイルを作成し、作成されたファイルのアクセス権をホストから確認する
- 結果、実行ユーザ`dockeruser`が所有者としてファイルが作成されている

### ③ イメージに作成された実行ユーザーのUID/GUIを、ホストの実行ユーザーのUID/GIDで変更する

- コンテナ開始時、イメージで作成済の実行ユーザーのUID/GIDを変更するスクリプト(modifyuser.sh)が実行されるようにする
- Dockerfileを下記のように修正する。

```diff_docker:Dockerfile
FROM ubuntu:latest
RUN mkdir /sample
+ RUN apt-get update && apt-get -y install gosu
+ RUN groupadd -g 2000 dockeruser && \
+     useradd -m -s /bin/bash -u 2000 -g 2000 dockeruser
+ COPY modifyuser.sh /usr/bin/modifyuser.sh
+ RUN chmod +x /usr/bin/modifyuser.sh
+ ENTRYPOINT ["/usr/bin/modifyuser.sh"]
CMD ["/bin/bash"]
```
- 実行ユーザーのUIDを変更するmodifyuser.sh

```shell:modifyuser.sh
#!/bin/bash
USERID=${USER_ID}
GROUPID=${GROUP_ID}
echo "Modify User = $USERID  Group = $GROUP_ID"
groupmod -g $GROUP_ID dockeruser
usermod -u $USERID -m -d /home/dockeruser dockeruser
exec /usr/sbin/gosu dockeruser "$@"
```

- docker-compose。コンテナ実行時の環境変数にホストのユーザー`dockeruser`のUID (1001)とGIUD (1001)を指定

```diff_docker:docker-compose.yml
version: "3.3"
services:
  sample:
    build:
    context: .
    dockerfile: Dockerfile
+ environment:
+   - USER_ID=1001
+   - GROUP_ID=1001
  volumes:
    - /sample:/sample
```
- `docker-compose up --build` → `docker-compose run sample`を実行
- コンテナにアクセスし、マウント先のディレクトリ`/sample`にファイルを作成。作成されたファイルのアクセス権をホスト側から確認する
- 結果、実行ユーザ`dockeruser`が所有者としてファイルが作成されている

## コンテナの実行ユーザーをホストのユーザーにマッピングする

- コンテナ内の実行ユーザーは`root`のまま、ホストの実行ユーザーをホストの任意のユーザーに置き換える方法

- まず、`/etc/docker`に`daemon.json`を作成する

```json:daemon.json
  {
    "userns-remap" : "default"
  }
```

- 作成後、`sudo systemctl restart docker`でdockerを再起動する
- 再起動後、`/etc/subuid`を確認すると、以下のような`dockermap`のエントリーが作成されている。

```:subuid
  dockremap:231072:65536
```
- これにより、コンテナの実行ユーザーのUIDは、ホスト側のUID(231072~)にマッピングされる

- 次に、以下のDockerfileとdocker-composeを作成。実行ユーザーは特に指定していないため、コンテナの実行ユーザーは`root`となる

 ```docker:Dockerfile
  FROM ubuntu:latest
  RUN mkdir /sample
  CMD ["/bin/bash"]
  ```
  ```docker:docker-compose.yml
  version: "3.3"
  services:
    sample:
      build:
      context: .
      dockerfile: Dockerfile
    volumes:
      - /sample:/sample
  ```
- `docker-compose up --build` → `docker-compose run sample`を実行

- コンテナにアクセスし、`id`を実行して実行ユーザーのUIDを確認する

```
uid=0(root) gid=0(root) groups=0(root)
```

- 結果は上記の通りで、コンテナ側の実行ユーザーは`root`であることがわかる

- 次に、コンテナ側からマウント先のディレクトリ`/sample`に移動し、ファイル`sample04.txt`を作成する

```
echo sample04 > sample04.txt
```

- 作成された`sample04.txt`のアクセス権をホスト側から`ls -l`で確認する

```
-rw-r--r-- 1     231072     231072  8  5月 14 22:03 sample4.txt
```

- `sample04.txt`の所有者のUIDは`231072`。これは`/etc/subuid`に新しく追加された`dockremap`の最初のサブUIDとなっている

## RootlessモードでDockerを実行する

- userns-remapを利用する場合、コンテナ側の実行ユーザーは`root`となってしまう
- これが許容できない場合、rootlessモードでdockerを実行する
- 参考：[Dockerデーモンをル－ト以外のユーザーで実行する (Rootlessモード)](https://docs.docker.jp/engine/security/rootless.html)