Cloudflareは、エンジニア作業におけるKubernetesのヘビーユーザーです。当社のAPIのバックエンド、分析集約やボット検出などのバッチ処理、CI/CDパイプラインなどのエンジニアリングツールに使用されています。ただし、ロードバランサー、APIサーバー、etcd、イングレス、ポッドの間では、Kubernetesにより公開される領域がかなり拡大する可能性があります。
この投稿では、CloudflareのエンジニアリングチームがCloudflare Zero TrustをドッグフーディングしてKubernetesを保護し、プロキシなしでkubectlを使用できるようにした方法について少しご紹介します。
Kubernetesのセキュリティに対する一般的なアプローチ
Cloudflareでは、セキュリティ対策の一環として、ネットワーク経由で当社のクラスターにアクセスできるものを大幅に制限しています。ネットワークサービスが公開されている場合は、Cloudflare Access認証またはMutual TLS(またはその両方)をイングレスリソースへのアクセスに要求するなど、追加の保護措置を導入しています。
これらのネットワーク制限には、クラスターのAPIサーバーへのアクセスも含まれます。アクセスできない場合、Cloudflareのエンジニアはkubectlのようなツールを使ってチームのリソースを省みることができません。ベストプラクティスは継続的デプロイとGitOpsですが、開発者がKubernetes APIを使えるようにすることで、トラブルシューティングと開発者の速度を向上させることができます。アクセスできないことは、契約違反になります。
Cloudflareではセキュリティ要件を満たすためにCloudflare Zero Trustを使用していますが、その使用方法と、ここに至るまでの過程を紹介したいと思います。
Zero Trust以前
Zero Trust以前の世界では、エンジニアはVPNアプライアンスに接続することでKubernetes APIにアクセスできました。これは、業界では一般的な手法であり、APIへのアクセスを許可するものですが、エンジニアをクライアントとして内部ネットワークに落とし込んでしまい、必要以上にネットワークにアクセスすることになっていました。
この状況に満足していたわけではありませんが、数年間はこの状態が続いていました。2020年初頭にVPNを廃止したため、Kubernetesチームは別のソリューションを見つける必要がありました。
Cloudflare Tunnelを利用したKubernetes
当時、私たちはCloudflare Tunnelの開発チームと密接に協力し、Accessを使用したkubectl接続とCloudflareトンネルの処理のサポートを追加しました。
これは、当社のエンジニアにとっては機能しましたが、新入社員のオンボーディングの際には大きなハードルとなっていました。Kubernetesのクラスターごとに、エンジニアのデバイスからの独自のトンネル接続が必要で、クラスター間のシャッフルが煩わしかったのです。kubectlはSOCKSプロキシ経由の接続をサポートしていましたが、このサポートはKubernetesエコシステムのすべてのツールに普遍的なものではありませんでした。
このソリューションを社内で使い続けながら、より良いソリューションの実現に向けて取り組んできたのです。
Zero Trustを使用したKubernetes
Cloudflare Oneのリリース以来、私たちはさまざまな構成でZero Trustエージェントをドッグフーディングしてきました。当初は1.1.1.1でセキュアなDNSを実装するために使われていましたが、じきにZero Trustの追加機能のドッグフーディングに使われるようになりました。
今では、Cloudflare Zero Trustのプライベートネットワークルーティングを活用することで、Cloudflareトンネルをセットアップしたり、kubectlなどのKubernetesエコシステムのツールがトンネルを使用するように設定したりしなくても、エンジニアがKubernetes APIにアクセスできるようになりました。これはCloudflareに限ったことではなく、お客様のチームでも今すぐできることです。
Zero Trustの設定
私たちは構成管理ツールを使って、コードとしてのインフラストラクチャを実現するためのZero Trustの構成を行っており、以下にそれをご紹介します。ただし、Cloudflare Zero Trustダッシュボードを使用しても同じ構成を行うことができます。
まず最初に、新しいトンネルを作成する必要があります。このトンネルは、CloudflareのエッジネットワークをKubernetes APIに接続するために使用されます。後述する構成で、Kubernetes内でトンネルエンドポイントを実行します。
「tunnel_secret」シークレットは32バイトの乱数にする必要があります。後でKubernetesの設定に再利用するので、一時的に保存しておきます。
resource "cloudflare_argo_tunnel" "k8s_zero_trust_tunnel" {
account_id = var.account_id
name = "k8s_zero_trust_tunnel"
secret = var.tunnel_secret
}
トンネルを作成したら、Cloudflareネットワークがトンネルを通してどのようなトラフィックを送信するかを知るために、ルートを作成する必要があります。
IPv4とIPv6の両方のアドレス経由でKubernetes APIにアクセスすることをサポートしているため、両方に対応したルートを設定します。ホスト名でAPIサーバーに接続している場合、これらのIPアドレスはDNSルックアップで返されるものと一致する必要があります。
resource "cloudflare_tunnel_route" "k8s_zero_trust_tunnel_ipv4" {
account_id = var.account_id
tunnel_id = cloudflare_argo_tunnel.k8s_zero_trust_tunnel.id
network = "198.51.100.101/32"
comment = "Kubernetes API Server (IPv4)"
}
resource "cloudflare_tunnel_route" "k8s_zero_trust_tunnel_ipv6" {
account_id = var.account_id
tunnel_id = cloudflare_argo_tunnel.k8s_zero_trust_tunnel.id
network = "2001:DB8::101/128"
comment = "Kubernetes API Server (IPv6)"
}
次に、Cloudflare Gatewayの設定を行い、APIサーバーやクライアントと互換性を持たせるようにします。
クライアントとAPIサーバー間では相互TLSを使用しており、kubectlとAPIサーバー間のトラフィックはすべてHTTPではないため、これらの接続についてはHTTPインスペクションを無効にしています。
resource "cloudflare_teams_list" "k8s_apiserver_ips" {
account_id = var.account_id
name = "Kubernetes API IPs"
type = "IP"
items = ["198.51.100.101/32", "2001:DB8::101/128"]
}
resource "cloudflare_teams_rule" "k8s_apiserver_zero_trust_http" {
account_id = var.account_id
name = "Don't inspect Kubernetes API"
description = "Allow connections from kubectl to API"
precedence = 10000
action = "off"
enabled = true
filters = ["http"]
traffic = format("any(http.conn.dst_ip[*] in $%s)", replace(cloudflare_teams_list.k8s_apiserver_ips.id, "-", ""))
}
これらのルールを、デバイスアテステーション、セッションの存続時間、ユーザーとグループのアクセスポリシーなどの追加のZero Trustルールと組み合わせることで、さらにセキュリティをカスタマイズできます。
トンネルのデプロイ
トンネルの作成と設定が完了したら、そのエンドポイントをネットワークにデプロイできます。トンネルをポッドとしてデプロイすることにより、アップグレードのロールアウトやノード障害の処理にKubernetesのデプロイメント戦略を利用できるようになります。
これにより、指定したトンネルIDのWARPルーティングを有効にする基本設定を持つKubernetes ConfigMapが作成されます。このトンネルIDは、設定管理システム、Cloudflare Zero Trustダッシュボード、または同じアカウントにログインした別のデバイスから以下のコマンドを実行することで取得できます。
apiVersion: v1
kind: ConfigMap
metadata:
name: tunnel-zt
namespace: example
labels:
tunnel: api-zt
data:
config.yaml: |
tunnel: 8e343b13-a087-48ea-825f-9783931ff2a5
credentials-file: /opt/zt/creds/creds.json
metrics: 0.0.0.0:8081
warp-routing:
enabled: true
cloudflared tunnel list
次に、トンネルのクレデンシャル用のシークレットを作成する必要があります。本来はシークレット管理システムを使うべきですが、簡単にするためにここでは直接作成することにします。
これにより、新しいシークレット「tunnel-creds」が「example」ネームスペースに、トンネルが想定するクレデンシャルファイルとともに作成されます。
jq -cn --arg accountTag $CF_ACCOUNT_TAG \
--arg tunnelID $CF_TUNNEL_ID \
--arg tunnelName $CF_TUNNEL_NAME \
--arg tunnelSecret $CF_TUNNEL_SECRET \
'{AccountTag: $accountTag, TunnelID: $tunnelID, TunnelName: $tunnelName, TunnelSecret: $tunnelSecret}' | \
kubectl create secret generic -n example tunnel-creds --from-file=creds.json=/dev/stdin
これで、トンネル自体をデプロイできるようになりました。複数のレプリカをデプロイすることで、ノードが枯渇している間でも、常にいくつかのレプリカを利用できるようにしています。
Cloudflare Zero TrustとKubectlを使用する
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
tunnel: api-zt
name: tunnel-api-zt
namespace: example
spec:
replicas: 3
selector:
matchLabels:
tunnel: api-zt
strategy:
rollingUpdate:
maxSurge: 0
maxUnavailable: 1
template:
metadata:
labels:
tunnel: api-zt
spec:
containers:
- args:
- tunnel
- --config
- /opt/zt/config/config.yaml
- run
env:
- name: GOMAXPROCS
value: "2"
- name: TZ
value: UTC
image: cloudflare/cloudflared:2022.5.3
livenessProbe:
failureThreshold: 1
httpGet:
path: /ready
port: 8081
initialDelaySeconds: 10
periodSeconds: 10
name: tunnel
ports:
- containerPort: 8081
name: http-metrics
resources:
limits:
cpu: "1"
memory: 100Mi
volumeMounts:
- mountPath: /opt/zt/config
name: config
readOnly: true
- mountPath: /opt/zt/creds
name: creds
readOnly: true
volumes:
- secret:
name: tunnel-creds
name: creds
- configMap:
name: tunnel-api-zt
name: config
Cloudflare Zero Trustエージェントをデプロイすると、チームのメンバーは特別なSOCKSトンネルを設定することなく、Kubernetes APIにアクセスできるようになります。
今後の展開は?
kubectl version --short
Client Version: v1.24.1
Server Version: v1.24.1
試された方は、ぜひフィードバックをお寄せください。Cloudflareは、HTTP以外のワークフローに対するZero Trustの改良を続けてまいります。