Implémenter une CI/CD sécurisée avec Workload Identity Federation, GitLab CI & Cloud Deploy.

EZEKIAS BOKOVE
7 min readJan 7, 2023
Implémenter une CI/CD sécurisée avec Workload Identity Federation, GitLab CI & Cloud Deploy.

Actuellement, il existe plusieurs articles et tutoriels sur la mise en place de pipeline CI/CD avec GitLab et Cloud Build. Ces différents articles ont en commun l’utilisation des Keys de service account (ce qui n’est pas recommandé) et Cloud Build.

  • L’utilisation des Keys de services account n’étant pas une bonne pratique, il faut les remplacer par Workload Identity Federation.
  • Dans la plupart des cas, Cloud Build est utilisé comme un outil de déploiement ce qui n’est pas mal, mais un outil spécialement conçu pour le déploiement comme Cloud Deploy serait mieux.

Ici, nous allons voir comment :

  • Établir une connexion sécurisée entre GitLab et Google Cloud avec Workload Identity Federation.
  • Configurer un CI/CD avec GitLab CI et Cloud Deploy. Les opérations d’intégration continue (CI) seront gérées entièrement par GitLab CI et les opérations de livraison continue (CD) par Cloud Deploy.

- Connexion sécurisée entre GitLab et Google Cloud avec Workload Identity Federation.

Pour la configuration, nous allons suivre les instructions de GitLab qui sont énuméré dans cet article👇.

Les plus importantes pour nous ici sont les deux premières étapes à savoir :

Si vous êtes intéressé par le paramètre Provider attributes, voici quelques attributs issus du projet exemple de GitLab.

    "google.subject"           = "assertion.sub", # Required
"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",

Une fois que c’est fait, nous allons créer un service account que nous allons rattacher à notre Workload Identity Pool.

Notre service account aura pour rôles : Artifact Registry Writer ,Cloud Deploy Operator ,Service Account User ,Storage Admin

Maintenant, on ira dans IAM & Admin > Workload Identity Federation depuis la console Google Cloud. Une fois dans l’interface, cliquer sur le nom du Pool créé précédemment (si vous avez bien suivi depuis le début, le nom doit être GitLab). Ensuite, appuyer sur Grant Access et sélectionner le service account que nous avons créé. Enfin, sauvegarder et cliquer sur DISMISS .

Pour finaliser la configuration, nous allons ajouter quatre variables d’environnement dans GitLab. Parmi les variables d’environnement, il y en a deux qui sont indispensables pour Workload Identity Federation à savoir GCP_SERVICE_ACCOUNT et GCP_WORKLOAD_IDENTITY_PROVIDER.

GCP_PROJECT_ID : ID de notre projet Google Cloud.

GCP_REGION : la région de votre Artifact Registry et de Cloud Deploy.

GCP_SERVICE_ACCOUNT : l’email du service account que nous avons créé. (Exemple: xxxxxxxxxxx@xxxxxxxxx.iam.gserviceaccount.com)

GCP_WORKLOAD_IDENTITY_PROVIDER : projects/<project_number>/locations/global/workloadIdentityPools/gitlab/providers/gitlab-gitlab

NB : remplacer <project_number> par sa valeur.

Nous avons fini la configuration de la connexion sécurisée entre GitLab et Google Cloud. C’est le moment de configurer notre CI/CD.

- Configurer un CI/CD avec GitLab CI et Cloud Deploy.

La configuration, nécessite un certain nombre de fichiers que nous allons analyser.

.gitlab-ci.yml

Dans ce fichier, nous avions notre CI qui contient 3 étapes à savoir :

  • build : construire et pousser notre image docker dans la registry de GitLab.
  • push : récupérer l’image docker stockée dans la registry de GitLab et le pousser dans Google Cloud Artifact Registry.
  • deploy : avec la commande gcloud iam workload-identity-pools create-cred-config on crée un fichier de configuration pour les identifiants générés afin de se connecter à Google Cloud. Ensuite, on crée notre Pipeline avec la commande gcloud deploy apply. Pour finir, on va créer une release Cloud Deploy avec la commande gcloud deploy releases create .
# File: .gitlab-ci.yml
stages:
- build
- push
- deploy

docker-build:
# Use the official docker image.
stage: build
only:
refs:
- dev
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
# Default branch leaves tag empty (= latest tag)
# All other branches are tagged with the escaped branch name (commit ref slug)
script:
- |
if [[ "$CI_COMMIT_BRANCH" == "$CI_DEFAULT_BRANCH" ]]; then
tag=""
echo "Running on default branch '$CI_DEFAULT_BRANCH': tag = 'latest'"
else
tag=":$CI_COMMIT_REF_SLUG"
echo "Running on branch '$CI_COMMIT_BRANCH': tag = $tag"
fi
- docker build --pull -t "$CI_REGISTRY_IMAGE${tag}" .
- docker push "$CI_REGISTRY_IMAGE${tag}"
# Run this job in a branch where a Dockerfile exists
interruptible: true
allow_failure: true
environment:
name: build/$CI_COMMIT_REF_NAME

push:
# Use the official docker image.
stage: push
only:
refs:
- dev
image: docker:latest
services:
- docker:dind
before_script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
script:
- apk upgrade --update-cache --available
- apk add openssl
- apk add --update --no-cache python3 py-crcmod bash libc6-compat
- wget https://dl.google.com/dl/cloudsdk/channels/rapid/downloads/google-cloud-cli-412.0.0-linux-x86_64.tar.gz > /tmp/google-cloud-sdk.tar.gz -O /tmp/google-cloud-sdk.tar.gz | bash
- mkdir -p /usr/local/gcloud && tar -C /usr/local/gcloud -xvf /tmp/google-cloud-sdk.tar.gz && /usr/local/gcloud/google-cloud-sdk/install.sh --quiet
- export PATH=$PATH:/usr/local/gcloud/google-cloud-sdk/bin
- echo ${CI_JOB_JWT_V2} > .ci_job_jwt_file
- gcloud iam workload-identity-pools create-cred-config ${GCP_WORKLOAD_IDENTITY_PROVIDER}
--service-account="${GCP_SERVICE_ACCOUNT}"
--output-file=.gcp_temp_cred.json
--credential-source-file=.ci_job_jwt_file
- gcloud auth login --cred-file=`pwd`/.gcp_temp_cred.json
- gcloud config set project $GCP_PROJECT_ID
- printf 'yes' | gcloud auth configure-docker $GCP_REGION-docker.pkg.dev
- docker pull "$CI_REGISTRY_IMAGE${tag}"
- docker images
- docker tag "$CI_REGISTRY_IMAGE${tag}" $GCP_REGION-docker.pkg.dev/$GCP_PROJECT_ID/gitlab-wif-deploy/hello-word:latest
- docker push $GCP_REGION-docker.pkg.dev/$GCP_PROJECT_ID/Artifact-Registry-Name/hello-word:latest

allow_failure: true
environment:
name: push/$CI_COMMIT_REF_NAME
# when: on_success

deploy:
stage: deploy
only:
refs:
- dev
image: google/cloud-sdk:latest

script:
- export RELEASE_TIMESTAMP=$(date '+%Y%m%d-%H%M%S')
- echo ${CI_JOB_JWT_V2} > .ci_job_jwt_file
- gcloud iam workload-identity-pools create-cred-config ${GCP_WORKLOAD_IDENTITY_PROVIDER}
--service-account="${GCP_SERVICE_ACCOUNT}"
--output-file=.gcp_temp_cred.json
--credential-source-file=.ci_job_jwt_file
- gcloud auth login --cred-file=`pwd`/.gcp_temp_cred.json
- gcloud config set project $GCP_PROJECT_ID
- gcloud deploy apply --file clouddeploy.yaml --region $GCP_REGION --project $GCP_PROJECT_ID
- gcloud deploy releases create release-$RELEASE_TIMESTAMP --delivery-pipeline cloud-run-pipeline --region $GCP_REGION --images app=$GCP_REGION-docker.pkg.dev/$GCP_PROJECT_ID/Artifact-Registry-Name/hello-word:latest --skaffold-file skaffold.yaml

environment:
name: deploy/$CI_COMMIT_REF_NAME
action: stop

NB : remplacer Artifact-Registry-Name par le nom de votre repositorie d’Artifact Registry que vous allez créer. Pour plus d’informations.

clouddeploy.yaml

Ce fichier contient la configuration de notre pipeline. Avec requireApproval: true vous exigez une approbation pour l’environnement de production.

apiVersion: deploy.cloud.google.com/v1
kind: DeliveryPipeline
metadata:
name: cloud-run-pipeline
description: application deployment pipeline
serialPipeline:
stages:
- targetId: dev-env
profiles: [dev]
- targetId: qa-env
profiles: [qa]
- targetId: prod-env
profiles: [prod]
---

apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: dev-env
description: Cloud Run development service
run:
location: projects/PROJECT_ID/locations/us-central1
---

apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: qa-env
description: Cloud Run QA service
run:
location: projects/PROJECT_ID/locations/us-west1
---

apiVersion: deploy.cloud.google.com/v1
kind: Target
metadata:
name: prod-env
description: Cloud Run PROD service
requireApproval: true
run:
location: projects/PROJECT_ID/locations/us-south1

NB : remplacer PROJECT_ID par sa valeur réelle dans le fichier clouddeploy.yaml

skaffold.yaml

Skaffold gère le workflow pour déployer votre application sur Cloud Run.

apiVersion: skaffold/v3alpha1
kind: Config
metadata:
name: cloud-run-app
profiles:
- name: dev
manifests:
rawYaml:
- deploy-dev.yaml
- name: qa
manifests:
rawYaml:
- deploy-qa.yaml
- name: prod
manifests:
rawYaml:
- deploy-prod.yaml
deploy:
cloudrun: {}

deploy-dev.yaml

Il contient le fichier de configuration pour l’environnement de dev.

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: app-dev
spec:
template:
spec:
containers:
- image: app
resources:
limits:
cpu: 1000m
memory: 128Mi

deploy-qa.yaml

Il contient le fichier de configuration pour l’environnement de QA.

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: app-qa
spec:
template:
spec:
containers:
- image: app

deploy-prod.yaml

Il contient le fichier de configuration pour l’environnement de prod.

apiVersion: serving.knative.dev/v1
kind: Service
metadata:
name: app-prod
spec:
template:
spec:
containers:
- image: app

C’est dans les fichiers deploy-dev.yaml, deploy-qa.yaml et deploy-prod.yaml que vous allez mettre les configurations de votre service Cloud Run. Pour la rédaction de ces fichiers n’hésitez pas à lire cet article 👇.

À l’aide de Cloud Console ou de Cloud Shell, vous pouvez passer d’une version à une autre et d’un environnement à une autre avec le bouton Promote ou gcloud deploy releases promote.

Comme vous pouvez le voir, toutes les ressources sont déployées.

Le code est disponible ici 👉 https://gitlab.com/ricardobokove/gitlab-wif-deploy/

Maintenant, c’est à vous d’adapter cet exemple à vos besoins en tenant compte de vos objectifs.

Merci pour votre lecture.

--

--

EZEKIAS BOKOVE

GDE & Champion Innovators for Google Cloud. Serverless & DevOps enthusiast. I like to learn from others, to share my knowledge with other people.