CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/npm-pulumi--aws

A Pulumi package for creating and managing Amazon Web Services (AWS) cloud resources with infrastructure-as-code.

Pending

Quality

Pending

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

Overview
Eval results
Files

authentication.mddocs/guides/

AWS Authentication Guide

This guide covers authentication and authorization patterns for Pulumi AWS Provider, including credential management, IAM roles, and cross-account access.

Table of Contents

  • Overview
  • AWS Credentials Setup
  • Provider Configuration
  • IAM Roles and Assume Role
  • Web Identity and OIDC
  • Cross-Account Access
  • Best Practices

Overview

The Pulumi AWS Provider uses the AWS SDK for authentication, supporting multiple credential sources in the following precedence order:

  1. Explicit provider configuration
  2. Environment variables
  3. Shared credentials file (~/.aws/credentials)
  4. Shared configuration file (~/.aws/config)
  5. IAM role from EC2 instance metadata
  6. ECS task role

AWS Credentials Setup

Environment Variables

The most common method for local development and CI/CD pipelines:

// Set environment variables before running Pulumi
// export AWS_ACCESS_KEY_ID="your-access-key"
// export AWS_SECRET_ACCESS_KEY="your-secret-key"
// export AWS_REGION="us-west-2"

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Provider will automatically use environment variables
const bucket = new aws.s3.Bucket("my-bucket");

Key environment variables:

  • AWS_ACCESS_KEY_ID: Your AWS access key
  • AWS_SECRET_ACCESS_KEY: Your AWS secret key
  • AWS_SESSION_TOKEN: Session token for temporary credentials
  • AWS_REGION: Default AWS region
  • AWS_PROFILE: Named profile from credentials file
  • AWS_SHARED_CREDENTIALS_FILE: Custom credentials file path
  • AWS_CONFIG_FILE: Custom config file path

Shared Credentials File

Store credentials in ~/.aws/credentials:

[default]
aws_access_key_id = AKIAIOSFODNN7EXAMPLE
aws_secret_access_key = wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY

[production]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY

[development]
aws_access_key_id = AKIAI44QH8DHBEXAMPLE
aws_secret_access_key = je7MtGbClwBF/2Zp9Utk/h3yCo8nvbEXAMPLEKEY

Use specific profile:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Use production profile
const prodProvider = new aws.Provider("prod-provider", {
    profile: "production",
    region: "us-east-1",
});

const bucket = new aws.s3.Bucket("prod-bucket", {}, {
    provider: prodProvider,
});

Shared Configuration File

Store additional settings in ~/.aws/config:

[default]
region = us-west-2
output = json

[profile production]
region = us-east-1
role_arn = arn:aws:iam::123456789012:role/ProductionRole
source_profile = default

[profile development]
region = us-west-2
role_arn = arn:aws:iam::123456789012:role/DevelopmentRole
source_profile = default

Provider Configuration

Explicit Credentials

Provide credentials directly in provider configuration:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Not recommended for production - use for testing only
const provider = new aws.Provider("explicit-provider", {
    accessKey: "AKIAIOSFODNN7EXAMPLE",
    secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    region: "us-west-2",
});

const bucket = new aws.s3.Bucket("test-bucket", {}, {
    provider: provider,
});

Using Pulumi Config

Store sensitive credentials in Pulumi config (encrypted):

# Set encrypted configuration values
pulumi config set aws:accessKey AKIAIOSFODNN7EXAMPLE --secret
pulumi config set aws:secretKey wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY --secret
pulumi config set aws:region us-west-2
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config("aws");

const provider = new aws.Provider("config-provider", {
    accessKey: config.get("accessKey"),
    secretKey: config.requireSecret("secretKey"),
    region: config.get("region") || "us-west-2",
});

const bucket = new aws.s3.Bucket("config-bucket", {}, {
    provider: provider,
});

Multiple Providers

Use multiple providers for different accounts or regions:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// US provider
const usProvider = new aws.Provider("us-provider", {
    region: "us-east-1",
    profile: "production",
});

// EU provider
const euProvider = new aws.Provider("eu-provider", {
    region: "eu-west-1",
    profile: "production",
});

// Create resources in different regions
const usBucket = new aws.s3.Bucket("us-bucket", {}, {
    provider: usProvider,
});

const euBucket = new aws.s3.Bucket("eu-bucket", {}, {
    provider: euProvider,
});

IAM Roles and Assume Role

Assume Role Basic

Assume an IAM role for resource creation:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const provider = new aws.Provider("assume-role-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
        sessionName: "pulumi-deployment",
    },
});

const bucket = new aws.s3.Bucket("assumed-bucket", {}, {
    provider: provider,
});

Assume Role with External ID

Use external ID for enhanced security:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const provider = new aws.Provider("external-id-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/ThirdPartyRole",
        sessionName: "pulumi-external",
        externalId: "unique-external-id-12345",
    },
});

const bucket = new aws.s3.Bucket("external-bucket", {}, {
    provider: provider,
});

Assume Role with MFA

Require MFA token for assume role:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const config = new pulumi.Config();
const mfaToken = config.require("mfaToken");

const provider = new aws.Provider("mfa-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/MFARequiredRole",
        sessionName: "pulumi-mfa-session",
        serialNumber: "arn:aws:iam::123456789012:mfa/user",
        tokenCode: mfaToken,
    },
});

const bucket = new aws.s3.Bucket("mfa-bucket", {}, {
    provider: provider,
});

Assume Role with Session Duration

Control session duration:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const provider = new aws.Provider("duration-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/LongLivedRole",
        sessionName: "pulumi-long-session",
        duration: "3600s", // 1 hour
    },
});

const bucket = new aws.s3.Bucket("duration-bucket", {}, {
    provider: provider,
});

Creating IAM Role for Pulumi

Create an IAM role that Pulumi can assume:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Trust policy for the role
const assumeRolePolicy = aws.iam.getPolicyDocument({
    statements: [{
        effect: "Allow",
        principals: [{
            type: "AWS",
            identifiers: ["arn:aws:iam::987654321098:root"],
        }],
        actions: ["sts:AssumeRole"],
        conditions: [{
            test: "StringEquals",
            variable: "sts:ExternalId",
            values: ["unique-external-id"],
        }],
    }],
});

// Create the role
const pulumiRole = new aws.iam.Role("pulumi-deployment-role", {
    assumeRolePolicy: assumeRolePolicy.then(policy => policy.json),
    description: "Role for Pulumi deployments",
});

// Attach policies
const policyAttachment = new aws.iam.RolePolicyAttachment("pulumi-policy", {
    role: pulumiRole.name,
    policyArn: "arn:aws:iam::aws:policy/PowerUserAccess",
});

export const roleArn = pulumiRole.arn;

Web Identity and OIDC

GitHub Actions OIDC

Authenticate using GitHub Actions OIDC provider:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create OIDC provider for GitHub
const githubOidc = new aws.iam.OpenIdConnectProvider("github-oidc", {
    url: "https://token.actions.githubusercontent.com",
    clientIdLists: ["sts.amazonaws.com"],
    thumbprintLists: ["6938fd4d98bab03faadb97b34396831e3780aea1"],
});

// Create role that trusts GitHub OIDC
const githubRole = new aws.iam.Role("github-actions-role", {
    assumeRolePolicy: pulumi.interpolate`{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {
                "Federated": "${githubOidc.arn}"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
                },
                "StringEquals": {
                    "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
                }
            }
        }]
    }`,
});

// Attach permissions
const githubPolicy = new aws.iam.RolePolicyAttachment("github-policy", {
    role: githubRole.name,
    policyArn: "arn:aws:iam::aws:policy/PowerUserAccess",
});

export const githubRoleArn = githubRole.arn;

GitHub Actions workflow usage:

# .github/workflows/deploy.yml
name: Deploy
on: [push]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v3

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@v2
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-actions-role
          aws-region: us-west-2

      - name: Deploy with Pulumi
        run: pulumi up --yes

GitLab OIDC

Authenticate using GitLab OIDC provider:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create OIDC provider for GitLab
const gitlabOidc = new aws.iam.OpenIdConnectProvider("gitlab-oidc", {
    url: "https://gitlab.com",
    clientIdLists: ["https://gitlab.com"],
    thumbprintLists: ["7e04e2ddc6e1e5f3f14dd0297e99c1c23f6b1d92"],
});

// Create role that trusts GitLab OIDC
const gitlabRole = new aws.iam.Role("gitlab-ci-role", {
    assumeRolePolicy: pulumi.interpolate`{
        "Version": "2012-10-17",
        "Statement": [{
            "Effect": "Allow",
            "Principal": {
                "Federated": "${gitlabOidc.arn}"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringLike": {
                    "gitlab.com:sub": "project_path:mygroup/myproject:ref_type:branch:ref:main"
                }
            }
        }]
    }`,
});

export const gitlabRoleArn = gitlabRole.arn;

Cross-Account Access

Cross-Account Resource Access

Access resources in another AWS account:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Account A (current account) - create role
const crossAccountRole = new aws.iam.Role("cross-account-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Principal: {
                AWS: "arn:aws:iam::999999999999:root", // Account B
            },
            Action: "sts:AssumeRole",
        }],
    }),
});

const rolePolicy = new aws.iam.RolePolicy("cross-account-policy", {
    role: crossAccountRole.id,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: ["s3:*"],
            Resource: "*",
        }],
    }),
});

// Account B (remote account) - assume role and access resources
const remoteProvider = new aws.Provider("account-b-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/cross-account-role",
        sessionName: "cross-account-session",
    },
});

const remoteBucket = new aws.s3.Bucket("account-b-bucket", {}, {
    provider: remoteProvider,
});

Multi-Account Organization Structure

Manage multiple accounts in an organization:

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

interface AccountConfig {
    name: string;
    accountId: string;
    roleArn: string;
    region: string;
}

const accounts: AccountConfig[] = [
    {
        name: "production",
        accountId: "111111111111",
        roleArn: "arn:aws:iam::111111111111:role/OrganizationAccountAccessRole",
        region: "us-east-1",
    },
    {
        name: "staging",
        accountId: "222222222222",
        roleArn: "arn:aws:iam::222222222222:role/OrganizationAccountAccessRole",
        region: "us-east-1",
    },
    {
        name: "development",
        accountId: "333333333333",
        roleArn: "arn:aws:iam::333333333333:role/OrganizationAccountAccessRole",
        region: "us-west-2",
    },
];

// Create providers for each account
const providers = accounts.map(account => ({
    name: account.name,
    provider: new aws.Provider(`${account.name}-provider`, {
        region: account.region,
        assumeRole: {
            roleArn: account.roleArn,
            sessionName: `pulumi-${account.name}`,
        },
    }),
}));

// Deploy resources to each account
providers.forEach(({ name, provider }) => {
    const bucket = new aws.s3.Bucket(`${name}-bucket`, {
        tags: {
            Environment: name,
        },
    }, {
        provider: provider,
    });
});

Best Practices

1. Never Hardcode Credentials

// BAD - Never do this
const provider = new aws.Provider("bad", {
    accessKey: "AKIAIOSFODNN7EXAMPLE",
    secretKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
});

// GOOD - Use environment variables or config
const provider = new aws.Provider("good", {
    profile: "production",
    region: "us-west-2",
});

2. Use IAM Roles in Production

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Prefer IAM roles over access keys
const provider = new aws.Provider("prod-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: pulumi.getStack() === "production"
            ? "arn:aws:iam::123456789012:role/ProductionRole"
            : "arn:aws:iam::123456789012:role/DevelopmentRole",
        sessionName: `pulumi-${pulumi.getStack()}`,
    },
});

3. Implement Least Privilege

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Create role with minimal permissions
const deployRole = new aws.iam.Role("deploy-role", {
    assumeRolePolicy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Principal: { Service: "ec2.amazonaws.com" },
            Action: "sts:AssumeRole",
        }],
    }),
});

// Only grant necessary permissions
const policy = new aws.iam.RolePolicy("deploy-policy", {
    role: deployRole.id,
    policy: JSON.stringify({
        Version: "2012-10-17",
        Statement: [{
            Effect: "Allow",
            Action: [
                "s3:GetObject",
                "s3:PutObject",
            ],
            Resource: "arn:aws:s3:::specific-bucket/*",
        }],
    }),
});

4. Use Session Tags

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

const provider = new aws.Provider("tagged-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
        sessionName: "pulumi-deployment",
        tags: {
            Project: pulumi.getProject(),
            Stack: pulumi.getStack(),
            DeployedBy: "pulumi",
        },
    },
});

5. Rotate Credentials Regularly

import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";

// Use temporary credentials with short duration
const provider = new aws.Provider("short-lived-provider", {
    region: "us-west-2",
    assumeRole: {
        roleArn: "arn:aws:iam::123456789012:role/DeploymentRole",
        sessionName: "pulumi-deployment",
        duration: "900s", // 15 minutes
    },
});

Install with Tessl CLI

npx tessl i tessl/npm-pulumi--aws@7.16.0

docs

index.md

quickstart.md

README.md

tile.json