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//        "AccessKeyId" : "MUA...",
12//        "SecretAccessKey" : "/7PC5om....",
13//    }
14//
15// Refreshable credentials will expire within the "ExpiryWindow" of the Expiration
16// value in the response. The format of the refreshable credentials response:
17//    {
18//        "AccessKeyId" : "MUA...",
19//        "SecretAccessKey" : "/7PC5om....",
20//        "Token" : "AQoDY....=",
21//        "Expiration" : "2016-02-25T06:03:31Z"
22//    }
23//
24// Errors should be returned in the following format and only returned with 400
25// or 500 HTTP status codes.
26//    {
27//        "code": "ErrorCode",
28//        "message": "Helpful error message."
29//    }
30package endpointcreds
31
32import (
33	"context"
34	"fmt"
35	"net/http"
36
37	"github.com/aws/aws-sdk-go-v2/aws"
38	"github.com/aws/aws-sdk-go-v2/credentials/endpointcreds/internal/client"
39	"github.com/aws/smithy-go/middleware"
40)
41
42// ProviderName is the name of the credentials provider.
43const ProviderName = `CredentialsEndpointProvider`
44
45type getCredentialsAPIClient interface {
46	GetCredentials(context.Context, *client.GetCredentialsInput, ...func(*client.Options)) (*client.GetCredentialsOutput, error)
47}
48
49// Provider satisfies the aws.CredentialsProvider interface, and is a client to
50// retrieve credentials from an arbitrary endpoint.
51type Provider struct {
52	// The AWS Client to make HTTP requests to the endpoint with. The endpoint
53	// the request will be made to is provided by the aws.Config's
54	// EndpointResolver.
55	client getCredentialsAPIClient
56
57	options Options
58}
59
60// HTTPClient is a client for sending HTTP requests
61type HTTPClient interface {
62	Do(*http.Request) (*http.Response, error)
63}
64
65// Options is structure of configurable options for Provider
66type Options struct {
67	// Endpoint to retrieve credentials from. Required
68	Endpoint string
69
70	// HTTPClient to handle sending HTTP requests to the target endpoint.
71	HTTPClient HTTPClient
72
73	// Set of options to modify how the credentials operation is invoked.
74	APIOptions []func(*middleware.Stack) error
75
76	// The Retryer to be used for determining whether a failed requested should be retried
77	Retryer aws.Retryer
78
79	// Optional authorization token value if set will be used as the value of
80	// the Authorization header of the endpoint credential request.
81	AuthorizationToken string
82}
83
84// New returns a credentials Provider for retrieving AWS credentials
85// from arbitrary endpoint.
86func New(endpoint string, optFns ...func(*Options)) *Provider {
87	o := Options{
88		Endpoint: endpoint,
89	}
90
91	for _, fn := range optFns {
92		fn(&o)
93	}
94
95	p := &Provider{
96		client: client.New(client.Options{
97			HTTPClient: o.HTTPClient,
98			Endpoint:   o.Endpoint,
99			APIOptions: o.APIOptions,
100			Retryer:    o.Retryer,
101		}),
102		options: o,
103	}
104
105	return p
106}
107
108// Retrieve will attempt to request the credentials from the endpoint the Provider
109// was configured for. And error will be returned if the retrieval fails.
110func (p *Provider) Retrieve(ctx context.Context) (aws.Credentials, error) {
111	resp, err := p.getCredentials(ctx)
112	if err != nil {
113		return aws.Credentials{}, fmt.Errorf("failed to load credentials, %w", err)
114	}
115
116	creds := aws.Credentials{
117		AccessKeyID:     resp.AccessKeyID,
118		SecretAccessKey: resp.SecretAccessKey,
119		SessionToken:    resp.Token,
120		Source:          ProviderName,
121	}
122
123	if resp.Expiration != nil {
124		creds.CanExpire = true
125		creds.Expires = *resp.Expiration
126	}
127
128	return creds, nil
129}
130
131func (p *Provider) getCredentials(ctx context.Context) (*client.GetCredentialsOutput, error) {
132	return p.client.GetCredentials(ctx, &client.GetCredentialsInput{AuthorizationToken: p.options.AuthorizationToken})
133}
134