本文の内容は、2021年3月9日にÁlvaro Iradierが投稿したブログ(https://sysdig.com/blog/dockerfile-best-practices/)を元に日本語に翻訳・再構成した内容となっております。
Dockerfileのベストプラクティスのクイックセットをイメージビルドに適用することで、セキュリティ問題を防ぎ、コンテナ化されたアプリケーションを最適化する方法を学びます。
コンテナ化されたアプリケーションやマイクロサービスに精通している人なら、自分のサービスがマイクロサービスであることに気づいているかもしれません。しかし、脆弱性の検出、セキュリティ問題の調査、デプロイ後の報告や修正など、管理のオーバーヘッドがマクロな問題になっています。
このオーバーヘッドの多くは、セキュリティをシフトレフトし、開発ワークフローの中で可能な限り早く潜在的な問題に取り組むことで防ぐことができます。先日、このブログでは、イメージスキャンのベストプラクティスがどのようにセキュリティをシフトレフトするのに役立つかを取り上げました。
巧みに作られたDockerfileは、特権コンテナの必要性を回避し、不要なポート、未使用のパッケージ、漏洩する可能性のある資格情報など、攻撃に利用される可能性のあるものは何でも排除します。既知のリスクを事前に取り除くことで、セキュリティ管理や運用上のオーバーヘッドを減らすことができます。
使用するツールのベストプラクティス、パターン、推奨事項に従うことで、よくあるエラーや落とし穴を回避することができます。
この記事では、Dockerfilesの記述とコンテナセキュリティに焦点を当てたDockerセキュリティのベストプラクティスのリストにダイブして行きますが、イメージの最適化のような他の関連トピックもカバーしています:
- 不要な特権を避ける
- コンテナを root で実行しないようにする
- 特定のUIDにバインドしない
- 実行可能ファイルを root が所有し、書き込み不可とする
- 攻撃対象を減らす
- マルチステージビルドを活用する
- distroless イメージを使うか、ゼロからビルドする
- イメージを頻繁に更新する
- 暴露されたポートに注意する
- 機密データの漏洩を防ぐ
- Dockerfileの命令にシークレットや認証情報を入れないようにする
- ADDよりもCOPYを優先する
- Dockerのコンテキストを意識し、.dockerignoreを使用する
- その他
- レイヤーの数を減らし、インテリジェントに並べる
- メタデータとラベルを追加する
- lintersを活用してチェックを自動化する
- 開発中にイメージをローカルでスキャンする
- イメージのビルドの後では
- docker socketとTCP接続を保護する
- イメージを署名付きにし、ランタイム時に検証する
- タグのミュータビリティを避ける
- 環境を root で実行しない
- ヘルスチェックを含める
- アプリケーションの機能を制限する
私たちが選んだDockerfileのベストプラクティスをトピック別にまとめました。Dockerfileのベストプラクティスは、開発プロセス全体の一部に過ぎないことを覚えておいてください。このブログの最後では、イメージビルドの前後に適用できる、コンテナイメージ・セキュリティとシフトレフト・セキュリティの関連リソースを紹介しています。
#1 不必要な特権を避ける
これらの助言は、サービスやアプリケーションがその目的を果たすために必要なリソースや情報にのみアクセスできるようにするための最小特権の原則に従っています。
#1.1 ルートレスコンテナ
私たちの最近のレポートでは、58% のイメージが root (UID 0) としてコンテナのエントリポイントを実行していることを発表しています。しかし、Dockerfileのベストプラクティスとして、そのようなことは避けるべきです。コンテナをrootで実行する必要があるユースケースはほとんどないので、デフォルトの実効UIDをrootではないユーザに変更するためのUSER命令を含めることを忘れないでください。
さらに、実行環境によっては、デフォルトで root で実行するコンテナがブロックされているかもしれません (例として、Openshift は追加の SecurityContextConstraints を必要とします)。
非 root として実行するには、Dockerfile にいくつかの追加ステップが必要になるかもしれません:
- USER命令で指定したユーザーがコンテナ内に存在することを確認する。
- プロセスが読み書きする場所に適切なファイルシステムパーミッションを与える。
FROM alpine:3.12 # Create user and set ownership and permissions as required RUN adduser -D myuser && chown -R myuser /myapp-data # ... copy application files USER myuser ENTRYPOINT ["/myapp"]
root で起動した後、gosu や su-exec を使って標準ユーザに移行するコンテナを見かけることがあるかもしれません。
また、コンテナが非常に特殊なコマンドを root で実行する必要がある場合、sudo に頼ることもあります。
この2つの方法はrootで実行するよりは良いのですが、Openshiftのような制限された環境では動作しないかもしれません。
#1.2 特定の UID にバインドしない
コンテナを非 root ユーザとして実行しますが、そのユーザ UID を要件にしないでください。
なぜでしょうか?
- Openshift はデフォルトでは、コンテナを実行する際にランダムな UID を使用します。
- 特定の UID (すなわち、UID 1000 の最初の標準ユーザ) を強制するには、データ永続化のためのホストフォルダのようなバインドマウントのパーミッションを調整する必要があります。あるいは、ホスト UID でコンテナを実行すると(docker の -u オプション) 、コンテナ内のフォルダから読み書きしようとするときにサービスが壊れてしまう可能性があります。
... RUN mkdir /myapp-tmp-dir && chown -R myuser /myapp-tmp-dir USER myuser ENTRYPOINT ["/myapp"]
このコンテナは、アプリケーションが /myapp-tmp-dir フォルダに書き込むことができないため、myuser 以外の UID で実行すると問題が発生します。
myuser のみが書き込み可能なハードコードされたパスを使用しないでください。代わりに、一時的なデータを /tmp (スティッキービットのパーミッションのおかげで、どのユーザでも書き込み可能な場所) に書き込んでください。リソースを誰でも読み込めるようにし(すなわち、0640 の代わりに 0644)、UID が変更されてもすべてが動作するようにします。
... USER myuser ENV APP_TMP_DATA=/tmp ENTRYPOINT ["/myapp"]
この例では、アプリケーションは環境変数 APP_TMP_DATA のパスを使用します。デフォルト値の /tmp は、アプリケーションを任意の UID で実行し、一時的なデータを /tmp に書き込むことを可能にします。設定可能な環境変数としてパスを持つことは必ずしも必要ではありませんが、永続化のためにボリュームを設定したりマウントしたりする際に、より簡単になります。
#1.3 実行ファイルの所有者を root にして書き込み不可にする
コンテナ内のすべての実行ファイルは、たとえそれが非ルートユーザによって実行されたものであっても、ルートユーザが所有し、誰でも書き込み可能なものであるべきではないというのがDockerfileのベストプラクティスです。
これにより、実行ユーザが既存のバイナリやスクリプトを変更することができなくなり、さまざまな攻撃を防げる可能性があります。このベストプラクティスに従うことで、コンテナの不変性を効果的に強制することができます。不変性コンテナは実行時に自動的にコードを更新しないので、実行中のアプリケーションが誤って変更されたり、悪意を持って変更されるのを防ぐことができます。
このベストプラクティスに沿って避けましょう:
... WORKDIR $APP_HOME COPY --chown=app:app app-files/ /app USER app ENTRYPOINT /app/my-app-entrypoint.sh
ほとんどの場合、–chown app:appオプション(またはRUN chown …コマンド)をドロップするだけでOKです。アプリのユーザが必要とするのはファイルの実行権限だけで、所有権ではありません。
#2 攻撃対象を減らす
イメージを最小限に抑えるのはDockerfileのベストプラクティスです。
攻撃対象を減らすために、不必要なパッケージを入れたり、ポートを露出させたりするのは避けてください。コンテナの中に多くのコンポーネントを入れれば入れるほど、システムが露出してしまい、特に自分の管理下にないコンポーネントのメンテナンスが難しくなります。
#2.1 マルチステージビルド
マルチステージビルド機能を利用して、コンテナ内で再現性のあるビルドを行います。
マルチステージビルドでは、最終的な成果物(最終的な実行ファイル)をコンパイルまたは生成するために必要なすべてのツールを備えた中間コンテナ(ステージ)を作成します。そして、追加の開発依存関係や一時的なビルドファイルなどを使用せずに、結果として得られた成果物だけを最終イメージにコピーします。
巧みに作られたマルチステージビルドでは、ビルドツールや中間ファイルは使用せず、必要最小限のバイナリと依存関係だけを最終イメージに含めます。これにより、攻撃の対象となる領域が減り、脆弱性が減少します。
より安全で、イメージサイズも小さくなります。
goアプリケーションの場合、マルチステージビルドの例は次のようになります。
#This is the "builder" stage FROM golang:1.15 as builder WORKDIR /my-go-app COPY app-src . RUN GOOS=linux GOARCH=amd64 go build ./cmd/app-service #This is the final stage, and we copy artifacts from "builder" FROM gcr.io/distroless/static-debian10 COPY --from=builder /my-go-app/app-service /bin/app-service ENTRYPOINT ["/bin/app-service"]
これらのDockerfileの指示で、golang:1.15コンテナを使ってビルダーステージを作成します。
FROM golang:1.15 as builder
そこにソースコードをコピーしてビルドします。
WORKDIR /my-go-app COPY app-src . RUN GOOS=linux GOARCH=amd64 go build ./cmd/app-service
次に、Debian の distroless イメージに基づいて別のステージを定義します (次のヒントを参照してください)。
FROM gcr.io/distroless/static-debian10
ビルダー段階から –from=builder フラグを使用して、結果の実行ファイルをコピーします。
COPY --from=builder /my-go-app/app-service /bin/app-service
最終的なイメージには、distroless/static-debian-10イメージの最小限のライブラリセットとアプリの実行ファイルのみが含まれます。
ビルドツールチェーンもソースコードもありません。
このNodeJSアプリケーションの例や、効率的なPythonとDjangoのマルチステージビルドをチェックすることをお勧めします。
#2.2 distroless イメージを使うか、ゼロからビルドする
Dockerfileのベストプラクティスに従うために、最低限必要なベースコンテナを使用します。
理想的なのは、ゼロからコンテナを作成し、100%静的なバイナリのみが動作する状態です。
Distrolessは良い代替手段です。これらは、GoやPython、その他のフレームワークを実行するために必要な、最小限のライブラリのみを含むように設計されています。
例えば、一般的なubuntu:xenialイメージのコンテナをベースにしたとします:
FROM ubuntu:xenial-20210114
Sysdig のインラインスキャナーで検出された 100 以上の脆弱性は、あなたが含めている大量のパッケージに関連しており、おそらく必要もなければ決して使うこともないでしょう:
❯ docker run -v /var/run/docker.sock:/var/run/docker.sock --rm quay.io/sysdig/secure-inline-scan:2 image-ubuntu -k $SYSDIG_SECURE_TOKEN --storage-type docker-daemon Inspecting image from Docker daemon -- distroless-1:latest Full image: docker.io/library/image-ubuntu Full tag: localbuild/distroless-1:latest … Analyzing image… Analysis complete! ... Evaluation results - warn dockerfile:instruction Dockerfile directive 'HEALTHCHECK' not found, matching condition 'not_exists' check - warn dockerfile:instruction Dockerfile directive 'USER' not found, matching condition 'not_exists' check - warn files:suid_or_guid_set SUID or SGID found set on file /bin/mount. Mode: 0o104755 - warn files:suid_or_guid_set SUID or SGID found set on file /bin/su. Mode: 0o104755 - warn files:suid_or_guid_set SUID or SGID found set on file /bin/umount. Mode: 0o104755 - warn files:suid_or_guid_set SUID or SGID found set on file /sbin/pam_extrausers_chkpwd. Mode: 0o102755 - warn files:suid_or_guid_set SUID or SGID found set on file /sbin/unix_chkpwd. Mode: 0o102755 - warn files:suid_or_guid_set SUID or SGID found set on file /usr/bin/chage. Mode: 0o102755 … Vulnerabilities report Vulnerability Severity Package Type Fix version URL - CVE-2019-18276 Low bash-4.3-14ubuntu1.4 dpkg None http://people.ubuntu.com/~ubuntu-security/cve/CVE-2019-18276 - CVE-2016-2781 Low coreutils-8.25-2ubuntu3~16.04 dpkg None http://people.ubuntu.com/~ubuntu-security/cve/CVE-2016-2781 - CVE-2017-8283 Negligible dpkg-1.18.4ubuntu1.6 dpkg None http://people.ubuntu.com/~ubuntu-security/cve/CVE-2017-8283 - CVE-2020-13844 Medium gcc-5-base-5.4.0-6ubuntu1~16.04.12 dpkg None http://people.ubuntu.com/~ubuntu-security/cve/CVE-2020-13844 ... - CVE-2018-20839 Medium systemd-sysv-229-4ubuntu21.29 dpkg None http://people.ubuntu.com/~ubuntu-security/cve/CVE-2018-20839 - CVE-2016-5011 Low util-linux-2.27.1-6ubuntu3.10
コンテナに gcc コンパイラや systemd SysV 互換性が必要ですか?ほとんどの場合、必要ありません。dpkg や bash も同様です。
gcr.io/distroless/base-debian10 をベースにしている場合は、以下のようになります。
FROM gcr.io/distroless/base-debian10
そうすると、glibc, libssl, openssl のような必要なライブラリだけを含む基本的なパッケージのセットだけが含まれることになります。
Goのようにlibcを必要としない静的にコンパイルされたアプリケーションの場合は、よりスリムなものを使用することもできます。
FROM gcr.io/distroless/static-debian10
#2.3 信頼できるベースイメージを使う
イメージのベース(FROM命令)を慎重に選択してください。
信頼されていないイメージやメンテナンスされていないイメージの上にビルドすると、そのイメージの問題や脆弱性をすべてコンテナに継承してしまいます。
ベースとなるイメージの選択は、以下のDockerfileのベストプラクティスに従ってください。
- 無名のユーザが作成したイメージよりも、信頼できるリポジトリやプロバイダが作成した検証済みの公式なイメージを選ぶべきです。
- カスタムイメージを使用する場合は、イメージのソースとDockerfileを確認し、自分でベースイメージをビルドしてください。パブリックレジストリで公開されているイメージが、与えられたDockerfileから本当にビルドされたものであるという保証はありません。また、イメージが最新の状態に保たれているという保証もありません。
- セキュリティやミニマリズムの観点から、公式のイメージの方が適していないこともあります。例えば、公式のnodeイメージとbitnami/nodeイメージを比較すると、後者はminidebディストリビューションの上にカスタマイズされたバージョンを提供しています。これらは最新のバグフィックスで頻繁に更新され、Docker Content Trustで署名され、既知の脆弱性を追跡するためのセキュリティスキャンにパスしています。
#2.4 こまめにイメージを更新する
頻繁に更新されるベースイメージを使用し、その上にあなたのイメージを再ビルドしてください。
新しいセキュリティ脆弱性は継続的に発見されているので、最新のセキュリティパッチにこだわるのが一般的なセキュリティのベストプラクティスです。
常に最新のバージョンを使用する必要はありませんが、バージョン管理戦略を定義してください。
- セキュリティ修正をすぐに、そして頻繁に提供するStable版やlong-term版に固執する。
- 事前に計画を立てる。ベースとなるイメージのバージョンが寿命を迎えてアップデートを受けられなくなる前に、古いバージョンを削除して移行する準備をしておきましょう。
- また、定期的に自分のイメージをリビルドし、同じような戦略でベースとなるディストロ、Node、Golang、Python などから最新のパッケージを入手しましょう。ほとんどのパッケージマネージャや依存関係マネージャ (npm や go mod など) は、最新のセキュリティアップデートに対応するためにバージョン範囲を指定する方法を提供しています。
#2.5 公開されたポート
コンテナ内で開かれているポートはすべて、あなたのシステムへのオープンドアです。アプリケーションが必要とするポートだけを公開し、SSH (22) のようなポートの公開は避けてください。
DockerfileはEXPOSOSEコマンドを提供していますが、このコマンドは情報提供と文書化のみを目的としたものであることに注意してください。ポートを公開したからといって、コンテナを実行したときに、すべてのEXPOSEDポートに対して自動的に接続が許可されるわけではありません(docker run –publish-allを使用しない限り)。コンテナの実行時に公開されるポートを指定する必要があります。
EXPOSEを使用して、Dockerfileに必要なポートのみにフラグを付けて文書化し、実行時にはそれらのポートのみを公開するようにします。
#3 機密データの漏洩を防ぐ
コンテナを扱う際には、機密データには本当に気をつけましょう。
以下のDockerfileのベストプラクティスでは、コンテナのクレデンシャルを扱う際のアドバイスや、誤って望ましくないファイルや情報が漏洩しないようにする方法を紹介しています。
#3.1 クレデンシャルと機密性
Dockerfileの指示(環境変数、引数、コマンドにハードコードされたもの)にシークレットや資格情報を入れないようにしてください。
コンテナにコピーされるファイルには特に注意してください。Dockerfileの後の命令でファイルが削除されたとしても、そのファイルは実際には削除されておらず、最終的なファイルシステムの中に「隠されている」だけなので、前のレイヤーにアクセスすることができます。ですから、イメージをビルドするときには、以下のことを実践してください:
- アプリケーションが環境変数を介した設定をサポートしている場合は、実行時にシークレットを設定するためにそれらを使用する(docker runの-eオプション)か、Dockerのシークレット、Kubernetesのシークレットを使用して、環境変数として値を提供します。
- 設定ファイルとbind mountsを使用して、docker内の設定ファイルをマウントするか、Kubernetesのシークレットからマウントします。
また、イメージには機密情報や特定の環境(本番環境、ステージング環境など)に結びつける設定値を含めるべきではありません。
その代わりに、実行時に値をインジェクトしてイメージをカスタマイズできるようにしてください。例として、安全な値やダミーの値を含む設定ファイルのみをインクルードするようにしてください。
#3.2 Add、COPY
ADD命令とCOPY命令はどちらもDockerfileの中で似たような機能を提供します。しかし、COPYの方がより明示的です。
URLやtarファイルからファイルを追加するなど、本当にADD機能が必要な場合を除き、COPYを使用してください。COPYの方が予測しやすく、エラーが発生しにくいからです。
場合によっては、addではなくrun命令を使用して、curlやwgetを使用してパッケージをダウンロードし、それを展開してから元のファイルを1回のステップで削除することが好ましい場合があり、レイヤー数を減らすことができます。
マルチステージビルドもまた、この問題を解決し、前のステージから最終的に抽出したファイルだけをコピーできるようにすることで、Dockerfileのベストプラクティスに従うのに役立ちます。
#3.3 ビルドコンテキストとdockerignore
ここでは、デフォルトのDockerfileとカレントフォルダ内のコンテキストで、dockerを使ったビルドの典型的な実行例を示します。
docker build -t myimage .
注意してください!
パラメーターはビルドのコンテキストです。コンテキストとして “.” を使用すると、設定ファイル、資格情報、バックアップ、ロックファイル、一時ファイル、ソース、サブフォルダ、ドットファイルなどの機密ファイルや不要なファイルをコンテナにコピーしてしまう可能性があるので危険です。
Dockerfileの中に以下のようなコマンドがあると想像してみてください。
COPY . /my-app
これにより、ビルドコンテキスト内のすべてのファイルがコピーされますが、”. “の例ではDockerfile自体も含まれます。
コンテナ内にコピーする必要のあるファイルを含むサブフォルダを作成し、それをビルドコンテキストとして使用し、可能であればCOPY命令を明示的に指定するのがDockerfileのベストプラクティスです(ワイルドカードは避けてください)。例えば、以下のようになります。
docker build -t myimage files/
また、.dockerignore ファイルを作成して、ファイルやディレクトリを明示的に除外するようにしてください。
COPY指示に細心の注意を払っていたとしても、イメージのビルドを開始する前に、すべてのビルドコンテキストがdockerデーモンに送られます。つまり、ビルドコンテキストを小さく制限しておくと、ビルドが速くなるということです。
ビルドコンテキストを自分のフォルダに置き、.dockerignore を使って可能な限り小さくしてください。
#4 その他
#4.1 レイヤーの正当性
Dockerfileの命令の順番が非常に重要であることを覚えておいてください。
RUN、COPY、ADDなどの命令は新しいコンテナレイヤーを作成するので、複数の命令をまとめてグループ化することでレイヤー数を減らすことができます。
例えば、下記の代わりとして:
FROM ubuntu RUN apt-get install -y wget RUN wget https://…/downloadedfile.tar RUN tar xvzf downloadedfile.tar RUN rm downloadedfile.tar RUN apt-get remove wget
これはDockerfileのベストプラクティスと言えるでしょう:
FROM ubuntu RUN apt-get install wget && wget https://…/downloadedfile.tar && tar xvzf downloadedfile.tar && rm downloadedfile.tar && apt-get remove wget
また、変更の可能性が低く、キャッシュしやすいコマンドを最初に配置します。
下記の代わりとして:
FROM ubuntu COPY source/* . RUN apt-get install nodejs ENTRYPOINT ["/usr/bin/node", "/main.js"]
よりベターなやり方は:
FROM ubuntu RUN apt-get install nodejs COPY source/* . ENTRYPOINT ["/usr/bin/node", "/main.js"]
nodejsパッケージは、私たちのアプリケーションソースよりも変更される可能性が低いからです。
rm コマンドを実行すると、次のレイヤーのファイルは削除されますが、最終的なイメージファイルシステムは前のレイヤーから構成されているので、まだ利用可能でアクセス可能であることを覚えておいてください。
ですから、機密ファイルをコピーして削除しないようにしてください。最終的なコンテナファイルシステムには表示されませんが、簡単にアクセスできます。
#4.2 メタデータラベル
Dockerfileのベストプラクティスとして、イメージを構築する際にメタデータのラベルを含めることがあります。
ラベルは、アプリケーションのバージョン、ウェブサイトへのリンク、メンテナーへの連絡方法など、イメージの管理に役立ちます。
OCI imege specの定義済みアノテーションは、以前のLabelスキーマ標準ドラフトを廃止したものです。
#4.3 リンティング
Haskell Dockerfile Linter (hadolint)のようなツールは、Dockerfileの悪習を検出したり、RUN命令で実行されるシェルコマンド内の問題を暴いたりすることができます。
このようなツールをCIパイプラインに組み込むことを検討してください。
また、イメージスキャナーは、カスタマイズ可能なルールを介して不正行為を検出し、イメージの脆弱性と一緒にレポートすることができます:
上記はSysdig Secureのイメージスキャンポリシーの画面です。Dockerfileの設定ミスをチェックすることができます。
検出できる設定ミスの例としては、root で実行されているイメージ、ポートが公開されている、ADD 命令の使用、ハードコードされたシークレット、推奨されない RUN コマンドなどがあります。
#4.4 開発中のイメージをローカルでスキャンする
イメージスキャンは、コンテナを実行する前に潜在的な問題を検出するもう一つの方法です。イメージスキャンのベストプラクティスに従うためには、イメージがすでにコンテナレジストリにプッシュされているときだけでなく、イメージのライフサイクルのさまざまな段階でスキャンを実行する必要があります。
イメージがビルドされるとすぐに、レジストリにプッシュされる前に CI パイプラインで直接スキャンすることで、「シフトレフトセキュリティ」のパラダイムを適用するのがセキュリティのベストプラクティスです。
これには、開発者のコンピュータ内で、JenkinsやGithubアクションなどのCI/CDツールとのさまざまな統合を提供するSysdigインラインスキャナーを使用することも含まれます。
そして、スキャンされたイメージは今は「安全」かもしれないことを覚えておいてください。しかし、古くなり、新たな脆弱性が発見されると、危険なイメージになるかもしれません。
定期的に新しい脆弱性を再評価してください。
脆弱性は、発見される前にソフトウェアに存在しています。脆弱性があるイメージをデプロイした場合、後から脆弱性が発見される可能性があります。つまり、常に脆弱性が存在していたことになりますので、新たに発見された脆弱性から守るためには、本番環境で稼働しているイメージを継続的にスキャンする必要があります。
#5 イメージのビルドの先には
ここまではイメージのビルドプロセスに焦点を当て、最適なDockerfilesを作成するためのヒントを説明してきました。しかし、いくつかの追加の事前チェックと、イメージをビルドした後の実行についても忘れないようにしましょう。
#5.1 DockerポートソケットとTCP保護
docker ソケットは、最近見られるように、侵入や悪意のあるソフトウェアの使用に利用される可能性がある、ホストシステムへの大きな特権的なドアです。あなたの /var/run/docker.sock が正しい権限を持っていることを確認し、もし docker が TCP 経由で公開されている場合は (これは全く推奨されません)、それが適切に保護されていることを確認してください。
#5.2 イメージへの署名と署名の検証
Dockerfileのベストプラクティスの1つとして、docker content trust、Docker notary、Harbor notary、または類似のツールを使用して、イメージにデジタル署名を行い、ランタイムで署名検証を行うことが挙げられます。
署名検証を有効にするかどうかはランタイムごとに異なります。例えば、dockerでは、これは環境変数DOCKER_CONTENT_TRUSTで行います。
#5.3 タグのミュータビリティ
コンテナの世界では、タグは特定の時点での具体的なイメージバージョンを一時的に参照するものです。
タグは予期せず、いつでも変更される可能性があります。詳しくは “ミュータントタグの攻撃” を参照してください。
#5.4 非 root で実行する
前回は、コンテナをビルドする際に非rootユーザーを使用する話をしました。USER命令はコンテナのデフォルトユーザーを設定しますが、実行中のコンテナの有効なユーザーはオーケストレータやランタイム環境(docker run、kubernetesなど)が決めることになります。
環境をrootで実行するのは本当に避けましょう。
Openshiftや一部のKubernetesクラスターでは、デフォルトで制限的なポリシーが適用され、rootコンテナを実行できないようになっています。パーミッションや所有権の問題を回避するために root で実行する誘惑を避け、代わりに真の問題を解決してください。
#5.5 ヘルス/ライブネスチェックを含める
プレーンなDockerやDocker Swarmを使用する場合は、可能な限りDockerfileにHEALTHCHECK命令を含めてください。これは、長期間稼働しているサービスや永続的なサービスが健全であることを確認し、それ以外の場合はサービスの再起動を管理するために重要です。
イメージをKubernetesで実行している場合、docker HEALTHCHECK命令は適用されないので、コンテナ定義の中でlivenessProbe設定を使用してください。
#5.6 機能をドロップする
また、実行時には、Dockerの場合は–cap-dropフラグ、Kubernetesの場合はsecurityContext.capabilities.dropを使用して、アプリケーションの機能を必要最小限のセットに制限することができます。そうすることで、コンテナが危うくなった場合に、攻撃者が利用できる行動範囲が制限されます。
また、コンテナの権限を制限するための追加のメカニズムとして、AppArmorとSeccompを適用する方法についての詳細を参照してください:
- DockerやKubernetesでのAppArmor
- DockerやKubernetesでのSeccomp
まとめ
コンテナイメージのセキュリティは複雑ですが重要なトピックであり、恐ろしい結果を引き起こすまで単純に無視することはできないことを見てきました。
予防とセキュリティをシフトレフトすることは、セキュリティの姿勢を改善し、管理のオーバーヘッドを減らすために不可欠です。
Dockerfilesのベストプラクティスに焦点を当てたこの一連の推奨事項は、このミッションに役立つでしょう。
さらに一歩進んで、セキュリティをシフトレフトするのに役立つ12のコンテナイメージスキャンのベストプラクティスの記事もチェックしてみてください。
Sysdig Secure のイメージスキャン機能は、これらの Dockerfile のベストプラクティスに従うのに役立ちます。脆弱性や設定ミスをチェックして、脅威がデプロイされる前に行動できるようにすることで、セキュリティをシフトレフトするのに役立ちます。わずか数分で設定が完了します。今すぐ試してみてください!