セキュリティのシフトレフトを考えると、開発段階から考えるということが重要ということはわかってはいても、どう実践すれば良いものか、、、
わかってはいつつ、なんとなく不安になりつつも、忙しく作業に没頭せざるをえないJavaデベロッパの方も多いのではないでしょうか。
######今回はJavaデベロッパ向けDockerのセキュリティの短めの記事のご紹介です。
参考資料:
- JavaデベロッパのためのDocker:セキュリティを失敗させないために知っておくべき5つのこと(原文)
- Docker for Java developers: How not to fail your security(動画)
#JavaデベロッパのためのDocker:セキュリティを失敗させないために知っておくべき5つのこと
Brian Vermeer
ブライアンフェルメール
2020年11月20日
Dockerは、アプリケーションをコンテナ化するために最も広く使用されている方法です。Docker Hubを使えば、事前に作っておいたイメージを簡単に作成したりプルできます。Docker Hubにあるイメージを使用して、Javaアプリケーションのイメージをすばやく構築できるため、これは非常に便利です。ただし、Javaアプリケーション用のカスタムDockerイメージを作成する単純な方法には、多くのセキュリティ上の懸念が伴います。では、JavaデベロッパにとってセキュリティをDockerの必須要素にするにはどうすればよいでしょうか。
アプリケーション用の優れたDockerJavaイメージを作成する方法を説明する前に、このトピックに関するよくある質問をいくつか見てみましょう。
######JavaアプリケーションをDocker化するには?
JavaアプリケーションをDockerコンテナで実行するには、.jar
または.war
ファイルをJREベースイメージにコピーするだけですが、その際にはいくつか注意すべき点があります。適切なJVM引数を選択し、コンテナのランタイム設定を一致させることは、対策の半分にすぎません。選択によっては、脆弱性があるため、どのベースイメージを使用するかは、セキュリティの観点から非常に重要です。
この記事は、ベースイメージの選択による影響をより深く理解するための情報を提供し、アプリケーションに利用可能な最も安全なイメージを見つけるためのガイドです。
######JavaデベロッパにとってDockerはどのように役立ちますか?
Javaアプリケーションをコンテナにパッケージ化することで、JRE、構成設定、OSレベルの依存関係、ビルドアーティファクトを含む完全なアプリケーションをコンテナイメージと呼ばれる自己完結型のデプロイ可能なアーティファクトに定義することができます。これらのイメージはソフトウェアで定義されており、作成に完全な再現性があり、開発者はすべての環境で同じプラットフォームを実行することができます。さらに、コンテナを利用することで、開発者は特別な権限を必要とせずに、デスクトップ上で新しいプラットフォームのリリースやその他の変更を簡単に試すことができます。
##Javaアプリケーションに適したDockerベースイメージの選択
Dockerイメージを作成する際には、DockerHubから取得したいくつかのイメージをベースにしてイメージを作成します。これをベースイメージと呼びます。ベースイメージは、Javaアプリケーションのビルドに関する新しいイメージの基盤です。ベースイメージを選択することで、そのイメージで利用可能なすべてのものを利用することができるため、ベースイメージは不可欠です。ただし、これには代償が伴います。ベースイメージに脆弱性がある場合、新しく作成するイメージにもそれが引き継がれてしまうのです。
ベースイメージを見てみると、脆弱性の多くは、このベースイメージが使用するオペレーティングシステム(OS)レイヤーの一部です。2019年の以前の調査Dockerセキュリティのシフトレフトでは、OSレイヤーによってもたらされる脆弱性は、何を選択しているかによって大きく変わる可能性があることが示されています。
2019年のレポート–Dockerセキュリティのシフトレフト
AdoptopenjdkのDockerJavaベースイメージの人気セット、openjdk11を見てみましょう。デフォルトタグを使用すると、このイメージはubuntuディストリビューションの上に構築されます。ただし、たとえばDebian、Centos、Alpineをベースにした特定のバージョンのタグを選択することもできます(注意:Alpineはglibcベースではないため、ネイティブのJNIコールを行うアプリケーションとの互換性がない可能性があります)。
正しいベースイメージを選択することは、セキュリティの観点から非常に重要であると結論づけることができます。おそらく、完全なオペレーティングシステムに付属するすべてのバイナリは必要ないでしょう。最アプリケーション用に新しいDocker Javaイメージを構築するには、最小限のベースイメージをベースにすることをお勧めします。持っていないバイナリが害を及ぼすことはできません。
セキュリティの面に加えて、最小限のベースイメージは、新しく作成するイメージのサイズを小さくします。Dockerイメージが小さければ、フットプリントも小さくなり、おそらく起動時間も速くなるでしょう。もう1つの考慮できる事項として、Dockerfileを必要としない最小限のJavaイメージを作成するjibでビルドすることも考えられます。
##JDKではなくJREを使用する
Dockerイメージを作成する際には、正しく機能するために必要なリソースのみを割り当てるべきです。つまり、本番イメージは、完全なJava Development Kit (JDK)ではなく、適切なJava Runtime Environment (JRE)を使用することから始める必要があります。さらに、本番イメージにはMavenやGradleなどのビルドシステムを含めべきではありません。ビルドの成果物、たとえばjarファイルがあれば十分です。
Dockerコンテナ内でアプリケーションをビルドする場合でも、マルチステージビルドを使用して、ビルドイメージを本番イメージから簡単に分離できます。
######例:
java-code-workshopアプリケーション用にDockerJavaイメージを作成したいと思います。これはmavenで構築されたspring-bootベースのアプリケーションで、Javaバージョン8を必要とします。
このDockerJavaイメージを作成する単純な方法は、次のようなものです。
mavenとopenjdk8を含むベースイメージを選択し、そのイメージにソースをコピーし、mavenを呼び出してアプリケーションをビルドして実行します。この例は全く問題なく動作します。アプリケーションは起動し、スムーズに動作します。しかし、先ほど作成したDockerイメージのサイズが631MBになっています。
このDockerfileを変更して、マルチステージビルドを使用してみましょう。
ここで何が起こっているのかというと、プロジェクトのビルドにmaven-openjdk8
のイメージを使用しているということです。しかし、これはアウトプットではありません。かなり小さいjava 8 JREイメージをベースにした新しいイメージを作成し、実行可能なspring-boot jarだけをコピーします。あとは、そのjar-file
、を実行するだけで完了です。その結果、JDKやmavenを含まない、JREのみのDockerイメージが作成されます。イメージサイズは132MBと劇的に小さくなりました。
イメージ小さいほど、アップロードが簡単で起動時間を短縮できるだけでなく、より安全です。もし何らかの理由で、JDK、ソースコード、ビルドツールが入った稼働中のコンテナに攻撃者がアクセスしたらどうなるか想像できますか?
プライベートリポジトリにアクセスするためのシークレットを含める必要がある場合にも、これを使用できます。これらの種類のシークレットを本番用イメージのキャッシュに入れたくありません。本番環境ではビルドイメージを使用しないため、シークレットを使用してもまったく問題ありません。このテクニックを使えば、他のイメージから必要なものをチェリーピックして、必要なリソースだけを持つ製品Dockerイメージを作ることができます。
##Dockerコンテナをrootで実行しない
Dockerコンテナを作成する際、デフォルトではrootとして実行されます。これは開発時には便利ですが、本番用のイメージではこのようなことは避ける必要があります。何らかの理由で、攻撃者が端末にアクセスできたり、コードを実行できたとします。その場合、実行中のコンテナに対して重要な権限を持っているだけでなく、不適切に高いアクセス権でファイルシステムバインドマウントを介してホストファイルシステムにアクセスできる可能性があります。
これを防ぐための最も簡単な方法は、以下のように特定のユーザーを作成することです。
3行目では、新しいグループを作成し、ユーザーを追加しています。このユーザーは、パスワードとホームディレクトリを持たないシステムユーザー(-r)です。また、新しく作成したグループにも追加しています。
次に6行目で、このユーザーにアプリケーションフォルダへのアクセス権を与えています。7行目も忘れないでください。ここでは、使用したいユーザーを設定しています。こうすることで、新しく作成された制限付きユーザーが最後の行のコマンドを実行します。
##開発中のDockerイメージとJavaアプリケーションのスキャン
DockerfileからDockerイメージを作成し、イメージを再構築することでさえ、システムに新しい脆弱性をもたらす可能性があります。開発中のDockerイメージをスキャンすることは、脆弱性を可能な限り早期に発見するためのワークフローの一部であるべきです。
Snyk CLIを使えば、Dockerイメージを簡単にスキャンできます。ローカルマシンでも、パイプラインの一部として、またはその両方で使えます。Snyk CLIをインストールして認証した後イメージをスキャンするためにしなければならないことはただ一つです。
$ snyk container test
最初のセクションで説明したように adoptopenjdk
のイメージをスキャンしたい場合、コマンドは次のようになります。
$ docker pull adoptopenjdk/opendjdk11:latest
$ snyk container test adoptopenjdk/opendjdk11:latest
アウトプット:
Docker イメージのテストとモニタリングの両方が可能です。モニタリングには snyk container monitor <image>
を使用します。モニタリングでは、スナップショットを取り、時間の経過とともにイメージに新しい脆弱性や修正プログラムが利用できるかどうかを監視します。
イメージをスキャンしてDockerfile(新しいDocker Javaイメージを作成)がある場合は、フラグ--Dockerfile=<dockerfile>
をsnyk container testか
snyk container monitor`のいずれかに追加する必要があります。これで、より適切な修復アドバイスが得られます。たとえば、利用可能な脆弱性の数を減らす利用可能なベースイメージがある場合、それを知ることができます。
例:
$ snyk container test myImage:mytag --Dockerfile=path/Dockerfile
$ snyk container monitor myImage:mytag --Dockerfile=path/Dockerfile`
##Javaアプリケーションのスキャン
構築しているDockerJavaイメージには、アプリケーションも含まれています。明らかに、これは攻撃のポイントとなり得ます。あなたのJavaアプリケーションにセキュリティ脆弱性がないことを確認する必要があり、JavaデベロッパのDocker環境において、最初の段階でセキュアな方法を採用したことになります。アプリケーションに、 あなたのアプリケーションが、REST エンドポイントを呼び出すときにリモートでコードの実行を可能にするライブラリを含んでいると想像してください。あなたのイメージの残りの部分に何の脆弱性もないとしても、これは悲惨な結果になる可能性があります。
Dockerイメージに挿入するJavaバイナリの大部分は、おそらくインポートするコードです。アプリケーションが持つライブラリとフレームワークは依存関係と考えることができます。Snyk CLIを使用で、依存関係を簡単に確認できます。これは、さきほどイメージをスキャンするために使用したものと同じCLIです。ルートフォルダでsnyk test
またはsnyk monitor
を呼び出すと、ライブラリのセキュリティの脆弱性についてアプリケーションをスキャンまたは監視することができます。
自分が書いたコードには、コード分析ツールまたはSonarLint、PMD、spotbugsなどのリンターを使用することをお勧めします。これらのツールは、より優れたコードを作成するための汎用ツールですが、明らかなセキュリティミスを防ぐのにも役立ちます。
##再構築するためのビルド
Dockerイメージ用のJavaアプリケーションを、いつでも破棄して再構築できるようにビルドします。実行中のコンテナに問題があることに気付いたとしましょう。単にkillし(プロセス停止)し、新しいインスタンスを起動することができれば最高です。そのためには、データがコンテナの外部に格納されるように、ステートレスJavaアプリケーションを設計する必要があります。次のことが考えられます。
- コンテナ内でデータベースのデータストアを実行しない。
- コンテナ内にファイル(ログ)を保存しない。
- 自動回復のキャッシュを確認する(該当する場合)。
アプリケーションを破棄して、いつでも新しいインスタンスを起動できるようにビルドしている場合、Dockerイメージ全体も安全に再構築することができます。脆弱なDockerイメージの20%は、イメージを再構築するだけで、1つ以上のセキュリティ問題を修正できることをご存知ですか?Dockerイメージは、多くの場合、ベースイメージの「最新の」タグに基づいています。この「最新」は時間の経過とともに変化し、より新しく改良されたバージョンに置き換えられます。aptやyumなどのパッケージマネージャーを使用してコンテナーにインストールされたキーバイナリについても同様です。もちろん、最新バージョンを使用することは、最新のセキュリティ修正が自動的に適用されるため、セキュリティの観点からは良いことです。しかし、ベースイメージは時間の経過とともに変化し、結果として特定の時間のスナップショットでイメージを再現することが難しくなるということとのバランスを取る必要があります。
アプリケーションに変更がなかったとしても、定期的にDockerイメージを再構築し、場合によってはベースイメージのバージョンタグを新しくしたり、最新のものにしたりしてください。OSレイヤーのような基礎的なレイヤーの改善は、イメージの品質を向上させ、セキュリティの脆弱性を減らすことができます。
最後に、一般的な最適なDockerイメージまたはJavaアプリケーションを構築するためのセキュリティのベストプラクティスについて把握しておきたい方は、以下をご覧ください。
10 best practices to build a Java container with Docker<原文>
- Javaアプリケーション用に安全でパフォーマンスの高いDockerイメージを構築する方法を、ステップバイステップで詳細に説明しています。
[Javaセキュリティ・ベスト・プラクティス10<原文>] (https://snyk.io/blog/10-java-security-best-practices/)→<翻訳記事coming soon> - あらゆる環境でJavaアプリケーションを構築する際に従うべきセキュリティ・プラクティスです。
Contents provided by:
Jesse Casman, Fumiko Doi, Content Strategists for Snyk, Japan, and Randell Degges, Community Manager for Snyk Global