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 (
"github.com/aws/aws-sdk-go-v2/credentials/processcreds"
)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:
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:
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
}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
}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
}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"
}const (
ProviderName = "ProcessProvider"
DefaultTimeout = time.Duration(1) * time.Minute
)Configuration constants for the process provider.
Fields:
ProviderName: Name identifier for the process credentials providerDefaultTimeout: Default timeout for process execution (1 minute)func NewProvider(command string, options ...func(*Options)) *ProviderCreates a credentials provider that executes the specified command and parses credentials from its JSON output.
Parameters:
command (string): Command to execute for retrieving credentialsoptions (...func(*Options)): Functional options for configurationReturns:
*Provider: Credentials provider wrapping the processExample:
// Simple command
provider := processcreds.NewProvider("get-credentials")
// With options
provider := processcreds.NewProvider("get-credentials", func(o *processcreds.Options) {
o.Timeout = 30 * time.Second
})func NewProviderCommand(builder NewCommandBuilder, options ...func(*Options)) *ProviderCreates a credentials provider with a custom command builder, providing advanced control over command execution.
Parameters:
builder (NewCommandBuilder): Custom command builder implementationoptions (...func(*Options)): Functional options for configurationReturns:
*Provider: Credentials provider with custom command builderExample:
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)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 killedCredentialSources ([]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
})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.
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 timeoutsReturns:
aws.Credentials: Retrieved credentials with expiration timeerror: Error if process execution fails or credentials are invalidExample:
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)func (p *Provider) ProviderSources() []aws.CredentialSourceReturns the credential chain that was used to construct this provider.
Returns:
[]aws.CredentialSource: Credential source chain for debuggingtype 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 1AccessKeyID (string): AWS access key ID (JSON key: "AccessKeyId")SecretAccessKey (string): AWS secret access keySessionToken (string): Session token for temporary credentialsExpiration (*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",
}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 executeExample Implementation:
type MyCommandBuilder struct {
BasePath string
}
func (b *MyCommandBuilder) NewCommand(ctx context.Context) (*exec.Cmd, error) {
return exec.CommandContext(ctx, b.BasePath), nil
}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"})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 executionReturns:
*exec.Cmd: Initialized command ready for executionerror: Error if initialization failsExample:
builder := processcreds.DefaultNewCommandBuilder{
Args: []string{"python", "-m", "aws_credentials"},
}
cmd, err := builder.NewCommand(context.Background())
if err != nil {
panic(err)
}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.
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 executionReturns:
*exec.Cmd: Initialized commanderror: Error if creation failsExample:
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())type ProviderError struct {
Err error
}Error type indicating failure initializing or executing the process credentials provider.
Fields:
Err (error): Underlying error that occurredfunc (e *ProviderError) Error() stringReturns the error message describing the provider failure.
Returns:
string: Error messagefunc (e *ProviderError) Unwrap() errorReturns the underlying error wrapped by the ProviderError, enabling error chain inspection.
Returns:
error: Underlying errorExample:
creds, err := provider.Retrieve(ctx)
if err != nil {
if procErr, ok := err.(*processcreds.ProviderError); ok {
fmt.Printf("Provider failed: %v\n", procErr.Unwrap())
}
}Common error scenarios and how to handle them:
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)
}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, ...}`)The response must include all required fields:
// BAD: Missing required fields
{"Version": 1}
// GOOD: Complete response
{
"Version": 1,
"AccessKeyId": "AKIA...",
"SecretAccessKey": "...",
"SessionToken": "..."
}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
}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)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
})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"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
})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
})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 expirationLimit 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-helperUse 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 cancelledThe processcreds provider executes external processes. Be aware of these security implications:
Process Environment Inheritance: The subprocess inherits the parent process's environment variables
File System Access: The subprocess runs with the parent process's permissions
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)Credential Leakage: Processes may inadvertently leak credentials
// 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)You can configure a process credential provider in the AWS shared config file:
[profile myprofile]
credential_process = /opt/aws-credentials/get-credentials.shThe SDK's default credential chain will automatically use this process provider when loading the profile.
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
}#!/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)#!/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')"
}
EOFpackage 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)
}
}