Dockerクローンもどきを実装する際のセキュリティ

# 概要
先日[CodeCrafters](https://app.codecrafters.io/tracks)というサービスで、ジェネリックDockerをGolangで開発しました。

実装する中で、注意すべき点がいくつかあったためその内容と理由をまとめます。

# Dockerとは

この記事ではDockerそのものについて詳しく解説しませんが、
[SI→Web業界への転職に利用したポートフォリオのDocker環境を振り返る](https://qiita.com/kotobuki5991/items/dccae16b2ae6af0d4512)や
[docker composeとdocker-compose、ハイフン有無で何が違うのか？](https://qiita.com/kotobuki5991/items/e2af78c11b4571f904e5)で説明しています。

# 事前準備

本コースの中では以下のようなコマンドを実行します。まずはそのために必要な準備を説明します。
```
$ mydocker run ubuntu:latest /usr/local/bin/docker-explorer echo hey

$ mydocker run ubuntu:latest /usr/local/bin/docker-explorer exit 1
```

### mydockerコマンドという名前で、エイリアスを作成する

まずは`mydocker`エイリアスを設定します。
```
alias mydocker='docker build -t mydocker . && docker run --cap-add="SYS_ADMIN" mydocker'
```

mydockerエイリアスで実行できるコマンドは以下のとおりです。

#### 1.　Dockerイメージのビルド

https://docs.docker.jp/get-started/part2.html#build-and-test-your-image
```
docker build -t mydocker .
```
これはカレントディレクトリにあるDockerfileを使用して、Dockerイメージをビルドするコマンドです。
-tオプションは、イメージにタグを付けるためのもので、ここでは"mydocker"というタグを指定しています。
.はDockerfileが存在するディレクトリ（今回はカレントディレクトリ）を表します。

#### 2.　Dockerイメージの実行

https://docs.docker.jp/get-started/part2.html#run-your-image-as-a-container

```
docker run --cap-add="SYS_ADMIN" mydocker
```
ビルドしたDockerイメージを実行します。
--cap-add="SYS_ADMIN"オプションは、実行中のコンテナにシステム管理者の特権（SYS_ADMIN）を付与するためのものです。
mydockerは先ほどビルドしたイメージのタグです。

Dockerコンテナに管理者特権を与える理由は、デフォルトでDockerコンテナは権限を持たず、コンテナ内でDockerデーモンを実行できないからです。
また、後述のPID namespaceの生成するために権限が必要となります。

https://docs.docker.jp/engine/reference/run.html#runtime-privilege-linux-capabilities

# 注意すべき点
それでは、注意すべき点を説明します。

## ファイルシステムの保護

ジェネリックDockerはマシンのローカルに存在するプログラムを実行します。
ただし、実行するプログラムは、ファイルシステム全体への書き込み権限を持っているため、悪意のある操作が行われる危険があります。
その対応策として、chrootが挙げられます。

### chrootとは

https://linuc.org/study/knowledge/420/


UNIX系システムでは、ファイルシステムのルートディレクトリは「/」です。chrootは、このルートディレクトリを任意のサブディレクトリに変更する技術です。
例えば「/mydir/chroot」に変更すると、プログラムはそこを起点としてファイルの読み書きを実行します。

この時、プログラムは/mydir/chroot 配下へはアクセスできますが、上の階層のファイルへはアクセスでません。

また、全てのソフトウェアでルートディレクトリが変更されるわけではなく、特定のソフトウェアで、chrootを使用する設定を行った時のみ変更されます。

chrootを使用する主な目的は、外部からの侵入阻止です。
chrootを使用していない場合、「/」を起点とした全てのファイルを操作される危険があります。一方でルートを「/mydir/chroot」に変更している場合は、その外部にあるディレクトリ・ファイル（/etc など）を操作されることを防止できます。

golangで/chrootディレクトリにルートディレクトリを変更するには以下のように記述します。
```go
syscall.Chroot("/chroot") // chrootする
os.Chdir("/chroot") // カレントディレクトリを変更する
```


## プロセスの保護

### PID namespace

https://docs.docker.jp/v1.12/engine/understanding-docker.html#namespaces

PID namespaceは、プロセスを分離し、保護するために使用します。
今回は、開発したプログラムを実行するプロセスを、dockerコンテナ内で他のプロセスから孤立させるために生成する必要があります。


プログラムを実行するプロセスを孤立させなければ、
ホストで実行している全プロセスを確認し、シグナルを送ることが可能となってしまうためセキュリティ上の問題が発生します。

> 名前空間は、グローバルなシステムリソースを抽象化して包み込み、名前空間内のプロセスには、グローバルリソースの独自の孤立したインスタンスがあるように見えるようにします。グローバルリソースの変更は、名前空間のメンバーである他のプロセスからは見えますが、他のプロセスからは見えません。
[Linux namespace in Go - Part 1, UTS and PID](https://songrgg.github.io/programming/linux-namespace-part01-uts-pid/)

- プロセスを孤立させない場合
```mermaid
flowchart TB
    subgraph PID namespace 1
    A[プログラムを実行するプロセス]
    B[ホストで実行している他プロセス] 
    A --> B
    end
```

- プロセスを孤立させた場合

```mermaid
flowchart TB
    subgraph PID namespace 1
    A[プログラムを実行するプロセス]
    end
    subgraph PID namespace 2
    B[ホストで実行している他プロセス] 
    end
    A -.-> |X| B
```

golangでPID namespaceを新たに生成するには、以下のような設定が必要です。
```go
cmd := exec.Command(command, args...)

cmd.SysProcAttr = &syscall.SysProcAttr{
    Cloneflags: syscall.CLONE_NEWPID,
}
```
また、CLONE_NEWPIDはLinuxで使用可能な定数です。VSCodeをMacOSで使用している場合、setting.jsonに以下の設定を追加しなければエラー表示されます。

```setting.json
"go.toolsEnvVars": {"GOOS" : "linux"}
```

# まとめ

このように、コンテナ技術やホストマシン上のファイルを実行するプログラムを用いるとき、悪意のある操作からの保護が重要となります。