Introduction

Le sujet de la sécurité, et en particulier la gestion des accès et des autorisations, est traité dans certaines compagnies qui migrent vers le cloud, de la façon suivante : le risque vient toujours de l'extérieur. De manière caricaturale, cela revient à dire “les méchants sont à l'extérieur du réseau, et les gentils sont à l’intérieur”.

Dans une telle approche, l’équipe sécurité met en place des pare-feux et des VPN dont le but est de créer un périmètre de confiance autour des applications et notamment celles hébergées dans le Cloud.

Cette philosophie peut rapidement s’avérer compliquée à gérer. Des collaborateurs externes doivent (ou demandent à) avoir accès à quelques services en particulier. Certaines applications mobiles ou OS ne sont pas compatibles avec le client VPN, situation très problématique en période de télétravail. Pire, elle n’apporte qu’une illusion de sécurité et des pirates peuvent toujours s'introduire dans le réseau à partir de machines ou d’IP autorisées.

Google a bien compris les limitations de ce modèle par rapport aux enjeux de l’époque : applications dans le cloud, espace de travail mondial, collaboration multi-régionale. En introduisant le modèle "confiance zéro" (Zero trust), Google prend parti de traiter d’un point de vue sécurité les flux externes et internes de manière identique : tous nécessitent une authentification. Cet article à pour but de vous introduire à cette philosophie et d’en comprendre les avantages par rapport à l’approche classique des VPNs.

BeyondCorp

Le BeyondCorp est l’implémentation Google du modèle dit "confiance zéro" il s’agit d’autoriser les accès en se basant sur le contexte de la requête :

  • Vérification de l’identité de l’émetteur de la requête
  • Vérification des droits d’accès de l'émetteur à la ressource.
  • Vérification de la fiabilité du périphérique émetteur de la requête.

Si toutes ces conditions sont réunies, l'utilisateur peut accéder à la ressource sinon l’accès est refusé.

Ce modèle est qualifié de "confiance zéro" car indépendamment de la localisation de l’utilisateur il aura toujours la même capacité d’accès. Une des applications les plus communes et connues de ce modèle c’est l’accès à la boîte Gmail, un certain nombre de vérifications liées au contexte de la requête sont effectuées afin de s’assurer qu’il ne s’agit pas d’un accès suspicieux, comme par exemple si l’utilisateur se connecte à partir d’un nouvel appareil ou d’une autre région géographique.

Identity Aware Proxy (IAP)

Le proxy IAP est un service managé qui permet d’implémenter le principe BeyondCorp dans GCP, tout ce dont on a besoin c’est des comptes d'utilisateurs ou de service, une règle d'accès et une ressource déployée dans GCP.

Cette ressource peut être une application web hébergée dans AppEngine, des VM Compute Engine ou dans GKE.  Il est impératif d’avoir un équilibreur de charge HTTPS placé devant la ressource à sécuriser avec IAP.

Dans ce qui suit on va utiliser le proxy IAP pour sécuriser une application déployée sur GKE.

Cloud IAP va se placer en tant que proxy au niveau de l’équilibreur de charge HTTPS pour intercepter les requêtes entrantes et lancer le processus d’authentification via OAuth2.

Une fois authentifié, le moteur de règles va s’assurer que cet utilisateur remplit tous les critères qui lui permettent d'accéder à la ressource.

Il existe deux types de règles IAP :

  • Règles basiques : Un filtre sur des adresses IP, sur une région et sur un ensemble d'utilisateurs authentifiés ou des comptes de services
  • Règles avancées (Disponibles en souscrivant à la version payante de BeyondCorp) : Filtres sur le type de l’appareil, version de l’OS et du browser

Dans le scénario du tutoriel suivant, les règles qu’on souhaite appliquer seront des règles basiques :

  • L’utilisateur doit appartenir à un groupe Google Workplace (anciennement Gsuite) bien défini
  • On autorise les accès que depuis des IP françaises

Pour activer IAP sur un cluster GKE avec les contraintes de contexte déjà listées, on aura besoin de créer les ressources suivantes :

  • Un cluster GKE qui héberge un serveur web exposé via un équilibreur de charge HTTPS
  • Cloud IAP activé sur l’endpoint du serveur web
  • Une policy IAM permettant aux utilisateurs du groupe d'accéder au service
  • Un client OpenId qui va servir à authentifier notre application web
  • Une règle dans Access Control Manager pour autoriser les IP françaises

Tutoriel (En utilisant Terraform)

Avant de commencer assurez vous de disposer des droits nécessaire pour créer un projet GCP, un cluster GKE et des règles d’accès, vous pouvez utiliser les rôles prédéfinis suivants :

  • roles/resourcemanager.projectCreator
  • roles/container.clusterAdmin
  • roles/accesscontextmanager.policyAdmin

Premièrement on va configurer le client OpenID , on doit configurer l’écran d’autorisation (consent screen) et créer un client OpenID pour gérer la partie authentification et redirections nécessaires au protocole OAuth2.


resource "google_project_service" "project_service" {
 project = "iap-sandbox-299117"
 service = "iap.googleapis.com"
}
 
resource "google_iap_brand" "project_brand" {
 support_email = "bassem.benlazreg@wescale.fr"
 application_title = "IAP_APP"
}
 
resource "google_iap_client" "project_client" {
 display_name = "IAP GKE Client"
 brand        =  google_iap_brand.project_brand.name
}

Ensuite, on va créer le cluster et déployer un secret contenant les identifiants du client OpenID (client_id, client_secret)  qu’on vient de créer dans l’étape précédente.


resource "google_container_cluster" "gke_cluster" {
 name               = "my-gke-cluster"
 location           = "europe-west1-c"
 project = "iap-sandbox-299117"
 initial_node_count = 3
 
 master_auth {
   username = ""
   password = ""
 
   client_certificate_config {
     issue_client_certificate = false
   }
 }
 
 node_config {
   oauth_scopes = [
     "https://www.googleapis.com/auth/cloud-platform"
   ]
 
   metadata = {
     disable-legacy-endpoints = "true"
   }
}
}
 
data "google_client_config" "provider" {}
 
provider "kubernetes" {
 load_config_file = false
 
 host  = "https://${google_container_cluster.gke_cluster.endpoint}"
 token = data.google_client_config.provider.access_token
 cluster_ca_certificate = base64decode(
   google_container_cluster.gke_cluster.master_auth[0].cluster_ca_certificate,
 )
}
 
resource "kubernetes_secret" "iap-secret" {
 metadata {
   name = "my-iap-secret"
 }
 
 data = {
   client_id = google_iap_client.project_client.client_id
   client_secret = google_iap_client.project_client.secret
 }
 
}

Le secret va être référencé dans une CRD "BackendConfig" qui va servir à activer IAP sur notre service Kubernetes.

apiVersion: cloud.google.com/v1
kind: BackendConfig
metadata:
 name: config-default
spec:
 iap:
   enabled: true
   oauthclientCredentials:
     secretName: my-iap-secret

Ensuite, on déploie notre application dans Kubernetes derrière un équilibreur de charge HTTPS en utilisant la ressource Ingress.

Le déploiement :

apiVersion: apps/v1
kind: Deployment
metadata:
 name: web
 namespace: default
spec:
 selector:
   matchLabels:
     run: web
 template:
   metadata:
     labels:
       run: web
   spec:
     containers:
     - image: gcr.io/google-samples/hello-app:1.0
       imagePullPolicy: IfNotPresent
       name: web
       ports:
       - containerPort: 8080
         protocol: TCP

Le service (noter que dans les annotations on fait référence au BackendConfig déjà créé) :

apiVersion: v1
kind: Service
metadata:
 name: web
 namespace: default
 annotations:
   beta.cloud.google.com/backend-config: '{"default": "config-default"}'
spec:
 ports:
 - port: 80
   protocol: TCP
   targetPort: 8080
 selector:
   run: web
 type: NodePort

Ensuite, on crée un Ingress HTTPS (en prérequis un certificat TLS valide est provisionné dans un secret "tls-secret") :

apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
 name: basic-ingress
 annotations:
   # Adresse IP fixe au niveau du loadbalancer HTTPS pour le routage DNS
   kubernetes.io/ingress.global-static-ip-name: "iap-ingress-ip"
spec:
 tls:
   - secretName: tls-secret 
 rules:
 # Le domaine a été utilisé a des fins d’exemple 
 # et n’est plus accessible aujourd’hui.
 - host: test.bbenlazreg.fr
   http:
     paths:
     - backend:
         serviceName: web
         servicePort: 80

Maintenant, si on accède à notre application depuis un navigateur, on va être redirigé vers la page d'authentification google, une fois authentifié on va arriver sur cet écran, l’accès est refusé.

Ce qui nous manque c’est un Rôle IAM permettant au groupe d'utilisateurs d'accéder au service :

resource "google_iap_web_iam_member" "member" {
 project = google_project_service.project_service.project
 role = "roles/iap.httpsResourceAccessor"
 member = "user:tous@wescale.fr"
 
 condition {
   title       = "iap_gke_access"
   expression  = "request.host == \"test.bbenlazreg.fr\" "
 }
}


La règle IAM qu’on vient de définir autorise l’accès à tous les utilisateurs du groupe "tous@wescale.fr"  d’aller uniquement sur l’hôte "test.bbenlazreg.fr"

Le champ expression nous permet aussi de mettre en place des règles plus avancées, par exemple sur des verbes HTTP en particulier (POST/GET etc) ou un chemin URI dans notre application web, on pourrait imaginer par exemple qu’un groupe d’admin a exclusivement accès à une interface d’administration sur /admin.

Maintenant, un utilisateur authentifié appartenant au groupe "tous@wescale.fr" pourra accéder à l’application web déployée sur Kubernetes.  Aucune modification du code de notre application n’est requise pour gérer cette authentification. Tout de même une bonne pratique serait de valider dans le code de l'application le token généré par IAP afin de s’assurer que la requête est bien passée par IAP (par exemple si IAP est désactivé accidentellement), ce token peut être récupéré dans l’entête  x-goog-iap-jwt-assertion.

Dernière étape, afin de s’assurer que les connexions proviennent d’IPs en France, il faudrait créer une politique d’accès au niveau de l’organisation GCP.

Cette politique d’accès peut être définie à l’aide d’un fichier yaml, dans ce fichier on peut aussi mettre des restrictions par plage d’IP.

En souscrivant à la version payante de BeyondCorp on pourrait également rajouter des contraintes sur le contexte de la connexion lié à l’appareil ou au navigateur.

Dans le cadre de ce Tutoriel, on va se contenter d’une contrainte sur la région. Ainsi on crée la règle au niveau de l’organisation avec context manager.

resource "google_access_context_manager_access_level" "access-level" {
 parent = "accessPolicies/${google_access_context_manager_access_policy.access-policy.name}"
 name   = "accessPolicies/${google_access_context_manager_access_policy.access-policy.name}/accessLevels/only_fr"
 title  = "only_fr"
 basic {
   conditions {
 
     regions = [
   "FR",
     ]
   }
 }
}
 
resource "google_access_context_manager_access_policy" "access-policy" {
 parent = "organizations/177396800055"
 title  = "mypolicy"
}

Ensuite, on récupère l’identifiant de la règle qu’on vient de créer et il ne reste plus qu'à modifier les conditions IAM pour l’appliquer :

resource "google_iap_web_iam_member" "member" {
 project = google_project_service.project_service.project
 role = "roles/iap.httpsResourceAccessor"
 member = "user:tous@wescale.fr"
 
 condition {
   title       = "iap_gke_access"
   expression  = " \" ${google_access_context_manager_access_level.access-level.id} \" in request.auth.access_levels && request.host == \"test.bbenlazreg.fr\" "
 }
}

Une fois appliqué, au moment de l’authentification le proxy IAP récupère la région du contexte de la connexion et la compare avec la règle définie pour autoriser la requête

Conclusion

Cloud IAP permet de mettre facilement un périmètre de sécurité autour des applications hébergées dans GCP, en se plaçant en proxy devant le service il permet d’apporter l’authentification et l'autorisation sans changement dans le code en se basant sur le contexte de la requête, il offre ainsi une nouvelle façon de voir la sécurité en dehors des solutions classiques purement réseau tels qu’un VPN.