1package client
2
3import (
4 "context"
5 "fmt"
6 "net/http"
7 "time"
8
9 "github.com/aws/aws-sdk-go-v2/aws"
10 "github.com/aws/aws-sdk-go-v2/aws/middleware"
11 "github.com/aws/aws-sdk-go-v2/aws/retry"
12 awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
13 "github.com/aws/smithy-go"
14 smithymiddleware "github.com/aws/smithy-go/middleware"
15 smithyhttp "github.com/aws/smithy-go/transport/http"
16)
17
18// ServiceID is the client identifer
19const ServiceID = "endpoint-credentials"
20
21// HTTPClient is a client for sending HTTP requests
22type HTTPClient interface {
23 Do(*http.Request) (*http.Response, error)
24}
25
26// Options is the endpoint client configurable options
27type Options struct {
28 // The endpoint to retrieve credentials from
29 Endpoint string
30
31 // The HTTP client to invoke API calls with. Defaults to client's default HTTP
32 // implementation if nil.
33 HTTPClient HTTPClient
34
35 // Retryer guides how HTTP requests should be retried in case of recoverable
36 // failures. When nil the API client will use a default retryer.
37 Retryer aws.Retryer
38
39 // Set of options to modify how the credentials operation is invoked.
40 APIOptions []func(*smithymiddleware.Stack) error
41}
42
43// Copy creates a copy of the API options.
44func (o Options) Copy() Options {
45 to := o
46 to.APIOptions = make([]func(*smithymiddleware.Stack) error, len(o.APIOptions))
47 copy(to.APIOptions, o.APIOptions)
48 return to
49}
50
51// Client is an client for retrieving AWS credentials from an endpoint
52type Client struct {
53 options Options
54}
55
56// New constructs a new Client from the given options
57func New(options Options, optFns ...func(*Options)) *Client {
58 options = options.Copy()
59
60 if options.HTTPClient == nil {
61 options.HTTPClient = awshttp.NewBuildableClient()
62 }
63
64 if options.Retryer == nil {
65 // Amazon-owned implementations of this endpoint are known to sometimes
66 // return plaintext responses (i.e. no Code) like normal, add a few
67 // additional status codes
68 options.Retryer = retry.NewStandard(func(o *retry.StandardOptions) {
69 o.Retryables = append(o.Retryables, retry.RetryableHTTPStatusCode{
70 Codes: map[int]struct{}{
71 http.StatusTooManyRequests: {},
72 },
73 })
74 })
75 }
76
77 for _, fn := range optFns {
78 fn(&options)
79 }
80
81 client := &Client{
82 options: options,
83 }
84
85 return client
86}
87
88// GetCredentialsInput is the input to send with the endpoint service to receive credentials.
89type GetCredentialsInput struct {
90 AuthorizationToken string
91}
92
93// GetCredentials retrieves credentials from credential endpoint
94func (c *Client) GetCredentials(ctx context.Context, params *GetCredentialsInput, optFns ...func(*Options)) (*GetCredentialsOutput, error) {
95 stack := smithymiddleware.NewStack("GetCredentials", smithyhttp.NewStackRequest)
96 options := c.options.Copy()
97 for _, fn := range optFns {
98 fn(&options)
99 }
100
101 stack.Serialize.Add(&serializeOpGetCredential{}, smithymiddleware.After)
102 stack.Build.Add(&buildEndpoint{Endpoint: options.Endpoint}, smithymiddleware.After)
103 stack.Deserialize.Add(&deserializeOpGetCredential{}, smithymiddleware.After)
104 addProtocolFinalizerMiddlewares(stack, options, "GetCredentials")
105 retry.AddRetryMiddlewares(stack, retry.AddRetryMiddlewaresOptions{Retryer: options.Retryer})
106 middleware.AddSDKAgentKey(middleware.FeatureMetadata, ServiceID)
107 smithyhttp.AddErrorCloseResponseBodyMiddleware(stack)
108 smithyhttp.AddCloseResponseBodyMiddleware(stack)
109
110 for _, fn := range options.APIOptions {
111 if err := fn(stack); err != nil {
112 return nil, err
113 }
114 }
115
116 handler := smithymiddleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
117 result, _, err := handler.Handle(ctx, params)
118 if err != nil {
119 return nil, err
120 }
121
122 return result.(*GetCredentialsOutput), err
123}
124
125// GetCredentialsOutput is the response from the credential endpoint
126type GetCredentialsOutput struct {
127 Expiration *time.Time
128 AccessKeyID string
129 SecretAccessKey string
130 Token string
131 AccountID string
132}
133
134// EndpointError is an error returned from the endpoint service
135type EndpointError struct {
136 Code string `json:"code"`
137 Message string `json:"message"`
138 Fault smithy.ErrorFault `json:"-"`
139 statusCode int `json:"-"`
140}
141
142// Error is the error mesage string
143func (e *EndpointError) Error() string {
144 return fmt.Sprintf("%s: %s", e.Code, e.Message)
145}
146
147// ErrorCode is the error code returned by the endpoint
148func (e *EndpointError) ErrorCode() string {
149 return e.Code
150}
151
152// ErrorMessage is the error message returned by the endpoint
153func (e *EndpointError) ErrorMessage() string {
154 return e.Message
155}
156
157// ErrorFault indicates error fault classification
158func (e *EndpointError) ErrorFault() smithy.ErrorFault {
159 return e.Fault
160}
161
162// HTTPStatusCode implements retry.HTTPStatusCode.
163func (e *EndpointError) HTTPStatusCode() int {
164 return e.statusCode
165}