github.com/aavshr/aws-sdk-go@v1.41.3/aws/ec2metadata/service.go (about) 1 // Package ec2metadata provides the client for making API calls to the 2 // EC2 Metadata service. 3 // 4 // This package's client can be disabled completely by setting the environment 5 // variable "AWS_EC2_METADATA_DISABLED=true". This environment variable set to 6 // true instructs the SDK to disable the EC2 Metadata client. The client cannot 7 // be used while the environment variable is set to true, (case insensitive). 8 // 9 // The endpoint of the EC2 IMDS client can be configured via the environment 10 // variable, AWS_EC2_METADATA_SERVICE_ENDPOINT when creating the client with a 11 // Session. See aws/session#Options.EC2IMDSEndpoint for more details. 12 package ec2metadata 13 14 import ( 15 "bytes" 16 "io" 17 "net/http" 18 "net/url" 19 "os" 20 "strconv" 21 "strings" 22 "time" 23 24 "github.com/aavshr/aws-sdk-go/aws" 25 "github.com/aavshr/aws-sdk-go/aws/awserr" 26 "github.com/aavshr/aws-sdk-go/aws/client" 27 "github.com/aavshr/aws-sdk-go/aws/client/metadata" 28 "github.com/aavshr/aws-sdk-go/aws/corehandlers" 29 "github.com/aavshr/aws-sdk-go/aws/request" 30 ) 31 32 const ( 33 // ServiceName is the name of the service. 34 ServiceName = "ec2metadata" 35 disableServiceEnvVar = "AWS_EC2_METADATA_DISABLED" 36 37 // Headers for Token and TTL 38 ttlHeader = "x-aws-ec2-metadata-token-ttl-seconds" 39 tokenHeader = "x-aws-ec2-metadata-token" 40 41 // Named Handler constants 42 fetchTokenHandlerName = "FetchTokenHandler" 43 unmarshalMetadataHandlerName = "unmarshalMetadataHandler" 44 unmarshalTokenHandlerName = "unmarshalTokenHandler" 45 enableTokenProviderHandlerName = "enableTokenProviderHandler" 46 47 // TTL constants 48 defaultTTL = 21600 * time.Second 49 ttlExpirationWindow = 30 * time.Second 50 ) 51 52 // A EC2Metadata is an EC2 Metadata service Client. 53 type EC2Metadata struct { 54 *client.Client 55 } 56 57 // New creates a new instance of the EC2Metadata client with a session. 58 // This client is safe to use across multiple goroutines. 59 // 60 // 61 // Example: 62 // // Create a EC2Metadata client from just a session. 63 // svc := ec2metadata.New(mySession) 64 // 65 // // Create a EC2Metadata client with additional configuration 66 // svc := ec2metadata.New(mySession, aws.NewConfig().WithLogLevel(aws.LogDebugHTTPBody)) 67 func New(p client.ConfigProvider, cfgs ...*aws.Config) *EC2Metadata { 68 c := p.ClientConfig(ServiceName, cfgs...) 69 return NewClient(*c.Config, c.Handlers, c.Endpoint, c.SigningRegion) 70 } 71 72 // NewClient returns a new EC2Metadata client. Should be used to create 73 // a client when not using a session. Generally using just New with a session 74 // is preferred. 75 // 76 // Will remove the URL path from the endpoint provided to ensure the EC2 IMDS 77 // client is able to communicate with the EC2 IMDS API. 78 // 79 // If an unmodified HTTP client is provided from the stdlib default, or no client 80 // the EC2RoleProvider's EC2Metadata HTTP client's timeout will be shortened. 81 // To disable this set Config.EC2MetadataDisableTimeoutOverride to false. Enabled by default. 82 func NewClient(cfg aws.Config, handlers request.Handlers, endpoint, signingRegion string, opts ...func(*client.Client)) *EC2Metadata { 83 if !aws.BoolValue(cfg.EC2MetadataDisableTimeoutOverride) && httpClientZero(cfg.HTTPClient) { 84 // If the http client is unmodified and this feature is not disabled 85 // set custom timeouts for EC2Metadata requests. 86 cfg.HTTPClient = &http.Client{ 87 // use a shorter timeout than default because the metadata 88 // service is local if it is running, and to fail faster 89 // if not running on an ec2 instance. 90 Timeout: 1 * time.Second, 91 } 92 // max number of retries on the client operation 93 cfg.MaxRetries = aws.Int(2) 94 } 95 96 if u, err := url.Parse(endpoint); err == nil { 97 // Remove path from the endpoint since it will be added by requests. 98 // This is an artifact of the SDK adding `/latest` to the endpoint for 99 // EC2 IMDS, but this is now moved to the operation definition. 100 u.Path = "" 101 u.RawPath = "" 102 endpoint = u.String() 103 } 104 105 svc := &EC2Metadata{ 106 Client: client.New( 107 cfg, 108 metadata.ClientInfo{ 109 ServiceName: ServiceName, 110 ServiceID: ServiceName, 111 Endpoint: endpoint, 112 APIVersion: "latest", 113 }, 114 handlers, 115 ), 116 } 117 118 // token provider instance 119 tp := newTokenProvider(svc, defaultTTL) 120 121 // NamedHandler for fetching token 122 svc.Handlers.Sign.PushBackNamed(request.NamedHandler{ 123 Name: fetchTokenHandlerName, 124 Fn: tp.fetchTokenHandler, 125 }) 126 // NamedHandler for enabling token provider 127 svc.Handlers.Complete.PushBackNamed(request.NamedHandler{ 128 Name: enableTokenProviderHandlerName, 129 Fn: tp.enableTokenProviderHandler, 130 }) 131 132 svc.Handlers.Unmarshal.PushBackNamed(unmarshalHandler) 133 svc.Handlers.UnmarshalError.PushBack(unmarshalError) 134 svc.Handlers.Validate.Clear() 135 svc.Handlers.Validate.PushBack(validateEndpointHandler) 136 137 // Disable the EC2 Metadata service if the environment variable is set. 138 // This short-circuits the service's functionality to always fail to send 139 // requests. 140 if strings.ToLower(os.Getenv(disableServiceEnvVar)) == "true" { 141 svc.Handlers.Send.SwapNamed(request.NamedHandler{ 142 Name: corehandlers.SendHandler.Name, 143 Fn: func(r *request.Request) { 144 r.HTTPResponse = &http.Response{ 145 Header: http.Header{}, 146 } 147 r.Error = awserr.New( 148 request.CanceledErrorCode, 149 "EC2 IMDS access disabled via "+disableServiceEnvVar+" env var", 150 nil) 151 }, 152 }) 153 } 154 155 // Add additional options to the service config 156 for _, option := range opts { 157 option(svc.Client) 158 } 159 return svc 160 } 161 162 func httpClientZero(c *http.Client) bool { 163 return c == nil || (c.Transport == nil && c.CheckRedirect == nil && c.Jar == nil && c.Timeout == 0) 164 } 165 166 type metadataOutput struct { 167 Content string 168 } 169 170 type tokenOutput struct { 171 Token string 172 TTL time.Duration 173 } 174 175 // unmarshal token handler is used to parse the response of a getToken operation 176 var unmarshalTokenHandler = request.NamedHandler{ 177 Name: unmarshalTokenHandlerName, 178 Fn: func(r *request.Request) { 179 defer r.HTTPResponse.Body.Close() 180 var b bytes.Buffer 181 if _, err := io.Copy(&b, r.HTTPResponse.Body); err != nil { 182 r.Error = awserr.NewRequestFailure(awserr.New(request.ErrCodeSerialization, 183 "unable to unmarshal EC2 metadata response", err), r.HTTPResponse.StatusCode, r.RequestID) 184 return 185 } 186 187 v := r.HTTPResponse.Header.Get(ttlHeader) 188 data, ok := r.Data.(*tokenOutput) 189 if !ok { 190 return 191 } 192 193 data.Token = b.String() 194 // TTL is in seconds 195 i, err := strconv.ParseInt(v, 10, 64) 196 if err != nil { 197 r.Error = awserr.NewRequestFailure(awserr.New(request.ParamFormatErrCode, 198 "unable to parse EC2 token TTL response", err), r.HTTPResponse.StatusCode, r.RequestID) 199 return 200 } 201 t := time.Duration(i) * time.Second 202 data.TTL = t 203 }, 204 } 205 206 var unmarshalHandler = request.NamedHandler{ 207 Name: unmarshalMetadataHandlerName, 208 Fn: func(r *request.Request) { 209 defer r.HTTPResponse.Body.Close() 210 var b bytes.Buffer 211 if _, err := io.Copy(&b, r.HTTPResponse.Body); err != nil { 212 r.Error = awserr.NewRequestFailure(awserr.New(request.ErrCodeSerialization, 213 "unable to unmarshal EC2 metadata response", err), r.HTTPResponse.StatusCode, r.RequestID) 214 return 215 } 216 217 if data, ok := r.Data.(*metadataOutput); ok { 218 data.Content = b.String() 219 } 220 }, 221 } 222 223 func unmarshalError(r *request.Request) { 224 defer r.HTTPResponse.Body.Close() 225 var b bytes.Buffer 226 227 if _, err := io.Copy(&b, r.HTTPResponse.Body); err != nil { 228 r.Error = awserr.NewRequestFailure( 229 awserr.New(request.ErrCodeSerialization, "unable to unmarshal EC2 metadata error response", err), 230 r.HTTPResponse.StatusCode, r.RequestID) 231 return 232 } 233 234 // Response body format is not consistent between metadata endpoints. 235 // Grab the error message as a string and include that as the source error 236 r.Error = awserr.NewRequestFailure( 237 awserr.New("EC2MetadataError", "failed to make EC2Metadata request\n"+b.String(), nil), 238 r.HTTPResponse.StatusCode, r.RequestID) 239 } 240 241 func validateEndpointHandler(r *request.Request) { 242 if r.ClientInfo.Endpoint == "" { 243 r.Error = aws.ErrMissingEndpoint 244 } 245 }