Using Workload Identity Continuous Integration(CI) Pipelines
Published: 2023-03-23 05:22:53

## Overview

In the [first part](https://dev.to/kameshsampath/what-is-workload-identity--120l) of this series we understood what a **Workload Identity** is and in the [second part](https://dev.to/kameshsampath/applying-workload-identity-with-a-demo-1bf9) how it can help in doing keyless Google API invocations by deploying a demo application. In this blog we learn how to use  **Workload Identity** with SaaS Continuous Integration(**CI**) providers.

Many SaaS Continuous Integration(**CI**) providers e.g [Harness CI](https://app.harness.io/auth/#/signup/?module=ci?utm_source=internal&utm_medium=social&utm_campaign=community&utm_content=kamesh-wi-delegate&utm_term=tutorial) uses what is called a [**Delegate**](https://developer.harness.io/docs/platform/Delegates/get-started-with-delegates/delegates-overview) 

> Harness Delegate is a service you run in your local laptop or on Cloud to connect your artifacts, infrastructure, collaboration, verification and other providers, with Harness Manager.

Here are some advantages of using Delegates,

- Total control of source code, as they can be within organisations cloud infrastructure
- Cloud Cost Optimisations as CI pipelines can leverage organisations existing cloud infrastructure
- Leverage the native cloud services from CI pipelines

In this tutorial we will deploy a Harness Delegate on to GKE and understand how enabling Workload Identity on GKE can simplify the CI Pipeline.

## CI Pipeline UseCase

- Builds [go](https://go.dev/) application, ideally you can build any application but go is taken as an example here.
- Package application build artifact as a container image
- Push the image to [Google Artifact Registry(**GAR**)](https://cloud.google.com/artifact-registry/). 
- Cache the build artifacts, dependencies(go modules) on to [Google Cloud Storage(**GCS**)](https://cloud.google.com/storage/) to make build process faster and quicker.

## Tutorial

Before we get to tutorial make sure you have signed up for free tier [Harness CI](https://app.harness.io/auth/#/signup/?module=ci&utm_source=internal&utm_medium=social&utm_campaign=community&utm_content=kamesh-wi-delegate&utm_term=tutorial) account.

### Pre-requisites

- A [Google Cloud Account](https://cloud.google.com) with a Service Account with roles:
    - `Kubernetes Engine Admin` - to create GKE cluster
    - `Service Account` roles used to create/update/delete Service Account
      - _iam.serviceAccounts.actAs_
      - _iam.serviceAccounts.get_
      - _iam.serviceAccounts.create_
      - _iam.serviceAccounts.delete_
      - _iam.serviceAccounts.update_
      - _iam.serviceAccounts.get_
      - _iam.serviceAccounts.getIamPolicy_
      - _iam.serviceAccounts.setIamPolicy_
     
      - (OR) simply you can add `Service Account Admin` and `Service Account User` roles
    - `Compute Network Admin`   - to create the VPC networks
- [Google Cloud SDK](https://cloud.google.com/sdk)
- [terraform](https://terraform.build)
- [kubectl](https://kubernetes.io/docs/tasks/tools/)
- [helm](https://helm.sh)
- [Taskfile](https://taskfile.dev)

### Download Sources

```shell
git clone https://github.com/harness-apps/workload-identity-gke-demo.git && cd "$(basename "$_" .git)"
export DEMO_HOME="$PWD"
```
### Environment Setup

### Variables

When working with Google Cloud the following environment variables helps in setting the right Google Cloud context like Service Account Key file, project etc., You can use [direnv](https://direnv.net) or set the following variables on your shell,

```shell
export GOOGLE_APPLICATION_CREDENTIALS="the google cloud service account key json file to use"
export CLOUDSDK_ACTIVE_CONFIG_NAME="the google cloud cli profile to use"
export GOOGLE_CLOUD_PROJECT="the google cloud project to use"
export KUBECONFIG="$DEMO_HOME/.kube/config"
```

You can find more information about gcloud cli configurations at <https://cloud.google.com/sdk/docs/configurations>.

As you may need to override few terraform variables that you don't want to check in to VCS, add them to a file called `.local.tfvars` and set the following environment variable to be picked up by terraform runs,

```shell
export TFVARS_FILE=.local.tfvars
```

Check the [Inputs](https://github.com/harness-apps/workload-identity-gke-demo#inputs) section for all possible terraform variables that are configurable.

An example `.local.tfvars` looks like,

```hcl
project_id                 = "my-awesome-gcp-project"
region                     = "asia-south1"
cluster_name               = "wi-demos"
kubernetes_version         = "1.24."
harness_account_id         = "REPLACE WITH YOUR HARNESS ACCOUNT ID"
harness_delegate_token     = "REPLACE WITH YOUR HARNESS DELEGATE TOKEN"
harness_delegate_name      = "wi-demos-delegate"
harness_delegate_namespace = "harness-delegate-ng"
harness_manager_endpoint   = "https://app.harness.io/gratis"
```

### Create Environment

We will use terraform to create a GKE cluster with `WorkloadIdentity` enabled for its nodes,

```shell
task init
```

### Create GKE cluster

The terraform apply will creates a GKE Cluster,

```shell
task create_cluster
```

### Deploy Harness Delegate

The following section deploys a Harness Delegate on to our GKE cluster. To be able to successfully deploy a Harness Delegate we need update the following values in our `.local.tfvars` file,

- `harness_account_id`
- `harness_delegate_token`
- `harness_delegate_namespace`
- `harness_manager_endpoint` 

- Use **Account Id** from Account Overview as the value for **harness_account_id**,

![account details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/eom3g1bo25v0rc1iehjl.png)

- Use the **Harness Cluster Hosting Account** from the account details to find the matching endpoint URL. e.g for `prod-2` it is <https://app.harness.io/gratis> and set that as value for `harness_manager_endpoint`. 

> TIP: 
> You can find the endpoint corresponding to your **Harness Cluster Hosting Account** from <https://developer.harness.io/tutorials/platform/install-delegate/>

- Copy the default token from **Projects** --> **Project Setup** --> **Delegates**(**Tokens**) and set it as value for `harness_delegate_token`.

![copy default token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r0y1vxdxunxal3li1xug.png)

- `harness_delegate_name`: defaults to **harness-delegate**
- `harness_delegate_namespace`: defaults to **harness-delegate-ng**

With us having updated the `.local.tfvars`, run the following command to deploy the Harness Delegate,

```shell
task deploy_harness_delegate
```

> NOTE: It will take some time for delegate to come up and get connected.

Wait for the delegate to be connected before proceeding to next steps. 

You can view status of the delegate from the **Project** --> **Project Setup** --> **Delegates** page,

![delegate status](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yrjd9xd1k3r2pa6g35c3.png)

You can also check the running Harness delegate pods by using `kubectl`,

```shell
kubectl get pods -n harness-delegate-ng    
```

The output should be something like the pod name may vary based on your `harness_delegate_name` value.

```text                                        
NAME                                 READY   STATUS    RESTARTS   AGE
harness-delegate-6bfd78d5cb-5h8x9   1/1     Running   0          2m23s
```

### Build Application

Having deployed the Harness delegate, let us build a CI pipeline that will build and push the same [go app](https://github.com/harness-apps/workload-identity-gke-demo/tree/main/app) to GAR.

#### Import Template

The sources already has [build stage](https://github.com/harness-apps/workload-identity-gke-demo/blob/main/.harness/ko_gar_build_push_1.yaml) template that can be used to create the CI pipeline.

Navigate to your Harness Account, **Account Overview** --> **Organizations** and select **default**.

![default org select](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j07f38navg5c3udfjwan.png)

From the Organization overview page select **Templates**,

![templates select](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2nii2f5wqwsr8lkvo5l1.png)

Click **New Template** and choose **Import From Git** option,

![import from git](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/me4lm1hzsqhuygwoid4t.png)

Fill the wizard with values as shown,

![import from git details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iff39t3r5tmaxr1fcrem.png)

>**NOTE**: If you want to use your fork of `harness-apps/workload-identity-gke-demo` then update _Repository_ with your fork.
>

![import template successful](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0grrjx3t1mq74xje7tg0.png)

## Create Pipeline

Navigate to **Builds** --> **Pipelines**, click **Create Pipeline**.

![create pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r5cyqbs2xb43l9uzyt2f.png)

Click **Add Stage** and click **Use template**, choose **ko_gar_build_push** template that we imported earlier and click **Use template** to complete import.

Enter details about the stage,

![stage details](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ma34ba7s3dqw0y8qqlm1.png)

Click **Setup Stage** to create the stage and fill other details i.e **Template Inputs**,

![template inputs](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r6crm2c5ygs72juades6.png)

We use `default` namespace to run builder pods. The build pod runs with a Kubernetes Service Account(KSA) `harness-builder`.

> **NOTE**:
> The `harness-builder` KSA is mapped to Google IAM Service Account(GSA) `harness-delegate` to inherit the GCP roles using Workload Identity in this case to push the images to Google Artifact Registry(GAR).
>

Click **Run** to run the pipeline to see the image build and pushed to GAR,

![Run Pipeline](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kbidxfctui4b3zmmoyuv.png)

As successful run would have pushed the image into GAR in this example its `asia-south1-docker.pkg.dev/pratyakshika/demos/lingua-greeter:latest`

![Build Success](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hlo8z6klt1qtfidzgn8h.png)

## Cleanup 

To clean up all the Google Cloud resources that were created as part of this demo, 

```shell
task destroy
```

## Summary

By using Workload Identity Delegate we have simplified and secured our CI pipelines which can now use any Google API services by configuring the GSA with right roles/permissions. The CI SaaS platform no longer need to store/update the Google API credentials.

Having deployed Workload Identity Delegate you can also do [keyless signing](https://docs.sigstore.dev/cosign/sign/#keyless-signing) of your container images using Google Application Credentials. For more info check [cosign](https://sigstore.dev).

For more tutorials and documentation please visit https://developer.harness.io.