Exposer des applications Kubernetes via la Gateway API dans GKE

Comment rendre les applications exécutées dans GKE accessibles publiquement via la Gateway API, avec des certificats TLS wildcard managés et une protection WAF Cloud Armor.

Exposer des applications Kubernetes via la Gateway API dans GKE

Vue d'ensemble

Si vous ne savez pas ce que la Gateway API , voici ce que le Groupe d'Intérêt Spécial (SIG) dédié au Réseau dans Kubernetes dit à ce sujet :

Gateway API est un projet officiel de Kubernetes axé sur le routage L4 et L7 dans Kubernetes. Ce projet représente la nouvelle génération d'API Kubernetes Ingress, Load Balancing et Service Mesh. Dès le départ, il a été conçu pour être générique, expressif et orienté rôles.

En d'autres termes, la Gateway API est une spécification qui vise à améliorer et à normaliser la mise en réseau des services dans Kubernetes. C'est l'avenir de l'exposition des applications Kubernetes. Pour en savoir plus, consultez Présentation de la Gateway API .

Ensuite, nous utiliserons l' Implémentation de la Gateway API dans GKE pour exposer publiquement certaines de nos applications exécutées dans GKE.

Création de la Gateway

Activation de la fonctionnalité Gateway API

Avant de créer la ressource Gateway, nous devons nous assurer que la fonctionnalité Gateway API est activée sur le cluster GKE. Jetez un œil à Activer la Gateway API sur les clusters GKE ou Vérifier l'activation de la Gateway API sur les clusters GKE pour celà.

Pour ceux qui utilisent le cluster GKE standard, voici la commande à utiliser pour activer la Gateway API sur un cluster existant :

gcloud container clusters update <cluster_name> --gateway-api=standard --location=<cluster_region>

Une fois activée, nous devrions voir les CRD liées à la Gateway API :

$ kubectl api-resources | grep gateway
gatewayclasses                    gc           gateway.networking.k8s.io/v1beta1      false        GatewayClass
gateways                          gtw          gateway.networking.k8s.io/v1beta1      true         Gateway
httproutes                                     gateway.networking.k8s.io/v1beta1      true         HTTPRoute
referencegrants                   refgrant     gateway.networking.k8s.io/v1beta1      true         ReferenceGrant
gcpgatewaypolicies                             networking.gke.io/v1

Sélection de la GatewayClass

Nous devons également choisir une GatewayClass pour la ressource Gateway que nous allons créer. La GatewayClass choisie déterminera les capacités/fonctionnalités disponibles pour la Gateway. Voir Capacités/fonctionnalités disponibles de la Gateway selon la classe pour en savoir plus.

Actuellement, la GatewayClass avec le plus de fonctionnalités est la gke-l7-global-external-managed. C'est celle que nous utiliserons pour créer la ressource Gateway. Voici une liste des ressources GatewayClass actuellement disponibles pour notre cluster GKE :

$ kubectl get gatewayclass
NAME                               CONTROLLER                  ACCEPTED   AGE
gke-l7-global-external-managed     networking.gke.io/gateway   True       54m
gke-l7-gxlb                        networking.gke.io/gateway   True       54m
gke-l7-regional-external-managed   networking.gke.io/gateway   True       54m
gke-l7-rilb                        networking.gke.io/gateway   True       54m

Voici le résultat d'un describe sur la ressource GatewayClass gke-l7-global-external-managed :

$ kubectl describe gatewayclass/gke-l7-global-external-managed
Name:         gke-l7-global-external-managed
Namespace:
Labels:       <none>
Annotations:  <none>
API Version:  gateway.networking.k8s.io/v1beta1
Kind:         GatewayClass
Metadata:
  Creation Timestamp:  ***
  Generation:          1
  Resource Version:    27032
  UID:                 6062d521-6d0f-46f7-bc0d-19d3c968d4f2
Spec:
  Controller Name:  networking.gke.io/gateway
  Description:      Preview class for new L7 External Load Balancers.
Status:
  Conditions:
    Last Transition Time:  ***
    Message:
    Observed Generation:   1
    Reason:                Accepted
    Status:                True
    Type:                  Accepted
Events:
  Type    Reason  Age   From                   Message
  ----    ------  ----  ----                   -------
  Normal  ADD     55m   sc-gateway-controller  gke-l7-global-external-managed
  
# Result on older versions
$ kubectl describe gatewayclass/gke-l7-global-external-managed
Name:         gke-l7-global-external-managed
Namespace:    
Labels:       <none>
Annotations:  <none>
API Version:  gateway.networking.k8s.io/v1beta1
Kind:         GatewayClass
Metadata:
  Creation Timestamp:  *****
  Generation:          1
  Managed Fields:
    API Version:  gateway.networking.k8s.io/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:spec:
        .:
        f:controllerName:
        f:description:
    Manager:      GoogleGKEGatewayController
    Operation:    Update
    Time:         *****
    API Version:  gateway.networking.k8s.io/v1beta1
    Fields Type:  FieldsV1
    fieldsV1:
      f:status:
        f:conditions:
          k:{"type":"Accepted"}:
            f:lastTransitionTime:
            f:message:
            f:observedGeneration:
            f:reason:
            f:status:
    Manager:         GoogleGKEGatewayController
    Operation:       Update
    Subresource:     status
    Time:            *****
  Resource Version:  585627605
  UID:               f67bc0b6-5da4-441d-bad6-cdf9a4195f56
Spec:
  Controller Name:  networking.gke.io/gateway
  Description:      Preview class for new L7 External Load Balancers.
Status:
  Conditions:
    Last Transition Time:  *****
    Message:               
    Observed Generation:   1
    Reason:                Accepted
    Status:                True
    Type:                  Accepted
Events:                    <none>

La ressource Gateway

Créons maintenant la Gateway. Voici le manifeste « gateway.yml » :

kind: Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  name: testgwapi-global-external-managed
  namespace: default
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
  - name: http
    protocol: HTTP
    port: 80
    allowedRoutes:
      namespaces:
        from: All

Pour obtenir la liste de tous les champs pris en charge par la ressource « Gateway » GKE, consultez Champs d'API de la Gateway API pris en charge par la ressource Gateway de GKE par GatewayClass.

Appliquons le manifeste et voyons ce qui se passe :

$ kubectl apply -f gateway.yml
gateway.gateway.networking.k8s.io/testgwapi-global-external-managed created

$ kubectl get gateway -w
NAME                                CLASS                            ADDRESS          PROGRAMMED   AGE
testgwapi-global-external-managed   gke-l7-global-external-managed   Unknown          17s
testgwapi-global-external-managed   gke-l7-global-external-managed   Unknown          82s
testgwapi-global-external-managed   gke-l7-global-external-managed   34.111.146.21   True         82s
testgwapi-global-external-managed   gke-l7-global-external-managed   34.111.146.21   True         114s

$ kubectl get events
LAST SEEN   TYPE     REASON   OBJECT                                      MESSAGE
2m23s       Normal   ADD      gateway/testgwapi-global-external-managed   default/testgwapi-global-external-managed
61s         Normal   UPDATE   gateway/testgwapi-global-external-managed   default/testgwapi-global-external-managed
29s         Normal   SYNC     gateway/testgwapi-global-external-managed   SYNC on default/testgwapi-global-external-managed was a success

Après avoir créé la ressource « Gateway », un équilibreur de charge HTTP/HTTPS Google Cloud a été automatiquement provisionné par le Contrôleur de Gateway GKE.

Cet équilibreur de charge pourra acheminer le trafic externe vers les pods des clusters GKE selon nos configurations de routage « Gateway API ». Notez qu'une adresse IP publique aléatoire a également été automatiquement attribuée à l'équilibreur de charge. Cette adresse IP apparaît dans la colonne « ADDRESS » du résultat de la commande « kubectl get gateway » précédente.

Utilisation d'une adresse IP statique sur la Gateway

Sans spécifier une adresse IP statique publique déjà réservée à utiliser sur la « Gateway », une adresse sera automatiquement réservée et attribuée à l'équilibreur de charge L7 provisionné suite à la création de la ressource « Gateway ».

L'adresse IP réservée automatiquement sera également libérée dès la suppression de la Gateway. Ainsi, si, pour une raison quelconque, nous souhaitons supprimer et recréer la Gateway, son adresse IP pourrait changer.

Pour éviter cela, nous pouvons utiliser notre propre adresse IP statique publique réservée sur la « Gateway ». L'adresse IP statique publique peut être réservée depuis la Page de réservation d'adresses IP publiques de la console GCP ou en utilisant la CLI « gcloud » comme suit :

gcloud compute addresses create ADDRESS_NAME --project=GCP_PROJECT_ID --global

Pour voir l'adresse IP, utilisez :

gcloud compute addresses list --filter="name=( 'ADDRESS_NAME' )" --project=GCP_PROJECT_ID

Une fois l'adresse IP réservée, nous devons indiquer à la Gateway qu'elle doit l'utiliser pendant ou après sa création. Pour cela, nous devons renseigner le champ « gateway.spec.addresses » dans le manifeste de la passerelle comme suit :

kind: Gateway
...
spec:
  (...)
  addresses:
  - type: NamedAddress
    value: ADDRESS_NAME

Configuration de TLS sur la Gateway

Vue d'ensemble

Dans cette section, nous allons configurer TLS sur la Gateway pour sécuriser les communications avec les clients accédant à nos applications. Nous utiliserons le Gestionnaire de certificats Google pour générer des certificats TLS (y compris des certificats wildcard) qui seront automatiquement renouvelés.

Le Listener HTTPS

Nous avons besoin d'un nouveau listener sur la Gateway pour le protocole HTTPS. Voici le manifeste permettant d'ajouter ce listener :

kind: Gateway
(...)
spec:
  gatewayClassName: gke-l7-global-external-managed
  listeners:
  (...)
  - name: https
    protocol: HTTPS
    port: 443
    allowedRoutes:
      namespaces:
        from: All

Création d'une autorisation DNS

Avant de créer le certificat TLS géré par Google avec le Gestionnaire de certificats Google, nous devons créer une autorisation DNS et ajouter l'entrée DNS requise à l'intérieur de la zone DNS correspondante, afin de prouver que nous possédons le nom de domaine pour lequel nous souhaitons générer un certificat TLS.

Créons une autorisation DNS appelée « testgatewayapi » et affichons son contenu :

$ gcloud certificate-manager dns-authorizations create testgatewayapi --domain="testgatewayapi.hackerstack.org"
Create request issued for: [testgatewayapi]
Waiting for operation [projects/(...)] to complete...done.
Created dnsAuthorization [testgatewayapi].

$ gcloud certificate-manager dns-authorizations describe testgatewayapi
createTime: '***'
dnsResourceRecord:
  data: 8bf4bd32-c3c7-4af1-ac25-7b38e07807f6.10.authorize.certificatemanager.goog.
  name: _acme-challenge.testgatewayapi.hackerstack.org.
  type: CNAME
domain: testgatewayapi.hackerstack.org
name: projects/***/locations/global/dnsAuthorizations/testgatewayapi
type: FIXED_RECORD
updateTime: '***'

Cette autorisation DNS nous demande de créer un enregistrement DNS « CNAME » appelé « _acme-challenge.testgatewayapi.hackerstack.org. » ayant pour valeur « 8bf4bd32-c3c7-4af1-ac25-7b38e07807f6.10.authorize.certificatemanager.goog. ».

Après avoir créé l'enregistrement DNS, vérifions en effectuant une requête DNS :

$ dig +short -t CNAME _acme-challenge.testgatewayapi.hackerstack.org
8bf4bd32-c3c7-4af1-ac25-7b38e07807f6.10.authorize.certificatemanager.goog.

Ça a l'air bien. Nous sommes prêts à générer le certificat TLS pour « testgatewayapi.hackerstack.org » et « *.testgatewayapi.hackerstack.org » avec le Gestionnaire de certificats Google.

Nous utilisons l'autorisation DNS pour la vérification de la propriété des domaines car elle permet la création de certificats TLS indépendamment de la ressource Gateway et prend en charge les certificats génériques (wildcards). Pour en savoir plus sur les différentes méthodes d'autorisation de domaine, consultez Autorisation de domaine ou Autorisation de domaine pour les certificats gérés par Google.

Création du certificat TLS géré par Google

Nous utilisons le Gestionnaire de certificats Google pour cela.

Générons un certificat TLS pour les domaines « testgatewayapi.hackerstack.org » et « *.testgatewayapi.hackerstack.org », en utilisant l'autorisation DNS précédemment créée appelée « testgatewayapi » :

$ gcloud certificate-manager certificates create testgatewayapi --domains="*.testgatewayapi.hackerstack.org,testgatewayapi.hackerstack.org" --dns-authorizations=testgatewayapi
Create request issued for: [testgatewayapi]
Waiting for operation [projects/***/locations/global/operations/operation-***] to complete...done.
Created certificate [testgatewayapi].

$ gcloud certificate-manager certificates list
NAME            SUBJECT_ALTERNATIVE_NAMES         DESCRIPTION  SCOPE  EXPIRE_TIME  CREATE_TIME     UPDATE_TIME
testgatewayapi  *.testgatewayapi.hackerstack.org                                   ***             ***
                testgatewayapi.hackerstack.org

Pour voir l’état de la génération du certificat, nous utilisons la commande suivante :

$ gcloud certificate-manager certificates describe testgatewayapi
createTime: '***'
managed:
  authorizationAttemptInfo:
  - domain: '*.testgatewayapi.hackerstack.org'
    state: AUTHORIZING
  - domain: testgatewayapi.hackerstack.org
    state: AUTHORIZING
  dnsAuthorizations:
  - projects/***/locations/global/dnsAuthorizations/testgatewayapi
  domains:
  - '*.testgatewayapi.hackerstack.org'
  - testgatewayapi.hackerstack.org
  state: PROVISIONING
name: projects/***/locations/global/certificates/testgatewayapi
sanDnsnames:
- '*.testgatewayapi.hackerstack.org'
- testgatewayapi.hackerstack.org
updateTime: '***'

Une fois le certificat généré (cela a pris environ 6 minutes dans ce cas), nous devrions voir quelque chose comme ceci :

createTime: '***'
expireTime: '***'
managed:
  authorizationAttemptInfo:
  - domain: '*.testgatewayapi.hackerstack.org'
    state: AUTHORIZED
  - domain: testgatewayapi.hackerstack.org
    state: AUTHORIZED
  dnsAuthorizations:
  - projects/***/locations/global/dnsAuthorizations/testgatewayapi
  domains:
  - '*.testgatewayapi.hackerstack.org'
  - testgatewayapi.hackerstack.org
  state: ACTIVE
name: projects/***/locations/global/certificates/testgatewayapi
pemCertificate: |
  -----BEGIN CERTIFICATE-----
  MIIFpDCCBIygAwIBAgIQQP/U4oP/gZASSvJLrgPhTDANBgkqhkiG9w0BAQsFADBG
  MQswCQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExM
  (...)
  IkoroZytqlFE5vKpJfN0tvxsS877ZnXhEtcAjvlqzRoeq2CjmecBl85009LAQd0f
  az2mOclaqYlrxP2APwJIvJLoTJ8Y5TsGiPcHcGFcSQEEyaoAx9vUBpd/Dwvxn5Bp
  VYomKUfHZyw=
  -----END CERTIFICATE-----
  -----BEGIN CERTIFICATE-----
  MIIFjDCCA3SgAwIBAgINAgCOsgIzNmWLZM3bmzANBgkqhkiG9w0BAQsFADBHMQsw
  CQYDVQQGEwJVUzEiMCAGA1UEChMZR29vZ2xlIFRydXN0IFNlcnZpY2VzIExMQzEU
  (...)
  JDwRjW/656r0KVB02xHRKvm2ZKI03TglLIpmVCK3kBKkKNpBNkFt8rhafcCKOb9J
  x/9tpNFlQTl7B39rJlJWkR17QnZqVptFePFORoZmFzM=
  -----END CERTIFICATE-----
  -----BEGIN CERTIFICATE-----
  MIIFYjCCBEqgAwIBAgIQd70NbNs2+RrqIQ/E8FjTDTANBgkqhkiG9w0BAQsFADBX
  MQswCQYDVQQGEwJCRTEZMBcGA1UEChMQR2xvYmFsU2lnbiBudi1zYTEQMA4GA1UE
  (...)
  +qduBmpvvYuR7hZL6Dupszfnw0Skfths18dG9ZKb59UhvmaSGZRVbNQpsg3BZlvi
  d0lIKO2d1xozclOzgjXPYovJJIultzkMu34qQb9Sz/yilrbCgj8=
  -----END CERTIFICATE-----
sanDnsnames:
- '*.testgatewayapi.hackerstack.org'
- testgatewayapi.hackerstack.org
updateTime: '***'

Si la génération du certificat échoue à cause d'un problème avec l'autorisation DNS, nous devrions voir quelque chose comme ceci :

createTime: '***'
managed:
  authorizationAttemptInfo:
  - domain: testgatewayapi.hackerstack.org
    failureReason: CONFIG
    state: FAILED
  dnsAuthorizations:
  - projects/***/locations/global/dnsAuthorizations/testgatewayapi
  domains:
  - testgatewayapi.hackerstack.org
  provisioningIssue:
    reason: AUTHORIZATION_ISSUE
  state: PROVISIONING
name: projects/***/locations/global/certificates/testgatewayapi
sanDnsnames:
- testgatewayapi.hackerstack.org
updateTime: '***'

Voir Gestion des certificats Google Certificate Manager pour plus d'informations.

Ajout du certificat TLS dans une map de certificats

Nous avons besoin d'une map de certificatspour stocker un ou plusieurs de nos certificats, géré par Google ou non, afin d'avoir un endroit unique à partir duquel la « Gateway » récupérera les certificats pour nos applications utilisant le protocole HTTPS.

Créons et affichons une « map de certificats » appelée « testgatewayapi » :

$ gcloud certificate-manager maps create testgatewayapi
Waiting for 'operation-***' to complete...done.
Created certificate map [testgatewayapi].

$ gcloud certificate-manager maps list
NAME            ENDPOINTS  DESCRIPTION  CREATE_TIME
testgatewayapi  -                        ***

$ gcloud certificate-manager maps describe testgatewayapi
createTime: '***'
name: projects/***/locations/global/certificateMaps/testgatewayapi
updateTime: '***'

Pour ajouter des certificats dans la « map de certificats » pour des noms de domaine spécifiques, nous devons créer des entrées de map de certificats comme suit:

$ gcloud certificate-manager maps entries create testgatewayapi1 --map="testgatewayapi" --certificates="testgatewayapi" --hostname="testgatewayapi.hackerstack.org"
Waiting for 'operation-***' to complete...done.
Created certificate map entry [testgatewayapi1].

$ gcloud certificate-manager maps entries create testgatewayapi2 --map="testgatewayapi" --certificates="testgatewayapi" --hostname="*.testgatewayapi.hackerstack.org"
Waiting for 'operation-***' to complete...done.
Created certificate map entry [testgatewayapi2].

Nous avons créé deux « entrées de map de certificats » appelées « testgatewayapi1 » et « testgatewayapi2 » référençant respectivement les certificats TLS pour les domaines « testgatewayapi.hackerstack.org » et « *.testgatewayapi.hackerstack.org ».

$ gcloud certificate-manager maps entries list --map="testgatewayapi"
NAME             DESCRIPTION  HOSTNAME                          MATCHER  CERTIFICATES    STATE   CREATE_TIME
testgatewayapi1               testgatewayapi.hackerstack.org             testgatewayapi  ACTIVE  ***
testgatewayapi2               *.testgatewayapi.hackerstack.org           testgatewayapi  ACTIVE  ***

Ensuite, nous allons indiquer à la Gateway d'utiliser la map de certificats « testgatewayapi » comme magasin de certificats TLS. Consultez Gestion de map de certificats et Gestion des entrées de map de certificats pour plus d'informations.

Utilisation du certificat TLS sur la passerelle

Pour dire à la « Passerelle » qui map de certificats pour utiliser la recherche de certificats pour les noms de domaine de nos applications accessibles via l'écouteur HTTPS, nous utilisons la clé « networking.gke.io/certmap » dans le champ « metadata.annotations » des ressources « Gateway » comme suit :

kind: Gateway
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  (...)
  annotations:
    networking.gke.io/certmap: testgatewayapi # Name of the certificate map
spec:
  (...)

Après cela, lorsque la « passerelle » reçoit des requêtes HTTPS pour l'un des noms de domaine présents dans la « carte de certificats », elle examinera l'« entrée de la carte de certificats » pour ce domaine pour obtenir le nom du certificat à utiliser pour configurer les sessions TLS.

Création de HTTPRoutes

La ressource « HTTPRoute » peut être utilisée pour acheminer le trafic HTTP ou HTTPS vers nos charges de travail d'applications Kubernetes via la « passerelle ».

Sur la « passerelle » que nous avons créée précédemment, nous avons utilisé le champ « gateway.spec.allowedRoutes » pour autoriser les ressources « HTTPRoute » de n'importe quel espace de noms du cluster, pour utiliser la « passerelle » pour acheminer le trafic vers les applications backend.

Voici le manifeste d'un exemple d'application exécutée à l'intérieur du cluster que nous allons exposer via la « passerelle » :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
  name: nginx
  namespace: default
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    targetPort: 80
    name: http
  selector:
    app: nginx
  type: ClusterIP

Voici le manifeste que nous utilisons pour créer la ressource « HTTPRoute » pour exposer l'application via la « passerelle » :

kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  name: nginx-https
  namespace: default
spec:
  parentRefs:
  - kind: Gateway
    name: testgwapi-global-external-managed # Name of the Gateway to use
    namespace: default
    sectionName: https # Which Gateway listener to use
  hostnames:
    - testgatewayapi.hackerstack.org
  rules:
  - backendRefs:
    - name: nginx # Name of the backend service on which traffic will be routed
      kind: Service
      namespace: default # Namespace where the backend service resides
      port: 80
      weight: 100

Pour plus d'informations sur les champs de ressources « HTTPRoute » que nous pouvons utiliser et une explication détaillée de chacun d'eux, consultez Champs d'API de passerelle pris en charge par la ressource GKE HTTPRoutes par classe de passerelle.

Jetons un œil à l’état de la ressource « HTTPRoute » :

$ kubectl get httproute
NAME          HOSTNAMES                            AGE
nginx-https   ["testgatewayapi.hackerstack.org"]   35m

$ kubectl describe httproute/nginx-https
Name:         nginx-https
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  gateway.networking.k8s.io/v1beta1
Kind:         HTTPRoute
Metadata:
  Creation Timestamp:  ***
  Generation:          2
  Resource Version:    93649
  UID:                 ef84cf65-37d7-4b32-94ec-e4926596c0ee
Spec:
  Hostnames:
    testgatewayapi.hackerstack.org
  Parent Refs:
    Group:         gateway.networking.k8s.io
    Kind:          Gateway
    Name:          testgwapi-global-external-managed
    Namespace:     default
    Section Name:  https
  Rules:
    Backend Refs:
      Group:
      Kind:       Service
      Name:       nginx
      Namespace:  default
      Port:       80
      Weight:     100
    Matches:
      Path:
        Type:   PathPrefix
        Value:  /
Status:
  Parents:
    Conditions:
      Last Transition Time:  ***
      Message:
      Observed Generation:   2
      Reason:                Accepted
      Status:                True
      Type:                  Accepted
      Last Transition Time:  ***
      Message:
      Observed Generation:   2
      Reason:                ReconciliationSucceeded
      Status:                True
      Type:                  Reconciled
    Controller Name:         networking.gke.io/gateway
    Parent Ref:
      Group:         gateway.networking.k8s.io
      Kind:          Gateway
      Name:          testgwapi-global-external-managed
      Namespace:     default
      Section Name:  https
Events:
  Type    Reason  Age   From                   Message
  ----    ------  ----  ----                   -------
  Normal  ADD     17m   sc-gateway-controller  default/nginx-https
  Normal  UPDATE  2m1s  sc-gateway-controller  default/nginx-https
  Normal  SYNC    9s    sc-gateway-controller  Bind of HTTPRoute "default/nginx-https" to ParentRef {Group:       "gateway.networking.k8s.io",
 Kind:        "Gateway",
 Namespace:   "default",
 Name:        "testgwapi-global-external-managed",
 SectionName: "https",
 Port:        nil} was a success
  Normal  SYNC  9s  sc-gateway-controller  Reconciliation of HTTPRoute "default/nginx-https" bound to ParentRef {Group:       "gateway.networking.k8s.io",
 Kind:        "Gateway",
 Namespace:   "default",
 Name:        "testgwapi-global-external-managed",
 SectionName: "https",
 Port:        nil} was a success

La ressource « nginx-https » et « HTTPRoute » a été correctement liée à la passerelle. Essayons d'atteindre l'application Nginx via la passerelle en effectuant une requête HTTPS :

$ curl -v https://testgatewayapi.hackerstack.org
*   Trying 34.111.146.21:443...
* TCP_NODELAY set
* Connected to testgatewayapi.hackerstack.org (34.111.146.21) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
* TLSv1.3 (IN), TLS handshake, Server hello (2):
* TLSv1.3 (IN), TLS handshake, Encrypted Extensions (8):
* TLSv1.3 (IN), TLS handshake, Certificate (11):
* TLSv1.3 (IN), TLS handshake, CERT verify (15):
* TLSv1.3 (IN), TLS handshake, Finished (20):
* TLSv1.3 (OUT), TLS change cipher, Change cipher spec (1):
* TLSv1.3 (OUT), TLS handshake, Finished (20):
* SSL connection using TLSv1.3 / TLS_AES_256_GCM_SHA384
* ALPN, server accepted to use h2
* Server certificate:
*  subject: CN=*.testgatewayapi.hackerstack.org
*  start date: ***
*  expire date: ***
*  subjectAltName: host "testgatewayapi.hackerstack.org" matched cert's "testgatewayapi.hackerstack.org"
*  issuer: C=US; O=Google Trust Services LLC; CN=GTS CA 1D4
*  SSL certificate verify ok.
* Using HTTP2, server supports multi-use
* Connection state changed (HTTP/2 confirmed)
* Copying HTTP/2 data in stream buffer to connection buffer after upgrade: len=0
* Using Stream ID: 1 (easy handle 0x559aaa0680e0)
> GET / HTTP/2
> Host: testgatewayapi.hackerstack.org
> user-agent: curl/7.68.0
> accept: */*
>
* 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 
< server: nginx/1.25.4
< date: ***
< content-type: text/html
< content-length: 615
< last-modified: ***
< etag: "65cce434-267"
< accept-ranges: bytes
< via: 1.1 google
< alt-svc: h3=":443"; ma=2592000,h3-29=":443"; ma=2592000
<
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>
* Connection #0 to host testgatewayapi.hackerstack.org left intact

Nous avons réussi à atteindre notre application nginx via la « passerelle » en utilisant le protocole HTTPS.

Protection des backends de la passerelle avec Cloud Armor

Vue d'ensemble

La routine shell Google Cloud Armor Un pare-feu d'applications Web (WAF) peut être utilisé pour protéger les applications exposées via l'API Gateway. Consultez Services Google Cloud supplémentaires pour la compatibilité avec la « Gateway » en fonction de la « GatewayClass » utilisée.

Pour utiliser le WAF afin de protéger les applications backends « Gateway », nous devons :

Ensuite, nous allons exposer le Boutique de jus OWASP application Web vulnérable via la « passerelle » que nous avons créée précédemment, effectuez une attaque par injection SQL réussie sur la page de connexion, puis protégez cette application contre les attaques par injection SQL en utilisant l'une des Règles WAF préconfigurées de Google Cloud Armor. Nous allons ensuite créer un règle de politique Cloud Armor personnalisée pour bloquer des demandes spécifiques.

Utilisation de l'application vulnérable OWASP Juiceshop comme backend

Voici les manifestes que nous utilisons pour déployer le Boutique de jus OWASP application web vulnérable :

apiVersion: apps/v1
kind: Deployment
metadata:
  name: juiceshop
  namespace: default
  labels:
    app: juiceshop
spec:
  replicas: 1
  selector:
    matchLabels:
      app: juiceshop
  template:
    metadata:
      labels:
        app: juiceshop
    spec:
      containers:
      - name: juiceshop
        image: bkimminich/juice-shop
        ports:
        - containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
  name: juiceshop
  namespace: default
  labels:
    app: juiceshop
spec:
  ports:
  - port: 80
    targetPort: 3000
    name: http
  selector:
    app: juiceshop
  type: ClusterIP

Après cela, nous rendons l'application disponible via la « Passerelle » en créant la ressource « HTTPRoute » suivante :

kind: HTTPRoute
apiVersion: gateway.networking.k8s.io/v1beta1
metadata:
  name: juiceshop-https
  namespace: default
spec:
  parentRefs:
  - kind: Gateway
    name: testgwapi-global-external-managed # Name of the Gateway to use
    namespace: default
    sectionName: https # Which Gateway listener to use
  hostnames:
    - "testgatewayapi.hackerstack.org"
  rules:
  - backendRefs:
    - name: juiceshop # Name of the backend service on which traffic will be routed
      kind: Service
      namespace: default # Namespace where the backend service resides
      port: 80
      weight: 100

Vérification:

$ curl https://testgatewayapi.hackerstack.org
<!--
  ~ Copyright (c) 2014-2023 Bjoern Kimminich & the OWASP Juice Shop contributors.
  ~ SPDX-License-Identifier: MIT
  --><!DOCTYPE html><html lang="en"><head>
  <meta charset="utf-8">
  <title>OWASP Juice Shop</title>
  <meta name="description" content="Probably the most modern and sophisticated insecure web application">
  (...)
</script><script src="vendor.js" type="module"></script><script src="main.js" type="module"></script></body></html>

Protection de l'application Juiceshop contre les attaques par injection SQL

La page de connexion du Boutique de jus OWASP l'application est vulnérable à certaines attaques par injection SQL. ' OR TRUE-- dans le champ connexion/e-mail avec n'importe quel caractère dans le champ mot de passe, connectez-nous en tant qu'utilisateur « admin ».

Bloquons cette attaque avec le WAF.

Tout d'abord, nous créons un politique de sécurité , l’aspect économique règle de politique de sécurité pour bloquer les attaques par injection SQL. Nous utilisons la méthode « sqli-v33-stable ». règle prédéfinie pour cela. Les règles prédéfinies proviennent de la Ensemble de règles de base OWASP ModSecurity. Voici les commandes :

# Create the Cloud Armor security policy
$ gcloud compute security-policies create protect

# Create a rule inside the security policy to block sql injections
$ gcloud compute security-policies rules create 1000 \
    --security-policy protect \
    --expression "evaluatePreconfiguredExpr('sqli-v33-stable')" \
    --action deny-403

Pour plus d'informations sur les autorisations requises pour la configuration des politiques de sécurité, consultez Politiques de sécurité Permissions IAM et Politiques de sécurité et autorisations IAM personnalisées.

La priorité de la règle est « 1 000 ». Les règles de sécurité les moins prioritaires sont évaluées en premier. Consultez Ordre d'évaluation des règles de politiques de sécurité pour en savoir plus.

Pour en savoir plus sur la création, la mise à jour et la suppression des politiques et des règles de politiques Cloud Armor, consultez Gérer les politiques de sécurité de Cloud Armor, Gérer les règles des politiques de sécurité Cloud Armor et Exemples de politiques.

Ensuite, nous devons indiquer à la « passerelle » d’utiliser la politique de sécurité « protéger » pour le service backend « juiceshop » en créant un Politique de GCPBackend Ressource:

apiVersion: networking.gke.io/v1
kind: GCPBackendPolicy
metadata:
  name: protect-juiceshop
  namespace: default
spec:
  default:
    securityPolicy: protect
    logging:
      enabled: true
      sampleRate: 500000
  targetRef:
    group: ""
    kind: Service
    name: juiceshop

Sans la section « spec.default.logging » activant la journalisation, les journaux Cloud Armor des requêtes bloquées ne seront pas visibles dans Google Cloud Logging. Pour en savoir plus sur les paramètres de configuration de la journalisation, consultez la section Journalisation de l'accès HTTP à la passerelle.

$ kubectl get gcpbackendpolicies
NAME                        AGE
protect-juiceshop           47m

$ kubectl describe gcpbackendpolicies/protect-juiceshop
Name:         protect-juiceshop
Namespace:    default
Labels:       <none>
Annotations:  <none>
API Version:  networking.gke.io/v1
Kind:         GCPBackendPolicy
Metadata:
  Creation Timestamp:  ***
  Generation:          1
  Resource Version:    58233
  UID:                 9fcacbb7-d874-4b7e-bf40-b5ca93c6681e
Spec:
  Default:
    Security Policy:  protect
  Target Ref:
    Group:
    Kind:   Service
    Name:   juiceshop
Status:
  Conditions:
    Last Transition Time:  ***
    Message:
    Reason:                Attached
    Status:                True
    Type:                  Attached
Events:
  Type    Reason  Age                 From                   Message
  ----    ------  ----                ----                   -------
  Normal  ADD     47m                 sc-gateway-controller  default/protect-juiceshop
  Normal  SYNC    49s (x14 over 46m)  sc-gateway-controller  Application of GCPGatewayPolicy "default/protect-juiceshop" was a success

Notez qu'une seule ressource GCPBackendPolicy peut être associée à un service backend Gateway. Jetez un œil à Dépannage de la passerelle GCPBackendPolicy pour en savoir plus.

Testons la protection en effectuant à nouveau l'attaque par injection SQL. L'attaque est bloquée avec succès par le WAF (Web Application Firewall) :

Attaque SQLi bloquée par Google Cloud Armor WAF

Utilisation de règles WAF Cloud Armor personnalisées

Maintenant, nous voulons bloquer les demandes en fonction de attributs spécifiques comme les IP sources, les en-têtes de requêtes... Pour cela, nous devons créer une règle de politique de sécurité personnalisée. Voici la règles de référence linguistique nous utilisons pour cela.

Créons une règle pour bloquer les requêtes contenant « user-agent : robot » dans l'en-tête :

$ gcloud compute security-policies rules create 1001 \
    --security-policy protect \
    --expression "has(request.headers['user-agent']) && request.headers['user-agent'].contains('robot')" \
    --action deny-403

La règle personnalisée est définie avec l'option « --expression ». Une fois la règle appliquée, vérifions :

$ curl -H 'user-agent: robot' https://testgatewayapi.hackerstack.org
<!doctype html><meta charset="utf-8"><meta name=viewport content="width=device-width, initial-scale=1"><title>403</title>403 Forbidden

Pour visualiser et personnaliser les sorties des journaux des politiques de sécurité pour Google Cloud Logging, consultez Entrées des journaux des politiques de sécurité et Journalisation détaillée des politiques de sécurité.

Autres fonctionnalités de Gateway

Les ressources « GCPGatewayPolicy » et « GCPBackendPolicy » pour la « passerelle » sont l'équivalent de Configuration du frontend et Configuration du backend ressources pour le GCP ingress-gce contrôleur ingress.

Ils peuvent être utilisés pour configurer des fonctionnalités supplémentaires telles que :

Pour une liste complète de ces fonctionnalités et des liens décrivant comment les activer, consultez Sécurité du front-end, Propriétés des services backend et Services Google Cloud supplémentaires.

Pour des fonctionnalités supplémentaires liées au routage et à la gestion du trafic disponibles via la ressource « HTTPRoute », consultez Routage et gestion du trafic.