Lasci da parte la VPN e adotti una moderna soluzione Zero Trust per le applicazioni HTTPS interne
Sta ancora gestendo connessioni VPN per consentire al personale remoto di accedere alle applicazioni interne ospitate su GKE (Google Kubernetes Engine)? C'è un modo migliore. In questo articolo vedremo come sostituire il classico accesso via VPN con Identity-Aware Proxy (IAP) di Google Cloud, sfruttando il GKE External Gateway. Un approccio moderno che non solo riduce l'overhead operativo, ma offre anche un controllo degli accessi più granulare grazie ad autenticazione e autorizzazione basate su IAM.
Perché cambiare?
L'accesso tradizionale alle applicazioni interne tramite VPN porta con sé diverse criticità:
- Elevato overhead operativo per la gestione dei tunnel VPN
- Costi aggiuntivi per l'infrastruttura VPN
- Configurazioni di routing di rete complesse
- Controllo degli accessi poco granulare
Adottando un External Gateway con IAP è invece possibile:
- Consolidare più applicazioni dietro un unico load balancer HTTPS
- Gestire in modo efficiente gli accessi tra namespace GKE diversi
- Sfruttare il solido sistema di autenticazione di Google Cloud
- Ridurre costi e complessità dell'infrastruttura
GKE Ingress vs Gateway: a confronto sull'implementazione di IAP
Per implementare IAP con GKE esistono due strade principali: GKE Ingress oppure GKE Gateway. Capirne le differenze è fondamentale per scegliere la soluzione più adatta al proprio ambiente.
I limiti di GKE Ingress
- Vincoli sui namespace: GKE Ingress può fare riferimento solo a Service presenti nello stesso namespace in cui è distribuita la risorsa Ingress:

Una GKE Ingress nel namespace Fu non può fare riferimento a un Service nel namespace Bar
- Risorse Load Balancer: possono servire più load balancer se le applicazioni sono distribuite su namespace diversi:

Quando i servizi si trovano in namespace diversi servono 2 HTTPS LB distinti — e occorre anche il routing su base dominio, che aggiunge un ulteriore livello di complessità
I vantaggi di GKE Gateway
- Supporto cross-namespace: un'unica risorsa Gateway può instradare il traffico verso Service in namespace diversi
- Architettura semplificata: un solo load balancer gestisce più applicazioni a prescindere dal namespace
- Routing più flessibile: è più semplice realizzare pattern di routing complessi tra namespace

Una GKE Gateway in un namespace può inviare traffico a Service in altri namespace tramite un unico HTTPS LB
Guida all'implementazione
Prerequisiti
- GKE Standard v1.24+ oppure GKE Autopilot v1.26+
- Cluster configurato in modalità VPC-native
- Add-on HttpLoadBalancing abilitato
Passo 1: creare il cluster GKE
gcloud container clusters create test-gateway \
--region=us-central1 \
--release-channel=regular \
--enable-ip-alias \
--num-nodes=1 \
--addons HttpLoadBalancing \
--gateway-api=standard
gcloud container clusters get-credentials test-gateway --region us-central1
Passo 2: configurare il certificato SSL
cat <<EOF | kubectl apply -f -
apiVersion: networking.gke.io/v1
kind: ManagedCertificate
metadata:
name: gateway-nir-dns-tests-google-com
namespace: default
spec:
domains:
- gateway.nir-dns-tests-google-com
EOF
Passo 3: configurare un IP esterno e creare un record DNS A che punti a tale IP
Per ottenere il provisioning del ManagedCertificate è necessario avere un record DNS A che punti all'HTTPS LB e avere il certificato collegato all'HTTPS LB stesso (operazione che gestiremo nel passo successivo)
gcloud compute addresses create gateway --project=nir-playground --global
RESERVED_IP=$(gcloud compute addresses describe gateway --global | grep address: | awk '{print $2}')
gcloud dns --project=nir-playground record-sets create gateway.nir-dns-tests-google-com. \
--zone="nir-dns-tests-google-com" --type="A" --ttl="60" --rrdatas=$RESERVED_IP
Passo 4: distribuire la risorsa Gateway
Si noti che gatewayClassName rappresenta un HTTPS LB esterno globale e che l'array dei listener consente l'attacco di HTTPRoute da qualsiasi namespace. Notiamo inoltre che impostiamo l'IP riservato indicando in addresses[0].type il valore NamedAddresses e come valore il nome usato in fase di prenotazione dell'IP statico ("gateway" in questo esempio):
CERT_NAME=$(kubectl get managedcertificate gateway-nir-dns-tests-google-com -o=jsonpath="{.status.certificateName}")
cat <<EOF | kubectl apply -f -
kind: Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
name: external-http
spec:
gatewayClassName: gke-l7-global-external-managed
listeners:
- name: https
protocol: HTTPS
port: 443
allowedRoutes:
namespaces:
from: All
tls:
mode: Terminate
options:
networking.gke.io/pre-shared-certs: $CERT_NAME
addresses:
- type: NamedAddress
value: gateway
EOF
# Verify Gateway IP assignment
while true; do
GATEWAY_IP=$(kubectl get gateway external-http -o=jsonpath="{.status.addresses[0].value}")
if [ "$GATEWAY_IP" != "" ]; then
break
fi
sleep 3
done
Passo 5: verificare lo stato del ManagedCertificate
# Verify certificate status
while true; do
STATUS=$(kubectl get managedcertificate gateway-nir-dns-tests-google-com -o=jsonpath="{.status.certificateStatus}")
if [ "$STATUS" = "Active" ]; then
break
fi
echo "Certificate is in $STATUS status, will check again if it became active in 3 seconds"
sleep 3
done
Passo 6: distribuire le applicazioni di esempio
kubectl create namespace first-namespace
kubectl create namespace second-namespace
# Deploy App A in first-namespace
cat <<EOF | kubectl apply -n first-namespace -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-a
spec:
replicas: 2
selector:
matchLabels:
app: app-a
template:
metadata:
labels:
app: app-a
spec:
containers:
- name: app-a
image: hashicorp/http-echo
args: ["-text=Hello from App A"]
ports:
- containerPort: 5678
EOF
# Create Service for App A
cat <<EOF | kubectl apply -n first-namespace -f -
apiVersion: v1
kind: Service
metadata:
name: service-a
spec:
selector:
app: app-a
ports:
- protocol: TCP
port: 80
targetPort: 5678
EOF
# Deploy App B in second-namespace
cat <<EOF | kubectl apply -n second-namespace -f -
apiVersion: apps/v1
kind: Deployment
metadata:
name: app-b
spec:
replicas: 2
selector:
matchLabels:
app: app-b
template:
metadata:
labels:
app: app-b
spec:
containers:
- name: app-b
image: hashicorp/http-echo
args: ["-text=Hello from App B"]
ports:
- containerPort: 5678
EOF
# Create Service for App B
cat <<EOF | kubectl apply -n second-namespace -f -
apiVersion: v1
kind: Service
metadata:
name: service-b
spec:
selector:
app: app-b
ports:
- protocol: TCP
port: 80
targetPort: 5678
EOF
Passo 7: abilitare IAP e creare un Secret k8s per le credenziali IAP
echo -n CLIENT_SECRET_REDACTED > iap-secret.txt
kubectl create secret generic iap --from-file=key=iap-secret.txt
Passo 8: creare la GCPBackendPolicy che applica IAP
# Enable IAP for App A
cat <<EOF | kubectl apply -n first-namespace -f -
apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
name: backend-policy
spec:
default:
iap:
enabled: true
oauth2ClientSecret:
name: iap
clientID: REPLACE_WITH_YOUR_IAP_OAUTH_CLIENT_ID
targetRef:
group: ""
kind: Service
name: service-a
EOF
# Enable IAP for App B
cat <<EOF | kubectl apply -n second-namespace -f -
apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
name: backend-policy
spec:
default:
iap:
enabled: true
oauth2ClientSecret:
name: iap
clientID: REPLACE_WITH_YOUR_IAP_OAUTH_CLIENT_ID
targetRef:
group: ""
kind: Service
name: service-b
EOF
Passo 9: creare l'HTTPRoute
# Create route for App A
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: route-a
namespace: first-namespace
spec:
parentRefs:
- name: external-http
kind: Gateway
namespace: default
rules:
- matches:
- path:
value: /first
backendRefs:
- name: service-a
port: 80
EOF
# Create route for App B
cat <<EOF | kubectl apply -f -
apiVersion: gateway.networking.k8s.io/v1beta1
kind: HTTPRoute
metadata:
name: route-b
namespace: second-namespace
spec:
parentRefs:
- name: external-http
kind: Gateway
namespace: default
rules:
- matches:
- path:
value: /second
backendRefs:
- name: service-b
port: 80
EOF
Passo 10: accedere alle applicazioni protette da IAP
Apra l'URL protetto da IAP. Verrà mostrata la pagina di accesso di Google. Potrà accedere all'applicazione qualsiasi utente che appartenga alla stessa organizzazione Google Cloud in cui è distribuita l'applicazione e a cui sia assegnato il ruolo IAM IAP-Secured Web App User:

[email protected] è un utente dell'organizzazione GCP doit.com e dispone delle autorizzazioni IAM necessarie per accedere all'applicazione. [email protected] non appartiene a tale organizzazione e quindi non potrà accedere all'applicazione
Quando un utente esterno all'organizzazione, oppure un utente privo delle autorizzazioni richieste, prova ad accedere all'applicazione, viene mostrata una pagina di accesso negato:

La pagina di accesso negato mostrata ai principal non autorizzati
Modernizzare le modalità di accesso a GKE passando dalla VPN a un External Gateway con IAP è solo uno dei tanti modi per rafforzare la sicurezza e l'efficienza operativa della propria infrastruttura Google Cloud. Un approccio che non solo migliora la postura di sicurezza, ma riduce anche overhead operativo e costi.
Questa guida si concentra sulla modernizzazione dell'accesso a GKE, ma analoghe opportunità di trasformazione esistono in tutta la sua infrastruttura cloud. Dall'ottimizzazione dei costi all'automazione dell'infrastruttura, dall'hardening della sicurezza al design dell'architettura cloud, DoiT International mette a disposizione una competenza estesa su numerosi ambiti del cloud. Per scoprire come DoiT può aiutarla a modernizzare altri aspetti della sua infrastruttura cloud o per conoscere le nostre soluzioni di cloud engineering, visiti doit.com/expertise/.