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//	}
 33package endpointcreds
 34
 35import (
 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.
 47const ProviderName = `CredentialsEndpointProvider`
 48
 49type 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.
 55type 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
 65type HTTPClient interface {
 66	Do(*http.Request) (*http.Response, error)
 67}
 68
 69// Options is structure of configurable options for Provider
 70type 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
103// AuthTokenProvider defines an interface to dynamically load a value to be passed
104// for the Authorization header of a credentials request.
105type AuthTokenProvider interface {
106	GetToken() (string, error)
107}
108
109// TokenProviderFunc is a func type implementing AuthTokenProvider interface
110// and enables customizing token provider behavior
111type TokenProviderFunc func() (string, error)
112
113// GetToken func retrieves auth token according to TokenProviderFunc implementation
114func (p TokenProviderFunc) GetToken() (string, error) {
115	return p()
116}
117
118// New returns a credentials Provider for retrieving AWS credentials
119// from arbitrary endpoint.
120func New(endpoint string, optFns ...func(*Options)) *Provider {
121	o := Options{
122		Endpoint: endpoint,
123	}
124
125	for _, fn := range optFns {
126		fn(&o)
127	}
128
129	p := &Provider{
130		client: client.New(client.Options{
131			HTTPClient: o.HTTPClient,
132			Endpoint:   o.Endpoint,
133			APIOptions: o.APIOptions,
134			Retryer:    o.Retryer,
135		}),
136		options: o,
137	}
138
139	return p
140}
141
142// Retrieve will attempt to request the credentials from the endpoint the Provider
143// was configured for. And error will be returned if the retrieval fails.
144func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error) {
145	resp, err := p.getCredentials(ctx)
146	if err != nil {
147		return aws.Credentials{}, fmt.Errorf("failed to load credentials, %w", err)
148	}
149
150	creds := aws.Credentials{
151		AccessKeyID:     resp.AccessKeyID,
152		SecretAccessKey: resp.SecretAccessKey,
153		SessionToken:    resp.Token,
154		Source:          ProviderName,
155		AccountID:       resp.AccountID,
156	}
157
158	if resp.Expiration != nil {
159		creds.CanExpire = true
160		creds.Expires = *resp.Expiration
161	}
162
163	return creds, nil
164}
165
166func (p *Provider) getCredentials(ctx context.Context) (*client.GetCredentialsOutput, error) {
167	authToken, err := p.resolveAuthToken()
168	if err != nil {
169		return nil, fmt.Errorf("resolve auth token: %v", err)
170	}
171
172	return p.client.GetCredentials(ctx, &client.GetCredentialsInput{
173		AuthorizationToken: authToken,
174	})
175}
176
177func (p *Provider) resolveAuthToken() (string, error) {
178	authToken := p.options.AuthorizationToken
179
180	var err error
181	if p.options.AuthorizationTokenProvider != nil {
182		authToken, err = p.options.AuthorizationTokenProvider.GetToken()
183		if err != nil {
184			return "", err
185		}
186	}
187
188	if strings.ContainsAny(authToken, "\r\n") {
189		return "", fmt.Errorf("authorization token contains invalid newline sequence")
190	}
191
192	return authToken, nil
193}