Configuring OpenID Connect for Pulumi ESC
Pulumi ESC can be configured to act as an OpenID Connect (OIDC) provider, issuing signed, short-lived tokens. These tokens can then be exchanged by external systems for temporary cloud provider credentials, eliminating the need for hard-coded credentials.
values:
aws:
login:
fn::open::aws-login:
oidc:
duration: 1h
roleArn: arn:aws:iam::123456789123:role/aws-role
sessionName: esc-${context.pulumi.user.login}
environmentVariables:
AWS_ACCESS_KEY_ID: ${aws.login.accessKeyId}
AWS_SECRET_ACCESS_KEY: ${aws.login.secretAccessKey}
AWS_SESSION_TOKEN: ${aws.login.sessionToken}
In this example we have an environment definition and ARN that identifies us to AWS. We’re requesting a short term token that will live for 1 hour for the session sessionName: esc-${context.pulumi.user.login}. This token will be used to provide access to the AWS services defined in the permissions policies set for the role.
Configuring OpenID with your cloud provider
To configure OIDC for your cloud provider, refer to one of our guides:
- Configuring OIDC for AWS
- Configuring OIDC for Azure
- Configuring OIDC for Doppler
- Configuring OIDC for Google Cloud
- Configuring OIDC for Infisical
- Configuring OIDC for Vault
Configuring trust relationships
Cloud providers use OIDC token’s claims to determine whether to trust and issue credentials. While the exact configuration varies by provider, most rely on core claims like Audience, Subject, and Issuer. Pulumi ESC includes these by default, along with additional custom claims that mirror the subject structure.
If you need to define fine-grained trust policies (such as allowing only a specific environment or user to assume a role), you can customize the subject format or reference custom claims. This gives you more control over how access is granted and ensures that only the right entities can request credentials.
The following claims are commonly used when configuring trust relationships:
- The Issuer claim is typically used to validate that the token is properly signed. The issuer’s public signing key is fetched and used to validate the token’s signature.
- The Audience claim contains the name of the organization, prefixed with the provider’s platform (
aws,azure,gcp). You can use this claim to restrict credentials to a specific organization or organizations. - The Subject claim contains a variety of information about the service. You can use this claim to restrict credentials to a specific organization/scope.
- The various custom claims contain the same information as the Subject claim. If your cloud provider supports configuring trust relationships based on custom claims, you can use these claims for the same purposes as the Subject claim.
Default token claim
Pulumi ESC’s default issued OIDC tokens include the following claims:
| Claim | Description |
|---|---|
| aud | (Audience) The name of the organization associated with the environment prefixed with the provider’s platform name (aws:{org}, azure:{org}, gcp:{org}). |
| iss | (Issuer) The issuer of the OIDC token: https://api.pulumi.com/oidc. |
| current_env | (Current Environment) The name of the environment where the ESC OIDC provider configuration is defined. |
| root_env | (Root Environment) The name of the environment that is opened first. This Root Environment in turn opens other imported environments. |
| trigger_user | (Trigger User) The user whose credentials are used to open an environment. |
| sub | (Subject) The subject of the OIDC token. Often used for configuring trust relationships, it contains information about the associated service. Each component is also available as a custom claim. |
Custom token claim
To securely exchange OIDC tokens for cloud provider credentials, Pulumi ESC must present claims that match the provider’s trust policy. In many cases the default claims are sufficient, but if you need to restrict access more precisely (for example, limiting credentials to a specific environment, user, or project), you can modify the subject claim format by using the subjectAttributes property. This is especially useful when you plan to reference the subject within your cloud provider’s trust policy to restrict which environments, users, or organizations may request credentials.
By default (when subjectAttributes is not set), the subject claim has the format:
pulumi:environments:org:<organization name>:env:<project name>/<environment name>
When you set subjectAttributes, the subject instead begins with the fixed prefix pulumi:environments:pulumi.organization.login:{ORGANIZATION_NAME}, and each configured attribute is appended to it as a :<attribute>:<value> pair, in the order listed.
The following attributes are available:
rootEnvironment.name: the name of the environment that is opened first. This root environment in turn opens other imported environmentscurrentEnvironment.name: the full name (including the project) of the environment where the ESC login provider andsubjectAttributesare definedpulumi.user.login: the login identifier of the user opening the environmentpulumi.organization.login: the login identifier of the organizationpulumi.token.type: the type of the access token used to open the environment, such aspersonal,team, ororganizationpulumi.token.team: the name of the team a team-scoped token belongs to, which lets a trust policy scope a role to one team
pulumi.token.name is one such exclusion: unlike the token’s type and team, which the platform determines, a token’s name is chosen by whoever creates it and could be set to a value that forges a subject.Example
Consider the project/development environment, opened by the user personA in the contoso organization, with the following login configuration:
values:
aws:
login:
fn::open::aws-login:
oidc:
roleArn: arn:aws:iam::123456789123:role/aws-role
sessionName: esc-${context.pulumi.user.login}
subjectAttributes:
- currentEnvironment.name
- pulumi.user.login
The resulting subject claim is:
pulumi:environments:pulumi.organization.login:contoso:currentEnvironment.name:project/development:pulumi.user.login:personA
To restrict a role to this environment and user, configure your cloud provider’s trust policy so that its subject (sub) condition matches this value exactly. The syntax for expressing that condition is provider-specific. See the corresponding provider guide for a complete trust-policy example, such as Configuring OIDC for AWS.
For environments within the legacy default project, the project is not present in the subject, to preserve backwards compatibility. This affects the subject claim in two ways:
- When
subjectAttributesis not set, the default subject claim omits the project:pulumi:environments:org:<organization name>:env:<environment name>(rather than the usualpulumi:environments:org:<organization name>:env:<project name>/<environment name>). - When
subjectAttributesis set, thecurrentEnvironment.nameattribute resolves to only the environment name (for example,development) rather than the usualproject/development. So for an environment nameddevelopmentopened bypersonA, the subject ispulumi:environments:pulumi.organization.login:contoso:currentEnvironment.name:development:pulumi.user.login:personA.
For this reason, we recommend moving your environments out of the default project for best security practices.
How attributes resolve across imported environments
The rootEnvironment.name and currentEnvironment.name attributes resolve relative to the environment that opens the chain. currentEnvironment.name resolves to the environment in which it is defined, while rootEnvironment.name resolves to the top-level environment that was opened. Consider the following definitions for two environments, Project/Environment-A and Project/Environment-B:
#Project/Environment-A
values:
enva-rootEnv: ${context.rootEnvironment.name}
enva-currentEnv: ${context.currentEnvironment.name}
#Project/Environment-B
imports:
- Project/Environment-A
values:
envb-rootEnv: ${context.rootEnvironment.name}
envb-currentEnv: ${context.currentEnvironment.name}
When you open Project/Environment-B, the output is:
{
"enva-currentEnv": "Project/Environment-A",
"enva-rootEnv": "Project/Environment-B",
"envb-currentEnv": "Project/Environment-B",
"envb-rootEnv": "Project/Environment-B"
}
Notice how enva-rootEnv resolves to Project/Environment-B. That’s because Project/Environment-A is opened from Project/Environment-B, which is the root (the top-level environment to be opened).
When importing multiple environments into Pulumi IaC stack config, each environment is resolved separately. For example, if you import multiple environments into your Pulumi stack with the rootEnvironment.name attribute defined in all of them, then each rootEnvironment.name resolves to the environment name where it is defined.