Configuring OpenID Connect for Elastic Kubernetes Service (EKS)
This document outlines the steps required to configure Pulumi to accept Elastic Kubernetes Service (EKS) id_tokens to be exchanged for a personal access token. With this configuration, Kubernetes pods authenticate to Pulumi Cloud using OIDC tokens issued by EKS.
Prerequisites
- You must be an admin of your Pulumi organization.
- You must have a EKS cluster.
- You must associate the EKS cluster with an OIDC provider.
Create and register an IAM role with a service account
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 necessary permissions for your workload.
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.Apply the service account to your Kubernetes cluster:
kubectl apply -f pulumi-service-account.yaml
Register the OIDC issuer
Navigate to OIDC Issuers under your Organization’s Settings and click on Register a new issuer.
Lookup your clusters 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
.Name the issuer, set a max expiration (in seconds), and add the issuer url:
Submit the form
Configure the Authorization Policies
Click on the issuer name.
Change the policy decision to
Allow
.Change the token type to
Personal
. See this page for other token types.The user login should default to your login, but change if using a different login.
Add a new rule and configure it to verify namespace and the service name:
Click on 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, you would use "kubernetes.io".pod.name
as the key path and the pod name (e.g. busyboxplus
in this case) 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 tokenParams = {
"audience": "urn:pulumi:org:ORG_NAME",
"token_type": "urn:pulumi:token-type:access_token:personal",
"expiration": 2 * 60 * 60,
"scope": "user:USER_LOGIN"
}
const script = new kubernetes.core.v1.ConfigMap("script", {
data: {
"entrypoint.sh": `#!/bin/bash
apt -qq install -y jq
# 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
export PULUMI_ACCESS_TOKEN=$(curl -sS -X POST \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'audience=${tokenParams.audience}' \
-d 'grant_type=urn:ietf:params:oauth:grant-type:token-exchange' \
-d 'subject_token_type=urn:ietf:params:oauth:token-type:id_token' \
-d 'requested_token_type=${tokenParams.token_type}' \
-d 'expiration=${tokenParams.expiration}' \
-d 'scope=${tokenParams.scope}' \
-d "subject_token=$EKS_ID_TOKEN" \
https://api.pulumi.com/api/oauth/token | jq -r '.access_token')
echo "Access Token:"
echo $PULUMI_ACCESS_TOKEN
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;
Thank you for your feedback!
If you have a question about how to use Pulumi, reach out in Community Slack.
Open an issue on GitHub to report a problem or suggest an improvement.