or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

credentials.mdec2rolecreds.mdendpointcreds.mdindex.mdlogincreds.mdprocesscreds.mdssocreds.mdstscreds.md
tile.json

processcreds.mddocs/

Process Credentials Provider

Package: github.com/aws/aws-sdk-go-v2/credentials/processcreds

The processcreds package provides credential retrieval from an external CLI process. This allows you to implement custom credential logic in any language and integrate it with the AWS SDK for Go v2.

Import

import (
    "github.com/aws/aws-sdk-go-v2/credentials/processcreds"
)

Overview

The Process Credentials Provider executes an external CLI command and parses its JSON output to extract AWS credentials. This mechanism enables flexible credential management by delegating to custom tools, scripts, or third-party credential management systems.

The external process must return credentials in the AWS credential process response format (JSON) within a configurable timeout period. This is useful for integrating with:

  • Custom credential management systems
  • Enterprise identity providers
  • Hardware security modules (HSMs)
  • Custom credential refresh logic
  • External credential caching systems

Important: The Provider is not safe for concurrent use. Wrap it with aws.CredentialsCache to provide concurrency safety and credential caching.

Security Warning: Executing external processes introduces security considerations:

  • The process environment inherits the parent process's environment
  • The process has access to all credentials already loaded by the parent process
  • Commands should be carefully validated and restricted to trusted sources
  • Avoid passing sensitive data as command-line arguments (use environment variables instead)
  • Ensure the process executable cannot be tampered with by unauthorized users

Usage Examples

Basic Usage

package main

import (
    "context"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/credentials/processcreds"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
    // Create process credentials provider with a simple command
    provider := processcreds.NewProvider("get-aws-credentials")

    // Wrap with CredentialsCache for concurrency safety and caching
    credentials := aws.NewCredentialsCache(provider)

    // Use with AWS service clients
    cfg := aws.Config{
        Region:      "us-east-1",
        Credentials: credentials,
    }

    client := s3.NewFromConfig(cfg)
    // Use client
}

Advanced Usage with Custom Command Builder

package main

import (
    "context"
    "os"
    "os/exec"
    "github.com/aws/aws-sdk-go-v2/aws"
    "github.com/aws/aws-sdk-go-v2/credentials/processcreds"
)

func main() {
    // Custom command builder for more control over command execution
    customBuilder := processcreds.NewCommandBuilderFunc(func(ctx context.Context) (*exec.Cmd, error) {
        // Create a command with specific environment variables
        cmd := exec.CommandContext(ctx, "python", "/opt/get-credentials.py")

        // Set custom environment for the subprocess
        cmd.Env = append(os.Environ(),
            "CREDENTIALS_ROLE=admin",
            "CREDENTIALS_REGION=us-west-2",
        )

        return cmd, nil
    })

    // Create provider with custom command builder
    provider := processcreds.NewProviderCommand(customBuilder, func(o *processcreds.Options) {
        o.Timeout = 30 * time.Second  // Custom timeout
    })

    credentials := aws.NewCredentialsCache(provider)
    // Use credentials
}

Using DefaultNewCommandBuilder

package main

import (
    "context"
    "time"
    "github.com/aws/aws-sdk-go-v2/credentials/processcreds"
)

func main() {
    // Using DefaultNewCommandBuilder directly
    builder := processcreds.DefaultNewCommandBuilder{
        Args: []string{"sh", "-c", "/opt/scripts/get-credentials.sh"},
    }

    provider := processcreds.NewProviderCommand(&builder, func(o *processcreds.Options) {
        o.Timeout = 2 * time.Minute
    })

    creds, err := provider.Retrieve(context.Background())
    if err != nil {
        panic(err)
    }
    // Use creds
}

Expected Process Output

The external process must output JSON in this format to stdout:

{
    "Version": 1,
    "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
    "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    "SessionToken": "AQoDYXdzEJr...",
    "Expiration": "2025-12-30T23:59:59Z",
    "AccountId": "123456789012"
}

API Reference

Constants

const (
    ProviderName   = "ProcessProvider"
    DefaultTimeout = time.Duration(1) * time.Minute
)

Configuration constants for the process provider.

Fields:

  • ProviderName: Name identifier for the process credentials provider
  • DefaultTimeout: Default timeout for process execution (1 minute)

NewProvider

func NewProvider(command string, options ...func(*Options)) *Provider

Creates a credentials provider that executes the specified command and parses credentials from its JSON output.

Parameters:

  • command (string): Command to execute for retrieving credentials
  • options (...func(*Options)): Functional options for configuration

Returns:

  • *Provider: Credentials provider wrapping the process

Example:

// Simple command
provider := processcreds.NewProvider("get-credentials")

// With options
provider := processcreds.NewProvider("get-credentials", func(o *processcreds.Options) {
    o.Timeout = 30 * time.Second
})

NewProviderCommand

func NewProviderCommand(builder NewCommandBuilder, options ...func(*Options)) *Provider

Creates a credentials provider with a custom command builder, providing advanced control over command execution.

Parameters:

  • builder (NewCommandBuilder): Custom command builder implementation
  • options (...func(*Options)): Functional options for configuration

Returns:

  • *Provider: Credentials provider with custom command builder

Example:

customBuilder := processcreds.NewCommandBuilderFunc(func(ctx context.Context) (*exec.Cmd, error) {
    cmd := exec.CommandContext(ctx, "custom-tool", "--credentials")
    // Customize the command
    return cmd, nil
})

provider := processcreds.NewProviderCommand(customBuilder)

Options

type Options struct {
    Timeout           time.Duration
    CredentialSources []aws.CredentialSource
}

Configuration options for the Provider.

Fields:

  • Timeout (time.Duration): Maximum duration allowed for process execution. Defaults to 1 minute. Process exceeding this timeout will be killed
  • CredentialSources ([]aws.CredentialSource): Credential chain information for reporting purposes (not meant to be set directly)

Example:

provider := processcreds.NewProvider("get-creds", func(o *processcreds.Options) {
    o.Timeout = 30 * time.Second
})

Provider

type Provider struct {
    // Has unexported fields
}

Retrieves credentials by executing an external process and parsing its output.

Note: Not safe for concurrent use. Wrap with aws.CredentialsCache for production use.

Retrieve

func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error)

Executes the credential process command and returns the parsed credentials.

Parameters:

  • ctx (context.Context): Context for the request, supports cancellation and timeouts

Returns:

  • aws.Credentials: Retrieved credentials with expiration time
  • error: Error if process execution fails or credentials are invalid

Example:

ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

creds, err := provider.Retrieve(ctx)
if err != nil {
    log.Fatalf("Failed to retrieve credentials: %v", err)
}

fmt.Printf("Access Key: %s\n", creds.AccessKeyID)
fmt.Printf("Expires: %s\n", creds.Expires)

ProviderSources

func (p *Provider) ProviderSources() []aws.CredentialSource

Returns the credential chain that was used to construct this provider.

Returns:

  • []aws.CredentialSource: Credential source chain for debugging

CredentialProcessResponse

type CredentialProcessResponse struct {
    Version         int
    AccessKeyID     string `json:"AccessKeyId"`
    SecretAccessKey string
    SessionToken    string
    Expiration      *time.Time
    AccountID       string `json:"AccountId"`
}

The expected response format from the credential process. The process must output this as JSON to stdout.

Fields:

  • Version (int): Must be set to 1
  • AccessKeyID (string): AWS access key ID (JSON key: "AccessKeyId")
  • SecretAccessKey (string): AWS secret access key
  • SessionToken (string): Session token for temporary credentials
  • Expiration (*time.Time): Date and time when credentials expire (optional)
  • AccountID (string): AWS account ID (JSON key: "AccountId")

JSON Example:

response := processcreds.CredentialProcessResponse{
    Version:         1,
    AccessKeyID:     "AKIAIOSFODNN7EXAMPLE",
    SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
    SessionToken:    "AQoDYXdzEJr...",
    Expiration:      &expirationTime,
    AccountID:       "123456789012",
}

NewCommandBuilder

type NewCommandBuilder interface {
    NewCommand(context.Context) (*exec.Cmd, error)
}

Interface that defines how commands are created for credential retrieval. Allows for custom command execution logic.

Methods:

  • NewCommand(context.Context) (*exec.Cmd, error): Creates the command to execute

Example Implementation:

type MyCommandBuilder struct {
    BasePath string
}

func (b *MyCommandBuilder) NewCommand(ctx context.Context) (*exec.Cmd, error) {
    return exec.CommandContext(ctx, b.BasePath), nil
}

DefaultNewCommandBuilder

type DefaultNewCommandBuilder struct {
    Args []string
}

Default implementation of NewCommandBuilder. Executes a command with the provided arguments using the current process environment.

Fields:

  • Args ([]string): Command and arguments to invoke (e.g., []string{"get-credentials", "--json"})

NewCommand

func (b DefaultNewCommandBuilder) NewCommand(ctx context.Context) (*exec.Cmd, error)

Returns an initialized exec.Cmd with the builder's arguments and the current process environment.

Parameters:

  • ctx (context.Context): Context for command execution

Returns:

  • *exec.Cmd: Initialized command ready for execution
  • error: Error if initialization fails

Example:

builder := processcreds.DefaultNewCommandBuilder{
    Args: []string{"python", "-m", "aws_credentials"},
}

cmd, err := builder.NewCommand(context.Background())
if err != nil {
    panic(err)
}

NewCommandBuilderFunc

type NewCommandBuilderFunc func(context.Context) (*exec.Cmd, error)

Function type that implements the NewCommandBuilder interface. Provides a convenient way to create custom command builders using function literals.

NewCommand

func (fn NewCommandBuilderFunc) NewCommand(ctx context.Context) (*exec.Cmd, error)

Calls the underlying function pointer to create and return a command.

Parameters:

  • ctx (context.Context): Context for command execution

Returns:

  • *exec.Cmd: Initialized command
  • error: Error if creation fails

Example:

builder := processcreds.NewCommandBuilderFunc(func(ctx context.Context) (*exec.Cmd, error) {
    cmd := exec.CommandContext(ctx, "sh", "-c", "echo '{\"Version\": 1, ...}'")
    return cmd, nil
})

cmd, err := builder.NewCommand(context.Background())

ProviderError

type ProviderError struct {
    Err error
}

Error type indicating failure initializing or executing the process credentials provider.

Fields:

  • Err (error): Underlying error that occurred

Error

func (e *ProviderError) Error() string

Returns the error message describing the provider failure.

Returns:

  • string: Error message

Unwrap

func (e *ProviderError) Unwrap() error

Returns the underlying error wrapped by the ProviderError, enabling error chain inspection.

Returns:

  • error: Underlying error

Example:

creds, err := provider.Retrieve(ctx)
if err != nil {
    if procErr, ok := err.(*processcreds.ProviderError); ok {
        fmt.Printf("Provider failed: %v\n", procErr.Unwrap())
    }
}

Error Handling

Common error scenarios and how to handle them:

Process Execution Errors

creds, err := provider.Retrieve(ctx)
if err != nil {
    if strings.Contains(err.Error(), "no such file or directory") {
        log.Fatal("Credential process command not found")
    }
    if strings.Contains(err.Error(), "context deadline exceeded") {
        log.Fatal("Credential process timeout exceeded")
    }
    log.Fatalf("Failed to retrieve credentials: %v", err)
}

Invalid JSON Response

The process must output valid JSON. Common issues:

// BAD: Process outputs multiple lines of JSON
fmt.Println("Fetching credentials...")
fmt.Println(`{"Version": 1, ...}`)

// GOOD: Process outputs only the JSON response
fmt.Print(`{"Version": 1, ...}`)

Missing Required Fields

The response must include all required fields:

// BAD: Missing required fields
{"Version": 1}

// GOOD: Complete response
{
    "Version": 1,
    "AccessKeyId": "AKIA...",
    "SecretAccessKey": "...",
    "SessionToken": "..."
}

Handling Expiration

creds, err := provider.Retrieve(ctx)
if err != nil {
    log.Fatal(err)
}

// Check if credentials will expire soon
if creds.Expires.Before(time.Now().Add(5 * time.Minute)) {
    log.Println("Credentials will expire in less than 5 minutes")
    // CredentialsCache will automatically refresh them
}

Best Practices

  1. Always wrap with CredentialsCache: The provider is not thread-safe and doesn't cache credentials internally

    provider := processcreds.NewProvider("get-credentials")
    creds := aws.NewCredentialsCache(provider)
  2. Use appropriate timeouts: Set timeouts that account for process startup and execution time

    provider := processcreds.NewProvider("get-credentials", func(o *processcreds.Options) {
        o.Timeout = 2 * time.Minute
    })
  3. Validate process output format: Ensure your external process outputs valid JSON with required fields

    #!/bin/bash
    # Example credential process script
    if ! credentials=$(call_credential_service); then
        echo '{"Version": 1, "Error": "Failed to retrieve credentials"}' >&2
        exit 1
    fi
    # Output only the JSON to stdout
    echo "$credentials"
  4. Use environment variables for sensitive data: Don't pass secrets as command-line arguments

    builder := processcreds.NewCommandBuilderFunc(func(ctx context.Context) (*exec.Cmd, error) {
        cmd := exec.CommandContext(ctx, "get-aws-credentials")
    
        // Use environment variables
        cmd.Env = append(os.Environ(),
            "CREDENTIAL_CACHE_DIR=/var/cache/aws-creds",
            "PROFILE=production",
        )
    
        return cmd, nil
    })
  5. Set output redirection for logging: Capture stderr for troubleshooting without affecting credential parsing

    builder := processcreds.NewCommandBuilderFunc(func(ctx context.Context) (*exec.Cmd, error) {
        cmd := exec.CommandContext(ctx, "get-credentials")
    
        // Redirect stderr to application logs
        cmd.Stderr = os.Stderr
    
        return cmd, nil
    })
  6. Handle credential refresh timing: Respect credential expiration to avoid mid-request refreshes

    // Let CredentialsCache handle automatic refresh
    cfg := aws.Config{
        Credentials: aws.NewCredentialsCache(provider),
    }
    // Cache will automatically refresh before expiration
  7. Limit process privileges: Run credential processes with minimal required permissions

    # Create a restricted service account
    useradd -r -s /bin/false aws-cred-helper
    
    # Assign minimal permissions
    chmod 700 /opt/aws-cred-helper
    chown aws-cred-helper:aws-cred-helper /opt/aws-cred-helper
  8. Use context for cancellation: Properly handle context cancellation to clean up processes

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    creds, err := provider.Retrieve(ctx)
    // Process will be terminated if context is cancelled

Security Considerations

External Process Execution Risks

The processcreds provider executes external processes. Be aware of these security implications:

  1. Process Environment Inheritance: The subprocess inherits the parent process's environment variables

    • All currently loaded AWS credentials become accessible
    • Other sensitive environment variables may be exposed
    • Minimize environment variables passed to the process
  2. File System Access: The subprocess runs with the parent process's permissions

    • Can read/write files accessible to the parent
    • Can access all current AWS credentials
    • Ensure process executable cannot be replaced by untrusted users
  3. Command Injection: Be careful when constructing commands dynamically

    // DANGEROUS: User input in command
    cmd := exec.Command("sh", "-c", fmt.Sprintf("get-creds %s", userInput))
    
    // SAFE: Use arguments, not shell interpolation
    cmd := exec.Command("get-creds", userInput)
  4. Credential Leakage: Processes may inadvertently leak credentials

    • Log process output carefully (exclude credentials)
    • Use stdout for JSON response only
    • Avoid printing credentials during debugging

Recommended Security Measures

// Secure command builder example
secureBuilder := processcreds.NewCommandBuilderFunc(func(ctx context.Context) (*exec.Cmd, error) {
    cmd := exec.CommandContext(ctx, "/opt/trusted-bin/get-credentials")

    // Limit environment variables to only what's needed
    cmd.Env = []string{
        "HOME=" + os.Getenv("HOME"),
        "PATH=/usr/bin:/bin",
        "LANG=C.UTF-8",
        // Add only essential variables, not all from parent environment
    }

    // Don't expose stdin/stderr unless needed
    cmd.Stdin = nil
    cmd.Stderr = nil

    return cmd, nil
})

provider := processcreds.NewProviderCommand(secureBuilder)

Configuration via AWS Shared Config

You can configure a process credential provider in the AWS shared config file:

[profile myprofile]
credential_process = /opt/aws-credentials/get-credentials.sh

The SDK's default credential chain will automatically use this process provider when loading the profile.

Integration with AWS Config

import (
    "context"
    "github.com/aws/aws-sdk-go-v2/config"
    "github.com/aws/aws-sdk-go-v2/service/s3"
)

func main() {
    // Load default config - will use credential_process from shared config
    cfg, err := config.LoadDefaultConfig(context.TODO())
    if err != nil {
        panic(err)
    }

    // Create service client
    client := s3.NewFromConfig(cfg)
    // Use client - credentials will be refreshed automatically via the process
}

Process Credential Lifecycle

  1. Provider calls NewCommand to create the command
  2. Command is executed with a configurable timeout
  3. Command outputs JSON credentials to stdout
  4. Provider parses JSON and returns credentials
  5. CredentialsCache monitors expiration time
  6. Cache automatically triggers refresh before expiration (step 1-4 repeat)

Examples with Different Languages

Python Credential Process

#!/usr/bin/env python3
import json
import sys
import os
from datetime import datetime, timedelta

def get_credentials():
    # Your credential retrieval logic here
    expiration = datetime.utcnow() + timedelta(hours=1)

    response = {
        "Version": 1,
        "AccessKeyId": "AKIAIOSFODNN7EXAMPLE",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "SessionToken": "AQoDYXdzEJr...",
        "Expiration": expiration.isoformat() + "Z",
        "AccountId": "123456789012"
    }

    # Output ONLY valid JSON to stdout
    json.dump(response, sys.stdout)

if __name__ == "__main__":
    try:
        get_credentials()
    except Exception as e:
        # Errors should go to stderr, not stdout
        print(f"Error: {e}", file=sys.stderr)
        sys.exit(1)

Bash Credential Process

#!/bin/bash
set -e

# Call your credential service
CREDENTIALS=$(curl -s https://credential-service.internal/aws-creds)

# Parse and format the response
cat <<EOF
{
    "Version": 1,
    "AccessKeyId": "$(echo "$CREDENTIALS" | jq -r '.access_key')",
    "SecretAccessKey": "$(echo "$CREDENTIALS" | jq -r '.secret_key')",
    "SessionToken": "$(echo "$CREDENTIALS" | jq -r '.session_token')",
    "Expiration": "$(echo "$CREDENTIALS" | jq -r '.expiration')",
    "AccountId": "$(echo "$CREDENTIALS" | jq -r '.account_id')"
}
EOF

Go Credential Process

package main

import (
    "encoding/json"
    "log"
    "os"
    "time"
)

func main() {
    // Your credential retrieval logic
    expiration := time.Now().Add(time.Hour)

    response := map[string]interface{}{
        "Version":         1,
        "AccessKeyId":     "AKIAIOSFODNN7EXAMPLE",
        "SecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
        "SessionToken":    "AQoDYXdzEJr...",
        "Expiration":      expiration.UTC().Format(time.RFC3339),
        "AccountId":       "123456789012",
    }

    // Output JSON to stdout
    encoder := json.NewEncoder(os.Stdout)
    if err := encoder.Encode(response); err != nil {
        log.Fatalf("Failed to encode response: %v", err)
    }
}