Workload Identity Federation enables workloads running in Google Kubernetes Engine (GKE) to access Google Cloud APIs. It requires that the cluster is configured accordingly, and can be achieved in two ways: by directly assigning IAM policies to a Kubernetes service account, or by linking a Kubernetes service account with an IAM service account.
Linking a Kubernetes service account on a GKE cluster to an IAM service account requires an annotation on the Kubernetes service account and an IAM policy granting the it the iam.workloadIdentityUser
role on the IAM service account.
With Pulumi, it's straightforward to create and link the two service accounts:
import pulumi
import pulumi_gcp as gcp
import pulumi_kubernetes as k8s
k8s_provider = k8s.provider("k8s-provider")
gcp_service_account = gcp.serviceaccount.Account(
"gcp-service-account",
account_id="my-gcp-sa",
display_name="My GCP service account",
)
k8s_service_account = k8s.core.v1.ServiceAccount(
"k8s-service-account",
metadata=k8s.meta.v1.ObjectMetaArgs(
name="my-sa",
annotations={
"iam.gke.io/gcp-service-account": gcp_service_account.email
},
),
opts=pulumi.ResourceOptions(provider=k8s_provider),
)
gcp_k8s_iam_member = gcp.serviceaccount.IAMMember(
"gcp-k8s-iam-member",
service_account_id=gcp_service_account.name,
role="roles/iam.workloadIdentityUser",
member=pulumi.Output.concat(
"serviceAccount:",
gcp.config.project,
".svc.id.goog[",
k8s_service_account.metadata.namespace,
"/",
k8s_service_account.metadata.name,
"]",
),
)
Depending on your taste for DRYness and how often you need to link accounts in your programs, it can make sense to create a utility function that constructs the gcp.serviceaccount.IAMMember
resource:
def link_service_accounts(
resource_prefix: str,
gcp_service_account: gcp.serviceaccount.Account,
k8s_service_account: k8s.core.v1.ServiceAccount,
) -> gcp.serviceaccount.IAMMember:
return gcp.serviceaccount.IAMMember(
f"{resource_prefix}-gcp-k8s-iam-member",
service_account_id=gcp_service_account.name,
role="roles/iam.workloadIdentityUser",
member=pulumi.Output.concat(
"serviceAccount:",
gcp.config.project,
".svc.id.goog[",
k8s_service_account.metadata.namespace,
"/",
k8s_service_account.metadata.name,
"]",
),
)
If you only set a few additional arguments on either service account, you can also consider creating a create_serviceaccount_pair()
function.
In any case, you'll need access to the gcp.serviceaccount.IAMMember
resource and any IAM role assignments in your main program to declare the dependencies of resources that require the link between the Kubernetes and IAM service accounts to be in place:
gcp_k8s_iam_member = link_service_accounts(...)
gcp_iam_member = gcp.projects.IAMMember(
"gcp-iam-member",
project=gcp.config.project,
role="roles/storage.objectUser",
member=gcp_service_account.member,
)
other_resource = k8s.apps.v1.Deployment(
"my-deployment",
# ...
opts=pulumi.ResourceOptions(
depends_on=[gcp_k8s_iam_member, gcp_iam_member]
),
)
Make sure to not mix up gcp.serviceaccount.IAMMember
and gcp.projects.IAMMember
here. The former is used to allow the Kubernetes service account to act as the IAM service account, while the latter is used to assign IAM roles to the IAM service account.