こんにちは、Necoプロジェクトの池添(@zoetro)です。
今回は、安全なKubernetesクラスタを構築するために、我々がどのようなポリシーを適用しているのかを紹介したいと思います。
Kubernetesクラスタのセキュリティ対策
安全なKubernetesクラスタを構築するためには、非常にたくさんの項目について検討しなければなりません。 ざっと挙げてみただけでも以下のような項目があります。(詳細は Kubernetesの公式ガイド を参照)
- Role-Based Access Control (RBAC)
- ネットワークアクセスの制御(Network Policy)
- コンテナの権限(Pod Security Policy)
- 通信の暗号化
- Secretの暗号化
- 信頼できるコンテナイメージの利用
- 安全なコンテナランタイムの利用
- ユーザー/グループの管理
- API ServerのAudit Log
- KubeletのAuthorization
- サービスメッシュによるトラフィック制御
これらの項目はそれぞれどのように設定すべきでしょうか?
それを決めるためには、どのような脅威から自分たちのシステムを守りたいのかという基本方針を明確にしておくことが重要です。 例えば基本方針には関係のないセキュリティ対策を実施しても、コストやオーバーヘッドが増えるだけで意味がありません。
今回は Pod Security Policy と Network Policy そして Open Policy Agent に焦点を当てたいと思います。 これらのポリシーを設定することでコンテナができることを制限することができます。 それにより悪意のあるユーザーの攻撃や通常のユーザーの誤りによってコンテナが管理者の意図しない危険なことをしようとした場合に、被害を最小限に食い止めることが可能になります。
ポリシー適用例
本記事では、我々が適用しているポリシーの一例を紹介します。 なお、ここで紹介している例はサイボウズの環境に適したポリシーなので、あくまでも参考として見ていただければと思います。
Pod Security Policy (PSP)
Pod Security Policyとは、 Kubernetes クラスタ上で動作する Pod に与える権限を制御するための機能です。 例えば、以下のような制限を与えることができます。
- privilegedコンテナの利用禁止
- Linux Capability による制限
- 利用可能な Volume の種類を制限
- hostNetworkの利用禁止
- rootでのコンテナの実行禁止
- ルートファイルシステムの書き込み禁止
Necoではクラスタ全体としてはほとんど何もできないようにしておいて、個々のアプリについて必要最小限の権限を与えるようにしています。
まずは、もっとも厳しい制限として下記のようなポリシーを用意します。
apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: restricted spec: # 特権コンテナの実行を禁止 privileged: false # 親プロセスよりも強い権限を許可しない allowPrivilegeEscalation: false # すべてのCapabilityをDROPする requiredDropCapabilities: - ALL # 利用可能なVolumeを制限 volumes: - 'configMap' - 'emptyDir' - 'projected' - 'secret' - 'downwardAPI' - 'persistentVolumeClaim' # ホストとネットワーク、プロセス間通信、プロセスIDの共有を許可しない hostNetwork: false hostIPC: false hostPID: false # rootでの実行を許可しない runAsUser: rule: 'MustRunAsNonRoot' # 任意のSELinuxのラベルを利用可能 seLinux: rule: 'RunAsAny' # コンテナが追加可能なグループIDの範囲を指定 (rootを禁止) supplementalGroups: rule: 'MustRunAs' ranges: - min: 1 max: 65535 # ボリュームに適用されるグループIDの範囲を指定 (rootを禁止) fsGroup: rule: 'MustRunAs' ranges: - min: 1 max: 65535 # ルートファイルシステムへの書き込みを禁止 readOnlyRootFilesystem: true
上記のポリシーを、すべてのユーザーとアカウントサービスに適用されるように ClusterRoleとClusterRoleBindingを用意します。
kind: ClusterRole apiVersion: rbac.authorization.k8s.io/v1 metadata: name: psp:restricted annotations: rbac.authorization.kubernetes.io/autoupdate: "true" rules: - apiGroups: ['policy'] resources: ['podsecuritypolicies'] verbs: ['use'] resourceNames: - restricted --- kind: ClusterRoleBinding apiVersion: rbac.authorization.k8s.io/v1 metadata: name: default:psp:restricted annotations: rbac.authorization.kubernetes.io/autoupdate: "true" roleRef: kind: ClusterRole name: psp:restricted apiGroup: rbac.authorization.k8s.io subjects: - kind: Group apiGroup: rbac.authorization.k8s.io name: system:serviceaccounts - kind: Group apiGroup: rbac.authorization.k8s.io name: system:authenticated
このポリシーは非常に厳しい制限がかかっています。 Helm Chartで公開されているマニフェストをそのまま持ってくると、多くの場合動作しないでしょう。
例えば、ネットワークプラグインを動作させるためには hostNetwork の利用が必要になりますし、 DNSサーバーを動かすためには NET_BIND_SERVICE を許可して、53番ポートで待ち受けられるようにする必要があります。
そこで、各アプリケーションごとに制限を緩めたポリシーを用意して適用していきます。 例えば、DNS Cacheのunboundには以下のようなポリシーを適用しています。
apiVersion: policy/v1beta1 kind: PodSecurityPolicy metadata: name: unbound spec: allowPrivilegeEscalation: false # 利用可能なCapabilityを追加 allowedCapabilities: - NET_BIND_SERVICE volumes: - 'configMap' - 'emptyDir' hostNetwork: false runAsUser: rule: 'RunAsAny' seLinux: rule: 'RunAsAny' supplementalGroups: rule: 'RunAsAny' fsGroup: rule: 'RunAsAny' readOnlyRootFilesystem: true
Network Policy
Network Policyは、Pod間の通信や Podと他のネットワークエンドポイントとの通信の可否を制御するための仕組みです。 なお、Network Policyの機能を利用するためには何らかのネットワークプラグインが必要となります。
我々は Calico Network Policy を利用しています*1。
Calico Network Policy のカスタムリソースは、Kubernetes 標準のNetwork Policyと比較してより高度な機能を提供しています。
例えば、order
フィールドを利用してネットワークポリシーの適用順序を制御することができます。
Network Policyでは、Podに入ってくるトラフィックを制御するためのIngressと、Podから出ていくトラフィックを制御するためのEgressという2種類の設定があります。
Ingress
NecoのIngress ルールでは、基本的にデータセンター内の通信であれば許可し、データーセンター外からの通信は拒否しています。
apiVersion: crd.projectcalico.org/v1 kind: GlobalNetworkPolicy metadata: name: ingress-cluster-allow spec: order: 9000.0 types: - Ingress ingress: - action: Allow source: nets: - 10.0.0.0/8 --- apiVersion: crd.projectcalico.org/v1 kind: GlobalNetworkPolicy metadata: name: ingress-all-deny spec: order: 10000.0 types: - Ingress ingress: - action: Log - action: Deny
Ingress という namespace を用意し、インターネット経由でアクセスされる Pod を配置します。 L7ロードバランサーである contour には下記のようなポリシーを適用し、インターネットからのアクセスを許可します。
apiVersion: crd.projectcalico.org/v1 kind: NetworkPolicy metadata: name: ingress-contour namespace: ingress spec: order: 1000.0 selector: app.kubernetes.io/name == 'contour' types: - Ingress ingress: - action: Allow protocol: TCP destination: ports: - 8080 - 8443
なお、他のnamespaceからのアクセスを遮断したいケースもあるでしょう。 そういう場合は、各namespaceごとに通信を受け入れない設定を適用します。
Egress
一方の Egress ルールでは、Pod から Node へのアクセスを禁止していますが、それ以外は基本的に許可しています。
apiVersion: crd.projectcalico.org/v1 kind: GlobalNetworkPolicy metadata: name: egress-node-deny spec: order: 1000.0 types: - Egress egress: - action: Deny destination: nets: - 10.69.0.0/16 --- apiVersion: crd.projectcalico.org/v1 kind: GlobalNetworkPolicy metadata: name: egress-all-allow spec: order: 10000.0 types: - Egress egress: - action: Allow
ただし、Nodeへのアクセスであっても、kube-apiserver(6443ポート)やetcd(2379ポート), DNS Cache(53ポート)については、下記のようにアクセスを許可します。
apiVersion: crd.projectcalico.org/v1 kind: GlobalNetworkPolicy metadata: name: egress-controlplane-allow spec: order: 500.0 types: - Egress egress: - action: Allow protocol: UDP destination: nets: - 10.69.0.0/16 ports: - 53 - action: Allow protocol: TCP destination: nets: - 10.69.0.0/16 ports: - 53 - 2379 - 6443
また、インターネットにアクセス可能なPodを配置するために Internet-Egress という namespace を用意し、 Web Proxy である squid や DNS キャッシュサーバである unbound を配置しています。 この namespace から他の namespace やホストへの通信を禁止しています。
Open Policy Agent
RBACとPodSecurityPolicy, NetworkPolicyを適用することで、Podに対して様々な制限をかけることができます。 しかし、これらのポリシーだけでは以下のような細かな制限をおこなうことはできません。
- コンテナイメージを取得するレジストリを制限したい
- system namespaceへのアクセスを制限したい
- 一部のクラスタリソースの操作を許可したい
- すべてのPodにResource Limitの付与を強制したい
そこで我々はOpenPolicyAgentを利用して、より柔軟なポリシーを適用しています。
OpenPolicyAgentでは、Regoという独自の言語でポリシーを記述することができます。
例えば先程紹介したNetworkPolicyでは、order
フィールドでポリシーの適用順序を指定していました。
開発者が適用順序の早いポリシーを作成できてしまうと、本来適用したいポリシーが無視されてしまいます。
そこで以下のようなルールを用意し、order
が1,000以下のNetworkPolicyの作成や更新を禁止することで、重要なNetworkPolicyを必ず適用することが可能となります。
package kubernetes.admission operations = {"CREATE", "UPDATE"} system_namespaces = {"kube-system", "ingress", "internet-egress"} deny[msg] { input.request.kind.kind == "NetworkPolicy" input.request.kind.group == "crd.projectcalico.org" operations[input.request.operation] not system_namespaces[input.request.namespace] input.request.object.spec.order <= 1000 msg := "cannot create/update non-system NetworkPolicy with order <= 1000" }
OpenPolicyAgent の活用方法については、機会があれば別途紹介したいと思います。
まとめ
Kubernetesクラスタをセキュアにするためのポリシーとして、 Pod Security Policy, Network Policy, OpenPolicyAgentの適用例を紹介しました。
なお、このポリシーを適用していればどんな環境でもセキュアで安心というわけではありません。 本記事の例を参考に、それぞれの環境に適したポリシーを適用していただければと思います。
また、Kubernetesにデプロイされているアプリケーションが増えれば増えるほど、 あとからポリシーを適用するのは非常に大変になります。 ポリシーを適用したいと考えているのであれば、早い段階での適用をおすすめします。