Kubernetesアプリケーションセキュリティ対策テクニック トップ10 #aqua #コンテナ #セキュリティ #k8s
この記事は1年以上前に投稿されました。情報が古い可能性がありますので、ご注意ください。
本ブログは「Aqua Security」社の技術ブログで2021年8月18日に公開された「 Top 10 Kubernetes Application Security Hardening Techniques 」の日本語翻訳です。
Kubernetesアプリケーションセキュリティ対策テクニック トップ10
開発者が直面する主な課題の1つは、アプリケーションを Kubernetes クラスターにデプロイする際のセキュリティリスクを、どのように管理するかということです。この問題を早期に対処するため、開発プロセスにおいてアプリケーションマニフェストにセキュリティ対策を適用することが有効です。この記事では、開発者がアプリケーションにセキュリティ対策を適用するための 10 の手法を紹介します。
これらの手法を利用することで、開発中にセキュリティ対策されたマニフェストをテストでき、本番環境で適用される制御が実行中のワークロードに悪影響を及ぼすリスクを軽減できます。また、Pod Security Policies のような強制的な制御が行われていないクラスターでは、自発的にセキュリティ対策をすることで、コンテナブレークアウト攻撃のリスクを低減できます。
一般的なアプローチ
Kubernetes ワークロードのマニフェストを書くとき、それが Pod であれ、Deployment や DaemonSet のような上位のものであれ、マニフェストには securityContext というセクションがあります。このセクションには、ワークロードに適用されるべきセキュリティパラメータを指定できます。
たとえば、以下のスニペットは利用できる capability を指定し、また、読み取り専用のルートファイルシステムを設定するマニフェストを示しています。
runAsUser, runAsGroup
デフォルトでは、Docker コンテナは root ユーザとして実行されますが、これはセキュリティの観点からは理想的ではありません。コンテナ内部からのアクセスにはまだ制限がありますが、過去1年間に、コンテナが root ユーザとして実行されている場合にのみ悪用可能なコンテナの脆弱性が複数ありました。すべてのコンテナを非 root ユーザとして実行することは、優れた対策の 1 つです。
基本的なレベルでは、Pod マニフェストでこの設定をすることはかなり簡単です。最も良い方法は、securityContext の runAsUser と runAsGroup フィールドに 0 以外の値を使用することです。
ただし、その際には、非 root ユーザで実行してもコンテナが動作することを確認することが重要です。オリジナルのコンテナイメージが root での実行を前提に設計されており、ファイルのパーミッションが制限されている場合、アプリケーションの動作に問題が発生する可能性もあります。
最善の方法は、コンテナの Dockerfile に同じ UID と GID の組み合わせを設定し、開発やテストのプロセスを通じてその組み合わせで実行されるようにすることです。これを実現するには、Dockerfile に USER 変数で設定します。上記の例では、この行で同じ UID と GID の組み合わせを設定します。
Privileged
Docker やその他のコンテナランタイムは、コンテナからセキュリティ分離を取り除く便利な方法として、特権フラグを提供しています。これは、アプリケーションワークロードでは基本的に使用すべきではなく、必要な場合にのみ使用すべきです。
一般に、Linux コンテナは非常に柔軟なセキュリティモデルを持っているため、コンテナの運用に特定のパーミッションが必要な場合は、特権設定を使わずに追加できます。
コンテナのマニフェストを設計する際に重要なのは、すべてのマニフェストの securityContext で、privileged を false に設定するようにデフォルト設定することです。
Capabilities
Linux の capability は、従来 root ユーザにのみ与えられていた1つまたは複数の権限を、プロセスへ与えるために使用されます。デフォルトでは、Docker やその他のコンテナランタイムは、利用可能な capability のサブセットをコンテナに提供します。
アプリケーションが必要とする特定の capability のみを許可することは、良い対策の 1 つです。アプリケーションが非 root ユーザとして実行されるように設計されている場合は、そもそも capability を必要としないことがあります。
一般に、capability に関するアプローチは、まずすべての capability を削除し、アプリケーションが必要とする場合に特定の capability を追加するというものです。例えば、CHOWN capability が必要な場合は、次のような securityContext を作成します。
readOnlyRootFilesystem
この設定を使用することで、コンテナのエフェメラルな性質を利用できます。一般的に、実行中のコンテナはアプリケーションに関するいかなる状態もコンテナのファイルシステムに保存すべきではありません。このような行いは、ワークロード内に管理外の新しいバージョンのコンテナが作成されてしまうという事を意味します。
このような状況を考慮して、ワークロードマニフェストに readOnlyRootFilesystem フラグを設定することで、コンテナのルートファイルシステムを読み取り専用にできます。これは、アプリケーションの脆弱性が発見されたとしても、コンテナにツールをインストールしようとする攻撃者から防御することができる対策の 1 つです。
この設定に関連してよくある質問は、アプリケーションプロセスの実行中に必要となる一時ファイルをどのように処理するかというものです。この場合、コンテナに emptyDir ボリュームをマウントすることで、ファイルをある場所に書き込み、コンテナが破棄されたときに自動的に削除できます。
readOnlyRootFilesystem の設定は、次のように securityContext 内に真偽値で定義します。
AllowPrivilegeEscalation
この設定は Linux カーネルで公開されているもうひとつのセキュリティ設定で、通常は影響の少ない優れた強化オプションです。このフラグは、子プロセスが親プロセスよりも多くの特権を得ることができるかどうかを制御しますが、コンテナ内で実行されるアプリケーションプロセスでは、その必要はほとんどありません。
これも securityContext で以下のように設定できます。
Seccomp
マニフェストで注目すべき最後のセキュリティ層は、seccomp です。seccomp プロファイルは、セキュリティリスクを引き起こす可能性のある特定の Linux システムコールのアクセスを防止します。デフォルトでは、Docker のようなコンテナランタイムは、いくつかの特定のシステムコールのアクセスをブロックする syscall フィルタを提供します。しかし、Kubernetes で実行すると、このフィルタはデフォルトで無効になります。
そのため、ワークロードマニフェストに追加して、フィルターが再び有効になっていることを確認することが重要です。ランタイムのデフォルトプロファイルを使用するか、AppArmor や SELinux のようなカスタムプロファイルを使用できます。
seccomp フィルターは、使用している Kubernetes のバージョンに応じて、2 つの場所のいずれかで再有効化できます。v1.18 以降では、AppArmor のようにマニフェストの metadata セクションの annotations で有効化します。annotations のサンプルは以下のようになります。
v1.19 以降では、seccomp フィルターは securityContext フィールドに統合されているため、pod にデフォルトの seccomp フィルタを使用するように設定するには、次のようにします。
Resource limits
Kubernetes のワークロードは基盤となるノードを共有しているため、個々のコンテナがノード上のすべてのリソースを使用できないようにすることが重要です。これを行わないと、クラスター内で実行されている他のコンテナへの、パフォーマンス問題発生の可能性があります。コンテナレベルでは、コンテナが必要とするリソースの量と、許可されるべきリソースの制限を指定するリソース制限の設定が可能です。
コンテナのリソースは次のように定義します。
メモリの要求値と制限値は簡単に読み取れますが、CPU の制限値は少しわかりにくいことがあります。CPU の制限は、「millicpus」という単位で表されます。1,000 は CPU 1コアまたはハイパースレッドに相当します。つまり、上記の例では、リクエストは 1 つのコアの25%であり、制限は 1 つのコアの50%となります。
リソース制限を設計する際のもう1つの注意点は、制限を超えたときにコンテナのランタイムがどのように反応するかということです。CPU の場合、プロセスは抑えられ、パフォーマンスが低下します。一方、メモリの制限を超えた場合は、コンテナランタイムがプロセスを強制終了させる可能性があります。したがって、アプリケーションの通常運用に合理的に要求される値に制限値を合わせることが重要です。
imageTag
Docker スタイルのコンテナは一般的に、イメージ名とタグ名を指定して指定します。ただし、Docker には特殊なケースがあり、タグが指定されていない場合は「最新」のタグが使用されます。しかし、どのイメージが使用されるかは、イメージレジストリの更新に応じて変化します。例えば、OS の新しいリリースが行われた場合、最新のタグは新しいバージョンに変更される可能性があります。
このようにターゲットが固定されていないため、pod で使用するコンテナイメージを指定する際に、特定されていないタグや特に「latest」タグを使用するのはよくありません。代わりに、明示的なタグを使用してください。これには、レジストリに存在する名前付きのタグを使用するか、イメージを一意に識別する SHA-256 ハッシュを使用して指定する方法があります。
1 つ目の方法では、コンテナごとにイメージとタグを指定します。この方法では、タグは一般的に変更可能なポインタであり、別のイメージにリダイレクトできるため、デプロイメントに悪影響を与えるような方法でイメージを変更しないよう、メンテナンスへ依存することになります。
SHA-256 ハッシュを指定すると、そのハッシュに対応するイメージのみが使用されます。ただし、このオプションはメンテナンスが大変です。なぜなら、イメージにパッチが適用されるたびに、新しいハッシュを反映させるためにマニフェストを更新する必要があるからです。
この記事を書いている時点で、上記で指定したイメージに相当するものは以下となります。
AppArmor
このオプションは、AppArmor を使用する Linux ディストリビューション(主に Debian 系)で適用されます。AppArmor は、他の分離層が破られたり、攻撃者にバイパスされたりした場合でも、保護を提供する強制的なセキュリティレベルを追加できます。
AppArmor のポリシーを指定しない場合、コンテナランタイムのデフォルトが適用されるため、多くの場合、アプリケーションマニフェストに明示的な記述を追加する必要はありません。しかし、カスタム AppArmor プロファイルを追加してコンテナをさらに強化したい場合は、他のほとんどの強化設定とは異なり、securityContext フィールドでは設定されないことに注意する必要があります。代わりに、マニフェストの metadata 配下の annotations を介して行われます(将来のKubernetesバージョンでこれを変更するための提案があげられています)。
指定されたプロファイルは事前にクラスターノードへ配置されている必要があり、以下の例では <profile> の代わりに指定されています。
SELinux
このオプションは、SELinux を使用する Linux ディストリビューション(主に Red Hat 系)で適用されます。SELinux は AppArmor と同様に、プロセスにセキュリティの追加レイヤーを追加します。
ただし、ポリシーの設定はやや複雑で、有効にするかどうかは、コンテナランタイムとホスト OS の組み合わせによって異なります。
AppArmor と同様に、カスタム SELinux ポリシーを作成することは、よりセキュリティの高い環境では有用ですが、ほとんどの場合デフォルトのポリシーを使用することで、有用な追加のセキュリティ層を提供できます。
まとめ
安全な Kubernetes 環境の構築には、コントロールプレーンからクラスター上で実行されるアプリケーションまで、さまざまな側面があります。ワークロードのデプロイに使用される Kubernetes マニフェストを積極的にセキュリティ対策することは、このプロセスの重要な部分です。開発ライフサイクルの早い段階で行うことにより、セキュリティを大幅に向上させ、侵害のリスクを軽減することができます。