Cloudflare Zero Trust Tunnel: udostępnianie serwisów Kubernetes bez publicznych IP i reguł firewall
Wdrożenie Cloudflare Tunnel (cloudflared) jako Deployment w Kubernetes do udostępniania wewnętrznych serwisów ClusterIP bez otwierania portów. Działa identycznie na EKS, GKE, AKS i on-prem.
Objawy
Musisz udostępnić wewnętrzne serwisy, ale napotykasz ograniczenia uniemożliwiające tradycyjny ingress:
# Wewnętrzne serwisy dostępne tylko przez ClusterIP
kubectl get svc
# NAME TYPE CLUSTER-IP PORT(S)
# my-api ClusterIP 10.96.0.15 8080/TCP
# admin-panel ClusterIP 10.96.0.22 3000/TCP
# Brak publicznych IP lub zakaz ich używania przez politykę
# Firewall blokuje CAŁY ruch przychodzący (sieć zero-trust)
# Cloud NAT lub korporacyjny proxy blokuje tradycyjne przekierowanie portów
# Klaster on-prem za CGNAT bez publicznego adresu
Tradycyjne rozwiązania (serwis typu LoadBalancer, kontroler Ingress z zewnętrznym IP) wymagają reguł firewall dla ruchu przychodzącego, publicznych IP i zarządzania certyfikatami TLS. W środowiskach zero-trust lub klastrach on-prem za NAT nie są to dostępne opcje.
Przyczyna
Potrzeba Cloudflare Tunnel wynika z uzasadnionych ograniczeń architektonicznych:
- Polityki sieci zero-trust zabraniają połączeń przychodzących do nodów klastra. Cały ruch musi być inicjowany na zewnątrz.
- Klastry on-prem za CGNAT nie mają publicznych adresów IP i nie mogą odbierać połączeń przychodzących z internetu.
- Klastry w chmurze w prywatnych podsieciach celowo nie mają load balancerów z dostępem z internetu. Udostępnienie serwisów wymaga dodatkowej infrastruktury (NAT, bastion host, VPN).
- Wdrożenia wieloklastrowe lub hybrydowe gdzie serwisy obejmują EKS, GKE, AKS i bare metal. Zunifikowana warstwa dostępu eliminuje konfigurację ingress per dostawca.
Cloudflare Tunnel rozwiązuje te wszystkie problemy ustanawiając trwałe połączenia wyłącznie wychodzące z klastra do sieci brzegowej Cloudflare. Przepływ ruchu: Klient -> Cloudflare Edge -> Tunel -> pod cloudflared -> serwis ClusterIP.
Rozwiązanie
Krok 1: Utwórz tunel przez panel Cloudflare lub CLI
# Zainstaluj CLI cloudflared lokalnie
brew install cloudflare/cloudflare/cloudflared
# Lub: curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
# Zaloguj się do Cloudflare
cloudflared tunnel login
# Otwiera przeglądarkę do uwierzytelnienia OAuth - wybierz swoją strefę
# Utwórz nowy tunel
cloudflared tunnel create k8s-production
# Dane uwierzytelniające tunelu zapisane w ~/.cloudflared/<TUNNEL_ID>.json
# Zanotuj ID tunelu
export TUNNEL_ID="a1b2c3d4-e5f6-7890-abcd-ef1234567890"
Krok 2: Utwórz Secret z danymi uwierzytelniającymi w Kubernetes
# Utwórz namespace dla infrastruktury tunelu
kubectl create namespace cloudflared
# Utwórz secret z pliku danych uwierzytelniających tunelu
kubectl create secret generic tunnel-credentials \
--namespace cloudflared \
--from-file=credentials.json=$HOME/.cloudflared/${TUNNEL_ID}.json
Krok 3: Utwórz ConfigMap cloudflared z regułami ingress
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: ConfigMap
metadata:
name: cloudflared-config
namespace: cloudflared
data:
config.yaml: |
tunnel: ${TUNNEL_ID}
credentials-file: /etc/cloudflared/creds/credentials.json
metrics: 0.0.0.0:2000
no-autoupdate: true
ingress:
- hostname: api.example.com
service: http://my-api.default.svc.cluster.local:8080
- hostname: admin.example.com
service: http://admin-panel.default.svc.cluster.local:3000
originRequest:
noTLSVerify: true
- service: http_status:404
EOF
Reguły ingress mapują zewnętrzne hosty na wewnętrzne serwisy ClusterIP za pomocą nazw DNS Kubernetes (<serwis>.<namespace>.svc.cluster.local).
Krok 4: Wdróż cloudflared jako Deployment
cat <<EOF | kubectl apply -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: cloudflared
namespace: cloudflared
labels:
app: cloudflared
spec:
replicas: 2
selector:
matchLabels:
app: cloudflared
template:
metadata:
labels:
app: cloudflared
spec:
containers:
- name: cloudflared
image: cloudflare/cloudflared:2024.6.1
args:
- tunnel
- --config
- /etc/cloudflared/config/config.yaml
- run
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
livenessProbe:
httpGet:
path: /ready
port: 2000
initialDelaySeconds: 10
periodSeconds: 10
readinessProbe:
httpGet:
path: /ready
port: 2000
initialDelaySeconds: 5
periodSeconds: 5
volumeMounts:
- name: config
mountPath: /etc/cloudflared/config
readOnly: true
- name: creds
mountPath: /etc/cloudflared/creds
readOnly: true
volumes:
- name: config
configMap:
name: cloudflared-config
- name: creds
secret:
secretName: tunnel-credentials
EOF
Krok 5: Utwórz rekordy DNS wskazujące na tunel
# Skieruj hosty na tunel
cloudflared tunnel route dns ${TUNNEL_ID} api.example.com
cloudflared tunnel route dns ${TUNNEL_ID} admin.example.com
# To tworzy rekordy CNAME: api.example.com -> <TUNNEL_ID>.cfargotunnel.com
Krok 6: Dodaj polityki Cloudflare Access dla uwierzytelniania
Zabezpiecz serwisy politykami dostępu opartymi na tożsamości:
# Przez panel Cloudflare Zero Trust:
# Access > Applications > Add an Application > Self-hosted
# Lub przez API/Terraform:
cat <<EOF
# Przykład Terraform - Cloudflare Access Application
resource "cloudflare_access_application" "admin_panel" {
zone_id = var.cloudflare_zone_id
name = "Admin Panel"
domain = "admin.example.com"
session_duration = "24h"
}
resource "cloudflare_access_policy" "admin_policy" {
zone_id = var.cloudflare_zone_id
application_id = cloudflare_access_application.admin_panel.id
name = "Allow team members"
precedence = 1
decision = "allow"
include {
email_domain = ["example.com"]
}
}
EOF
To dodaje uwierzytelnianie SSO przed dowolnym udostępnionym serwisem bez modyfikowania samej aplikacji.
Weryfikacja
Po wdrożeniu zweryfikuj, że tunel jest połączony i serwisy są dostępne:
# 1. Zweryfikuj, że pody cloudflared działają
kubectl get pods -n cloudflared
# Oczekiwane:
# NAME READY STATUS AGE
# cloudflared-7b9f4d-abc12 1/1 Running 2m
# cloudflared-7b9f4d-def34 1/1 Running 2m
# 2. Sprawdź status tunelu
cloudflared tunnel info ${TUNNEL_ID}
# Oczekiwane: Status: healthy, Connections: 4 (2 per replika)
# 3. Przetestuj zewnętrzny dostęp do serwisów
curl -s -o /dev/null -w "%{http_code}" https://api.example.com/health
# Oczekiwane: 200
# 4. Zweryfikuj, że polityka Access blokuje nieuwierzytelnione żądania
curl -s -o /dev/null -w "%{http_code}" https://admin.example.com/
# Oczekiwane: 302 (przekierowanie do logowania Cloudflare Access)
# 5. Sprawdź endpoint metryk
kubectl port-forward -n cloudflared deploy/cloudflared 2000:2000
curl http://localhost:2000/metrics | grep cloudflared_tunnel_active_streams
# Oczekiwane: cloudflared_tunnel_active_streams > 0
# 6. Zweryfikuj brak reguł firewall dla ruchu przychodzącego
# (zależne od klastra - przykład dla GKE)
gcloud compute firewall-rules list \
--filter="direction=INGRESS AND network=my-vpc" \
--format="table(name,direction,allowed)"
# Oczekiwane: brak reguł zezwalających na ruch przychodzący 80/443 z 0.0.0.0/0
Udostępnianie serwisów przez publiczne IP i reguły firewall dla ruchu przychodzącego tworzy powierzchnię ataku wymagającą stałego aktualizowania, ochrony przed DDoS i rotacji certyfikatów. Cloudflare Tunnel eliminuje to wszystko - brak publicznych IP do skanowania, brak portów do odpytywania, brak certyfikatów do zarządzania. Całe połączenie jest wyłącznie wychodzące, uwierzytelniane na brzegu sieci i szyfrowane end-to-end. To nie tylko wygoda - to fundamentalnie mniejsza powierzchnia ataku, która działa identycznie niezależnie czy klaster jest w AWS, GCP, Azure czy w szafie w Twoim centrum danych.
Potrzebujesz dostępu zero-trust do serwisów Kubernetes?
Umów bezpłatną 30-minutową rozmowę. Wdrażamy Cloudflare Tunnel z politykami Access w środowiskach wieloklastrowych - zarówno w chmurze jak i on-prem.