コンテナのネットワーク制限
コンテナのネットワーク制限
前回は、コンテナの静的・動的スキャンについて説明しました。第2回の「コンテナセキュリティの課題」でも触れましたが、コンテナおよびKubernetesはデフォルトでフラットなネットワーク構成となっており、コンテナ間のネットワーク通信は制限されていません。今回は、コンテナ環境でのネットワークにおけるセキュリティ上の課題について説明します。
コンテナのネットワーク
コンテナネットワークは、基本的にフラットなネットワークです。Dockerでは、デフォルトで「docker0」という仮想ブリッジに接続されます。そこに接続されるコンテナには、プライベートIPアドレスが割り当てられます。他のコンテナに対しては、このIPアドレスを使用して制限なくネットワーク通信が可能です。一方外部へ通信するためには、docker0ブリッジを使用します。
コンテナ間のホスト名解決は、hostsファイルにより行われます。コンテナのhostsファイルには、ブリッジに接続されたコンテナ名と割り当てられたIPアドレスが自動的に書き込まれます。
通信を分離するには、用途ごとに仮想ブリッジを作成・接続することで可能になります。外部からのコンテナへの受信は、コンテナホスト経由のポートフォワードによって受信します。このため、外部からの通信に対しレイヤ4のファイアウォールを必要としません。
Docker Swarmは、複数ホストに渡ってサービスとしてコンテナを提供できる仕組みです。Docker Swarmは通常のDockerと同様にフラットなネットワークを持ち、ブリッジにより通信を分離します。コンテナホスト間の通信はTLSにより暗号化されますが、その中ではブリッジ通信が行われます。
Kubernetesのネットワーク
Kubernetesのネットワークもコンテナと同じく、デフォルトではフラットなネットワークですが、事実上Container Network Interface(以下、CNI)プラグインを使用したネットワークを使います。Kubernetesでネットワーク制限を行うには、CNIプラグインやサービスを使用することで実現可能です。また、Kubernetes 内のネットワークはプライベートネットワークとして設定されます。では、もう一度、KubernetesのServiceについておさらいします。
Pod
Kubernetesの最小単位であるPodには1つ以上のコンテナが含まれ、同一Nodeに配置されます。Pod内のコンテナ同士は、ループバックデバイス・プロセス間通信を使用して通信可能です。Podには、プライベートIPアドレスが自動的に割り当てられます。この割り当てた情報は、CoreDNSなどKubernetes内部のDNSへ自動的に登録されます。選択すれば、コンテナ内に自動的に生成されるhostsファイルへ登録することもできます。PodはCNIによるネットワークを使用することで、同じKubernetes内の他のPodやNodeとネットワーク変換(NAT)なしに接続できます。複数Node間のネットワーク接続もKubernetesの内部ネットワークとして接続されるため、Pod側が意識するのはホスト名・IPアドレスのみです。
Endpoint
Kubernetes外部との接続点をEndpointと呼びます。KubernetesのEndpointにはいくつか種類がありますが、DNSネームもしくは外部ロードバランサのIPアドレスを設定します。CNIで設定したネットワークにより、どのようにEndpointとKubernetes内ネットワークとの接続方法が変わります。
Network
コンテナ間のネットワークは、Container Network Interface(CNI)の仕様を基準としています。CNIは数々のネットワークソリューションとコンテナ間で、共通のインタフェースを提供するもので、多くの実装がプラグインとして存在します。Weave、Flannel、Calicoなどの実装が有名です。CNIプラグインをデプロイすると、自動的に内部DNSが起動します。コンテナ間のネットワークは、Nodeのネットワークと分離して運用することが必要です。
CNIプラグインによって、Node間のネットワーク実装は多少異なります。
- ネットワークのルーティング
- BGP、VxLANなど
- オーバーレイネットワークによる暗号化
- TLS、VxLANなど
Service
Kubernetesでは、ServiceによってPod間および外部とのネットワーク通信を制御します。ServiceにはPod、Deployment+ReplicaSet、StatefulSet、DaemonSet を紐付けることができます。Serviceの種類として、ClusterIP、NodePort、LoadBalancerなどがあります。
ClusterIP
ClusterIPは、Serviceのデフォルトの種類で、Endpointを指定しない限り内部への通信のみを与えます。
Serviceは配下のPodへ通信された場合、L4のロードバランスを行い、他Service間および外部への通信の制御を行います。PodのNodeへの配置/オートスケール/障害時の削除などは、Deployment+ReplicaSet、StatefulSet、DaemonSetが管理します。Podの増減があった場合も、Serviceは配下のPodのすべてに対して管理されたL4ロードバランサを提供します。
また、KubernetesのNamespaceを使用することで、特定Namespaceからの他のNamespaceへの通信を遮断することも可能です。
NodePort
Nodeごとに割り当てられるIPアドレスをEndpointとします。試験用途もしくはデバッグ用途でのみ使うべきサービスです。
Nodeごとに異なるIPアドレスが使用されるため、スケールされたPod内で違うIPアドレスから外部へ接続します。論理的には以下のようにサービスと接続します。簡易にPodを外部へ接続する用途に使用することができます。外部からの接続は可能ですが、全NodeのIPアドレスを外部から接続できるようIPアドレスでアクセスするか、名前解決できるよう準備する必要があります。
LoadBalancer
Kubernetes外部のロードバランサと接続し、Endpointとします。クラウドプロバイダによるマネージドロードバランサや、ハードウェアアプライアンスを使用できます。ロードバランサの機能・性能などは、接続するロードバランサに依存します。以下のように、用途に応じて外部ロードバランサが使われます。
- HTTP/HTTPS以外のトラフィックを使用するとき
- 高速なハードウェアロードバランサが必要なとき
- セキュリティ上、WAFを含むL7レベルのロードバランサが必要なとき
Ingress
Ingressは、外部からのHTTP/HTTPS接続を、L7ロードバランサとして受け付けるリソースです。IngressをEndpointとすることで、HTTP/HTTPS接続のみを受け付けるEndpointとして使用可能です。Ingressの提供する機能は、以下のとおりです。
- 外部接続可能なDNSホスト名
- トラフィックの負荷分散
- SSL/TLSの終端処理
Ingressでは単一Serviceへの転送、ホスト名ベースやパスベースでの複数Serviceへ振り分けが可能です。負荷分散アルゴリズムも複数指定できます。
Ingressは、Kubernetes内部または外部で動作するIngressコントローラを必要とします。Kubernetes内部で動作するIngressコントローラの実装には、nginx、HAProxy、Istio等を使用したものがあります。Kubernetes外部で動作するIngressコントローラとしては、GKEなどマネージドKubernetesサービスで提供されるものが多くあります。
コンテナ環境におけるネットワークセキュリティ
コンテナ環境におけるネットワークセキュリティの考え方は、今までのオンプレミスやIaaSでのセキュリティと基本的に変わりません。しかし、コンテナ環境ならではの明らかな違いもあるので、1つずつ確認していきます。
インバウンド通信
コンテナ環境外部からの通信は、コンテナホストへのIPアドレス宛に行われます。ただし、docker0などのブリッジ通信を通して、コンテナへ提供されるポートを必要なポートのみに制限できます。そのため、sshへのアタックの対策はコンテナ側には必要ないでしょう。
Kubernetesの場合は、ロードバランサ、Ingressなどに設置されたEndpointを中継し、Serviceを経由してPodへ通信します。Ingressの場合はHTTP(80番)/HTTPS(443番)ポートに限定され、ロードバランサでも受信するポートを指定してServiceへ接続します。Serviceは指定されたポートをPodが必要とするポートに変更して通信を接続します。
セキュリティを考えると、Pod上のコンテナは非特権ユーザでアプリケーションを動作します。非特権ユーザでは1~1024番ポートのTCP/UDPを立ち上げることができません。そのため、PodでHTTP/HTTPSがそのままのポートで動作することはありません。PHP/Python/Rubyなどのアプリケーションを動作させるコンテナも同様です。
これらのことを考えると、コンテナ環境外部からのインバウンド通信はロードバランサ機能だけでよくなります。少なくとも、レイヤ4レベルのファイアウォール機能の必要性が薄くなります。ファイアウォールを使用するのであれば、レイヤ7レベルでふるまい検知を行うようなものが有効でしょう。
アウトバウンド通信
コンテナ環境の場合、外部への通信(=アウトバウンド通信)の制限に、より目を向ける必要があります。
コンテナは通常フラットなネットワークを持つため、外部への通信は規制されていません。DockerやKubernetesでは特に指定しない限り、外部への通信を制限しません。Dockerでは、名前解決をコンテナホストと同じものになるよう/etc/resolv.confを書き込み不可能にします。そのため、DNSポイズニングのような事は発生しませんが、マルウェアなどによる外部への接続が可能になってしまいます。この制限は、ホスト側のiptablesなどで実施する必要があります。
KubernetesにおけるPod内のDNSは、通常Kubernetes内のDNSを使用するため、外部サイトの名前解決を行うことはありません。ただしIPアドレスを指定すれば、http通信なども可能になります。以下は、「nginx:latest」を使用したPodで、curlにより外部サイト(www.google.com)へ接続する例です。
root@nginx:/# curl -vkI -H 'Host: www.google.com' https://172.217.25.196/ * Expire in 0 ms for 6 (transfer 0x56010be34f50) * Trying 172.217.25.196... * TCP_NODELAY set * Expire in 200 ms for 4 (transfer 0x56010be34f50) * Connected to 172.217.25.196 (172.217.25.196) port 443 (#0) * ALPN, offering h2 * ALPN, offering http/1.1 (中略) > HEAD / HTTP/2 > Host: www.google.com > User-Agent: curl/7.64.0 > Acce* TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * TLSv1.3 (IN), TLS handshake, Newsession Ticket (4): * old SSL session ID is stale, removing * Connection state changed (MAX_CONCURRENT_STREAMS == 100)! < HTTP/2 200 HTTP/2 200 < content-type: text/html; charset=ISO-8859-1 content-type: text/html; charset=ISO-8859-1 (後略)
この状況を許すと、Podから外部への不要なアクセスが可能になります。とくにマルウェアなどは、圧縮化などで難読化した小さいイメージを使用し、マルウェア本体を外部サイトから取得して実行するものもあります。
上記の理由から、コンテナから外部への通信はできるだけ避けるべきです。例外は外部からの通信に対する応答です。外部からの接続への応答は、エフェメラルポート(一時的に割り当てられる短命なポート)を使用します。このエフェメラルポートに割り当てられるポートの範囲は、Linuxの場合以下のコマンドで確認できます。
root@nginx:/# cat /proc/sys/net/ipv4/ip_local_port_range 32768 60999
外部への通信は、特に理由のない限りエフェメラルポートのみ許可とすべきです。また外部へのネットワーク通信を必要とするコンテナがある場合、サービスもしくはPod単位で許可すべきです。
コンテナ間通信
アウトバウンド通信と同様に、コンテナ間通信も必要なService、必要なポートへのみに許可すべきです。こちらは、Serviceを経由しての通信を徹底することで、不要な通信を解決可能です。
KubernetesのNamespaceを使用して、不要なServiceを分離することも考えましょう。
NetworkPolicyによる制限
KubernetesのNetworkPolicyは、Podsの集合同士やEndpointとの通信を、どのように許可するかを指定するものです。NetworkPolicy リソースはラベルを使用してPodを選択し、選択したPodに許可される通信を指定するルールを定義します。NetworkPolicyが使用できるかどうかは、CNIプラグインによって異なります。NetworkPolicyが選択できるCNIプラグインを使用することが望ましいです。
Namespace内において、ラベルなどにより特定のPodを指定するNetworkPolicyが設定されていると、そのPodはNetworkPolicyによって許可された通信以外を拒絶します。NetworkPolicyは競合せず、Podが複数のNetworkPolicyに当てはまっていても、各NetworkPolicyによって許可される通信が追加されます。NetworkPolicyは許可リストのため、評価の順番を考慮する必要はありません。
NetworkPolicyには、以下の項目を設定できます。
必須フィールド
通常のKubernetes リソースと同様に、apiVersion、kind、metadata、specフィールドなどが必要です。metadetaではnameや適用するnamespaceを指定できます。metadataで指定したnameは、このNetworkPolicyで選択されたすべてのPodに適用されます。
spec フィールド
指定されたNamespace 以下でNetworkPolicyを定義するための情報を含みます。
podSelector
このNetworkPolicyが適用されるPodを選択します。この条件はラベルベースで指定します。このpodSelectorは、指定されたNamespace全体のPodを対象とします。
policyTypes
NetworkPolicy には、ingress/egressを含むpolicyTypesリストが含まれています。この場合ingressはインバウンドの許可リスト、egressはアウトバウンドの許可リストを指します。NetworkPolicyにpolicyTypesが指定されていない場合、デフォルトではingressのみが設定されます。
ingress
NetworkPolicyには、ingressルールとして許可リストを設定できます。各ルールは、fromセクションとportsセクションの両方にマッチする通信を許可します。
egress
NetworkPolicyには、egressルールとして許可リストを含めることができます。各ルールは、toセクションとportsセクションの両方にマッチする通信を許可します。toはingresのfromと同様のルールを設定できます。portsの設定もingressと同じです。
ingress のfromセクションおよび egressのtoセクションでは、以下の条件を指定できます。
podSelector
ingressの送信元もしくはegressの送信先として、同じNamespaceにある特定のPodを指定します。この条件はラベルベースで指定します。
namespaceSelector
このNetworkPolicyが適用されるNamespaceを指定します。
ip
特定のCIDRでIPアドレスの範囲を選択し、通信元または通信先として許可します。Podには動的IPアドレスが指定されるため、このIPアドレス範囲は特定可能なクラスタ外部IP であるべきです。ロードバランサやIngressリソースを使用されたときは、宛先が内部のIPアドレスとなるため、指定したIPアドレスを適用できない場合があることに注意しましょう。
コンテナホストの防御
コンテナホストおよびNodeのネットワーク環境は、完全に外部から切り離されたクローズドネットワークで、特定のEndpointとの通信のみを許可することが望ましいです。複数のKubernetes Nodeが存在する場合は、VLANやVxLANなどで、経路を分離/暗号化することが望ましいです。
コンテナホスト側を乗っ取られることは、完全にコンテナ環境を乗っ取られるのと同義です。少なくともプライベートネットワーク内にあることが最低条件だと考えます。ただし、カーネル/コンテナデーモン/Kubernetesの脆弱性を利用され、権限昇格される場合がないとは言いきれません。運用時の操作ミス、ソーシャルハックなどによるアクセス経路の乗っ取り、悪意のある内部ユーザによる操作などもありえます。
そのため、コンテナホストやNodeは直接人間が操作することなく、API通信のみで操作することである程度対処可能です。コンテナホストやNodeの増減、切り替えの自動化も必要です。
おわりに
今回は、コンテナ環境におけるネットワークセキュリティの課題について説明しました。オンプレミスやIaaSでサービスを提供した経験のある方には当たり前の概念でしょう。しかし、アプリケーションレイヤからコンテナ環境へ触れるようになってきた方には、初めて知る概念かもしれません。DevOpsやDevSecOpsの考え方が当たり前になってくる昨今、どのレイヤの方もこのようなセキュリティの基本的な考え方に触れることはとても重要です。
次回は、実際のコンテナ環境における開発へ DevSecOps の適用する方法について説明します。次回をお楽しみに。