provider.go raw

   1  // Package endpointcreds provides support for retrieving credentials from an
   2  // arbitrary HTTP endpoint.
   3  //
   4  // The credentials endpoint Provider can receive both static and refreshable
   5  // credentials that will expire. Credentials are static when an "Expiration"
   6  // value is not provided in the endpoint's response.
   7  //
   8  // Static credentials will never expire once they have been retrieved. The format
   9  // of the static credentials response:
  10  //
  11  //	{
  12  //	    "AccessKeyId" : "MUA...",
  13  //	    "SecretAccessKey" : "/7PC5om....",
  14  //	}
  15  //
  16  // Refreshable credentials will expire within the "ExpiryWindow" of the Expiration
  17  // value in the response. The format of the refreshable credentials response:
  18  //
  19  //	{
  20  //	    "AccessKeyId" : "MUA...",
  21  //	    "SecretAccessKey" : "/7PC5om....",
  22  //	    "Token" : "AQoDY....=",
  23  //	    "Expiration" : "2016-02-25T06:03:31Z"
  24  //	}
  25  //
  26  // Errors should be returned in the following format and only returned with 400
  27  // or 500 HTTP status codes.
  28  //
  29  //	{
  30  //	    "code": "ErrorCode",
  31  //	    "message": "Helpful error message."
  32  //	}
  33  package endpointcreds
  34  
  35  import (
  36  	"context"
  37  	"fmt"
  38  	"net/http"
  39  	"strings"
  40  
  41  	"github.com/aws/aws-sdk-go-v2/aws"
  42  	"github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client"
  43  	"github.com/aws/smithy-go/middleware"
  44  )
  45  
  46  // ProviderName is the name of the credentials provider.
  47  const ProviderName = `CredentialsEndpointProvider`
  48  
  49  type getCredentialsAPIClient interface {
  50  	GetCredentials(context.Context, *client.GetCredentialsInput, ...func(*client.Options)) (*client.GetCredentialsOutput, error)
  51  }
  52  
  53  // Provider satisfies the aws.CredentialsProvider interface, and is a client to
  54  // retrieve credentials from an arbitrary endpoint.
  55  type Provider struct {
  56  	// The AWS Client to make HTTP requests to the endpoint with. The endpoint
  57  	// the request will be made to is provided by the aws.Config's
  58  	// EndpointResolver.
  59  	client getCredentialsAPIClient
  60  
  61  	options Options
  62  }
  63  
  64  // HTTPClient is a client for sending HTTP requests
  65  type HTTPClient interface {
  66  	Do(*http.Request) (*http.Response, error)
  67  }
  68  
  69  // Options is structure of configurable options for Provider
  70  type Options struct {
  71  	// Endpoint to retrieve credentials from. Required
  72  	Endpoint string
  73  
  74  	// HTTPClient to handle sending HTTP requests to the target endpoint.
  75  	HTTPClient HTTPClient
  76  
  77  	// Set of options to modify how the credentials operation is invoked.
  78  	APIOptions []func(*middleware.Stack) error
  79  
  80  	// The Retryer to be used for determining whether a failed requested should be retried
  81  	Retryer aws.Retryer
  82  
  83  	// Optional authorization token value if set will be used as the value of
  84  	// the Authorization header of the endpoint credential request.
  85  	//
  86  	// When constructed from environment, the provider will use the value of
  87  	// AWS_CONTAINER_AUTHORIZATION_TOKEN environment variable as the token
  88  	//
  89  	// Will be overridden if AuthorizationTokenProvider is configured
  90  	AuthorizationToken string
  91  
  92  	// Optional auth provider func to dynamically load the auth token from a file
  93  	// everytime a credential is retrieved
  94  	//
  95  	// When constructed from environment, the provider will read and use the content
  96  	// of the file pointed to by AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE environment variable
  97  	// as the auth token everytime credentials are retrieved
  98  	//
  99  	// Will override AuthorizationToken if configured
 100  	AuthorizationTokenProvider AuthTokenProvider
 101  
 102  	// The chain of providers that was used to create this provider
 103  	// These values are for reporting purposes and are not meant to be set up directly
 104  	CredentialSources []aws.CredentialSource
 105  }
 106  
 107  // AuthTokenProvider defines an interface to dynamically load a value to be passed
 108  // for the Authorization header of a credentials request.
 109  type AuthTokenProvider interface {
 110  	GetToken() (string, error)
 111  }
 112  
 113  // TokenProviderFunc is a func type implementing AuthTokenProvider interface
 114  // and enables customizing token provider behavior
 115  type TokenProviderFunc func() (string, error)
 116  
 117  // GetToken func retrieves auth token according to TokenProviderFunc implementation
 118  func (p TokenProviderFunc) GetToken() (string, error) {
 119  	return p()
 120  }
 121  
 122  // New returns a credentials Provider for retrieving AWS credentials
 123  // from arbitrary endpoint.
 124  func New(endpoint string, optFns ...func(*Options)) *Provider {
 125  	o := Options{
 126  		Endpoint: endpoint,
 127  	}
 128  
 129  	for _, fn := range optFns {
 130  		fn(&o)
 131  	}
 132  
 133  	p := &Provider{
 134  		client: client.New(client.Options{
 135  			HTTPClient: o.HTTPClient,
 136  			Endpoint:   o.Endpoint,
 137  			APIOptions: o.APIOptions,
 138  			Retryer:    o.Retryer,
 139  		}),
 140  		options: o,
 141  	}
 142  
 143  	return p
 144  }
 145  
 146  // Retrieve will attempt to request the credentials from the endpoint the Provider
 147  // was configured for. And error will be returned if the retrieval fails.
 148  func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error) {
 149  	resp, err := p.getCredentials(ctx)
 150  	if err != nil {
 151  		return aws.Credentials{}, fmt.Errorf("failed to load credentials, %w", err)
 152  	}
 153  
 154  	creds := aws.Credentials{
 155  		AccessKeyID:     resp.AccessKeyID,
 156  		SecretAccessKey: resp.SecretAccessKey,
 157  		SessionToken:    resp.Token,
 158  		Source:          ProviderName,
 159  		AccountID:       resp.AccountID,
 160  	}
 161  
 162  	if resp.Expiration != nil {
 163  		creds.CanExpire = true
 164  		creds.Expires = *resp.Expiration
 165  	}
 166  
 167  	return creds, nil
 168  }
 169  
 170  func (p *Provider) getCredentials(ctx context.Context) (*client.GetCredentialsOutput, error) {
 171  	authToken, err := p.resolveAuthToken()
 172  	if err != nil {
 173  		return nil, fmt.Errorf("resolve auth token: %v", err)
 174  	}
 175  
 176  	return p.client.GetCredentials(ctx, &client.GetCredentialsInput{
 177  		AuthorizationToken: authToken,
 178  	})
 179  }
 180  
 181  func (p *Provider) resolveAuthToken() (string, error) {
 182  	authToken := p.options.AuthorizationToken
 183  
 184  	var err error
 185  	if p.options.AuthorizationTokenProvider != nil {
 186  		authToken, err = p.options.AuthorizationTokenProvider.GetToken()
 187  		if err != nil {
 188  			return "", err
 189  		}
 190  	}
 191  
 192  	if strings.ContainsAny(authToken, "\r\n") {
 193  		return "", fmt.Errorf("authorization token contains invalid newline sequence")
 194  	}
 195  
 196  	return authToken, nil
 197  }
 198  
 199  var _ aws.CredentialProviderSource = (*Provider)(nil)
 200  
 201  // ProviderSources returns the credential chain that was used to construct this provider
 202  func (p *Provider) ProviderSources() []aws.CredentialSource {
 203  	if p.options.CredentialSources == nil {
 204  		return []aws.CredentialSource{aws.CredentialSourceHTTP}
 205  	}
 206  	return p.options.CredentialSources
 207  }
 208