Skip to main content
  1. Docs
  2. Administration
  3. Access & Identity
  4. OIDC Issuers
  5. Elastic Kubernetes Service (EKS) | OIDC

Configuring OpenID Connect for Elastic Kubernetes Service (EKS)

    This document outlines the steps required to configure Pulumi Cloud to accept Elastic Kubernetes Service (EKS) id_tokens and exchange them for a personal access token. With this configuration, Kubernetes pods authenticate to Pulumi Cloud using OIDC tokens issued by EKS.

    Common use cases

    This integration is most often used to authenticate workloads that run Pulumi operations from inside an EKS cluster, without storing long-lived Pulumi access tokens. Two common scenarios:

    • Pulumi Kubernetes Operator — Run Pulumi stacks as Kubernetes custom resources. The operator’s workspace pods authenticate to Pulumi Cloud using the cluster’s OIDC tokens instead of a static PULUMI_ACCESS_TOKEN.
    • Customer-managed deployment runners — Run Pulumi Deployments inside your own EKS cluster. The workflow runner fetches a Pulumi Pool token dynamically using its OIDC identity.
    This guide walks through the Pulumi Cloud UI. You can also configure OIDC Issuers via the REST API or the OidcIssuer resource in the Pulumi Service provider.
    This guide demonstrates using personal tokens. Depending on your Pulumi edition, you can also use organization or team tokens by adjusting the token type in the authorization policies and the pulumi login parameters.

    Prerequisites

    This guide provides step-by-step instructions based on the official provider documentation, which is subject to change. For the most current information, refer to Assign IAM roles to Kubernetes service accounts.

    Create and register an IAM role with a service account

    1. Create an IAM Role for Service Accounts.

      Define a trust relationship between the IAM role and the OIDC provider for your EKS cluster. Here’s an example trust policy:

      {
        "Version": "2012-10-17",
        "Statement": [
          {
            "Effect": "Allow",
            "Principal": {
              "Federated": "arn:aws:iam::<AWS_ACCOUNT_ID>:oidc-provider/<OIDC_PROVIDER_URL>"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
              "StringEquals": {
                "<OIDC_PROVIDER_URL>:sub": "system:serviceaccount:<namespace>:<service-account-name>"
              }
            }
          }
        ]
      }
      

      Replace <AWS_ACCOUNT_ID>, <OIDC_PROVIDER_URL>, <namespace>, and <service-account-name> with your values.

      Create the IAM role using this trust policy and attach the necessary permissions for your workload.

    2. Associate the IAM role with a Kubernetes service account.

      Create a Kubernetes service account annotated with the IAM role ARN:

      apiVersion: v1
      kind: ServiceAccount
      metadata:
        name: pulumi-service-account
        namespace: default
        annotations:
          eks.amazonaws.com/role-arn: "arn:aws:iam::<AWS_ACCOUNT_ID>:role/<IAM_ROLE_NAME>"
      

      Replace <AWS_ACCOUNT_ID> and <IAM_ROLE_NAME> with the appropriate values.

    3. Apply the service account to your Kubernetes cluster:

      kubectl apply -f pulumi-service-account.yaml
      

    Register the OIDC Issuer

    1. Navigate to Settings → Access Management → OIDC Issuers and select Register issuer.

    2. Look up your cluster’s OIDC issuer URL:

      aws eks describe-cluster --name <cluster-name> --query "cluster.identity.oidc.issuer" --output text
      

      This command returns the issuer URL, such as https://oidc.eks.us-west-2.amazonaws.com/id/EXAMPLEDOCID.

    3. Name the issuer, set a max expiration (in seconds), and enter the issuer URL.

    4. Submit the form.

    Configure the authorization policies

    1. Select the issuer name.
    2. Set Decision to Allow.
    3. Set Token type to Personal. See the token types section for other options.
    4. The user login should default to your login. Change it if you want to use a different login.
    5. Add a new rule and configure it to verify the namespace and the service account name.
    6. Select Save policies.

    There are other attributes you can use to fine-tune your policy, such as:

       "kubernetes.io": {
        "namespace": "default",
        "node": {
          "name": "ip-10-10-4-26.us-west-2.compute.internal",
          "uid": "ffc5761d-939e-4256-8f82-a847d532a0fa"
        },
        "pod": {
          "name": "busyboxplus",
          "uid": "3227b947-e0c8-4bd2-95b9-a0d8f69b3110"
        },
        "serviceaccount": {
          "name": "pulumi-service-account",
          "uid": "bece172b-0180-48d5-b762-e3c4501de4e2"
        }
      }
    

    For example, to reference the pod name, use "kubernetes.io".pod.name as the key path and the pod name (for example, busyboxplus) as the value.

    Sample

    // Pulumi program to run a bash script in a Kubernetes pod,
    // mount a service account token with an appropriate audience,
    // exchange the token for an ordinary Pulumi access token,
    // then run `pulumi whoami`.
    
    import * as kubernetes from "@pulumi/kubernetes";
    
    const loginParams = {
        "org_name": "MY_ORG_NAME",
        "user_login": "MY_USER_LOGIN"
    }
    
    const script = new kubernetes.core.v1.ConfigMap("script", {
        data: {
            "entrypoint.sh": `#!/bin/bash
    # This is the location of the EKS id token
    EKS_ID_TOKEN=$(cat /var/run/secrets/eks.amazonaws.com/serviceaccount/token)
    
    echo "OIDC Token:"
    echo $EKS_ID_TOKEN
    pulumi login --oidc-token $EKS_ID_TOKEN --oidc-org ${loginParams.org_name} --oidc-user ${loginParams.user_login}
    pulumi whoami
    `
        }
    });
    
    const job = new kubernetes.batch.v1.Job("runner", {
        metadata: {
        },
        spec: {
            template: {
                spec: {
                    serviceAccountName: "pulumi-service-account",
                    containers: [{
                        name: "runner",
                        image: "pulumi/pulumi:latest",
                        command: ["/bin/entrypoint.sh"],
                        volumeMounts: [
                            {
                                name: "script",
                                mountPath: "/bin/entrypoint.sh",
                                readOnly: true,
                                subPath: "entrypoint.sh",
                            },
                        ],
                    }],
                    restartPolicy: "Never",
                    volumes: [
                        {
                            name: "script",
                            configMap: {
                                defaultMode: 0o700,
                                name: script.metadata.name,
                            },
                        },
                    ],
                },
            },
            backoffLimit: 0,
        },
    });
    
    export const jobName = job.metadata.name;