Aller au contenu
SécuritéGCPGitlabTerraformCloud

GCP et GitLab Privé : Authentifier vos Pipelines CI/CD en Keyless avec Workload Identity

Découvrez comment configurer Workload Identity via Terraform pour sécuriser vos pipelines Gitlab avec OIDC, sans JSON Keys, tout en simplifiant la gestion des identités et renforçant leur sécurité.

Pipeline and strong security

Pourquoi utiliser Google Workload Identity ?

Objectifs de l'article

Dans un monde où la sécurité et l'efficacité des pipelines CI/CD sont primordiales, l'utilisation de Google Workload Identity afin de s'authentifier facilement et de manière sécurisée aux services de la GCP peut apporter des avantages significatifs.

Cet article vous guidera à travers chaque étape de cette intégration.

Nous utiliserons du code Terraform afin de tracer et d'automatiser la configuration, ce qui vous permettra également de récupérer facilement les actions effectuées.

Objectifs de l'article :

  1. Comprendre Google Workload Identity : Nous expliquerons ce qu'est Google Workload Identity et pourquoi il est aujourd'hui un choix de premier ordre dans l'authentification aux services Google.
  2. Configurer Google Workload Identity : Vous apprendrez à configurer un Workload Identity Pool Provider et un Workload Identity Pool en utilisant Terraform.
  3. Autoriser la CI/CD d'un repository à s'authentifier : Nous détaillerons comment créer, configurer un Service Account et le lier à un dépôt GitLab.
  4. Intégrer dans GitLab CI/CD : Vous verrez comment intégrer cette configuration dans vos pipelines CI/CD GitLab, avec des exemples de fichiers .gitlab-ci.yml.

Qu'est que Google Workload Identity exactement ?

Lien avec l'objectif de l'article

Pour bien comprendre l’intégration de Google Workload Identity dans un environnement GitLab CI/CD on-premise, il est essentiel de saisir les concepts de la fédération d’identité et leur application dans ce contexte.

Fédération d'identité : GitLab, ses utilisateurs et ses groupes

La fédération d’identité permet à des utilisateurs et groupes d’un système externe (comme GitLab) de s’authentifier et d’accéder à des ressources dans un autre système (comme Google Cloud) sans avoir à gérer des identités distinctes dans chaque système. Dans notre cas, GitLab et ses utilisateurs/groupes seront fédérés avec Google Cloud pour accéder aux ressources nécessaires à l’exécution des pipelines CI/CD.

Représentation dans Google Cloud : Workload Identity Pool et Pool Provider

Pour réaliser cette fédération d'identité, nous allons utiliser deux concepts clés de Google Cloud : le Workload Identity Pool et le Workload Identity Pool Provider.

  • Workload Identity Pool : Un Workload Identity Pool est une collection d’identités externes qui peuvent être mappées à des identités Google Cloud. Il sert de conteneur pour les identités provenant de systèmes externes comme GitLab.
  • Workload Identity Pool Provider : Un Provider au sein d’un Workload Identity Pool définit comment les identités externes sont mappées et authentifiées. Il décrit les protocoles d’authentification et les règles de mapping des identités.
👍
Bonnes pratiques : un seul Pool Provider par Pool
Il est recommandé de n’avoir qu’un seul Pool Provider par Workload Identity Pool pour simplifier la gestion et éviter les conflits de configuration.

Choix du protocole d'authentification : OIDC

Nous allons utiliser le protocole OIDC (OpenID Connect), car GitLab, même en version on-premise, supporte ce protocole.

OIDC repose sur OAuth 2.0 et permet une authentification sécurisée. Google Workload Identity supporte aussi d’autres protocoles comme SAML et JWT, mais OIDC reste notre choix privilégié ici pour sa compatibilité avec GitLab.

Les Avantages de Workload Identity par rapport aux JSON Keys

L’utilisation de Google Workload Identity offre plusieurs avantages significatifs par rapport à l’ancienne méthode dépréciée basée sur les JSON Keys pour l’authentification dans Google Cloud.

  1. Sécurité renforcée

Les JSON Keys sont des clés statiques stockées sur des serveurs ou des machines, ce qui les rend vulnérables à des fuites accidentelles ou des accès non autorisés. En revanche, Workload Identity utilise une approche de fédération d’identité qui permet de limiter les accès temporaires et dynamiques.

La rotation automatique des tokens d’identification dans Workload Identity supprime la nécessité de gérer manuellement les clés d’accès, réduisant ainsi le risque d’attaques potentielles.

  1. Simplification de la gestion des identités

Avec Workload Identity, il n’est plus nécessaire de gérer des fichiers de clés ou de distribuer ces fichiers aux utilisateurs ou aux pipelines CI/CD. Tout est géré de manière transparente à travers le mapping des identités externes vers des comptes de service Google Cloud.

Les accès sont accordés via des permissions IAM standard sans manipulation des secrets, ce qui simplifie la gestion et l’audit.

  1. Moins de responsabilités pour l’infrastructure

Les JSON Keys nécessitent de suivre des pratiques strictes pour assurer qu’elles ne soient pas exposées (mises à jour régulières, stockage sécurisé). Workload Identity élimine ce fardeau en utilisant des jetons éphémères qui n’ont pas besoin d’être stockés à long terme.

4. Meilleure intégration avec les environnements cloud natifs

Workload Identity est mieux intégré aux workflows cloud modernes, notamment pour les environnements CI/CD, car il s’intègre directement aux fournisseurs d’identité comme GitLab, ce qui facilite la configuration et la maintenance à long terme.

En résumé

Google Workload Identity permet à des identités externes, comme celles de GitLab, de s’authentifier auprès de Google Cloud en tant que comptes de service via des protocoles sécurisés comme OIDC. Cette approche élimine la gestion des clés statiques (JSON Keys) en faveur de jetons temporaires, renforçant ainsi la sécurité. La configuration se fait via le Workload Identity Pool Provider, qui définit les règles d’authentification et permet une gestion simplifiée et intégrée des identités entre les environnements.

Configuration de Workload Identity

On établit le lien GCP - Gitlab

Pour intégrer Google Workload Identity avec GitLab dans un environnement privé, il est nécessaire de configurer le Workload Identity Pool et le Workload Identity Pool Provider afin de gérer de manière sécurisée l’authentification des pipelines GitLab. Voici le guide pas à pas de la configuration en utilisant Terraform.

👍
Source : le git repository du code de l'article
Voici le lien vers un repository git public pour consulter les fichiers de configuration détaillés dans l'article.

Configuration du provider terraform Google

provider "google" {
  project = var.google_project_id
  region  = var.google_region
  default_labels = {
    project = "gitlab-workload-identity"
  }
}

Les variables google_project_id et google_region permettent de spécifier le projet et la région Google Cloud. Assurez-vous de les définir dans votre configuration :

variable "google_project_id" {
  description = "Identifiant du projet GCP où le Workload Identity sera configuré"
  type        = string
}

variable "google_region" {
  description = "Région du projet GCP"
  type        = string
  default     = "europe-west1"  # ou la région de votre choix
}

Création du Workload Identity Pool

Le Workload Identity Pool agit comme un conteneur pour les identités GitLab externes, nous permettant de fédérer des identités GitLab dans Google Cloud.

# Create a pool of identity providers (GitLab)
resource "google_iam_workload_identity_pool" "gitlab_pool" {
  workload_identity_pool_id = "private-gitlab-pool"
}

Ce pool, nommé private-gitlab-pool, contiendra toutes les identités issues de GitLab, fédérées pour accéder aux ressources GCP.

Récupération de la clé publique GitLab

Comme le serveur GitLab est privé, google ne peut pas récupérer la clef publique depuis son infrastructure cloud. Ce n'est pas un problème si nous exécutons le code terraform depuis un environnement qui a accès au serveur Gitlab. Nous devons alors récupérer un fichier JSON Web Key Set (JWKS) pour permettre la vérification des jetons JWT provenant de GitLab. Ce fichier contient les clés publiques utilisées pour valider les signatures de ces jetons.

# Download JWKS file from GitLab (public key used to verify JWT token)
data "http" "jwks_gitlab" {
  url = "${var.gitlab_url}/oauth/discovery/keys"

  # Optional request headers
  request_headers = {
    Accept = "application/json"
  }
}

La variable gitlab_url est nécessaire pour définir l’URL de votre instance GitLab. Ajoutez cette variable dans votre fichier de configuration Terraform :

variable "gitlab_url" {
  description = "URL de l'instance GitLab privée"
  type        = string
  default     = "https://my-private-gitlab.com"
}

Configuration du Workload Identity Pool Provider

Le Workload Identity Pool Provider gère la validation des identités provenant de GitLab. En définissant l’attribut oidc, nous précisons l’URL de l’émetteur GitLab et les clés pour valider les jetons JWT.

resource "google_iam_workload_identity_pool_provider" "gitlab_pool_provider" {
  workload_identity_pool_id          = google_iam_workload_identity_pool.gitlab_pool.workload_identity_pool_id
  workload_identity_pool_provider_id = "private-gitlab-pool-provider"

  attribute_mapping = {
    "google.subject"           = "assertion.sub", # Identifiant principal
    "attribute.aud"            = "assertion.aud",
    "attribute.project_path"   = "assertion.project_path",
    "attribute.project_id"     = "assertion.project_id",
    "attribute.namespace_id"   = "assertion.namespace_id",
    "attribute.namespace_path" = "assertion.namespace_path",
    "attribute.user_email"     = "assertion.user_email",
    "attribute.ref"            = "assertion.ref",
    "attribute.ref_type"       = "assertion.ref_type",
  }
  
  oidc {
    issuer_uri = var.gitlab_url
    jwks_json  = data.http.jwks_gitlab.response_body
  }
}

Cette configuration mappe les attributs de GitLab vers des identités GCP via les attributs de l’objet attribute_mapping. Le google.subject est requis, et les autres attributs (aud, project_id, namespace_id, etc.) permettent d’ajouter des détails supplémentaires à chaque identité.

Récupération des informations utiles

Enfin, nous ajoutons des outputs pour obtenir les informations de la configuration appliquée utiles dans les étapes suivantes pour référencer le Pool et le Pool Provider.

output "GCP_WORKLOAD_IDENTITY_PROVIDER" {
  value = google_iam_workload_identity_pool_provider.gitlab_pool_provider.name
}

output "GCP_WORKLOAD_IDENTITY_POOL" {
  value = google_iam_workload_identity_pool.gitlab_pool.name
}

En suivant ces étapes, le lien entre Google Cloud et GitLab est maintenant établi, permettant à GitLab de fédérer des identités vers GCP et d’accéder aux ressources Google de manière sécurisée.

De Gitlab vers un Service Account Google

Autoriser une identité Gitlab à utiliser un SA Google

Pour permettre aux jobs GitLab CI/CD d’accéder aux ressources Google Cloud via un Service Account (SA), nous configurons un module nommé workload-identity-account. Ce module va faciliter la création d’un Service Account et la liaison avec une identité GitLab en utilisant Workload Identity.

👍
Source : le module est dans le même repository
Voici le lien vers le module.

Création du Service Account (SA)

Le Service Account est dédié aux jobs GitLab Runner pour s’authentifier dans GCP

resource "google_service_account" "sa" {
  project      = var.google_project_id
  account_id   = var.service_account_name
  display_name = var.service_account_name
  description  = "Service account used by Gitlab Runner in order to authenticate to GCP"
}

Les variables google_project_id et service_account_name sont requises pour spécifier le nom du compte de service et dans quel projet GCP il sera créé.

variable "service_account_name" {
  description = "The name of the service account to create"
  type        = string
}

variable "google_project_id" {
  description = "The ID of the project in which the service account will be created"
  type        = string
}

Créer la liaison Workload Identity

💡
On peut utiliser différents attributs de l'identité configurée via le Workload Identity Pool Provider dans attribute_mapping pour autoriser des identités gitlab à utiliser des Service Accounts Google.
Dans cet exemple on prendra simplement le project_path du projet gitlab.

Le Service Account est associé au Workload Identity Pool via une autorisation roles/iam.workloadIdentityUser, permettant à une identité GitLab d’assumer le rôle défini pour accéder aux ressources :

# Create a workload identity binding for the service account
resource "google_service_account_iam_member" "workload_identity_binding" {
  service_account_id = google_service_account.sa.id
  role               = "roles/iam.workloadIdentityUser"
  member             = "principalSet://iam.googleapis.com/${var.gitlab_pool_name}/attribute.project_path/${var.gitlab_project_path}"
}

L'attribut member contient une référence au principalSet, identifiant unique pour les identités GitLab dans le Workload Identity Pool. La valeur ${var.gitlab_pool_name} correspond au nom du pool GitLab configuré dans GCP, tandis que ${var.gitlab_project_path} fait référence au chemin du projet GitLab. Cette configuration autorise spécifiquement les jobs exécutés sur ce projet GitLab à se faire passer pour le SA.

variable "gitlab_pool_name" {
  description = "The name of the gitlab pool"
  type        = string
}

variable "gitlab_project_path" {
  description = "The path of the gitlab project"
  type        = string
}

En pratique, dans GitLab CI/CD, ce lien permet à un job d’assumer l’identité du Service Account et d’accéder aux ressources sans JSON Key, rendant l’authentification plus sécurisée et simplifiée.

Tips : Impersonation du Service Account

Cette partie est facultative. Dans le but d'améliorer la flexibilité et assurer un fonctionnement identique en local et dans le CI/CD, nous autorisons le Service Account à s'utiliser lui-même 🧐 :

resource "google_service_account_iam_member" "impersonate" {
  service_account_id = google_service_account.sa.id
  role               = "roles/iam.serviceAccountUser"
  member             = google_service_account.sa.member
}

resource "google_service_account_iam_member" "impersonate_token_creator" {
  service_account_id = google_service_account.sa.id
  role               = "roles/iam.serviceAccountTokenCreator"
  member             = google_service_account.sa.member
}

Nous verrons par la suite un exemple avec du code terraform exécuté dans un job Gitlab CI/CD qui montrera tout l'intérêt de cette configuration.

Appel du module dans le code principal

Une fois le module workload-identity-account configuré, nous pouvons l’invoquer dans le code principal Terraform pour créer un Service Account spécifique, le lier au projet GitLab et configurer les permissions nécessaires pour GitLab CI/CD.

Voici comment appeler le module dans le fichier principal :

module "workload-identity-account" {
  for_each             = var.project_path_to_sa
  source               = "./workload-identity-account"
  google_project_id    = var.google_project_id
  service_account_name = each.value
  gitlab_project_path  = each.key
  gitlab_pool_name     = google_iam_workload_identity_pool.gitlab_pool.name
}

Dans cet appel :

  • for_each itère sur une map avec en clef le path du projet gitlab et en valeur le nom du service account créé et lié à ce projet.
  • source pointe vers le chemin du module, ici ./workload-identity-account.
  • google_project_id et service_account_name définissent respectivement le projet GCP et le nom du Service Account.
  • gitlab_project_path est le chemin du projet GitLab auquel le Service Account sera associé.
  • gitlab_pool_name fait référence au nom du Workload Identity Pool, ici récupéré depuis le pool gitlab_pool précédemment créé.

Voici la nouvelle variable nécessaire:

variable "project_path_to_sa" {
  description = "Gitlab project path to SA name"
  type        = map(string)
}

Il ne reste plus qu'à configurer les variables nécessaires dans un fichier terraform.tfvars par exemple exécuter le code Terraform

google_project_id = "my-google-project-id"
gitlab_url        = "https://my-private-gitlab.com"

project_path_to_sa = {
  "test/sandbox-workload-identity" = "gitlab-sandbox-wi"
}
terraform init
terraform apply
💡
Authentification au provider Google
Une manière simple de s'authentifier en local au provider Google terraform est d'utiliser le cli de Google gcloud
Ainsi si vous avez les accès nécessaires, gcloud auth application-default login permet de rapidement démarrer. Sinon vous pouvez vous référer à la documentation du provider Google : https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/getting_started#adding-credentials
👍
Bonnes pratiques : ne pas utiliser un backend local
Par défaut terraform utilise un backend local sauvegardé à l'endroit de l'exécution du code terraform. Il est recommendé d'utiliser un backend décentralisé pour pouvoir sécuriser et collaborer sur la stack mise en place. Vous pouvez vous référer à cette documentation : https://developer.hashicorp.com/terraform/language/backend

Dans la Gitlab CI/CD maintenant

Sans json_key !

Pour pouvoir réaliser l'authentification dans vos pipelines GitLab CI/CD, on va utiliser la variable d'environnement GOOGLE_APPLICATION_CREDENTIALS comme avec une json_key mais en se basant sur l'id_token généré par Gitlab.

Pour cela on utilise un modèle de job .gcp-auth qui génère un jeton d’authentification et configure l’environnement pour utiliser Workload Identity. Ensuite, on pourra réutiliser ce modèle de manière flexible dans d’autres jobs nécessitant une authentification GCP.

Créer le modèle .gcp-auth

Dans un fichier .gitlab-ci.yml :

.gcp-auth:
    id_tokens:
        WORKLOAD_IDENTITY_TOKEN:
            aud: https://iam.googleapis.com/## Remplacez ici par le nom du Workload Identity pool provider provenant de l'output Terraform `GCP_WORKLOAD_IDENTITY_PROVIDER` ##
    variables:
        GCP_WORKLOAD_IDENTITY_PROVIDER: ## Remplacez ici par `GCP_WORKLOAD_IDENTITY_PROVIDER` ##
        GOOGLE_APPLICATION_CREDENTIALS: $CI_BUILDS_DIR/.workload_identity.wlconfig
    before_script:
        - |
            if [ -z "${SERVICE_ACCOUNT}" ]; then
                echo "Erreur : la variable SERVICE_ACCOUNT n’est pas définie. Veuillez définir une variable SERVICE_ACCOUNT avec l'email du service account à utiliser."
                exit 1
            fi
        - |-
            # Enregistre le jeton JWT dans un fichier
            echo $WORKLOAD_IDENTITY_TOKEN > $CI_BUILDS_DIR/.workload_identity.jwt
            
            # Configure les credentials dans un fichier JSON pour impersonate le Service Account
            cat << EOF > $GOOGLE_APPLICATION_CREDENTIALS
            {
                "type": "external_account",
                "audience": "//iam.googleapis.com/$GCP_WORKLOAD_IDENTITY_PROVIDER",
                "subject_token_type": "urn:ietf:params:oauth:token-type:jwt",
                "token_url": "https://sts.googleapis.com/v1/token",
                "credential_source": {
                    "file": "$CI_BUILDS_DIR/.workload_identity.jwt"
                },
                "service_account_impersonation_url": "https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/$SERVICE_ACCOUNT:generateAccessToken"
            }
            EOF

Dans ce job .gcp-auth :

  • id_tokens : configure un jeton jwt Gitlab pour le Workload Identity Pool Provider provisionné par terraform plus haut dans l'article. Le champ audience doit être mis à jour pour inclure le nom de votre Workload Identity Pool Provider.
💡
La valeur de GCP_WORKLOAD_IDENTITY_PROVIDER est de la forme :
projects/PROJECT_NUMBER/locations/global/workloadIdentityPools/WORKLOAD_IDENTITY_POOL_NAME/providers/WORKLOAD_IDENTITY_POOL_PROVIDER_NAME
Avec dans notre cas :
WORKLOAD_IDENTITY_POOL_NAME = private-gitlab-pool
WORKLOAD_IDENTITY_POOL_PROVIDER_NAME = private-gitlab-pool-provider
  • variables : définit les variables nécessaires, dont GCP_WORKLOAD_IDENTITY_PROVIDER pour le nom complet du Pool Provider et GOOGLE_APPLICATION_CREDENTIALS pour le fichier de configuration de GCP que l'on va généré dans le before_script.
  • before_script : vérifie que la variable SERVICE_ACCOUNT est bien définie, puis crée un fichier, localisé à l'endroit défini par GOOGLE_APPLICATION_CREDENTIALS , configurant l’authentification à l’aide du Workload Identity pour impersonate le Service Account spécifié.

Utiliser .gcp-auth dans d’autres jobs

Ensuite, vous pouvez étendre ce modèle .gcp-auth dans un job spécifique, comme un job Terraform plan :

tf-plan:
    extends: .gcp-auth
    variables:
        SERVICE_ACCOUNT: gitlab-sandbox-wi@my-google-project-id.iam.gserviceaccount.com # Remplacez par l'email du Service Account à impersonate
    image: hashicorp/terraform:1.9
    script:
        - terraform init
        - terraform plan

Dans ce job tf-plan :

  • extends: .gcp-auth : permet d’hériter de la configuration de .gcp-auth pour l’authentification.
  • SERVICE_ACCOUNT : définit l’email du Service Account à utiliser pour ce job.
  • script : exécute les commandes Terraform avec les permissions et le contexte de sécurité établis par le Workload Identity.

Résultat attendu

Avec cette configuration, chaque job CI/CD qui étend .gcp-auth peut accéder de manière sécurisée aux ressources GCP en utilisant l’authentification dynamique du Workload Identity, sans JSON Key. Cela simplifie également la gestion de l’infrastructure et réduit les risques de sécurité, car il n’est plus nécessaire de stocker des clés d’accès statiques dans vos pipelines CI/CD.

Dernier