今日覚えて帰ること
Dockerfile Best Practices
- 最終的に動くコンテナは軽量になるようにマルチステージビルドを行う
- コンテナを動かすユーザーは非rootにする
- レイヤーが残ることを忘れない
Static Manual Analysis Docker
Dockerfile 1
Perform a manual static analysis on files
/root/apps/app1-*
considering security.Move the less secure file to
/root/insecure
ベストプラクティスに則っていないファイルを移動せよ、と言っています。
これが3問続きますが、問題文は同じなので以後省略します。
それでは見ていきます。
controlplane $ cd /root/apps/
controlplane $ ll
total 32
drwxr-xr-x 2 root root 4096 Sep 16 05:55 ./
drwx------ 14 root root 4096 Sep 16 05:55 ../
-rwxr-xr-x 1 root root 193 Sep 16 05:55 app1-214422c7-Dockerfile*
-rwxr-xr-x 1 root root 160 Sep 16 05:55 app1-9df32ce3-Dockerfile*
-rwxr-xr-x 1 root root 336 Sep 16 05:55 app2-2782517e-Dockerfile*
-rwxr-xr-x 1 root root 200 Sep 16 05:55 app2-5cde5c3d-Dockerfile*
-rwxr-xr-x 1 root root 295 Sep 16 05:55 app3-1c8650b1-Dockerfile*
-rwxr-xr-x 1 root root 346 Sep 16 05:55 app3-4049a117-Dockerfile*
controlplane $ cat app1-214422c7-Dockerfile
FROM ubuntu
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y golang-go
COPY app.go .
RUN CGO_ENABLED=0 go build app.go
FROM alpine
COPY --from=0 /app .
CMD ["./app"]
controlplane $
controlplane $ cat app1-9df32ce3-Dockerfile
FROM ubuntu
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y golang-go
COPY app.go .
RUN CGO_ENABLED=0 go build app.go
CMD ["./app"]
controlplane $
今回のポイントはマルチステージビルドです。
コンテナは基本的に軽量のほうが良いとされており、マルチステージビルドを利用することでアプリをビルドするための環境と実際にアプリを動かす環境を分離することが可能となり、コンテナの軽量化を実現することができます。
それでは、app1-214422c7-Dockerfileについて説明していきます。
まず最初のステージ (ubuntu
) で、Go言語のビルド環境をセットアップし、app.go
というGo言語のソースコードから実行可能なバイナリ(/app
)を生成します。
次に、軽量な alpine
イメージに切り替え、ビルドされた実行ファイルだけを持ち込んで、コンテナのサイズを小さくします。
最後に、そのビルド済みアプリケーションをコンテナ内で実行するように設定します。
ということで、ベストプラクティスに従っているのはapp1-214422c7-Dockerfileのほうでした。
alpineは互換性の問題からあまり推奨されない向きもありますが、とりあえず今回の問題に関してはこれが正解です。
Dockerfile 2
controlplane $ cat app2-2782517e-Dockerfile
FROM ubuntu:20.04
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y golang-go=2:1.13~1ubuntu2
COPY app.go .
RUN CGO_ENABLED=0 go build app.go
FROM alpine:3.12.0
RUN addgroup -S appgroup && adduser -S appuser -G appgroup -h /home/appuser
COPY --from=0 /app /home/appuser/
USER appuser
CMD ["/home/appuser/app"]
controlplane $
controlplane $ cat app2-5cde5c3d-Dockerfile
FROM ubuntu
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y golang-go
COPY app.go .
RUN CGO_ENABLED=0 go build app.go
FROM alpine:3.11.6
COPY --from=0 /app .
CMD ["./app"]
controlplane $
今回は非root化についてです。
コンテナが攻撃者に則られてしまった場合、そのコンテナがroot権限で動かされているなら攻撃者はそのホストに対するroot権限を持ったも同然の状況になってしまいます。
そのため、コンテナが乗っ取られた場合に備えてコンテナを起動するユーザーはなるべく最小権限を持ったユーザーにしておくべきです。
それでは、app2-2782517e-Dockerfile について説明していきます。
以下のコマンドポイントです。
RUN addgroup -S appgroup && adduser -S appuser -G appgroup -h /home/appuser
COPY --from=0 /app /home/appuser/
USER appuser
1. addgroup -S appgroup
addgroup
: 新しいグループを作成するためのコマンドです。-S
: “system group” を作成するオプションです。システムグループは、一般ユーザーではなく、システムプロセスや特定のサービスに関連付けられたグループです。このオプションにより、軽量でログインを必要としないグループが作成されます。appgroup
: ここで作成されるグループの名前です。この場合はappgroup
という名前のグループが作成されます。
2. adduser -S appuser -G appgroup -h /home/appuser
-G appgroup
: このオプションにより、appuser
ユーザーを既存のグループに追加します。-h /home/appuser
: このオプションは、ユーザーのホームディレクトリを指定します。
この一行目がポイントです。
あとは、COPY –from=0 /app /home/appuser/で一つ目のベースイメージで立てたコンテナ上の/appを新たなベースイメージのコンテナの/home/appuser/にコピーします。
そしてUSER appuser でコンテナ立ち上げ時のユーザーを指定してあげれば完了です。
ということで、ベストプラクティスに従っているのはapp2-2782517e-Dockerfileのほうでした。
Dockerfile 3
controlplane $ cat app3-1c8650b1-Dockerfile
FROM ubuntu
COPY my.cnf /etc/mysql/conf.d/my.cnf
COPY mysqld_charset.cnf /etc/mysql/conf.d/mysqld_charset.cnf
RUN apt-get update && \
apt-get -yq install mysql-server-5.6 &&
COPY import_sql.sh /import_sql.sh
COPY run.sh /run.sh
RUN /etc/register.sh $SECRET_TOKEN
EXPOSE 3306
CMD ["/run.sh"]
controlplane $
controlplane $ cat app3-4049a117-Dockerfile
FROM ubuntu
COPY my.cnf /etc/mysql/conf.d/my.cnf
COPY mysqld_charset.cnf /etc/mysql/conf.d/mysqld_charset.cnf
RUN apt-get update && \
apt-get -yq install mysql-server-5.6 &&
COPY import_sql.sh /import_sql.sh
COPY run.sh /run.sh
RUN echo $SECRET_TOKEN > /tmp/token
RUN /etc/register.sh /tmp/token
RUN rm /tmp/token
EXPOSE 3306
CMD ["/run.sh"]
controlplane $
今回のポイントはシークレットの渡し方です。
app3-1c8650b1-Dockerfileでは引数に直接渡しています。
app3-4049a117-Dockerfileでは一旦tmpファイルに値を記入して、そのファイルを引数にとり、最後にtmpファイルを削除しています。
一旦どちらも問題なさそうですが、セキュリティ的に優れているのはapp3-1c8650b1-Dockerfileです。
理由は、Dockerfileをビルドする際、各コマンドはレイヤーというキャッシュとしてコンテナイメージに保存されてしまいます。
そのため、Dockerfile内で削除していたとしてもコンテナ内に環境変数の値が残ってしまいます。
ということで、ベストプラクティスに従っているのはapp3-1c8650b1-Dockerfileのほうでした。