...

Source file src/github.com/aws/aws-sdk-go-v2/feature/ec2/imds/api_client.go

Documentation: github.com/aws/aws-sdk-go-v2/feature/ec2/imds

     1  package imds
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"net"
     7  	"net/http"
     8  	"os"
     9  	"strings"
    10  	"time"
    11  
    12  	"github.com/aws/aws-sdk-go-v2/aws"
    13  	"github.com/aws/aws-sdk-go-v2/aws/retry"
    14  	awshttp "github.com/aws/aws-sdk-go-v2/aws/transport/http"
    15  	internalconfig "github.com/aws/aws-sdk-go-v2/feature/ec2/imds/internal/config"
    16  	"github.com/aws/smithy-go"
    17  	"github.com/aws/smithy-go/logging"
    18  	"github.com/aws/smithy-go/middleware"
    19  	smithyhttp "github.com/aws/smithy-go/transport/http"
    20  )
    21  
    22  // ServiceID provides the unique name of this API client
    23  const ServiceID = "ec2imds"
    24  
    25  // Client provides the API client for interacting with the Amazon EC2 Instance
    26  // Metadata Service API.
    27  type Client struct {
    28  	options Options
    29  }
    30  
    31  // ClientEnableState provides an enumeration if the client is enabled,
    32  // disabled, or default behavior.
    33  type ClientEnableState = internalconfig.ClientEnableState
    34  
    35  // Enumeration values for ClientEnableState
    36  const (
    37  	ClientDefaultEnableState ClientEnableState = internalconfig.ClientDefaultEnableState // default behavior
    38  	ClientDisabled           ClientEnableState = internalconfig.ClientDisabled           // client disabled
    39  	ClientEnabled            ClientEnableState = internalconfig.ClientEnabled            // client enabled
    40  )
    41  
    42  // EndpointModeState is an enum configuration variable describing the client endpoint mode.
    43  // Not configurable directly, but used when using the NewFromConfig.
    44  type EndpointModeState = internalconfig.EndpointModeState
    45  
    46  // Enumeration values for EndpointModeState
    47  const (
    48  	EndpointModeStateUnset EndpointModeState = internalconfig.EndpointModeStateUnset
    49  	EndpointModeStateIPv4  EndpointModeState = internalconfig.EndpointModeStateIPv4
    50  	EndpointModeStateIPv6  EndpointModeState = internalconfig.EndpointModeStateIPv6
    51  )
    52  
    53  const (
    54  	disableClientEnvVar = "AWS_EC2_METADATA_DISABLED"
    55  
    56  	// Client endpoint options
    57  	endpointEnvVar = "AWS_EC2_METADATA_SERVICE_ENDPOINT"
    58  
    59  	defaultIPv4Endpoint = "http://169.254.169.254"
    60  	defaultIPv6Endpoint = "http://[fd00:ec2::254]"
    61  )
    62  
    63  // New returns an initialized Client based on the functional options. Provide
    64  // additional functional options to further configure the behavior of the client,
    65  // such as changing the client's endpoint or adding custom middleware behavior.
    66  func New(options Options, optFns ...func(*Options)) *Client {
    67  	options = options.Copy()
    68  
    69  	for _, fn := range optFns {
    70  		fn(&options)
    71  	}
    72  
    73  	options.HTTPClient = resolveHTTPClient(options.HTTPClient)
    74  
    75  	if options.Retryer == nil {
    76  		options.Retryer = retry.NewStandard()
    77  	}
    78  	options.Retryer = retry.AddWithMaxBackoffDelay(options.Retryer, 1*time.Second)
    79  
    80  	if options.ClientEnableState == ClientDefaultEnableState {
    81  		if v := os.Getenv(disableClientEnvVar); strings.EqualFold(v, "true") {
    82  			options.ClientEnableState = ClientDisabled
    83  		}
    84  	}
    85  
    86  	if len(options.Endpoint) == 0 {
    87  		if v := os.Getenv(endpointEnvVar); len(v) != 0 {
    88  			options.Endpoint = v
    89  		}
    90  	}
    91  
    92  	client := &Client{
    93  		options: options,
    94  	}
    95  
    96  	if client.options.tokenProvider == nil && !client.options.disableAPIToken {
    97  		client.options.tokenProvider = newTokenProvider(client, defaultTokenTTL)
    98  	}
    99  
   100  	return client
   101  }
   102  
   103  // NewFromConfig returns an initialized Client based the AWS SDK config, and
   104  // functional options. Provide additional functional options to further
   105  // configure the behavior of the client, such as changing the client's endpoint
   106  // or adding custom middleware behavior.
   107  func NewFromConfig(cfg aws.Config, optFns ...func(*Options)) *Client {
   108  	opts := Options{
   109  		APIOptions:    append([]func(*middleware.Stack) error{}, cfg.APIOptions...),
   110  		HTTPClient:    cfg.HTTPClient,
   111  		ClientLogMode: cfg.ClientLogMode,
   112  		Logger:        cfg.Logger,
   113  	}
   114  
   115  	if cfg.Retryer != nil {
   116  		opts.Retryer = cfg.Retryer()
   117  	}
   118  
   119  	resolveClientEnableState(cfg, &opts)
   120  	resolveEndpointConfig(cfg, &opts)
   121  	resolveEndpointModeConfig(cfg, &opts)
   122  	resolveEnableFallback(cfg, &opts)
   123  
   124  	return New(opts, optFns...)
   125  }
   126  
   127  // Options provides the fields for configuring the API client's behavior.
   128  type Options struct {
   129  	// Set of options to modify how an operation is invoked. These apply to all
   130  	// operations invoked for this client. Use functional options on operation
   131  	// call to modify this list for per operation behavior.
   132  	APIOptions []func(*middleware.Stack) error
   133  
   134  	// The endpoint the client will use to retrieve EC2 instance metadata.
   135  	//
   136  	// Specifies the EC2 Instance Metadata Service endpoint to use. If specified it overrides EndpointMode.
   137  	//
   138  	// If unset, and the environment variable AWS_EC2_METADATA_SERVICE_ENDPOINT
   139  	// has a value the client will use the value of the environment variable as
   140  	// the endpoint for operation calls.
   141  	//
   142  	//    AWS_EC2_METADATA_SERVICE_ENDPOINT=http://[::1]
   143  	Endpoint string
   144  
   145  	// The endpoint selection mode the client will use if no explicit endpoint is provided using the Endpoint field.
   146  	//
   147  	// Setting EndpointMode to EndpointModeStateIPv4 will configure the client to use the default EC2 IPv4 endpoint.
   148  	// Setting EndpointMode to EndpointModeStateIPv6 will configure the client to use the default EC2 IPv6 endpoint.
   149  	//
   150  	// By default if EndpointMode is not set (EndpointModeStateUnset) than the default endpoint selection mode EndpointModeStateIPv4.
   151  	EndpointMode EndpointModeState
   152  
   153  	// The HTTP client to invoke API calls with. Defaults to client's default
   154  	// HTTP implementation if nil.
   155  	HTTPClient HTTPClient
   156  
   157  	// Retryer guides how HTTP requests should be retried in case of recoverable
   158  	// failures. When nil the API client will use a default retryer.
   159  	Retryer aws.Retryer
   160  
   161  	// Changes if the EC2 Instance Metadata client is enabled or not. Client
   162  	// will default to enabled if not set to ClientDisabled. When the client is
   163  	// disabled it will return an error for all operation calls.
   164  	//
   165  	// If ClientEnableState value is ClientDefaultEnableState (default value),
   166  	// and the environment variable "AWS_EC2_METADATA_DISABLED" is set to
   167  	// "true", the client will be disabled.
   168  	//
   169  	//    AWS_EC2_METADATA_DISABLED=true
   170  	ClientEnableState ClientEnableState
   171  
   172  	// Configures the events that will be sent to the configured logger.
   173  	ClientLogMode aws.ClientLogMode
   174  
   175  	// The logger writer interface to write logging messages to.
   176  	Logger logging.Logger
   177  
   178  	// Configure IMDSv1 fallback behavior. By default, the client will attempt
   179  	// to fall back to IMDSv1 as needed for backwards compatibility. When set to [aws.FalseTernary]
   180  	// the client will return any errors encountered from attempting to fetch a token
   181  	// instead of silently using the insecure data flow of IMDSv1.
   182  	//
   183  	// See [configuring IMDS] for more information.
   184  	//
   185  	// [configuring IMDS]: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/configuring-instance-metadata-service.html
   186  	EnableFallback aws.Ternary
   187  
   188  	// By default, all IMDS client operations enforce a 5-second timeout. You
   189  	// can disable that behavior with this setting.
   190  	DisableDefaultTimeout bool
   191  
   192  	// provides the caching of API tokens used for operation calls. If unset,
   193  	// the API token will not be retrieved for the operation.
   194  	tokenProvider *tokenProvider
   195  
   196  	// option to disable the API token provider for testing.
   197  	disableAPIToken bool
   198  }
   199  
   200  // HTTPClient provides the interface for a client making HTTP requests with the
   201  // API.
   202  type HTTPClient interface {
   203  	Do(*http.Request) (*http.Response, error)
   204  }
   205  
   206  // Copy creates a copy of the API options.
   207  func (o Options) Copy() Options {
   208  	to := o
   209  	to.APIOptions = append([]func(*middleware.Stack) error{}, o.APIOptions...)
   210  	return to
   211  }
   212  
   213  // WithAPIOptions wraps the API middleware functions, as a functional option
   214  // for the API Client Options. Use this helper to add additional functional
   215  // options to the API client, or operation calls.
   216  func WithAPIOptions(optFns ...func(*middleware.Stack) error) func(*Options) {
   217  	return func(o *Options) {
   218  		o.APIOptions = append(o.APIOptions, optFns...)
   219  	}
   220  }
   221  
   222  func (c *Client) invokeOperation(
   223  	ctx context.Context, opID string, params interface{}, optFns []func(*Options),
   224  	stackFns ...func(*middleware.Stack, Options) error,
   225  ) (
   226  	result interface{}, metadata middleware.Metadata, err error,
   227  ) {
   228  	stack := middleware.NewStack(opID, smithyhttp.NewStackRequest)
   229  	options := c.options.Copy()
   230  	for _, fn := range optFns {
   231  		fn(&options)
   232  	}
   233  
   234  	if options.ClientEnableState == ClientDisabled {
   235  		return nil, metadata, &smithy.OperationError{
   236  			ServiceID:     ServiceID,
   237  			OperationName: opID,
   238  			Err: fmt.Errorf(
   239  				"access disabled to EC2 IMDS via client option, or %q environment variable",
   240  				disableClientEnvVar),
   241  		}
   242  	}
   243  
   244  	for _, fn := range stackFns {
   245  		if err := fn(stack, options); err != nil {
   246  			return nil, metadata, err
   247  		}
   248  	}
   249  
   250  	for _, fn := range options.APIOptions {
   251  		if err := fn(stack); err != nil {
   252  			return nil, metadata, err
   253  		}
   254  	}
   255  
   256  	handler := middleware.DecorateHandler(smithyhttp.NewClientHandler(options.HTTPClient), stack)
   257  	result, metadata, err = handler.Handle(ctx, params)
   258  	if err != nil {
   259  		return nil, metadata, &smithy.OperationError{
   260  			ServiceID:     ServiceID,
   261  			OperationName: opID,
   262  			Err:           err,
   263  		}
   264  	}
   265  
   266  	return result, metadata, err
   267  }
   268  
   269  const (
   270  	// HTTP client constants
   271  	defaultDialerTimeout         = 250 * time.Millisecond
   272  	defaultResponseHeaderTimeout = 500 * time.Millisecond
   273  )
   274  
   275  func resolveHTTPClient(client HTTPClient) HTTPClient {
   276  	if client == nil {
   277  		client = awshttp.NewBuildableClient()
   278  	}
   279  
   280  	if c, ok := client.(*awshttp.BuildableClient); ok {
   281  		client = c.
   282  			WithDialerOptions(func(d *net.Dialer) {
   283  				// Use a custom Dial timeout for the EC2 Metadata service to account
   284  				// for the possibility the application might not be running in an
   285  				// environment with the service present. The client should fail fast in
   286  				// this case.
   287  				d.Timeout = defaultDialerTimeout
   288  			}).
   289  			WithTransportOptions(func(tr *http.Transport) {
   290  				// Use a custom Transport timeout for the EC2 Metadata service to
   291  				// account for the possibility that the application might be running in
   292  				// a container, and EC2Metadata service drops the connection after a
   293  				// single IP Hop. The client should fail fast in this case.
   294  				tr.ResponseHeaderTimeout = defaultResponseHeaderTimeout
   295  			})
   296  	}
   297  
   298  	return client
   299  }
   300  
   301  func resolveClientEnableState(cfg aws.Config, options *Options) error {
   302  	if options.ClientEnableState != ClientDefaultEnableState {
   303  		return nil
   304  	}
   305  	value, found, err := internalconfig.ResolveClientEnableState(cfg.ConfigSources)
   306  	if err != nil || !found {
   307  		return err
   308  	}
   309  	options.ClientEnableState = value
   310  	return nil
   311  }
   312  
   313  func resolveEndpointModeConfig(cfg aws.Config, options *Options) error {
   314  	if options.EndpointMode != EndpointModeStateUnset {
   315  		return nil
   316  	}
   317  	value, found, err := internalconfig.ResolveEndpointModeConfig(cfg.ConfigSources)
   318  	if err != nil || !found {
   319  		return err
   320  	}
   321  	options.EndpointMode = value
   322  	return nil
   323  }
   324  
   325  func resolveEndpointConfig(cfg aws.Config, options *Options) error {
   326  	if len(options.Endpoint) != 0 {
   327  		return nil
   328  	}
   329  	value, found, err := internalconfig.ResolveEndpointConfig(cfg.ConfigSources)
   330  	if err != nil || !found {
   331  		return err
   332  	}
   333  	options.Endpoint = value
   334  	return nil
   335  }
   336  
   337  func resolveEnableFallback(cfg aws.Config, options *Options) {
   338  	if options.EnableFallback != aws.UnknownTernary {
   339  		return
   340  	}
   341  
   342  	disabled, ok := internalconfig.ResolveV1FallbackDisabled(cfg.ConfigSources)
   343  	if !ok {
   344  		return
   345  	}
   346  
   347  	if disabled {
   348  		options.EnableFallback = aws.FalseTernary
   349  	} else {
   350  		options.EnableFallback = aws.TrueTernary
   351  	}
   352  }
   353  

View as plain text