DEV Community

Cover image for Applying Workload Identity With A Demo
Kamesh Sampath
Kamesh Sampath

Posted on • Updated on

Applying Workload Identity With A Demo

Overview

As part of the first part of the series we understood what is Workload Identity. In this DIY blog we will apply Workload Identity to our GKE workloads by deploying a demo application called lingua-greeter to GKE, the lingua-greeter will call Translate API to translate the greeting text passed to it.

Glossary

Abbreviation Expansion
API Application Programming Interface
ACL Access Control List
GCS Google Cloud Storage
GKE Google Kubernetes Engine
GSA Google Service Account
IAM Identity and Access Management
KSA Kubernetes Service Account
RBAC Role Based Access Control
SA Service Account
VPC Virtual Private Cloud

Pre-requisites

  • Google Cloud Account
  • 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
  • Enable Translation API on your Google Cloud Account
  • Google Cloud SDK
  • terraform
  • kubectl
  • Taskfile

Optional

Download Sources

git clone https://github.com/kameshsampath/workload-identiy-gke-demo && cd "$(basename "$_" .git)"
export DEMO_HOME="$PWD"
Enter fullscreen mode Exit fullscreen mode

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 or set the following variables on your 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"
Enter fullscreen mode Exit fullscreen mode

(e.g.)

export CLOUDSDK_ACTIVE_CONFIG_NAME=personal
export GOOGLE_APPLICATION_CREDENTIALS=~/.ssh/my-sa-key.json
export GOOGLE_CLOUD_PROJECT=my-awesome-project
export KUBECONFIG="$DEMO_HOME/.kube"
Enter fullscreen mode Exit fullscreen mode

TIP If you are using direnv you can then create file .envrc.local and add the environment variables. They can then be loaded using direnv allow .

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

We will be using terraform to create the Google Cloud resources e.g. GKE Cluster with Workload Identity enabled, Google Service Accounts(GSA), IAM policies and bindings.

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. Set the following environment variable to make terraform use the variable values form the file .local.tfvars

export TFVARS_FILE=.local.tfvars
Enter fullscreen mode Exit fullscreen mode

Check the Inputs section for all possible terraform variables that are configurable.

Example

An example .local.tfvars that will use a Google Cloud project my-awesome-project, create a two node GKE cluster named wi-demo in region asia-south1 with Kubernetes version 1.24. from stable release channel. The machine type of each cluster node will be e2-standard-4. The demo will be deployed in Kubernetes namespace demo-apps, will use lingua-greeter as the Kubernetes Service Account.

app_ksa            = "lingua-greeter"
app_namespace      = "demo-apps"
cluster_name       = "wi-demo"
configure_app_workload_identity = false
gke_num_nodes      = 2
kubernetes_version = "1.24."
machine_type       = "e2-standard-4"
project_id         = "my-awesome-project"
region             = "asia-south1"
release_channel    = "stable"
Enter fullscreen mode Exit fullscreen mode

NOTE: For rest of the section we assume that your tfvars file is called .local.tfvars

Application Overview

As part of the demo, let us deploy a Kubernetes application called lingua-greeter. The application exposes a REST API /:lang , that allows you to translate a text Hello World! into the language :lang using Google Translate client.

NOTE: The :lang is the BCP 47 language code.

Apply Workload Identity

Create Environment

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

Initialize terraform and download its modules,

task init
Enter fullscreen mode Exit fullscreen mode

Create GKE cluster

The terraform apply will creates a Kubernetes(GKE) Cluster,

task create_cluster
Enter fullscreen mode Exit fullscreen mode

The terraform apply will create the following Google Cloud resources,

Deploy Application

To see Workload Identity in action we will deploy the application(workload) on to GKE in two parts,

  • Application is not enabled for Workload Identity
  • Application enabled for Workload Identity

Without Workload Identity Enabled

Create the namespace demo-apps to deploy the lingua-greeter application,

kubectl create ns demo-apps
Enter fullscreen mode Exit fullscreen mode

Run the following command to deploy the application,

kubectl apply -n demo-apps -k $DEMO_HOME/app/config
Enter fullscreen mode Exit fullscreen mode

Wait for application to be ready,

kubectl rollout status -n demo-apps deployment/lingua-greeter --timeout=60s
Enter fullscreen mode Exit fullscreen mode

Get the application service LoadBalancer IP,

kubectl get svc -n demo-apps lingua-greeter
Enter fullscreen mode Exit fullscreen mode

NOTE: If the EXTERNAL-IP is <pending> then wait for the IP to be assigned. It will take few minutes for the EXTERNAL-IP to be assigned.
You can use the following command to wait until External-IP is assigned,

while [ -z $(kubectl get svc -n demo-apps lingua-greeter -ojsonpath="{.status.loadBalancer.ingress[*].ip}") ]; do sleep .3; done;

Call Service

export SERVICE_IP=$(kubectl get svc -n demo-apps lingua-greeter -ojsonpath="{.status.loadBalancer.ingress[*].ip}")
Enter fullscreen mode Exit fullscreen mode

Call the service to return the translation of Hello World! in Tamil(ta),

curl "http://$SERVICE_IP/ta"
Enter fullscreen mode Exit fullscreen mode

The service should fail with a message,

{"message":"Internal Server Error"}
Enter fullscreen mode Exit fullscreen mode

When you check the logs of the lingua-greeter pod,

kubectl logs -n demo-apps -lapp=lingua-greeter
Enter fullscreen mode Exit fullscreen mode

You should see a message like,

 ____    __
  / __/___/ /  ___
 / _// __/ _ \/ _ \
/___/\__/_//_/\___/ v4.10.0
High performance, minimalist Go web framework
https://echo.labstack.com
____________________________________O/_______
                                    O\
⇨ http server started on [::]:8080
time="2023-03-10T07:36:35Z" level=error msg="googleapi: Error 401: Request had invalid authentication credentials. Expected OAuth 2 access token, login cookie or other valid authentication credential. See https://developers.google.com/identity/sign-in/web/devconsole-project.\nMore details:\nReason: authError, Message: Invalid Credentials\n"
Enter fullscreen mode Exit fullscreen mode

As it describes you don't have authentication credentials to call the API. All Google Cloud API requires GOOGLE_APPLICATION_CREDENTIALS to allow client to authenticate itself before calling the API. If you check the deployment manifest we dont have one configured.

Configure Application To Use Workload Identity

Run the following command to configure the application to use Workload Identity,

task use_workload_identity
Enter fullscreen mode Exit fullscreen mode

The terraform script rbac.tf does the following,

  1. Create a GSA called translator
  2. Add role roles/iam.workloadIdentityUser to translator with a single member serviceAccount:$GOOGLE_CLOUD_PROJECT.svc.id.goog[demo-apps/lingua-greeter]. This basically allows KSA to impersonate itself as GSA translator thereby allowing it to call Google Cloud services that are allowed for GSA translator, in this case to use Google Translate API.
  3. Add IAM policy binding to translator for role roles/cloudtranslate.user which allows it to call the Google Translate API.
  4. Finally an updated lingua-greeter KSA manifest $DEMO_HOME/k8s/sa.yaml, that is annotated with the GSA client_email that it is allowed to use, in this case it will be something like translator@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com.
apiVersion: v1
kind: ServiceAccount
metadata:
  name: lingua-greeter
  namespace: demo-apps
  annotations:
    iam.gke.io/gcp-service-account: "translator@$GOOGLE_CLOUD_PROJECT.iam.gserviceaccount.com"
Enter fullscreen mode Exit fullscreen mode

Run the following command to update the Kubernetes SA lingua-greeter to use the Google IAM Service Account(GSA) translator using Workload Identity mechanics and call the Google Translate API,

kubectl apply -n demo-apps -f "$DEMO_HOME/k8s/sa.yaml"
Enter fullscreen mode Exit fullscreen mode

Call the service again, the service should succeed with a response,

{"text":"Hello World!","translation":"வணக்கம் உலகம்!","translationLanguage":"ta"}
Enter fullscreen mode Exit fullscreen mode

NOTE: Sometimes it may take few seconds for the pods to refresh the metadata, in such cases try to call the service after few seconds.

Cleanup

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

task destroy
Enter fullscreen mode Exit fullscreen mode

Summary

  • Deploy GKE cluster with Workload Identity enabled
  • Deploy lingua-greeter application to GKE
  • Create Google Service Account translator with permissions to call Google Translate API
  • Annotating Kubernetes Service Account lingua-greeter with Google Service Account translator allowing it to impersonate Google Service Account.

Top comments (0)