Kubernetesクラスターに対するL4ネットワークアクセス制御を整理する


お仕事でプリセールスをしているため、お客様やSIパートナー様といっしょに「高い安全性が求められるシステム」について議論することが多くあります。

いわゆるクラウドネイティブアプリケーションの場合、クラウド時代の脅威に対応するためには「 内部を(ユーザーも含めて)信頼しない 」という考え方を前提に考えることが適していたりします。

境界ベースのセキュリティからゼロトラスト セキュリティへの移行

従来のセキュリティ モデルでは、組織のアプリケーションはプライベート データセンターの外部ファイアウォールに依存して、受信トラフィックを保護できません。クラウド ネイティブ環境でも、BeyondCorp モデルと同様にネットワーク境界を引き続き保護する必要がありますが、境界ベースのセキュリティ モデルでは十分に保護できません。新しいセキュリティ上の問題が発生するわけではありませんが、ファイアウォールで企業ネットワークを完全に保護できなければ、本番環境ネットワークも完全に保護できません。ゼロトラスト セキュリティ モデルでは、内部トラフィックが暗黙に信頼されず、認証や暗号化などのセキュリティ制御が必要になります。同時に、マイクロサービスへの移行は、従来のセキュリティ モデルを再検討する機会となります。単一のネットワーク境界(1 つのファイアウォールなど)への依存を排除すると、ネットワークをサービスごとに分割できるようになります。このアイデアをさらに進めて、サービス間に固有の相互信頼関係をなくして、マイクロサービス レベルのセグメンテーションを実装することもできます。マイクロサービスでは、トラフィックの信頼度がさまざまであるため、内部トラフィックと外部トラフィックを比較するだけでは済みません。

引用:BeyondProd: クラウド ネイティブ セキュリティに対する新しいアプローチより

とはいうものの、、、オンプレミス環境などでシステムを運用しているケースでは境界型モデルが馴染みが深く、まず最初に方式設計の検討テーマに上がります。

このブログでは、Kubernetesクラスターへのネットワークアクセスについて私自身が検証していて気づいたことをホワイトボードに書く感覚で、だらだらと書きとめます。

※ なお「Kuberntesのセキュリティ対策はネットワークアクセス制御さえすれば安全といえよう!」のようなシンプルなものではなく、認証認可・イメージの脆弱性検査・特権エスカレーションの阻止などなどなどなどなど、多岐にわたります。Kuberntesに対する脅威はこちらの「Attack matrix for Kubernetes」にまとまっておりますので、ぜひ。

Kubernetesクラスターへの2つのアクセス経路

Kubernetesクラスターを運用環境で稼働させるとき、安全なアクセス経路を検討するには、次の2つを考える必要があります。

なお、分かりやすさ重視のため「ほげ銀行」という妄想上の企業の社内システムを例に説明しています。

1. Kubernetesクラスター上で動かすアプリに対するアクセス

主にシステム利用者がKubernetesクラスター上で動くWebアプリケーションにアクセスするときのアクセス経路です。たとえば、ほげ銀行では市場国際部門・与信管理部門・証券投資部門のユーザがそれぞれの業務を行うためのアプリケーションにアクセスします。

2. Kubernetesクラスター管理のためのコントロープレーンに対するアクセス

Kubernetesクラスターへのアプリのデプロイやクラスターの運用などでKubernetesのAPI Serverに対してアクセスするための経路です。クラスターに対して強い権限が必要になるため、限られたアクセス経路からのみ通信できる必要があります。

今回のブログは、1.のケースのアクセス経路についてのみ整理します。

(この話をごちゃごちゃ混乱させずに文章で説明する能力がないので2.のプライベートクラスターのケースについては、別ブログにて改めて)


境界型モデルによるネットワークアクセスコントロール

従来のオンプレミス型のシステムの場合、業務システムへのアクセスは、すべてのトラフィックをFWを介してコントロールする場合が多いと思います。

基本的に社内システムはプライベートな閉塞区間で運用し、社外への通信が必要な場合はバリアセグメントを設け、そこを経由することでアクセスコントロール・監査・Lockdownを1か所で行う、という考え方です。

たとえば、Azureの場合、Azure Kubernetes Service(AKS)のアクセス制御するときは以下のようなアーキテクチャになります。

このクラスタ外に配置したAzure Firewallの主な目的は、組織がAKSクラスターに対して細かくIngressおよびEgress Traffic規則を設定できるようにすることです。

たとえばこの構成例では、Ingress Trafficはファイアウォール フィルターの経由が強制されています。 またEgress Trafficはユーザー定義ルートを使用して、NodeからAzure FirewallのInternal IPを介して、パブリックインターネットまたはその他のAzureサービスへのアクセスは、FWのPublic IPで行われます。

またAKSの場合、クラスターが機能するには、必須のEgress Endpointで定義されているすべてのEndpointをアプリケーション ファイアウォール規則で有効にする必要があります。 これらのエンドポイントが有効ではない場合、クラスターは正しく動かないので注意してください。

az network firewall application-rule create -g $RG -f $FWNAME \
    --collection-name 'AKS_Global_Required' \
    --action allow \
    --priority 100 \
    -n 'required' \
    --source-addresses '*' \
    --protocols 'http=80' 'https=443' \
    --target-fqdns \
        'aksrepos.azurecr.io' \
        '*blob.core.windows.net' \
        'mcr.microsoft.com' \
        '*cdn.mscr.io' \
        '*.data.mcr.microsoft.com' \
        'management.azure.com' \
        'login.microsoftonline.com' \
        'ntp.ubuntu.com' \
        'packages.microsoft.com' \
        'acs-mirror.azureedge.net'

詳しい手順はこちらにまとめましたので、興味のある方はお試しください。ただし、この構成は執筆時点ではプレビュー機能になりますので、プロダクション環境では利用しないようにしてください。

また、AKSもAzure Firewallもそれなりに料金がかかりますので、検証がおわったらきちんとリソースを削除しておきましょう!

ゼロトラスト型のネットワークアクセスコントロール

境界型によるアクセスコントロールは、オンプレミス環境などでは広く採用されていて、この境界型を前提にして運用組織体制やガバナンス・法規制などが行われています。

一方のクラウドネイティブアプリケーションではマイクロサービスアーキテクチャで実装されることが増えてきています。マイクロサービスアーキテクチャとは、機能単位に分割されたマイクロサービスの集合体として1つのサービスを構築してしまう考え方です。

分散システムにおいて複数のサービスが相互に通信する環境においては、従来のようにネットワークドメインの境界にファイアーウォールを設置するようなセキュリティの設計では、さまざまな脅威に対する対策としては不十分になってきます。そのため、 サービス単位にファイアーウォールを設定 し、アクセスを制限するという考え方が重要になってきます。

Kubernetes ServiceによるL4アクセスコントロール

たとえばKubernetesでは、「Service」リソースを使用して、一連のアプリケーションを論理的にグループ化してアクセスコントロールができます。

AKSでは、Azure Loadbalancerを使って、外部IPアドレスを構成し、要求されたバックエンド プールに接続します。

詳細な手順はこちらにまとめましたので、ポイントだけ。

まず、以下のシンプルなServiceリソースを作成します。

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
spec:
  type: LoadBalancer
  selector:
    app: nginx
  ports:
    - name: http
      port: 80
      targetPort: 80

次のコマンドでリソースをクラスターに展開します。

kubectl apply -f loadbalancer.yaml

これにより、クラスター外部からアクセス可能なEXTERNAL-IPが払い出されます。

kubectl get service

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP    PORT(S)        AGE
kubernetes   ClusterIP      10.0.0.1       <none>         443/TCP        12m
nginx        LoadBalancer   10.0.177.222   20.44.173.77   80:32076/TCP   33s

ここで、払い出されたアドレスに対して、アクセスしましょう。問題なくWebサイトにアクセスできるのが分かります。

curl http://20.44.173.77/

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
...
</head>
</body>
</html>

ここで、AKSクラスター内に作成されたAzure Network Security Group(NSG)を確認してみます。NSGとはAzureが提供するケットフィルタリングの設定の許可・拒否を行うサービスです。Amazon VPCのネットワークACLのようなものです。

az network nsg rule list \
    --nsg-name $AKS_NSG \
    -g $AKS_NODE_RG \
    -o table

Name                                              ResourceGroup                     Priority    SourcePortRanges    SourceAddressPrefixes    SourceASG    Access    Protocol    Direction    DestinationPortRanges    DestinationAddressPrefixes    DestinationASG
------------------------------------------------  --------------------------------  ----------  ------------------  -----------------------  -----------  --------  ----------  -----------  -----------------------  ----------------------------  ----------------
ab033568af7ac4439bf867f26cada197-TCP-80-Internet  mc_hoge-bank-lb-rg_aks_japaneast  500         *                   Internet                 None     
    Allow     Tcp         Inbound      80                       20.44.173.77                  None

これをみると、Internetからの80番ポートのInboundアクセスが許可されるルールが追加されていることが分かります。

このように kubectlコマンドでAKSのServiceを作成しただけで、AzureのNSGの設定が書き換わっている というのが分かります。


次は特定のアドレスからのみアクセスを許可させるようServiceのルールを設定変更します。

次のコマンドで自分のIPアドレスを調べます。

curl globalip.me
39.110.xxx.xxx

先ほどのServiceのマニュフェストに以下のようにSourceRangesを設定します。ここで指定したアドレスからのみ接続を許可します。

spec:
  type: LoadBalancer
  selector:
    app: nginx
  loadBalancerSourceRanges:
  - 39.110.xxx.xxx/32

次のコマンドで、マニュフェストの変更をKubernetes APIに対して通知します。

kubectl apply -f loadbalancer.yaml

ここで、再びNGSのルールを確認します。

az network nsg rule list \
    --nsg-name $AKS_NSG \
    -g $AKS_NODE_RG \
    -o table

SourcePortRangesがInternetから39.110.xxx.xxx/32に書き換わっているのが分かります。

Name                                                      ResourceGroup                                                            Priority    SourcePortRanges    SourceAddressPrefixes    SourceASG    Access    Protocol    Direction    DestinationPortRanges    DestinationAddressPrefixes    DestinationASG
--------------------------------------------------------  -----------------------------------------------------------------------  ----------  ------------------  -----------------------  -----------  --------  ----------  -----------  -----------------------  ----------------------------  ----------------
a89bb8932c6a54edcab0055ee4330474-TCP-80-39.110.159.85_32  mc_hoge-bank-networkpolicy41-rg_hoge-bank-networkpolicy41-aks_japaneast  500         *      
             39.110.xxx.xxx/32         None         Allow     Tcp         Inbound      80                       20.44.173.105                 None   

ここで疎通確認します。 39.110.xxx.xxxからのアクセスは許可されますが、それ以外のSourceからのアクセスは拒否されているのが分かります。

AKSでAzure LoadbalancerにパブリックIPを設定しないように構成する

AKSではKubernetes Serviceリソースを作成すると、どうやら内部で使用されているAzure Load Balancerが自動で作成され、Serviceの設定内容によって構成が変更されているぽいぞということが確認できました。

デフォルトではServiceから作成Azure LoadbalancerにはパブリックIPアドレスが割り当てられています。Azureの世界ではこれを外部Load Balancerと呼んだりしています。

az network public-ip list -g $AKS_RES_RG -o table
Name                                         ResourceGroup                     Location    Zones    Address       AddressVersion    AllocationMethod  
  IdleTimeoutInMinutes    ProvisioningState
-------------------------------------------  --------------------------------  ----------  -------  ------------  ----------------  ------------------  ----------------------  -------------------
1adbb0ce-665e-4876-9836-1f2bfe85534e         MC_hoge-bank-lb-rg_aks_japaneast  japaneast            20.44.169.1   IPv4              Static
  30                      Succeeded

インターネットからの送信を拒否したい場合は、Serviceのアノテーションに以下の値を設定をします。

apiVersion: v1
kind: Service
metadata:
  name: nginx-service
  annotations:
    service.beta.kubernetes.io/azure-load-balancer-internal: "true"
kubectl apply -f loadbalancer.yaml

しばらくして、次のコマンドでKubernetesのServiceを確認します。

kubectl get svc

NAME         TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)        AGE
kubernetes   ClusterIP      10.0.0.1       <none>        443/TCP        57m
nginx        LoadBalancer   10.0.177.222   10.240.0.35   80:32076/TCP   45m

EXTERNAL-IPにローカルIPが割り当てられているのがわかります。これはAzureの世界でいう内部Load Balancerになります。

このように、AKSでServiceを作成するとAzureのNSGか自動で更新されているのが分かりました。なおこのルールや設定を手動で書き換えるとAKSのサポート外になるので注意してください。

Podレベルで細かくアクセスコントロールするには?

Kubernetes NetworkPolicyを使用すると、クラスター内のPod間のIngressおよびEgress Trafficのアクセスをコントロールできます。Kubernetes Network Policyは、Podレベルのファイアーウォールルールを提供するセキュリティ機能です。トラフィックを送受信するための順序付けされたルールセットを定義し、ラベルセレクターに一致するPodのコレクションに適用できます。

なおNetwork Policyを使用するには、ネットワークプラグイン側がそれに対応した機能を持っている必要があります。

現在Kubernetesが対応しているネットワークプラグインは、Calico、Weave、Ciliumなどがあり、AKSの場合、Azure NetworkPolicyまたはCalicoが選べます。どちらの実装も Linux IP Tables を使用して、指定されたポリシーを適用します。両者の違いについてはこちらで。

なお、KubernetesのNetwork PolicyはAzureのNSGでの制御ではなくKubernetes内のコンポーネントだけで動作するところが、ServiceType:Loadbalancerとの違いです。

細かい手順についてはこちらにまとめていますので、ポイントだけ。

たとえばIPアドレスによるコントロールをしたい場合は、次のようにマニュフェストファイルを作成します。 もちろん、実運用時はよく利用するであろうNamespaceやPodのLabelによって細かくアクセスコントロールできます。

kind: NetworkPolicy
apiVersion: networking.k8s.io/v1
metadata:
  name: nginx-policy
  namespace: default

# すべてのインバウンド/アウトバウンドを拒否
# spec:
#   podSelector: {}
#   policyTypes:
#   - Ingress
#   - Egress

# すべてのインバウンドを許可
# spec:
#   podSelector: {}
#   ingress:
#   - {}

# ポリシーを適用するPodの条件
spec:
  podSelector: 
    matchLabels:
      app: nginx
  policyTypes:
  - Ingress # Ingressトラフィックをすべて拒否
  #- Egress  # Egressトラフィックをすべて拒否
  ingress:  # 許可するIngressトラフィックの条件
  - from:
    - ipBlock:
        cidr: 39.110.xxx.xxxx/32

Network Policyのマニフェストファイルの書式そのものについてはKubernetes公式サイトやその他分かりやすいサイトや書籍におまかせします。

が、NetworkPolicyのルールは、Kubernetesのマニフェストとして定義でき、Kubernetesの他のリソースと同様にソースコードとして管理できます。これはとても重要なポイント⤴です。

なぜ、KubernetesのServiceを作成すると、Azure Load Barancerが勝手に作成されたのか?

では、これまでの挙動をもう少しだけ深堀りします。

Azure Kubernetes Service(AKS)は、Kubernetesの機能を提供するマネージドサービスです。コントロールプレーンはMicrosoftが管理することで、一般的に技術的難易度が高いと言われているKubernetesクラスターを手軽に従量課金制で利用できるサービスです。

もうちょっというと、AKSはAzureのさまざまなサービス群を材料としてつかってMicrosoftのエンジニアが構築・運用するKubernetesクラスター です。

あまり知られていませんが、Microsoft社内にはKuberntesや周辺プロジェクトのUpstreamに貢献するソフトウエアエンジニアがたくさんいます。彼らがAKSの開発に携わっています。

Kubernetesクラウドコントローラーマネージャー

料理と同じですが、AKSのネットワーク部分ではAzure Networking群のサービスが材料として使われています。

たとえば、さきほどの検証で確認した通り、KubernetesのServiceを作成しただけで、裏でAzure Load Balancerが作成されていたのがわかりました。このAzure Load Balancerの実装はAzure AnantaというSoftware LBです。

これがGKEの場合はServiceでExternal TCP/UDP Network Load Balancingが作成され、これはAndromeda virtual networkingやGoogle Maglevなどで実装されています。

当然Azure畑で収穫した材料とGoogle Cloud畑で収穫した材料では、味や形、歯ごたえや風味が違います。

ではレシピにあたる「どのような方法で作っているか?」についてはどうなっているのかというと、Kubernetesでクラウドに依存している部分を吸収して実装するしくみが Cloud Controller Manager(CCM) です。

CCMは次の調整ループを動かします。

Node controller

ノードコントローラーは、クラウドプロバイダーからクラスター内で稼働しているノードの情報を取得し、初期化する責務を持ちます。

Route controller

ルートコントローラーは、クラスター内の異なるノード上で稼働しているコンテナが相互に通信出来るように、クラウド内のルートを適切に設定する責務を持ちます。

Service controller

サービスコントローラーは、サービスの作成、更新、そして削除イベントの待ち受けに責務を持ちます。Kubernetes内のサービスの現在の状態を、クラウド上のロードバランサーに反映するための設定を行います。そして、クラウドロードバランサーのバックエンドが最新の状態になっていることを保証します。

ちなみに、AKSの内部ではどのようなネットワークサービスが利用されているか?

では、AKSではどのようなAzureのサービスを材料としてつかっているのかを、Network部分に限ってみていきます。AKSクラスターを作成すると、オプションや構成にもよりますが、以下のサービス群がデプロイされてるのが確認できます。

Azure Virtual Network

Virtual Networkと接続したりVPN Gatewayを配置してオンプレミスなどと接続したりできます。

Azure Network Security Group

Virtual Network外との通信においてアクセス制御を行います。

Azure Application Gateway

KubernetesのリソースであるIngress Controllerとして利用されます。

Azure DNS

AKSのアドオンで、Kubernetesで作成したIngressに対し外部から解決できる名前をAzure DNSに登録できます。Load Balancerのルール更新などは、サービスプリンシパルを用いて行われます。

Azure Load Balancer

KubernetesのServiceが追加されたときに利用されます。Serviceにアノテーションを付けて、使用可能なロードバランサーを自動的に選択できます。ただし、AzureがLoad Balancerは、GCE/AWSとは異なり、複数のフロントエンドIP参照を設定できます。

詳細な挙動はServiceAnnotationに、以下の値を設定することでコントロールできます。

AnnotationValueDescription
service.beta.kubernetes.io/azure-load-balancer-internaltrue or falseロードバランサーを内部にするかどうかを指定。設定されていない場合は外部ロードバランサーとなる
service.beta.kubernetes.io/azure-load-balancer-internal-subnetサブネット名内部ロードバランサーをバインドするサブネットを指定
service.beta.kubernetes.io/azure-dns-label-nameDNSラベルの名前サービスのDNSラベル名を指定
service.beta.kubernetes.io/azure-load-balancer-resource-groupリソースグループ名クラスターと同じリソースグループにないロードバランサーオブジェクトのリソースグループを指定
service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeoutTCPアイドルタイムアウト(分単位)ロードバランサーでTCP接続のアイドルタイムアウトが発生する時間を分単位で指定

詳細についてはKubernetes cloud-controller-manager for Azureで確認できます。

まとめ

Kubernetesクラスターに対するL4ネットワークアクセス制御を整理しましたが、

というところあたりが分かりました。

各社各システムごとに、セキュリティポリシーや運用体制・システムのミッションクリティカル度合い・関連法規やガイドライン・監査などを鑑みて、どこまでをクラウドインフラ側で担保し、どこからをKubernetesでやるかを方式設計に盛り込むことが重要ですが、その線引きは非常に難しいところです。

つづく