Amazon Web Services ブログ

Bottlerocket のセキュリティ機能 〜オープンソースの Linux ベースオペレーティングシステム〜

この記事は Security features of Bottlerocket, an open source Linux-based operating system を翻訳したものです。

Bottlerocket は Amazon が提供するオープンソースの Linux ベースオペレーティングシステムで、セキュリティを重視してコンテナを実行するために作られています。その結果、コンテナ化されたワークロードを実行するための安全な環境を構築する、さまざまな制御機能が組み込まれたオペレーティングシステムとなっています。この記事では、Bottlerocket に搭載されているいくつかのセキュリティ機能と、それらがどのように環境を保護するかについてご紹介します。

イントロダクション

Amazon Elastic Kubernetes Service (Amazon EKS)Amazon Elastic Container Service (Amazon ECS) などのコンテナオーケストレーションサービスを使用してコンテナを実行する場合、セキュリティ担当者が考慮しなければならないさまざまな事項があります。いくつかの重要な例として、ソフトウェアサプライチェーンの保護、異なるクラスター間での一貫したポリシーの適用、インフラストラクチャのセキュリティ保護などがあります。スタックのさまざまなレベルでセキュリティ保護を実装することが最重要であるため、Amazon Web Services (AWS) ではセキュリティに対する階層型アプローチをお勧めしています。この階層型アプローチには、ホスト (コンテナが実行されているコンピュータ) の保護が含まれます。

コンテナに慣れていない方は、他のすべてのレイヤーに加えて、なぜホストのハードニングがそれほど重要なのかと思われるかもしれません。コンテナは共有されたオペレーティングシステムのカーネル上で動作するプロセスです。コンテナ間の隔離は、Linux namespace を使用して実現されます。namespace は、オペレーティングシステムレベルでカーネルリソースを分割する Linux カーネルの機能です。Docker コンテナでは、Linux カーネルの namespace を利用して、root を含むすべてのユーザーがマシンのリソースに直接アクセスできないようにしています。しかし、namespace はそれ自体、強力なセキュリティ境界とはみなされていません。namespace は、グローバルなリソースを一意に分離されているように見せるための単なる手段です。Docker や containerd などのランタイムも、デフォルトでは root としてコンテナを実行します。これは、アプリケーションを非 root ユーザーとして実行させるなど、他の追加の制御が行われていない場合、権限昇格を許す可能性があります。

強力な分離または緩和制御を持つことは、コンテナを実行するためのインフラストラクチャが異なる顧客間で共有される可能性のある、マルチテナント環境では重要です。このような環境で実行されるコードは、信頼できないものとして扱われるべきです。例えば、熟練した攻撃者はコンテナ、アプリケーション、またはクラスターの構成上の問題を悪用して、基盤となるホストにアクセスしてくる可能性があります。ホストアクセスが可能になると、コンテナの範囲を超えたリソースにアクセスできる可能性があります。

Bottlerocket のセキュリティ概要

この概要では、まず Bottlerocket の表面部分や、攻撃者がオペレーティングシステムに不正にアクセスするために使用する可能性のある攻撃ベクトルについて調べるところから始めます。また、The Update Framework (TUF) を利用した Bottlerocket のイメージベースのアップデート方法について説明します。カーネルロックダウンについてと、この機能によって未署名のモジュールがオペレーティングシステムに追加されるのを防ぐ方法について説明します。また、Bottlerocket の読み取り専用のルートファイルシステムの影響と、ファイルシステムの整合性を検証するための dm-verity の使用についても検証します。さらに、SELinux とBottlerocket のビルトインポリシーが、ホスト上で実行されるコンテナの動作をどのように制限するかについても触れます。この記事では、SELinux がさまざまな種類の攻撃を阻止する方法をサンプルとして出します。最後に、Bottlerocket のエフェメラルストレージの使用と、アップデート時の再起動後に設定の変更を保持する方法についても検証します。また、環境の安全性をさらに高めるためのベストプラクティスも紹介します。

攻撃対象領域の削減

Bottlerocket がセキュリティ体制を改善する 1 つ目の方法は、Bottlerocket のイメージからすべてのシェル、インタプリタ、パッケージマネージャを取り除くことです。Bottlerocket を実行する際には、ホスト上にパッケージをインストールする代わりに、コンテナ内で追加のソフトウェアを実行します。

Bottlerocket では、デフォルトでコントロールコンテナ管理コンテナが提供されています。これらのコンテナは Amazon Linux 2 をベースにしており、Amazon の Yum リポジトリで利用可能なパッケージにアクセスできますが、お好みのオペレーティングシステムとパッケージマネージャを利用してコンテナイメージをビルドすることもできます。これらのコンテナイメージは、Kubernetes や Amazon ECS などのお好みのオーケストレーターや、Bottlerocket のホストコンテナ機能を使って実行することができます。

また、Bottlerocket の API ファースト / コンテナ中心のアプローチは、フリート管理の簡素化にも役立ちます。例えば、Bottlerocket は AWS Systems Manager と統合しています。AWS Systems Manager は Bottlerocket インスタンスを含む AWS 上のインフラストラクチャの表示とコントロールに使用できるサービスの集合体です。

AWS クラウド上で Bottlerocket を実行する場合に推奨されるオプションは、オーケストレーター外で、分離された containerd インスタンスで実行される、AWS Systems Manager Session Manager へアクセス可能なコントロールコンテナを使用することです。AWS Systems Manager Session Manager では、AWS Identity and Access Management (IAM) を使ってオペレーティングシステムへのアクセスを規制することができます。コントロールコンテナは、control_t という SELinux ラベルで実行され、Bottlerocket API 用の Linux ソケットをマウントします。これらを組み合わせることで、Bottlerocket オペレーティングシステムを設定したり、必要に応じて管理コンテナを有効にする機能を提供します。

Bottlerocket のイメージをカスタマイズしたい場合、どうすればよいですか?

Bottlerocket の独自のバリアントを、ソフトウェアやカスタマイズで作成する仕組みがありますが、いくつかの注意点があります。

まず初めに、カスタムイメージは Bottlerocket の公式イメージという信頼性が無くなります。将来的に Bottlerocket の公式ビルドに切り替えたい場合は、すべてのノードを AWS のバリアントに置き換える必要があります。

次に、インプレースアップグレードをサポートする場合、署名鍵の保存場所や、リポジトリのコンテンツの配信方法を決める必要があります。

カスタムイメージの公開については、Publishing Bottlerocket のドキュメントを参照してください。

Bottlerocket は、ユーザーのユースケースに合わせて汎用性を高めることを目的としているため、バリアントを構築・メンテナンスする必要はありません。追加のパッケージを実行する必要がある場合、コンテナで実行することを検討してください。同様にオペレーティングシステムをカスタマイズする必要がある場合、バリアントを構築する前にブートストラップコンテナを試してみてください。

イメージベースのアップデート

Bottlerocket を実行しているインスタンスのアップグレードは、物理的なデバイスのファームウェアのアップグレードや、携帯電話にオペレーティングシステムのアップデートを適用するのと似ています。新しいバージョンにするために、たくさんのソフトウェアパッケージをインストールする必要はありません。代わりの方法として、システム全体のディスクイメージをダウンロードします。ディスクイメージは非アクティブなセカンダリパーティションに書き込まれ、アップデートが適用されます。インスタンスのアップグレードを有効にするには、インスタンスを再起動し、新しいプライマリパーティションから新しいバージョンのオペレーティングシステムで起動します。すべてのデータと永続的な設定は、別のデータパーティションに保存され、再起動後も利用可能です。

Bottoleroket について、AWS はパッチがリリースされると、タイムリーにイメージのアップデートを提供することをコミットしています。これらのアップデートは、管理コンテナやコントロールコンテナから apiclient update apply --check --reboot を実行することで適用できます。また Kubernetes の場合は Update Operator、Amazon ECS の場合は Bottlerocket ECS Updater を使用できます。Update Operator や Bottlerocket ECS Updater などのソリューションは、アップデートを実行する前にインスタンスからコンテナを DRAINING するという点で類似しています。アップグレード中に発生する可能性のある問題の影響を減らすために、アップデートを一度にすべてを行うのではなく、タイミングをずらして実行します。このような Waves の詳細については、update waves のドキュメントを参照してください。

Bottlerocket のイメージは、The Update Framework (TUF) で保護されたアップデートリポジトリを通じて提供されます。TUF は、Bottlerocket がオペレーティングシステムの安全なインプレースアップグレードを行うために使用するメカニズムも提供しています。

各 Bottlerocket イメージには root.json ファイルが埋め込まれています。このファイルには Bottlerocket が信頼する鍵のリストがあり、信頼のチェーンを開始することができます。このファイルは複数の鍵で署名されており、攻撃者が別のバージョンに置き換えることができないようになっています。TUF リポジトリには、targets.json (これも署名されています) ファイルも含まれています。このファイルにはリポジトリで利用可能なすべてのターゲットファイルと、そのハッシュがリストアップされています。マニフェストに記載されているファイルはすべて TUF ターゲットとみなされ、TUF リポジトリからのみダウンロードできます。これにより、Bottlerocket が信頼しないイメージを含んだ、信頼できないデータをダウンロードすることを防ぐことができるため、Bottlerocket のソフトウェアサプライチェーンの継続的な完全性を確保することができます。

マネージド型ノードグループ

セルフマネージド型の Kubernetes、または Amazon EKS のセルフマネージド型ノードを使用している場合、Bottlerocket の Update Operator は、Bottlerocket インスタンスをアップグレードするための優れたメカニズムを提供します。もしフルマネージドの体験をしたい場合は、EKS マネージド型ノードグループを使用して Bottlerocket をデプロイしてみてください。Update Operator とは異なり、マネージド型ノードグループは AWS CloudFormation または Amazon EKS API で作成することができます。これらはノードグループ内のインスタンスをより新しい、または異なるバージョンの Bottlerocket AMI で置き換えるために使用されます。

マネージド型ノードグループは、waves のようにアップグレードを実行するのではなく、最大同時実行数の設定に応じて、マネージド型ノードグループ内のインスタンスを徐々に置き換えていきます。さらに、マネージド型ノードグループでは、コンソールでアップデートが可能になったときに通知され、的確な時間にアップグレードを行えるようにできます。

明示的な “time to live” を経過したインスタンスをリプレースすることは、Bottlerocket でマネージド型ノードグループの使用を検討するもう一つの理由かもしれません。ホストを定期的にリサイクルすることで、ホストのカーネルを侵害した攻撃者を混乱させることができます。次のセクションでは、カーネルのセキュリティ問題のリスクを軽減する方法を検討します。

カーネルロックダウン

攻撃者がコンテナから脱出し、root として基盤となるホストにアクセスすることができたとします。Bottlerocket はどのようにしてセキュリティインシデントの重大性を軽減するのでしょうか?

1 つ目の方法は、integrity モードでカーネルロックダウンを設定することです。これにより、攻撃者がカーネルのメモリを上書きしたり、コードを変更したりすることが制限されます。また、攻撃者が未署名のカーネルモジュールをロードするのを防ぐこともできます。Bottlerocket イメージに含まれるカーネルモジュールのみをロードすることができます。

Bottlerocket バリアントの以前のバージョンでは、カーネルのロックダウンが none に設定されており、保護機能が無効になっていましたが、aws-ecs-1、aws-k8s >= 1.20、aws-dev、VMware などの新しいバージョンのバリアントでは、カーネルのロックダウンが integrity モードに設定されています。

あるいは、カーネルロックダウンを confidentiality モードに設定することもできます。これは、ユーザースペースからカーネルのメモリを読み取る方法を制限するものです。このモードの主な目的は、カーネルに保存されている機密情報を保護することです。confidentiality モードが、カーネルメモリの読み取りに依存している eBPF や perf などのツールに影響することを考えると、一般的に Bottlerocket では integrity モードを使用することをお勧めします。

カーネルロックダウンを integrity モードに設定するには、EC2 インスタンスのユーザーデータで参照される TOML ファイルを使用します。

[settings.kernel]
lockdown = "integrity"

また、コントロールコンテナから apiclient set kernel.lockdown=integrity を実行することもできます。

注: ロックダウンの設定を変更するには、システムの再起動が必要になる場合があります。

読み取り専用のルートファイルシステムと dm-verity

攻撃者はカーネルモジュールの追加だけでなく、ルートファイルシステム上のファイルを悪意のあるバージョンで置き換えようとするかもしれません。この場合も Bottlerocket はさらなる保護層を提供します。rootfs を読み取り専用のボリュームとしてマウントすることで、ルートファイルシステム上のファイルの変更を防ぎます。

また Bottlerocket は、暗号ダイジェストを用いてブロックデバイスの透過的な整合性チェックを行う Linux カーネルモジュールの dm-verity を利用しています。dm-verity は、データに対する偶発的または意図的な変更をキャッチし、そのような変更が発生した場合に (パニックによって) クローズドに失敗することで、ファイルシステムの整合性を維持します。システムが起動するたびに、dm-verify はルートファイルシステムの整合性を検証し、エラーや破損の証拠があった場合はオペレーティングシステムの起動を拒否します。これは、CVE-2019-5736 のような一部のコンテナエスケープの脆弱性からの保護に役立ちます。

dm-verity ハッシュを上書きして動作を確認するには、まず管理コンテナにログインします。ホストのルートファイルシステムが /.bottlerocket/rootfs にマウントされており、読み取り専用でマウントされていることを確認します。

注: これによりノードが操作できなくなります。保持する必要のあるノードでは試さないでください。

[ec2-user@ip-1-2-3-4 /]$ findmnt
TARGET                      SOURCE                 FSTYPE     OPTIONS                                                                      
├─/.bottlerocket/rootfs     dev/dm-0               ext4       ro,relatime,seclabel

次に管理コンテナ内で sheltie を実行し、以下のように dd コマンドを実行します。dm-verity はハッシュの破損を識別し、直ちにノードを再起動します。

bash-5.0# dd if=/dev/zero of= bs=1M count=1
bash-5.0# echo 1 > /proc/sys/vm/drop_caches

blkid を実行すると、ハッシュパーティションが見つかります。パーティションのタイプは DM_verity_hash となります。

ノードが unhealthy と判定され、Auto Scaling グループ によって置き換えられるまで、複数回再起動することがあります。

SELinux

Linux では通常、DAC (Discretionary Access Control: 任意アクセス制御) を用いてオブジェクトへのアクセスを制限します。例えば、リソースに設定されたパーミッションのマスクは、そのリソースの所有者によってコントロールされます。

SELinux は Linux のセキュリティモジュールで、リソースに対して実行が許可されるアクションを指定するルールベースのシステムを使用します。これらのルールは、DAC によって付与された権限を上書きします。特定のアクションに対してルールが指定されていない場合、そのアクションは自動的に拒否されます。これを MAC (Mandatory Access Control: 強制アクセス制御) といいます。

SELinux において管理者は、プロセス、ソケット、ファイルなどの異なるシステムリソースを識別するために、ラベル付けのスキームを使用します。これらのラベルは SELinux が強制するルールの中で参照されます。

SELinux を実行すると、アプリケーションの未知の脆弱性やゼロデイに対する保護がさらに強化されます。

Bottlerocket では SELinux をデフォルトで enforcing モードで実行しています。カーネルは SELinux が無効になったり、enforcing モードがオフになったりしないようにコンパイルされています。さらに、Bottlerocket の組み込みルールは、システム上で実行されるコンテナがアクセスできるリソースを自動的に制限します。これが Bottlerocket でのコンテナのエスケープにどのような影響を与えるかを説明します。

攻撃者が root ユーザーとしてホストにアクセスできるようになると、たいていはゲームオーバーです。root ユーザーとして、多くのダメージを与えることができます。例えば、カーネルモジュールをインストールして、強制のためのカーネルに依存する保護機能を回避したり、追加プロセスを実行したり、OS の構成を変更したりすることができます。

しかし Bottlerocket では、すべての非特権コンテナに制限付きの container_t ラベルが自動的に割り当てられます。これにより、コンテナが root 権限で実行されている場合でも、コンテナとその子プロセスがホストオペレーティングシステムに対して実行できるアクションが制限されます。

攻撃者が非特権の Pod 内で Bottlerocket API ソケットをマウントできたとします。次の出力が示すように、ソケットは /run/api.sock にマウントされています。攻撃者が apiclient を使用して Bottlerocket の設定を出力しようとすると、エラーが発生します。

$ df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          79G  5.1G   71G   7% /
tmpfs            64M     0   64M   0% /dev
tmpfs           3.8G     0  3.8G   0% /sys/fs/cgroup
tmpfs           1.6G  1.4M  1.6G   1% /run/api.sock
shm              64M     0   64M   0% /dev/shm
/dev/nvme1n1p1   79G  5.1G   71G   7% /etc/hosts
/dev/root       906M  619M  225M  74% /usr/bin/apiclient
tmpfs           3.8G   12K  3.8G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs           3.8G     0  3.8G   0% /proc/acpi
tmpfs           3.8G     0  3.8G   0% /sys/firmware
$ apiclient -u /settings
Failed GET request to '/settings': Failed to send request: error trying to connect: Permission denied (os error 13)

Bottlerocket の SELinux ポリシーは、container_t ラベルを持つコンテナが api_socket_t ラベルを持つソケットにアクセスすることを防止します。特権付きのコンテナまたは super_t ラベルで実行されるコンテナ (superpowered コンテナとも呼ばれる) のみが API ソケットにアクセスできます。

$ ls -Z /run/api.sock
system_u:object_r:api_socket_t:s0 /run/api.sock

以下の出力では、コンテナのプロセスに container_t というラベルが付けられていることがわかります。

$ /proc/1/root/run# ps -aZ
LABEL                               PID TTY          TIME CMD
system_u:system_r:container_t:s0:c458,c557 17 pts/0 00:00:00 sh
system_u:system_r:container_t:s0:c458,c557 18 pts/0 00:00:00 bash
system_u:system_r:container_t:s0:c458,c557 273 pts/0 00:00:00 ps

SELinux のルールを確認すると、特権を持つものや control_t ラベルで実行されるコンテナのみが API へのアクセスを許可されていることがわかります。

; Only the API server and specific components can use the API
; socket, as this provides a means to escalate privileges and
; persist changes.
(allow api_s api_socket_t (files (mutate)))
(allow control_s api_socket_t (files (mutate)))

; Unprivileged components are not allowed to use the API socket.
(neverallow unprivileged_s api_socket_t (files (mutate)))

特権として実行されるコンテナには  control_t ラベルが割り当てられ、container_t ドメインで許可されているアクションに加えて、Bottlerocket の API ソケットへの書き込みが許可されます。

このソケットへの書き込みアクセスにより、Bottlerocket のシステム構成を完全にコントロールできるようになります。これには、ホストコンテナの任意のソースを定義したり、他の制限を回避できる “superpowers” でコンテナを実行したりする機能が含まれます。

理想的には、オーケストレーターによってスケジューリングされたコンテナが Bottlerocket の API ソケットをマウントし、必要なとき以外は特権的に実行されないようにすることです。これは、Kubernetes クラスター内のアドミッションコントローラーとして動作する OPA/GatekeeperKyverno といった 2 つの policy as code ソリューションで実現できます。

オペレーティングシステムの設定を変更する必要がある場合は、代わりに Bottlerocket のコントロールコンテナを使用することをお勧めしました。コントロールコンテナは、オーケストレーターでスケジュールされたコンテナとは異なり、SSM セッションマネージャからのアクセスを想定しており、対話セッション中に発行されたすべてのコマンドを Amazon Simple Storage Service (Amazon S3) にログとして残すオプションが含まれています。このログは、必要に応じて監査目的で使用することができます。

Kubernetes では、Pod Security Standards (PSS) がアルファ版を抜けたら、PSS を使用して適切な Pod 制限を設定を検討したくなるかもしれません。PSS は異なる権限を持つ一連のセキュリティプロファイルを確立します。たとえば、特権 (privileged) プロファイルは無制限で、システムまたはインフラストラクチャタイプのワークロードを対象としており、ベースラインプロファイルは特権アクセスが不要な大多数のワークロードを対象としています。この方式では、Bottlerocket を設定する目的で実行される Pod は特権付きで実行され、その他の Pod はベースラインまたは制限付きのプロファイルで実行されます。

次の例では、攻撃者が特権コンテナを侵害することに成功し、イメージレイヤーを上書き (削除) しようとしています。以下の出力が示すように、イメージレイヤーには cache_t というラベルが付けられています。

$ /mnt/local/var/lib/containerd/io.containerd.content.v1.content/blobs/sha256# ls -Z
system_u:object_r:cache_t:s0 021c8ee1007da9a5b1241afbdd155df3f4cdd8698d074778c387019bd352a0d8
system_u:object_r:cache_t:s0 02ed246c254c5a7bdaadb5ceedf02619a655c626795921e4912647b8e9f41ae5
system_u:object_r:cache_t:s0 039713e93bc9b5976fb596a3b5eed3370eba602761b80b99022d7276f597b569

レイヤーを上書きする代わりに、書き込み権限が必要なレイヤーの削除を試してみましょう。root であり、特権付きで実行しているにもかかわらず、アクションが拒否されます。

$ /mnt/local/var/lib/containerd/io.containerd.content.v1.content/blobs/sha256# rm 021c8ee1007da9a5b1241afbdd155df3f4cdd8698d074778c387019bd352a0d8 
rm: cannot remove '021c8ee1007da9a5b1241afbdd155df3f4cdd8698d074778c387019bd352a0d8': Permission denied

SELinux のログを見ると、Bottlerocket の SELinux ポリシーによって書き込み動作がブロックされています。このポリシーは、ホストの永続的なストレージにキャッシュされている他のコンテナのイメージレイヤーを攻撃者が改ざんすることを妨げるものです。

$ /mnt/local/var/lib/containerd/io.containerd.content.v1.content/blobs/sha256# dmesg
[    0.000000] Linux version 5.10.50 (builder@buildkitsandbox) (x86_64-bottlerocket-linux-gnu-gcc (Buildroot 2021.02.3) 10.3.0, GNU ld (GNU Binutils) 2.35.2) #1 SMP Wed Aug 4 17:13:18 UTC 2021
...
[2522874.479664] audit: type=1400 audit(1631131343.009:4): avc:  denied  { write } for  pid=2066233 comm="rm" name="sha256" dev="nvme1n1p1" ino=3662970 scontext=system_u:system_r:control_t:s0 tcontext=system_u:object_r:cache_t:s0 tclass=dir permissive=0

ルールを見ると、ランタイムは cache_t ラベルを持つファイルを変更することができる唯一のコンポーネントであることがわかります。

; Only specific components can write to these objects, as they
; provide a means to persist changes across container restarts
; and reboots.
(allow runtime_s cache_t (files (mutate)))

super_t ラベルは、ホスト上の大多数のアクション (SELinux ポリシーで定義されたもの) を許可するもので、管理コンテナのためだけに予約されるべきものです。Bottlerocket では super_t ラベルは permissive とマークされており、SELinux ポリシーは施行されませんが、enforcing モードで実行していた場合に拒否されたであろうアクションの拒否が記録されます。

管理コンテナは特別なホストコンテナで、OS のトラブルシューティングやカーネルモジュールのインストールなどに使用します (カーネルロックダウンモードが none に設定されている場合に限ります)。管理コンテナが有効な場合、ログインには SSH キーが必要です。

管理コンテナの中にはシェルスクリプトの sheltie が入っており、これを使用して Bottlerocket のホスト上で完全な root シェルを取得し、ルートファイルシステムにアクセスすることができます。ベストプラクティスとして、コンテナの大部分が特権的に実行されることを禁止したり、control_tsuper_t のラベルで実行しようとするポリシーを作成する必要があります。Kubernetes では、OPA/Gatekeeper や Kyverno をポリシーの強制メカニズムとして使用することができます。OPA/Gatekeeper と Kyverno のポリシーのライブラリは、Amazon EKS ベストプラクティスガイドに掲載されています。

攻撃者が Bottlerocket 内部の保護機能を回避しようとする別の方法として、SELinux を無効にしたり、enforcing モードをオフにしたりする方法があります。この例では、攻撃者が特権的または superpowered コンテナから脱出することに成功し、以下のコマンドを実行しようとしたとします。

echo 1 > /sys/fs/selinux/disable
echo 1 > /sys/fs/selinux/enforce

これらのコマンドはいずれも、bash: echo: write error: Invalid argument となり、SELinux が無効にできないことを証明するものです。

SELinux の次の展開は?

近い将来、Multi-Category Security (MCS) のサポートが追加され、さらに別の保護レイヤーが加わる予定です。MCS では各コンテナに MCS ペアが自動的に割り当てられます。これにより、権限のないコンテナが同じホスト上で稼働している別のコンテナが所有するプロセスやファイルなどを改ざんすることができなくなります。これは、攻撃者がホストのファイルシステムにアクセスできてしまうような、コンテナエスケープの影響を最小限に抑えることができる重要な機能です。この機能の詳細については、GitHub のディスカッションをご覧ください。

設定用のエフェメラルストレージ

Bottlerocket において、ユーザーは /etc/resolv.conf/etc/containerd/config.toml などのシステム設定ファイルを直接変更することはできません。Bottlerocket では、/etc は永続的なファイルシステム上のディレクトリではなく、 Bottlerocket 上の tmpfs マウントです。攻撃者が /etc 内のファイルを変更したとしても、システムを再起動したときにその変更が永続化される可能性は低いです。tmpfs ボリュームと dm-verity を使用することで、攻撃者が再起動後もファイルシステムへの変更を持続させることが困難になります。

システムの設定を変更する必要がある場合は、インスタンス上でローカルに実行される Bottlerocket API を通じて行います。API は HTTP インターフェイスを備えた Linux ソケットとして実装されており、OS の設定を読み込んで変更したり、設定に基づいてサービスを更新したり、OS の状態を把握して変更したりするための主要な手段となっています。これらの設定は再起動後も保持され、OS のアップグレード時にも移行されます。また、起動のたびにテンプレートからシステム設定ファイルをレンダリングするために使用されます。

設定は、追加のコンテナを実行して適用することもできます。例えば Kubernetes では、CNI や CSI などのコンテナを使ってネットワークやストレージデバイスの設定を行うことができます。CNI や CSI は、ノードエージェント (別名: DaemonSet) としてデプロイされ、多くの場合、追加の権限で実行されます。

ここでは、攻撃者が非特権コンテナ内でホスト上の /etc/mnt/etc にマウントすることに成功した場合の動作を見てみましょう。/mnt/etc のファイルシステムタイプは tmpfs です。

$ /mnt/etc# df -h
Filesystem      Size  Used Avail Use% Mounted on
overlay          79G  4.6G   72G   6% /
tmpfs            64M     0   64M   0% /dev
tmpfs           3.8G     0  3.8G   0% /sys/fs/cgroup
tmpfs           3.8G  432K  3.8G   1% /mnt/etc
tmpfs           3.8G  4.0K  3.8G   1% /mnt/etc/cni
/dev/nvme1n1p1   79G  4.6G   72G   6% /etc/hosts
shm              64M     0   64M   0% /dev/shm
tmpfs           3.8G   12K  3.8G   1% /run/secrets/kubernetes.io/serviceaccount
tmpfs           3.8G     0  3.8G   0% /proc/acpi
tmpfs           3.8G     0  3.8G   0% /sys/firmware

マウントに etc_t ラベルが適用されていることに注目してください。これにより、特権を持つコンテナと非特権のコンテナがそのマウント上のファイルを変更することができなくなります。

$ /mnt/etc/cni/net.d# mount | grep /mnt/etc
tmpfs on /mnt/etc type tmpfs (rw,nosuid,nodev,noexec,noatime,context=system_u:object_r:etc_t:s0,mode=755)
tmpfs on /mnt/etc/cni type tmpfs (rw,nosuid,nodev,noexec,noatime,seclabel)

以下は、特権コンテナ (control_t) からの write 操作が拒否されたことを示す SELinux 監査ログの一行です。

[2543663.669086] audit: type=1400 audit(1631152132.402:6): avc:  denied  { write } for  pid=2272229 comm="touch" name="/" dev="tmpfs" ino=1 scontext=system_u:system_r:control_t:s0 tcontext=system_u:object_r:etc_t:s0 tclass=dir permissive=0

繰り返しになりますが、Bottlerocket の設定を変更する必要がある場合は、API を通じて行うか、Amazon EC2 のユーザーデータの TOML フォーム設定を参照し、ブートストラッププロセスの一部として行う必要があります。

まとめ

脅威から身を守ることは、セキュリティ担当者にとって大きな関心事です。Bottlerocket によって、AWS はコンテナを実行するための安全で専用のオペレーティングシステムを提供し、クラウドのセキュリティを向上させることができます。

Bottlerocket はセキュリティ対策を向上させることができますが、AWS はスタックの複数の層で制御を実装する、セキュリティの階層型アプローチを取ることも強く推奨しています。AWS では、コンテナ化された環境を保護するためのベストプラクティスに従うことを推奨しています。これには、コンテナが特権的 (または root) に実行されないようにすること、ホストパスをマウントしないこと、ホストネットワークまたはホスト PID で実行しないこと、そして SELinux ラベルを追加することが含まれます。

また、Docker (Amazon ECS バリアントではすでに有効) や containerd など、コンテナランタイムにデフォルトの seccomp ポリシーを導入することも検討してみてはいかがでしょうか。

便宜上、Bottlerocket の GitHub リポジトリには、先に推奨した内容の Pod Security Policies (PSP) のサンプルが含まれています。また、Amazon ECS および Amazon EKS のベストプラクティスガイドも公開しており、環境をより安全にするために実施すべき追加の対策についてもご紹介しています。

コンテナ化されたワークロードの実行に Bottlerocket を使用し、さまざまなセキュリティ機能を試してみてください。

Bottlerocket の使用中に、提案要望や問題が発生した場合は GitHub リポジトリに issue をあげてお知らせください。

翻訳はソリューションアーキテクト加治が担当しました。原文はこちらです。