OpenShift から GKE Enterprise への移行: OpenShift セキュリティ コンテキスト制約から GKE Enterprise への移行

Last reviewed 2022-01-24 UTC

このドキュメントは、ソース OpenShift クラスタで定義された OpenShift セキュリティ コンテキスト制約(SCC)からターゲット GKE クラスタへのセキュリティ ポリシーの移行を計画する際に役立ちます。この実装では、Policy Controller の制約を使用して、移行したポリシーをターゲット クラスタに定義します。

このドキュメントは、Google Cloud へのコンテナの移行: OpenShift から GKE Enterprise への移行を理解していることを前提としています。OpenShift とセキュリティ コンテキストの制約を理解し、ソース OpenShift クラスタとターゲット GKE クラスタにアクセスできることを前提としています。

このドキュメントは、Google Cloud への移行に関する複数のパートからなるシリーズの一部です。シリーズの概要については、Google Cloud への移行: 移行パスの選択をご覧ください。

このドキュメントは、コンテナを Google Cloud に移行する方法を説明するシリーズの一部です。

このドキュメントは、OpenShift SCCGKE Enterprise に移行する場合に役立ちます。また、移行について検討している場合、その概要を把握するうえでも、このドキュメントが役立ちます。

このドキュメントは、Google Cloud への移行: スタートガイドGoogle Cloud へのコンテナの移行: Kubernetes から GKE への移行Google Cloud へのコンテナの移行: OpenShift から GKE Enterprise への移行GKE ネットワーキングのベスト プラクティスで説明されているコンセプトに基づいています。必要に応じてそれらのドキュメントへのリンクも掲載しています。

OpenShift SCC

SCC は OpenShift 固有のリソースであり、Pod が実行できるアクションや Pod がノードでアクセスできるリソースを指定する Pod のポリシーを定義するために使用されます。Pod を作成する API リクエストを行うと、SCC は SCC によって定義された一連のポリシーに対するプロセス権限に照らし合わせて Pod リクエストを評価します。SCC は、構成されたポリシーに従って Pod の実行を許可または拒否するリクエストを評価します。OpenShift SCC の詳細については、セキュリティ コンテキスト制約の管理をご覧ください。

デフォルトの OpenShift SCC

OpenShift 4.x クラスタには、一連のデフォルトの SCC が含まれています。詳しくは、Red Hat による OpenShift での SCC の管理に関するブログ投稿をご覧ください。

Config Sync と Policy Controller

このセクションでは、Config Sync と Policy Controller について説明します。このドキュメントで後述する移行タスクを実行するため、Policy Controller の設定に関連するドキュメントとガイダンスへのリンクを示します。

Config Sync

Config Sync を使用すると、共通の Git 準拠リポジトリを使って、GKE Enterprise が管理する Kubernetes クラスタに適用されるあらゆるリソースの構成を一元的に定義できます。その構成を複数のクラスタに適用してください。

Policy Controller

Policy Controller は Kubernetes の動的アドミッション コントローラであり、中央で定義されたポリシーに照合してクラスタのコンプライアンスをチェック、監査、適用します。Policy Controller は、オープンソース プロジェクトである Open Policy Agent(OPA)Gatekeeper をベースにしています。

Config Sync と Policy Controller の設定

OpenShift SCC をミラーリングするセキュリティ ポリシーの実装を準備するには、各ターゲット クラスタに対して Config SyncPolicy Controller コンポーネントを有効にします。このセクションでは、これらのコンポーネントを設定する方法について説明します。このドキュメントの後半の OpenShift SCC を移行するセクションでは、Policy Controller の制約テンプレートを使用してセキュリティ ポリシーを実装する方法について説明します。

Policy Controller を設定する場合は、アプリケーション Pod を実行しないシステム関連の名前空間を [名前空間の除外] フィールドに入力することをおすすめします。システム関連の Namespace を除外すると、権限昇格を必要とするシステム Pod がブロックされてしまうリスクを回避できます。GKE クラスタのシステム関連の Namespace には次のものが含まれます。

  • kube-system
  • kube-public
  • gke-connect
  • gke-system
  • config-management-system
  • config-management-monitoring
  • gatekeeper-system
  • istio-system
  • cnrm-system
  • knative-serving
  • monitoring-system

上記の例外を除いて Policy Controller を設定後、Namespace ごとに admission.gatekeeper.sh/ignore=true ラベルを追加して、制約の適用から Namespace を除外します。各 Namespace にラベルを追加しないと、システム Pod(つまりクラスタ全体)が制限の厳しいポリシーの影響を受ける可能性があります。

OpenShift SCC を Policy Controller の制約に移行する

このセクションでは、OpenShift クラスタから SCC をエクスポートし、必要なポリシーに一致するようにターゲット GKE Enterprise Policy Controller の制約を構成する方法について説明します。また、SCC と Policy Controller の制約の違いについて説明し、それに応じて移行を計画できるようにします。

OpenShift SCC を評価する

OpenShift クラスタにインストールされている SCC のリストと構成をエクスポートするには、次のコマンドを使用します。

  1. すべての SCC のリストを取得します。

    oc get scc
    
  2. 各 SCC の構成をエクスポートします。

    oc get scc SCC_NAME > SCC_NAME.yaml
    

    SCC_NAME は、構成をエクスポートする SCC の名前に置き換えます。

構成のエクスポート後、分析を行い、次の OpenShift SCC のマッピング セクションの表を使用して、アプリケーションのセキュリティ要件に一致する Policy Controller の制約を構成できます。

OpenShift SCC を Policy Controller 制約テンプレートにマッピングする

次の表に、OpenShift SCC フィールドとその有効な値に対応する Policy Controller の制約と設定を示します。次の表を使用して、移行元の環境の OpenShift SCC によって実装されたアプリケーションのセキュリティ要件に一致するターゲット Policy Controller の制約を構成できます。このドキュメントで後述するエンドツーエンドの移行の例のセクションには、次の表の情報の使用方法の例が記載されています。

OpenShift SCC フィールド 型 / 取り得る値 Policy Controller の制約テンプレート Policy Controller の制約仕様
allowPrivilegedContainer: ブール値 K8sPSPPrivilegedContainer 適用された場合、特権コンテナをブロックします。
allowHostIPC: ブール値 K8sPSPHostNamespace 適用された場合、ホスト pid および ipc の名前空間へのアクセスを阻止します。
allowHostPID: ブール値 K8sPSPHostNamespace 適用された場合、ホスト pid および ipc の名前空間へのアクセスを阻止します。
allowHostNetwork: ブール値 K8sPSPHostNetworkingPorts ホスト ネットワークへのアクセスを阻止するブール値パラメータを持ち、アクセス可能なホストのポートの範囲を定義できます。
allowHostPorts: ブール値 K8sPSPHostNetworkingPorts ホスト ネットワークへのアクセスを阻止するブール値パラメータを持ち、アクセス可能なホストのポートの範囲を定義できます。
readOnlyRootFilesystem: ブール値 K8sPSPReadOnlyRootFilesystem 適用された場合、コンテナのルートファイル システムを読み取り専用としてのみマウントできます。
allowPrivilegeEscalation: true ブール値 K8sPSPAllowPrivilegeEscalationContainer 適用された場合、AllowPrivilegeEscalation セキュリティ コンテキストが true に設定された Pod をブロックします。
allowHostDirVolumePlugin: ブール値 K8sPSPVolumeTypes (SCC と同様に)ホスト ディレクトリを含む許可されたボリュームの種類のリストを定義するパラメータを持ちます。
volumes: 許可されたボリュームの種類の配列リスト K8sPSPVolumeTypes (SCC と同様に)ホスト ディレクトリを含む許可されたボリュームの種類のリストを定義するパラメータを持ちます。
allowedCapabilities: リクエストできる Linux 機能の配列リスト K8sPSPCapabilities リクエストできる Linux 機能(allowedCapabilities)と禁止されている Linux 機能(requiredDropCapabilites)を定義するパラメータを持ちます。

OpenShift SCC の defaultAddCapabilities:requiredDropCapabilities: とは異なり、リストされている機能の直接的な追加や削除には使用できません。

defaultAddCapabilities: 各コンテナに追加する必要のある Linux 機能の配列リスト K8sPSPCapabilities リクエストできる Linux 機能(allowedCapabilities)と禁止されている Linux 機能(requiredDropCapabilites)を定義するパラメータを持ちます。

OpenShift SCC の defaultAddCapabilities:requiredDropCapabilities: とは異なり、リストされている機能の直接的な追加や削除には使用できません。

requiredDropCapabilities: Pod またはコンテナから自動的に削除される Linux 機能の配列リスト K8sPSPCapabilities リクエストできる Linux 機能(allowedCapabilities)と禁止されている Linux 機能(requiredDropCapabilites)を定義するパラメータを持ちます。

OpenShift SCC の defaultAddCapabilities:requiredDropCapabilities: とは異なり、リストされている機能の直接的な追加や削除には使用できません。

fsGroup: 次のいずれかに設定可能な type: key を持ちます。
  • MustRunAs: 事前に割り当てられた値を使用しない場合は、1 つ以上の範囲を指定する必要があります。
  • RunAsAny: 任意の fsGroup ID を指定できます。
K8sPSPAllowedUsers SCC の type: keyrunAsUserrunAsGroupsupplementalGroupsfsGroup の各パラメータの id 範囲に対して類似した機能を有するルールを定義できます。

User、Groups、Supplemental Groups、FS Groups に許可される範囲を定義できるが、OpenShift SCC とは異なり、Pod で直接ユーザー ID を設定するために使用することはできません。

runAsUser: 次のいずれかに設定可能な type: key を持ちます。
  • MustRunAs: runAsUser を構成する必要があります。
  • MustRunAsRange: 名前空間の事前割り当てされた値を使用しない場合、最小値と最大値の定義が必要です。
  • MustRunAsNonRoot: ゼロ以外の runAsUser を指定して Pod を送信するか、イメージで USER ディレクティブを定義することが必要です。
  • RunAsAny: 任意の runAsUser を指定できます。
K8sPSPAllowedUsers SCC の type: keyrunAsUserrunAsGroupsupplementalGroupsfsGroup の各パラメータの id 範囲に対して類似した機能を有するルールを定義できます。

User、Groups、Supplemental Groups、FS Groups に許可される範囲を定義できるが、OpenShift SCC とは異なり、Pod で直接ユーザー ID を設定するために使用することはできません。

supplementalGroups: 次のいずれかに設定可能な type: key を持ちます。
  • MustRunAs: 名前空間の事前に割り当てられた値を使用しない場合、1 つ以上の範囲の指定が必要です。
  • RunAsAny: 任意の supplementalGroups を指定できます。
K8sPSPAllowedUsers SCC の type: keyrunAsUserrunAsGroupsupplementalGroupsfsGroup の各パラメータの id 範囲に対して類似した機能を有するルールを定義できます。

User、Groups、Supplemental Groups、FS Groups に許可される範囲を定義できるが、OpenShift SCC とは異なり、Pod で直接ユーザー ID を設定するために使用することはできません。

seLinuxContext: 次のいずれかに設定可能な type: key を持ちます。
  • MustRunAs: 名前空間から事前に割り振られた値を使用しない場合、seLinuxOptions を構成する必要があります。
  • RunAsAny: 任意の seLinuxOptions を指定できます。
K8sPSPSELinuxV2 許可されるレベル、ロール、タイプ、ユーザー seLinuxOptions を設定できる allowedSELinuxOptions パラメータを保有します。

OpenShift SCC と Policy Controller の制約の違い

このセクションでは、Policy Controller の制約と OpenShift SCC の違いについて説明します。上記の表を使用して移行先の環境に制約をデプロイする前に、これらの違いを考慮してください。

リソースに制約を適用する方法

SCC オブジェクトにある users: 仕様または group: 仕様を使用して、OpenShift SCC をユーザーとグループに割り当てることができます。OpenShift 4.x 以降のバージョンでは、ロールベース アクセス制御(RBAC)を使用してユーザーまたはグループに SCC を割り当てることもできます。SCC には、Pod に適用される SCC を並べ替えるための priority: フィールドもあります。

Policy Controller の制約は、ユーザーまたはサービス アカウントをターゲットにするのではなく、制約の特定のリソース セレクタを使用するターゲット クラスタ、Namespace、Pod に適用されます。詳細については、Policy Controller のドキュメントをご覧ください。特定のリソース セレクタを使用すると、権限の限られたユーザーがデプロイツールを使用して Pod を実行するか、クラスタ管理者がコマンドラインから Pod を起動するかにかかわらず、Pod の動作を同じにできます。

Policy Controller の制約は、ドライラン モードもサポートしています。このモードでは、ポリシーを実際に適用する前にテストを行い、違反を監査できます。このモードを使用すると、既存のワークロードへの影響が生じることを回避できます。また、ユーザーが SCC の適用対象である場合は、常に適用されます。

OpenShift 名前空間に事前に割り当てられた値を使用した SCC Pod ミューテーション

OpenShift SCC では、SCC が適用される各 Pod の関連するセキュリティ コンテキストを、名前空間のアノテーションによって提供される事前に割り当てられた範囲からの特定の ID で変更できます。そのためには、戦略タイプ MustRunAs または MustRunAsRangeRunAsUserfsGroupsupplementalGroupsseLinuxContext の各フィールドを使用します。

たとえば、restricted SCC に RunAsUser フィールドがあり、戦略タイプが MustRunAsRange で、SCC で範囲が定義されていないとします。このシナリオでは、SCC が適用される各 Pod は、Pod の Namespace の openshift.io/sa.scc.uid-range アノテーションで指定された範囲から RunAsUser ID を取得します。

Policy Controller の制約とミューテーション機能により、Pod の検証とミューテーションの両方を実現できます。ただし、この制約では、Pod セキュリティ コンテキストの値を提供するために Namespace のアノテーションを使用しません。次のセクションエンドツーエンドの移行の例は、アプリケーション配信チームが前述の制約と同様の制限を実装する制約に従うように Pod のセキュリティ コンテキストを明示的に構成する方法の例を示しています。

エンドツーエンドの移行の例

このセクションでは、次の OpenShift デフォルト SCC をターゲット GKE クラスタにマッピングするために必要な Policy Controller のすべての制約とミューテータを含む、ターゲット マニフェスト ファイルの例を示します。

  • privileged
  • anyuid
  • nonroot
  • restricted

ソース OpenShift 環境で定義されている SCC ポリシーをマッピングするときに、Pod が実行される Namespace に応じて、異なるポリシー コントローラ制約が適用されます。

  • 特権モードでの実行やホストリソースへのアクセスなど、最も高い特権アクセスを必要とするワークロードは、Policy Controller の構成で定義されている除外 Namespace のいずれかで行う必要があります。

    Namespace の除外に適用される制約はありません。通常、この最も高い特権アクセスを持つワークロードは、ソース OpenShift 環境で特権 SCC が適用されたシステム コンポーネントまたはワークロードです。

  • 免除対象外の名前空間で作成されたすべての Pod には、最も制限の厳しい制約が適用されます。これらの制約により、すべてのホスト機能へのアクセスが拒否されるため、Pod は特定の範囲内の UID で実行する必要があります。この構成は、OpenShift restricted SCC によって適用されるポリシーと一致します。この構成には次のような例外があります。

    • security=anyuid ラベルを持つ名前空間に作成された Pod は、上記の制限の厳しい制約を受けますが、UID と GID のどちらでも実行できます。これは OpenShift に対する anyuid SCC の制約と一致します。
    • security=nonroot ラベルを持つ名前空間に作成された Pod は、上記の制限の厳しい制約を受けます。ただし、Pod は root 以外の UID を指定して実行できます。これは OpenShift に対する nonroot SCC の制約と一致します。

ターゲット マニフェストの例

以下は、前のエンドツーエンドの移行例で説明した動作に一致する Policy Controller の制約とミューテータのセットを含む、単一マニフェストの例です。この例の制約または範囲は、組織のニーズに応じて確認、調整することをおすすめします。

apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPHostNamespace
metadata:
  name: psp-host-namespace
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPHostNetworkingPorts
metadata:
  name: psp-host-network-ports
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    hostNetwork: false
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPPrivilegedContainer
metadata:
  name: psp-privileged-container
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
---
apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: restricted-capabilities
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  match:
    scope: Namespaced
    kinds:
    - apiGroups: ["*"]
      kinds: ["Pod"]
    namespaceSelector:
      matchExpressions:
        - operator: NotIn
          key: security
          values: ["anyuid"]
  location: "spec.containers[name:*].securityContext.capabilities.drop"
  parameters:
    assign:
      value: ["KILL","MKNOD","SYS_CHROOT"]
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPCapabilities
metadata:
  name: restricted-capabilities
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaceSelector:
      matchExpressions:
        - operator: NotIn
          key: security
          values: ["anyuid"]
  parameters:
    requiredDropCapabilities: ["KILL","MKNOD","SYS_CHROOT"]
---
apiVersion: mutations.gatekeeper.sh/v1alpha1
kind: Assign
metadata:
  name: anyuid-capabilities
spec:
  applyTo:
  - groups: [""]
    kinds: ["Pod"]
    versions: ["v1"]
  match:
    scope: Namespaced
    kinds:
    - apiGroups: ["*"]
      kinds: ["Pod"]
    namespaceSelector:
      matchExpressions:
        - operator: In
          key: security
          values: ["anyuid"]
  location: "spec.containers[name:*].securityContext.capabilities.drop"
  parameters:
    assign:
      value: ["MKNOD"]
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPCapabilities
metadata:
  name: anyuid-capabilities
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaceSelector:
      matchExpressions:
        - operator: In
          key: security
          values: ["anyuid"]
  parameters:
    requiredDropCapabilities: ["MKNOD"]
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPAllowedUsers
metadata:
  name: restricted-users-and-groups
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaceSelector:
      matchExpressions:
        - operator: NotIn
          key: security
          values: ["anyuid","nonroot"]
  parameters:
    runAsUser:
      rule: MustRunAs # MustRunAsNonRoot # RunAsAny
      ranges:
        - min: 1000
          max: 2000
    fsGroup:
      rule: MustRunAs # MayRunAs # RunAsAny
      ranges:
        - min: 1000
          max: 2000
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPAllowedUsers
metadata:
  name: nonroot-users-and-groups
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
    namespaceSelector:
      matchExpressions:
        - operator: In
          key: security
          values: ["nonroot"]
  parameters:
    runAsUser:
      rule: MustRunAsNonRoot
    fsGroup:
      rule: MustRunAsNonRoot
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sPSPVolumeTypes
metadata:
  name: psp-volume-types
spec:
  match:
    kinds:
      - apiGroups: [""]
        kinds: ["Pod"]
  parameters:
    volumes:
      - configMap
      - downwardAPI
      - emptyDir
      - nfs
      - persistentVolumeClaim
      - projected
      - secret

サンプル マニフェストの restricted-users-and-groups 制約では、K8sPSPAllowedUsers テンプレートを使用して、runAsUser: パラメータと fsGroup: パラメータに 1,000~2,000 のサンプル範囲が明示的に設定されます。runAsUser:fsGroup: についてその範囲内の ID を使用するように設定されていない Pod はブロックされます。

GKE Enterprise と Kubernetes では、特定のユーザー ID またはグループ ID の Pod を自動的に変更するために Namespace アノテーションは使用されません。したがって、前の例のように UID 範囲を制限するには、アプリケーション配信チームは、作成した Pod で準拠する UID を明示的に設定する、または任意の ID を許可するために制約を完全に削除する必要があります。

作成した名前空間内の上記の制約を遵守している Pod マニフェストの例を次に示します(マニフェストは restricted SCC に準拠しています)。

apiVersion: v1
kind: Pod
metadata:
  name: restricted-pod-example
spec:
  securityContext:
    runAsUser: 1000
    fsGroup: 1100
  volumes:
  - name: sec-ctx-vol
    emptyDir: {}
  containers:
  - name: sec-ctx-demo
    image: busybox
    command: [ "sh", "-c", "sleep 1h" ]
    volumeMounts:
    - name: sec-ctx-vol
      mountPath: /data/demo

次のステップ