123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- package imds
- import (
- "context"
- "fmt"
- "net"
- "net/http"
- "os"
- "strings"
- "time"
- "github.com/aws/aws-sdk-go-v2/aws"
- "github.com/aws/aws-sdk-go-v2/aws/retry"
- awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
- internalconfig "github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config"
- "github.com/aws/smithy-go"
- "github.com/aws/smithy-go/logging"
- "github.com/aws/smithy-go/middleware"
- smithyhttp "github.com/aws/smithy-go/transport/http"
- )
- // ServiceID provides the unique name of this API client
- const ServiceID = "ec2imds"
- // Client provides the API client for interacting with the Amazon EC2 Instance
- // Metadata Service API.
- type Client struct {
- options Options
- }
- // ClientEnableState provides an enumeration if the client is enabled,
- // disabled, or default behavior.
- type ClientEnableState = internalconfig.ClientEnableState
- // Enumeration values for ClientEnableState
- const (
- ClientDefaultEnableState ClientEnableState = internalconfig.ClientDefaultEnableState // default behavior
- ClientDisabled ClientEnableState = internalconfig.ClientDisabled // client disabled
- ClientEnabled ClientEnableState = internalconfig.ClientEnabled // client enabled
- )
- // EndpointModeState is an enum configuration variable describing the client endpoint mode.
- // Not configurable directly, but used when using the NewFromConfig.
- type EndpointModeState = internalconfig.EndpointModeState
- // Enumeration values for EndpointModeState
- const (
- EndpointModeStateUnset EndpointModeState = internalconfig.EndpointModeStateUnset
- EndpointModeStateIPv4 EndpointModeState = internalconfig.EndpointModeStateIPv4
- EndpointModeStateIPv6 EndpointModeState = internalconfig.EndpointModeStateIPv6
- )
- const (
- disableClientEnvVar = "AWS_EC2_METADATA_DISABLED"
- // Client endpoint options
- endpointEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
- defaultIPv4Endpoint = "http://169.254.169.254"
- defaultIPv6Endpoint = "http://[fd00:ec2::254]"
- )
- // New returns an initialized Client based on the functional options. Provide
- // additional functional options to further configure the behavior of the client,
- // such as changing the client's endpoint or adding custom middleware behavior.
- func New(options Options, optFns ...func(*Options)) *Client {
- options = options.Copy()
- for _, fn := range optFns {
- fn(&options)
- }
- options.HTTPClient = resolveHTTPClient(options.HTTPClient)
- if options.Retryer == nil {
- options.Retryer = retry.NewStandard()
- }
- options.Retryer = retry.AddWithMaxBackoffDelay(options.Retryer, 1*time.Second)
- if options.ClientEnableState == ClientDefaultEnableState {
- if v := os.Getenv(disableClientEnvVar); strings.EqualFold(v, "true") {
- options.ClientEnableState = ClientDisabled
- }
- }
- if len(options.Endpoint) == 0 {
- if v := os.Getenv(endpointEnvVar); len(v) != 0 {
- options.Endpoint = v
- }
- }
- client := &Client{
- options: options,
- }
- if client.options.tokenProvider == nil && !client.options.disableAPIToken {
- client.options.tokenProvider = newTokenProvider(client, defaultTokenTTL)
- }
- return client
- }
- // NewFromConfig returns an initialized Client based the AWS SDK config, and
- // functional options. Provide additional functional options to further
- // configure the behavior of the client, such as changing the client's endpoint
- // or adding custom middleware behavior.
- func NewFromConfig(cfg aws.Config, optFns ...func(*Options)) *Client {
- opts := Options{
- APIOptions: append([]func(*middleware.Stack) error{}, cfg.APIOptions...),
- HTTPClient: cfg.HTTPClient,
- ClientLogMode: cfg.ClientLogMode,
- Logger: cfg.Logger,
- }
- if cfg.Retryer != nil {
- opts.Retryer = cfg.Retryer()
- }
- resolveClientEnableState(cfg, &opts)
- resolveEndpointConfig(cfg, &opts)
- resolveEndpointModeConfig(cfg, &opts)
- return New(opts, optFns...)
- }
- // Options provides the fields for configuring the API client's behavior.
- type Options struct {
- // Set of options to modify how an operation is invoked. These apply to all
- // operations invoked for this client. Use functional options on operation
- // call to modify this list for per operation behavior.
- APIOptions []func(*middleware.Stack) error
- // The endpoint the client will use to retrieve EC2 instance metadata.
- //
- // Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EndpointMode.
- //
- // If unset, and the environment variable AWS_EC2_METADATA_SERVICE_ENDPOINT
- // has a value the client will use the value of the environment variable as
- // the endpoint for operation calls.
- //
- // AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
- Endpoint string
- // The endpoint selection mode the client will use if no explicit endpoint is provided using the Endpoint field.
- //
- // Setting EndpointMode to EndpointModeStateIPv4 will configure the client to use the default EC2 IPv4 endpoint.
- // Setting EndpointMode to EndpointModeStateIPv6 will configure the client to use the default EC2 IPv6 endpoint.
- //
- // By default if EndpointMode is not set (EndpointModeStateUnset) than the default endpoint selection mode EndpointModeStateIPv4.
- EndpointMode EndpointModeState
- // The HTTP client to invoke API calls with. Defaults to client's default
- // HTTP implementation if nil.
- HTTPClient HTTPClient
- // Retryer guides how HTTP requests should be retried in case of recoverable
- // failures. When nil the API client will use a default retryer.
- Retryer aws.Retryer
- // Changes if the EC2 Instance Metadata client is enabled or not. Client
- // will default to enabled if not set to ClientDisabled. When the client is
- // disabled it will return an error for all operation calls.
- //
- // If ClientEnableState value is ClientDefaultEnableState (default value),
- // and the environment variable "AWS_EC2_METADATA_DISABLED" is set to
- // "true", the client will be disabled.
- //
- // AWS_EC2_METADATA_DISABLED=true
- ClientEnableState ClientEnableState
- // Configures the events that will be sent to the configured logger.
- ClientLogMode aws.ClientLogMode
- // The logger writer interface to write logging messages to.
- Logger logging.Logger
- // provides the caching of API tokens used for operation calls. If unset,
- // the API token will not be retrieved for the operation.
- tokenProvider *tokenProvider
- // option to disable the API token provider for testing.
- disableAPIToken bool
- }
- // HTTPClient provides the interface for a client making HTTP requests with the
- // API.
- type HTTPClient interface {
- Do(*http.Request) (*http.Response, error)
- }
- // Copy creates a copy of the API options.
- func (o Options) Copy() Options {
- to := o
- to.APIOptions = append([]func(*middleware.Stack) error{}, o.APIOptions...)
- return to
- }
- // WithAPIOptions wraps the API middleware functions, as a functional option
- // for the API Client Options. Use this helper to add additional functional
- // options to the API client, or operation calls.
- func WithAPIOptions(optFns ...func(*middleware.Stack) error) func(*Options) {
- return func(o *Options) {
- o.APIOptions = append(o.APIOptions, optFns...)
- }
- }
- func (c *Client) invokeOperation(
- ctx context.Context, opID string, params interface{}, optFns []func(*Options),
- stackFns ...func(*middleware.Stack, Options) error,
- ) (
- result interface{}, metadata middleware.Metadata, err error,
- ) {
- stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
- options := c.options.Copy()
- for _, fn := range optFns {
- fn(&options)
- }
- if options.ClientEnableState == ClientDisabled {
- return nil, metadata, &smithy.OperationError{
- ServiceID: ServiceID,
- OperationName: opID,
- Err: fmt.Errorf(
- "access disabled to EC2 IMDS via client option, or %q environment variable",
- disableClientEnvVar),
- }
- }
- for _, fn := range stackFns {
- if err := fn(stack, options); err != nil {
- return nil, metadata, err
- }
- }
- for _, fn := range options.APIOptions {
- if err := fn(stack); err != nil {
- return nil, metadata, err
- }
- }
- handler := middleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
- result, metadata, err = handler.Handle(ctx, params)
- if err != nil {
- return nil, metadata, &smithy.OperationError{
- ServiceID: ServiceID,
- OperationName: opID,
- Err: err,
- }
- }
- return result, metadata, err
- }
- const (
- // HTTP client constants
- defaultDialerTimeout = 250 * time.Millisecond
- defaultResponseHeaderTimeout = 500 * time.Millisecond
- )
- func resolveHTTPClient(client HTTPClient) HTTPClient {
- if client == nil {
- client = awshttp.NewBuildableClient()
- }
- if c, ok := client.(*awshttp.BuildableClient); ok {
- client = c.
- WithDialerOptions(func(d *net.Dialer) {
- // Use a custom Dial timeout for the EC2 Metadata service to account
- // for the possibility the application might not be running in an
- // environment with the service present. The client should fail fast in
- // this case.
- d.Timeout = defaultDialerTimeout
- }).
- WithTransportOptions(func(tr *http.Transport) {
- // Use a custom Transport timeout for the EC2 Metadata service to
- // account for the possibility that the application might be running in
- // a container, and EC2Metadata service drops the connection after a
- // single IP Hop. The client should fail fast in this case.
- tr.ResponseHeaderTimeout = defaultResponseHeaderTimeout
- })
- }
- return client
- }
- func resolveClientEnableState(cfg aws.Config, options *Options) error {
- if options.ClientEnableState != ClientDefaultEnableState {
- return nil
- }
- value, found, err := internalconfig.ResolveClientEnableState(cfg.ConfigSources)
- if err != nil || !found {
- return err
- }
- options.ClientEnableState = value
- return nil
- }
- func resolveEndpointModeConfig(cfg aws.Config, options *Options) error {
- if options.EndpointMode != EndpointModeStateUnset {
- return nil
- }
- value, found, err := internalconfig.ResolveEndpointModeConfig(cfg.ConfigSources)
- if err != nil || !found {
- return err
- }
- options.EndpointMode = value
- return nil
- }
- func resolveEndpointConfig(cfg aws.Config, options *Options) error {
- if len(options.Endpoint) != 0 {
- return nil
- }
- value, found, err := internalconfig.ResolveEndpointConfig(cfg.ConfigSources)
- if err != nil || !found {
- return err
- }
- options.Endpoint = value
- return nil
- }
|