[[426719]] [51CTO.com クイック翻訳] Kubernetes は、人気があり、一般的に使用されているコンテナ オーケストレーション ツールの 1 つです。 Kubernetes ワークロードは、単純な nginx サーバーや cron ジョブのように実行されるアプリケーションです。 Kubernetes デプロイメントは、簡単に更新、拡張、管理できるため、最も一般的に使用されるワークロードの 1 つになりました。 最近リリースされた Kubernetes Hardening Guide は、Kubernetes を効果的に保護する方法に関するガイダンスを提供する優れたリソースです。このガイドで提供される情報により、Kubernetes のセキュリティ保護と強化は Kubernetes 管理者の仕事であるだけでなく、クラスターにワークロードをデプロイする開発者の仕事でもあることが明確になります。 この記事では、Kubernetes ワークロードを展開する開発者が「Kubernetes 強化ガイド」で提供されているガイダンスの一部を適用して、セキュリティを強化する方法について説明します。 これは、シンプルな Dockerfile を基にセキュリティのベスト プラクティスを段階的に追加して、開発者がすぐに再利用できるテンプレート デプロイメント マニフェスト ファイルを作成する実用的なガイドです。 前提条件- ゼロから構築するため、Docker が必要です。
- minikube のような単一ノードの Kubernetes クラスターでは、kubectl ユーティリティとともにこのガイドに従う必要があります。開発者は公式の minikube ドキュメントを使用して、自分の環境で設定することができます。
WSL2 にバンドルされた Docker Desktop によって作成されたスタンドアロン クラスターをバックエンドとして使用します。 このガイドでは、次のコードに示すように、kubectl ユーティリティを介してアクセスできる実行中のクラスターがあることを前提としています。 - シェル
- git クローン [email protected]:salecharohit/bootstrapsecurityinkubernetesdeployment.git
- cd スプリングブートメイヴン
- docker ビルド 。 -f Dockerfile.basic -t springbootmaven
- docker run
- カール http://localhost:8080
- 予想される応答:
- Alpine OSでMaven を使用して Spring Boot ビルドからHello World にアクセスしましょう!
安全な導入Kubernetes ワークロードのセキュリティ保護は、実質的に「ビルド時」のセキュリティと「ランタイム」のセキュリティに分けることができます。これらの例を実行するには、このシンプルな Spring Boot Hello World アプリケーションを使用し、ビルド時のセキュリティとランタイム セキュリティの両方を適用して Kubernetes にデプロイします。関連するURLは次のとおりです:https://github.com/salecharohit/bootstrapsecurityinkubernetesdeployment 始める前に、このリポジトリをクローンし、Docker コンテナを構築して、アプリケーションをローカルで実行します。 ビルド時のセキュリティビルド時のセキュリティでは、基盤となるコンテナがフットプリントを削減してビルドされ、可能な限り少ない権限で実行されるようにプログラムされる方法に重点を置いています。 以下では、問題の解決策を使用して、両方のアプローチについて説明します。 (1)攻撃対象領域を減らす コンテナ内にアプリケーションを構築する場合の主な目標は、データセンター、クラウド プラットフォーム、オンプレミス施設など、実行される環境に関係なく、アプリケーションが常に独立して実行されるようにすることです。ただし、これらのアプリケーションを構築する際には、スタンドアロン アプリケーションであり、依存関係があまりないという暗黙のルールがあります。 SpringBoot アプリケーションを例に挙げます。このアプリケーションを実行するために必要な唯一の依存関係は、JVM または Java ランタイムです。コンテナ内の他のものは事実上役に立たなくなります。 たとえば、AlpineOS 上に構築された SpringBoot コンテナーでは、apk パッケージ マネージャーを特にインストールする必要はありません。 - シェル
- docker exec -it springboot /bin/sh
- apk追加カール
したがって、apk バイナリを削除して Docker イメージを再構築してみてください。 この時点で、Dockerfile.asr を使用して Docker コンテナが再構築され、次のように共有されます。 - Dockerファイル
- maven:3.8.1-openjdk-17-slimからMAVEN_BUILDとして
- ワークディレクトリ /build/
- pom.xml /build/ をコピーします。
- コピー src /build/src/
- mvnパッケージを実行する
- openjdk:17-alpineより
- 実行rm -f /sbin/apk && \
- rm -rf /etc/apk && \
- rm -rf /lib/apk && \
- rm -rf /usr/share/apk && \
- rm -rf rm -rf /var/lib/apk
- コピー
- エクスポーズ8080
- コマンド java -jar /springbootmaven.jar
ここで再構築して再実行します: - シェル
- #まず、以前実行していたコンテナを停止しましょう
- docker の springboot を停止する
- #次に再構築して再実行してみましょう
- docker ビルド 。 -f Dockerfile.asr -t springbootmaven
- docker run
- docker run
- カール http://localhost:8080
ここで、apk add curl コマンドをもう一度実行してみます。 - シェル
- docker exec -it springboot /bin/sh
- apk追加カール
つまり、apk の依存関係は正常に解消され、アプリケーションは正常に実行されました。 Alpine OS を強化するために特別に作成された優れたスクリプトをいくつか紹介します。プログラミング言語に基づいて選択し、それに応じてベースのアルパイン イメージを強化します。参考URLをいくつか紹介します。 - https://gist.github.com/kost/017e95aa24f454f77a37
- https://github.com/ironpeakservices/iron-alpine/blob/master/Dockerfile
一方、Google が作成した distroless コンテナもチェックしてみると良いでしょう。これも強くお勧めします。 - https://github.com/GoogleContainerTools/distroless/tree/main/examples
(2)ユーザーシナリオの切り替え サイバー攻撃者がコンテナ内で RCE を取得した場合、curl、wget などのパッケージをインストールして永続性を確立できなくなる可能性があると主張する人もいます。 ただし、引き続き「root」ユーザーとして実行している場合は、技術的には apk を再インストールすることは可能です。 ここで Docker コンテナを再実行し、現在実行されている権限を確認します。 - シェル
- docker exec -it springboot /bin/sh
- だれだ
- rohitsalecha.com にピン
したがって、コンテナを root として実行するのではなく、制限された権限のみを持つユーザーとして実行することが重要です。 Dockerfile.lpr には、「boot」というユーザーとグループを追加し、作業ディレクトリ (ホーム ディレクトリ) を割り当てるコマンドが追加されています。ユーザーとグループには数値も割り当てられます。これについては、以下の「ポッド セキュリティ シナリオ」セクションで詳しく説明します。 - Dockerファイル
- maven:3.8.1-openjdk-17-slimからMAVEN_BUILDとして
- ワークディレクトリ /build/
- pom.xml /build/ をコピーします。
- コピー src /build/src/
- mvnパッケージを実行する
- openjdk:17-alpineより
- # apk パッケージ マネージャーを削除する
- 実行rm -f /sbin/apk && \
- rm -rf /etc/apk && \
- rm -rf /lib/apk && \
- rm -rf /usr/share/apk && \
- rm -rf rm -rf /var/lib/apk
-
- #ユーザーの追加 そして 「ブート」と呼ばれるグループ
- addgroup boot -g 1337 && \ を実行します。
- adduser -D -h /home/boot -u 1337 -s /bin/ash ブート -G ブート
-
- # 以下のコマンドを実行するコンテキストを変更する ユーザー "ブート" その代わり ルートの
- ユーザーブート
- ワークディレクトリ /home/boot
- #による デフォルトでは、非ルート コンテキストでも、Docker はファイルをルートとしてコピーします。したがって、 chownするのがベストプラクティスです
- #ユーザーとしてコピーされるファイル。 https://stackoverflow.com/a/44766666/1679541
- コピー
- エクスポーズ8080
- コマンド java -jar /home/boot/springbootmaven.jar
再構築して再実行: - #まず、以前実行していたコンテナを停止しましょう
- docker の springboot を停止する
- #次に再構築して再実行してみましょう
- docker ビルド 。 -f Dockerfile.lpr -t springbootmaven docker run
次に、whoami コマンドを実行して、どのコンテナが現在どの権限で実行されているかを確認します。 - シェル
- docker exec -it springboot /bin/sh
- だれだ
- rohitsalecha.com にピン
ランタイムセキュリティパッケージが削除され、ユーザー シナリオが更新されて権限が制限されたコンテナーが実行されるようになったため、ビルド時のセキュリティに対する信頼が高まっています。これらのセキュリティ機能は、Docker コンテナを構築するときに適用されます。ただし、Kubernetes 環境でコンテナを実行する場合は、コンテナのセキュリティ体制にも重点を置く必要があります。これについては、以下で説明します。 Kubernetes デプロイメントのセキュリティ保護を開始する前に、まず Docker コンテナを hub.docker.com にプッシュして、Kubernetes クラスター上でアプリケーションを実行します。このガイドを使用して、同じことを始めることができます。 - シェル
- docker ビルド 。 -f Dockerfile.lpr -t springbootmaven
- docker タグ springbootmaven salecharohit/springbootmaven
- docker push salecharohit/springbootmaven
- docker run -d -p 8080:8080
- カール http://localhost:8080
Docker イメージの準備ができたので、kubernetes-basic.yaml ファイルを適用して、このアプリケーションと、それに接続するのに役立つサービスをデプロイします。 - ヤム
- #名前空間を作成する
- APIバージョン: v1
- 種類: 名前空間
- メタデータ:
- 名前: ブート
-
- # SpringBootデプロイメントを作成する
- APIバージョン: アプリ/v1
- 種類: デプロイメント
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- レプリカ: 1
- セレクタ:
- 一致ラベル:
- アプリ: springbootmaven
- テンプレート:
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 仕様:
- コンテナ:
- - 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
-
-
- # SpringBoot デプロイメント用のサービスを作成する
- APIバージョン: v1
- 種類: サービス
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- ポート:
- -名前: "http"
- ポート: 8080
- ターゲットポート: 8080
- セレクタ:
- アプリ: springbootmaven
Pod が Kubernetes API サーバーと通信する必要がある場合、認証にはサービス アカウント トークンが必要です。 - シェル
- kubectl を適用 -f kubernetes-basic.yaml
- kubectl get deploy -n ブート
- #ブートサービスのみをcurl する一時コンテナを実行します
- kubectl run -it testpod
- 期待される出力:
- Alpine OSでMaven を使用して Spring Boot ビルドからHello World を実行しました。pod "testpod"が削除されました
(1)サービスアカウントトークン Pod が Kubernetes API サーバーと通信する必要がある場合、認証用のサービス アカウント トークンが必要です。 デフォルトでは、各 Pod には /var/run/secrets/kubernetes.io/serviceaccount/token にマウントされるサービス アカウント トークンが割り当てられます。 Spring Boot アプリケーションをデプロイすると、これを実際に確認できます。 - シェル
- kubectl ポッドを取得 -n ブート
- kubectl exec -it springbootmaven-7d7c5c8597-mndv9 -n ブート
- トークン=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
- curl -k -H "Authorization:Bearer $TOKEN" https://kubernetes.docker.internal:6443/version
アプリケーションに RCE 脆弱性があると、このアクセス トークンがネットワーク攻撃者に漏洩する可能性があります。攻撃者は、グローバル読み取り権限があっても、このアクセス トークンを悪用して同じ名前空間内のリソースの読み取りと書き込みを行うことができます。 この問題には、具体的な状況に応じて 2 つの解決策があります。まず、Pod は API サーバーにアクセスする必要はありません。次に、Pod は API サーバーにアクセスする必要があります。 この状況は、次に示すように、Kubernetes マニフェスト ファイルに 2 行を追加することで簡単に修正できます。 - ヤム
- 1 サービス アカウント名: ""
- 2 automountServiceAccountToken: false
完全なデプロイメント ファイル kubernetes-nosa.yaml は次のとおりです。 - ヤム
- APIバージョン: アプリ/v1
- 種類: デプロイメント
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- レプリカ: 1
- セレクタ:
- 一致ラベル:
- アプリ: springbootmaven
- テンプレート:
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 仕様:
- コンテナ:
- 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
- サービスアカウント名: ""
- automountServiceAccountToken: false
次に、サービス アカウント トークンがインストールされていることを確認します。 - シェル
- # 以前のデプロイが削除されていることを確認します。
- kubectl ns ブートを削除する
- # 応募する サービス アカウント トークンがありません
- kubectl を適用 -f kubernetes-nosa.yaml
- kubectl ポッドを取得 -n ブート
- kubectl exec -it springbootmaven-5568b9874f-8nml8 -n ブート
- cat /var/run/secrets/kubernetes.io/serviceaccount/token
上記のように、デフォルトのサービス アカウント トークンはマウントされなくなりました。 この場合、ServiceAccount をロールにマップする ServiceAccount、Role、および RoleBinding を作成する必要があります。 Kubernetes チェックリストは次のとおりです。 - 特定の名前空間 (つまり boot) に対して bootserviceaccount という名前の ServiceAccount を作成します。
- 実行中の Pod を表示する権限のみを持つ bootservicerole というロールを作成します。
- bootservicerolebinding という名前の RoleBinding を作成します。
- 次の行を使用して、作成された ServiceAccount をデプロイメントにマウントします。
- ヤム
-
- 仕様:
- コンテナ:
- - 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
- サービスアカウント名: bootserviceaccount
-
これにより、「boot」名前空間内の Pod のみの読み取りが可能になります。 完全なデプロイメント ファイル kubernetes-withsa.yaml は次のとおりです。 - ヤム
- #名前空間を作成する
- APIバージョン: v1
- 種類: 名前空間
- メタデータ:
- 名前: ブート
-
- APIバージョン: v1
- 種類: サービスアカウント
- メタデータ:
- 名前: bootserviceaccount
- 名前空間: ブート
-
- 種類: 役割
- apiバージョン: rbac。認証.k8s.io/v1
- メタデータ:
- 名前: bootservicerole
- 名前空間: ブート
- ルール:
- -apiグループ: [ "" ]
- リソース: [ "ポッド" ]
- 動詞: [ 「取得する」 、 「一覧表示する」 、 「見る」 ]
-
- 種類: RoleBinding
- apiバージョン: rbac。認証.k8s.io/v1
- メタデータ:
- 名前: bootservicerolebinding
- 名前空間: ブート
- 科目:
- - 種類: サービスアカウント
- 名前: bootserviceaccount
- 名前空間: ブート
- ロールリファレンス:
- 種類: 役割
- 名前: bootservicerole
- apiGroup : rbac.authorization.k8s.io
-
- # SpringBootデプロイメントを作成する
- APIバージョン: アプリ/v1
- 種類: デプロイメント
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- レプリカ: 1
- セレクタ:
- 一致ラベル:
- アプリ: springbootmaven
- テンプレート:
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 仕様:
- コンテナ:
- - 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
- サービスアカウント名: bootserviceaccount
-
-
- # SpringBoot デプロイメント用のサービスを作成する
- APIバージョン: v1
- 種類: サービス
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- ポート:
- -名前: "http"
- ポート: 8080
- ターゲットポート: 8080
- セレクタ:
- アプリ: springbootmaven
次に実行して、アプリケーションが正常に実行されるかどうかを確認します。 - # 以前のデプロイが削除されていることを確認します。
- kubectl ns ブートを削除する
- kubectl を適用 -f kubernetes-withsa.yaml
- kubectl run -it testpod
# 以前のデプロイメントを必ず削除してください。 (2)ポッドセキュリティシナリオ 基本 Docker イメージは非ルート権限で実行するように構成されていますが、セキュリティのベスト プラクティスとして、少量の構成を追加する必要があります。したがって、次のことを行う必要があります。 ① コンテナとポッドの機能を制限します。 ②権限昇格を無効にします。 ③Dockerfile.lpr で先ほど作成した特定の uid/gid でコンテナを実行するように設定します。 Kubernetes マニフェスト ファイルでは、2 種類の「SecurityContexts」が定義されています。 - ポッドレベルで実行します。これは、このポッドで実行されているすべてのコンテナに適用されます。
- ヤム
-
- セキュリティコンテキスト:
- グループ: 1337
- 非ルートとして実行: true
- 実行ユーザー: 1337
- コンテナ:
-
- ヤム
-
- セキュリティコンテキスト:
- 権限昇格を許可: false
- 特権: false
- 実行ユーザー: 1337
- 機能:
- ドロップ: [ "SETUID" , "SETGID" ]
- サービスアカウント名: ""
- automountServiceAccountToken: false
-
PodSecurity シナリオを埋め込む完全なデプロイメント ファイル kubernetes-ps.yaml は次のとおりです。 - ヤム
- #名前空間を作成する
- APIバージョン: v1
- 種類: 名前空間
- メタデータ:
- 名前: ブート
-
- # SpringBootデプロイメントを作成する
- APIバージョン: アプリ/v1
- 種類: デプロイメント
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- レプリカ: 1
- セレクタ:
- 一致ラベル:
- アプリ: springbootmaven
- テンプレート:
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 仕様:
- セキュリティコンテキスト:
- グループ: 1337
- 非ルートとして実行: true
- 実行ユーザー: 1337
- コンテナ:
- - 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
- セキュリティコンテキスト:
- 権限昇格を許可: false
- 特権: false
- 実行ユーザー: 1337
- 機能:
- ドロップ: [ "SETUID" , "SETGID" ]
- サービスアカウント名: ""
- automountServiceAccountToken: false
-
- # SpringBoot デプロイメント用のサービスを作成する
- APIバージョン: v1
- 種類: サービス
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- ポート:
- -名前: "http"
- ポート: 8080
- ターゲットポート: 8080
- セレクタ:
- アプリ: springbootmaven
実行して、アプリケーションが実行されていることをテストします。 - シェル
- # 前回の適用が削除されていることを確認する
- kubectl ns ブートを削除する
- kubectl を適用 -f kubernetes-ps.yaml
- kubectl run -it testpod
- kubectl ポッドを取得 -n ブート
- kubectl exec -it springbootmaven-56c64ff85-mqz2z -n ブート
- だれだ
- id
- google.com にping
開発者はニーズに応じてさらに多くの機能を削除できます。 AppArmor や SecComp などの機能では、コントロール プレーン コンポーネントの追加構成が必要です。したがって、ここで説明する内容は、簡単に有効化でき、高いレベルのセキュリティ保証を保証する、すぐに使用できる機能に限定されます。 (3)不変ファイルシステム コンテナ化された環境で実行されるアプリケーションは、実際には不変のシステムを持つというロジックに反するため、データを書き込むことはほとんどありません。ただし、場合によってはファイルをキャッシュしたり、一時的にスワップ/処理したりする必要があることもあります。したがって、開発者にこの機能を提供するために、コンテナが強制終了されると失われる一時ボリュームとして emptyDir をマウントすることができます。 これにより、コンテナー内で実行されているアプリケーションは、「tmp」ディレクトリ以外のファイル システムのどこにも書き込む必要がなくなるため、「readOnlyRootFilesystem」という別のセキュリティ シナリオ プロパティを追加して true に設定することもできます。 上記の要件は以下のように設定できます。 - ヤム
-
- コンテナ:
- - 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
- セキュリティコンテキスト:
- readOnlyRootFilesystem: true
- ボリュームマウント:
- - マウントパス: /tmp
- 名前: tmp
- ボリューム:
- - 空のディレクトリ: {}
- 名前: tmp
-
完全なデプロイメント ファイル kubernetes-rofs.yaml は次のコードに示されています。 - ヤム
- #名前空間を作成する
- APIバージョン: v1
- 種類: 名前空間
- メタデータ:
- 名前: ブート
-
- # SpringBootデプロイメントを作成する
- APIバージョン: アプリ/v1
- 種類: デプロイメント
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- レプリカ: 1
- セレクタ:
- 一致ラベル:
- アプリ: springbootmaven
- テンプレート:
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 仕様:
- セキュリティコンテキスト:
- グループ: 1337
- 非ルートとして実行: true
- 実行ユーザー: 1337
- コンテナ:
- - 画像: salecharohit/springbootmaven
- 名前: springbootmaven
- ポート:
- - コンテナポート: 8080
- セキュリティコンテキスト:
- 権限昇格を許可: false
- readOnlyRootFilesystem: true
- 特権: false
- 実行ユーザー: 1337
- 機能:
- ドロップ: [ "SETUID" , "SETGID" ]
- ボリュームマウント:
- - マウントパス: /tmp
- 名前: tmp
- サービスアカウント名: ""
- automountServiceAccountToken: false
- ボリューム:
- - 空のディレクトリ: {}
- 名前: tmp
-
- # SpringBoot デプロイメント用のサービスを作成する
- APIバージョン: v1
- 種類: サービス
- メタデータ:
- ラベル:
- アプリ: springbootmaven
- 名前: springbootmaven
- 名前空間: ブート
- 仕様:
- ポート:
- -名前: "http"
- ポート: 8080
- ターゲットポート: 8080
- セレクタ:
- アプリ: springbootmaven
アプリケーションを起動し、アプリケーションが実行されているかどうかをテストします。 - シェル
- # 前回の適用が削除されていることを確認する
- kubectl ns ブートを削除する
- kubectl を適用 -f kubernetes-rofs.yaml
- kubectl run -it testpod
- kubectl ポッドを取得 -n ブート
- kubectl exec -it springbootmaven-56c64ff85-mqz2z -n ブート
- パスワード
- タッチテスト.txt
結論はこれで、コンテナ化されたアプリケーションに埋め込むことができるさまざまなコントロールがわかりました。また、サイバー攻撃者がコンテナ化されたシステムに足場を築くことを困難にするランタイム保護メカニズムを有効にする方法も理解します。 kubernetes-rofs.yaml は、Kubernetes 環境にデプロイされたときに、開発者がデフォルトのセキュリティ機能を使用してアプリケーションをコンテナ化するための優れたテンプレートとして機能します。 もちろん、特定のアプリケーション用の Dockerfile を作成する必要があります。 原題: Kubernetes デプロイメントにおけるブートストラップ セキュリティ、著者: Rohit Salecha [51CTOによる翻訳。パートナーサイトに転載する場合は、元の翻訳者と出典を51CTO.comとして明記してください。 |