src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

user_agent.go (13596B)


      1 package middleware
      2 
      3 import (
      4 	"context"
      5 	"fmt"
      6 	"os"
      7 	"runtime"
      8 	"sort"
      9 	"strings"
     10 
     11 	"github.com/aws/aws-sdk-go-v2/aws"
     12 	"github.com/aws/smithy-go/middleware"
     13 	smithyhttp "github.com/aws/smithy-go/transport/http"
     14 )
     15 
     16 var languageVersion = strings.TrimPrefix(runtime.Version(), "go")
     17 
     18 // SDKAgentKeyType is the metadata type to add to the SDK agent string
     19 type SDKAgentKeyType int
     20 
     21 // The set of valid SDKAgentKeyType constants. If an unknown value is assigned for SDKAgentKeyType it will
     22 // be mapped to AdditionalMetadata.
     23 const (
     24 	_ SDKAgentKeyType = iota
     25 	APIMetadata
     26 	OperatingSystemMetadata
     27 	LanguageMetadata
     28 	EnvironmentMetadata
     29 	FeatureMetadata
     30 	ConfigMetadata
     31 	FrameworkMetadata
     32 	AdditionalMetadata
     33 	ApplicationIdentifier
     34 	FeatureMetadata2
     35 )
     36 
     37 // Hardcoded value to specify which version of the user agent we're using
     38 const uaMetadata = "ua/2.1"
     39 
     40 func (k SDKAgentKeyType) string() string {
     41 	switch k {
     42 	case APIMetadata:
     43 		return "api"
     44 	case OperatingSystemMetadata:
     45 		return "os"
     46 	case LanguageMetadata:
     47 		return "lang"
     48 	case EnvironmentMetadata:
     49 		return "exec-env"
     50 	case FeatureMetadata:
     51 		return "ft"
     52 	case ConfigMetadata:
     53 		return "cfg"
     54 	case FrameworkMetadata:
     55 		return "lib"
     56 	case ApplicationIdentifier:
     57 		return "app"
     58 	case FeatureMetadata2:
     59 		return "m"
     60 	case AdditionalMetadata:
     61 		fallthrough
     62 	default:
     63 		return "md"
     64 	}
     65 }
     66 
     67 const execEnvVar = `AWS_EXECUTION_ENV`
     68 
     69 var validChars = map[rune]bool{
     70 	'!': true, '#': true, '$': true, '%': true, '&': true, '\'': true, '*': true, '+': true,
     71 	'-': true, '.': true, '^': true, '_': true, '`': true, '|': true, '~': true,
     72 }
     73 
     74 // UserAgentFeature enumerates tracked SDK features.
     75 type UserAgentFeature string
     76 
     77 // Enumerates UserAgentFeature.
     78 const (
     79 	UserAgentFeatureResourceModel UserAgentFeature = "A" // n/a (we don't generate separate resource types)
     80 
     81 	UserAgentFeatureWaiter    = "B"
     82 	UserAgentFeaturePaginator = "C"
     83 
     84 	UserAgentFeatureRetryModeLegacy   = "D" // n/a (equivalent to standard)
     85 	UserAgentFeatureRetryModeStandard = "E"
     86 	UserAgentFeatureRetryModeAdaptive = "F"
     87 
     88 	UserAgentFeatureS3Transfer      = "G"
     89 	UserAgentFeatureS3CryptoV1N     = "H" // n/a (crypto client is external)
     90 	UserAgentFeatureS3CryptoV2      = "I" // n/a
     91 	UserAgentFeatureS3ExpressBucket = "J"
     92 	UserAgentFeatureS3AccessGrants  = "K" // not yet implemented
     93 
     94 	UserAgentFeatureGZIPRequestCompression = "L"
     95 
     96 	UserAgentFeatureProtocolRPCV2CBOR = "M"
     97 
     98 	UserAgentFeatureAccountIDEndpoint      = "O" // DO NOT IMPLEMENT: rules output is not currently defined. SDKs should not parse endpoints for feature information.
     99 	UserAgentFeatureAccountIDModePreferred = "P"
    100 	UserAgentFeatureAccountIDModeDisabled  = "Q"
    101 	UserAgentFeatureAccountIDModeRequired  = "R"
    102 
    103 	UserAgentFeatureRequestChecksumCRC32          = "U"
    104 	UserAgentFeatureRequestChecksumCRC32C         = "V"
    105 	UserAgentFeatureRequestChecksumCRC64          = "W"
    106 	UserAgentFeatureRequestChecksumSHA1           = "X"
    107 	UserAgentFeatureRequestChecksumSHA256         = "Y"
    108 	UserAgentFeatureRequestChecksumWhenSupported  = "Z"
    109 	UserAgentFeatureRequestChecksumWhenRequired   = "a"
    110 	UserAgentFeatureResponseChecksumWhenSupported = "b"
    111 	UserAgentFeatureResponseChecksumWhenRequired  = "c"
    112 
    113 	UserAgentFeatureDynamoDBUserAgent = "d" // not yet implemented
    114 
    115 	UserAgentFeatureCredentialsCode                 = "e"
    116 	UserAgentFeatureCredentialsJvmSystemProperties  = "f" // n/a (this is not a JVM sdk)
    117 	UserAgentFeatureCredentialsEnvVars              = "g"
    118 	UserAgentFeatureCredentialsEnvVarsStsWebIDToken = "h"
    119 	UserAgentFeatureCredentialsStsAssumeRole        = "i"
    120 	UserAgentFeatureCredentialsStsAssumeRoleSaml    = "j" // not yet implemented
    121 	UserAgentFeatureCredentialsStsAssumeRoleWebID   = "k"
    122 	UserAgentFeatureCredentialsStsFederationToken   = "l" // not yet implemented
    123 	UserAgentFeatureCredentialsStsSessionToken      = "m" // not yet implemented
    124 	UserAgentFeatureCredentialsProfile              = "n"
    125 	UserAgentFeatureCredentialsProfileSourceProfile = "o"
    126 	UserAgentFeatureCredentialsProfileNamedProvider = "p"
    127 	UserAgentFeatureCredentialsProfileStsWebIDToken = "q"
    128 	UserAgentFeatureCredentialsProfileSso           = "r"
    129 	UserAgentFeatureCredentialsSso                  = "s"
    130 	UserAgentFeatureCredentialsProfileSsoLegacy     = "t"
    131 	UserAgentFeatureCredentialsSsoLegacy            = "u"
    132 	UserAgentFeatureCredentialsProfileProcess       = "v"
    133 	UserAgentFeatureCredentialsProcess              = "w"
    134 	UserAgentFeatureCredentialsBoto2ConfigFile      = "x" // n/a (this is not boto/Python)
    135 	UserAgentFeatureCredentialsAwsSdkStore          = "y" // n/a (this is used by .NET based sdk)
    136 	UserAgentFeatureCredentialsHTTP                 = "z"
    137 	UserAgentFeatureCredentialsIMDS                 = "0"
    138 
    139 	UserAgentFeatureBearerServiceEnvVars = "3"
    140 )
    141 
    142 var credentialSourceToFeature = map[aws.CredentialSource]UserAgentFeature{
    143 	aws.CredentialSourceCode:                 UserAgentFeatureCredentialsCode,
    144 	aws.CredentialSourceEnvVars:              UserAgentFeatureCredentialsEnvVars,
    145 	aws.CredentialSourceEnvVarsSTSWebIDToken: UserAgentFeatureCredentialsEnvVarsStsWebIDToken,
    146 	aws.CredentialSourceSTSAssumeRole:        UserAgentFeatureCredentialsStsAssumeRole,
    147 	aws.CredentialSourceSTSAssumeRoleSaml:    UserAgentFeatureCredentialsStsAssumeRoleSaml,
    148 	aws.CredentialSourceSTSAssumeRoleWebID:   UserAgentFeatureCredentialsStsAssumeRoleWebID,
    149 	aws.CredentialSourceSTSFederationToken:   UserAgentFeatureCredentialsStsFederationToken,
    150 	aws.CredentialSourceSTSSessionToken:      UserAgentFeatureCredentialsStsSessionToken,
    151 	aws.CredentialSourceProfile:              UserAgentFeatureCredentialsProfile,
    152 	aws.CredentialSourceProfileSourceProfile: UserAgentFeatureCredentialsProfileSourceProfile,
    153 	aws.CredentialSourceProfileNamedProvider: UserAgentFeatureCredentialsProfileNamedProvider,
    154 	aws.CredentialSourceProfileSTSWebIDToken: UserAgentFeatureCredentialsProfileStsWebIDToken,
    155 	aws.CredentialSourceProfileSSO:           UserAgentFeatureCredentialsProfileSso,
    156 	aws.CredentialSourceSSO:                  UserAgentFeatureCredentialsSso,
    157 	aws.CredentialSourceProfileSSOLegacy:     UserAgentFeatureCredentialsProfileSsoLegacy,
    158 	aws.CredentialSourceSSOLegacy:            UserAgentFeatureCredentialsSsoLegacy,
    159 	aws.CredentialSourceProfileProcess:       UserAgentFeatureCredentialsProfileProcess,
    160 	aws.CredentialSourceProcess:              UserAgentFeatureCredentialsProcess,
    161 	aws.CredentialSourceHTTP:                 UserAgentFeatureCredentialsHTTP,
    162 	aws.CredentialSourceIMDS:                 UserAgentFeatureCredentialsIMDS,
    163 }
    164 
    165 // RequestUserAgent is a build middleware that set the User-Agent for the request.
    166 type RequestUserAgent struct {
    167 	sdkAgent, userAgent *smithyhttp.UserAgentBuilder
    168 	features            map[UserAgentFeature]struct{}
    169 }
    170 
    171 // NewRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the
    172 // request.
    173 //
    174 // User-Agent example:
    175 //
    176 //	aws-sdk-go-v2/1.2.3
    177 //
    178 // X-Amz-User-Agent example:
    179 //
    180 //	aws-sdk-go-v2/1.2.3 md/GOOS/linux md/GOARCH/amd64 lang/go/1.15
    181 func NewRequestUserAgent() *RequestUserAgent {
    182 	userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder()
    183 	addProductName(userAgent)
    184 	addUserAgentMetadata(userAgent)
    185 	addProductName(sdkAgent)
    186 
    187 	r := &RequestUserAgent{
    188 		sdkAgent:  sdkAgent,
    189 		userAgent: userAgent,
    190 		features:  map[UserAgentFeature]struct{}{},
    191 	}
    192 
    193 	addSDKMetadata(r)
    194 
    195 	return r
    196 }
    197 
    198 func addSDKMetadata(r *RequestUserAgent) {
    199 	r.AddSDKAgentKey(OperatingSystemMetadata, getNormalizedOSName())
    200 	r.AddSDKAgentKeyValue(LanguageMetadata, "go", languageVersion)
    201 	r.AddSDKAgentKeyValue(AdditionalMetadata, "GOOS", runtime.GOOS)
    202 	r.AddSDKAgentKeyValue(AdditionalMetadata, "GOARCH", runtime.GOARCH)
    203 	if ev := os.Getenv(execEnvVar); len(ev) > 0 {
    204 		r.AddSDKAgentKey(EnvironmentMetadata, ev)
    205 	}
    206 }
    207 
    208 func addProductName(builder *smithyhttp.UserAgentBuilder) {
    209 	builder.AddKeyValue(aws.SDKName, aws.SDKVersion)
    210 }
    211 
    212 func addUserAgentMetadata(builder *smithyhttp.UserAgentBuilder) {
    213 	builder.AddKey(uaMetadata)
    214 }
    215 
    216 // AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
    217 func AddUserAgentKey(key string) func(*middleware.Stack) error {
    218 	return func(stack *middleware.Stack) error {
    219 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    220 		if err != nil {
    221 			return err
    222 		}
    223 		requestUserAgent.AddUserAgentKey(key)
    224 		return nil
    225 	}
    226 }
    227 
    228 // AddUserAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one.
    229 func AddUserAgentKeyValue(key, value string) func(*middleware.Stack) error {
    230 	return func(stack *middleware.Stack) error {
    231 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    232 		if err != nil {
    233 			return err
    234 		}
    235 		requestUserAgent.AddUserAgentKeyValue(key, value)
    236 		return nil
    237 	}
    238 }
    239 
    240 // AddSDKAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
    241 func AddSDKAgentKey(keyType SDKAgentKeyType, key string) func(*middleware.Stack) error {
    242 	return func(stack *middleware.Stack) error {
    243 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    244 		if err != nil {
    245 			return err
    246 		}
    247 		requestUserAgent.AddSDKAgentKey(keyType, key)
    248 		return nil
    249 	}
    250 }
    251 
    252 // AddSDKAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one.
    253 func AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) func(*middleware.Stack) error {
    254 	return func(stack *middleware.Stack) error {
    255 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    256 		if err != nil {
    257 			return err
    258 		}
    259 		requestUserAgent.AddSDKAgentKeyValue(keyType, key, value)
    260 		return nil
    261 	}
    262 }
    263 
    264 // AddRequestUserAgentMiddleware registers a requestUserAgent middleware on the stack if not present.
    265 func AddRequestUserAgentMiddleware(stack *middleware.Stack) error {
    266 	_, err := getOrAddRequestUserAgent(stack)
    267 	return err
    268 }
    269 
    270 func getOrAddRequestUserAgent(stack *middleware.Stack) (*RequestUserAgent, error) {
    271 	id := (*RequestUserAgent)(nil).ID()
    272 	bm, ok := stack.Build.Get(id)
    273 	if !ok {
    274 		bm = NewRequestUserAgent()
    275 		err := stack.Build.Add(bm, middleware.After)
    276 		if err != nil {
    277 			return nil, err
    278 		}
    279 	}
    280 
    281 	requestUserAgent, ok := bm.(*RequestUserAgent)
    282 	if !ok {
    283 		return nil, fmt.Errorf("%T for %s middleware did not match expected type", bm, id)
    284 	}
    285 
    286 	return requestUserAgent, nil
    287 }
    288 
    289 // AddUserAgentKey adds the component identified by name to the User-Agent string.
    290 func (u *RequestUserAgent) AddUserAgentKey(key string) {
    291 	u.userAgent.AddKey(strings.Map(rules, key))
    292 }
    293 
    294 // AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string.
    295 func (u *RequestUserAgent) AddUserAgentKeyValue(key, value string) {
    296 	u.userAgent.AddKeyValue(strings.Map(rules, key), strings.Map(rules, value))
    297 }
    298 
    299 // AddUserAgentFeature adds the feature ID to the tracking list to be emitted
    300 // in the final User-Agent string.
    301 func (u *RequestUserAgent) AddUserAgentFeature(feature UserAgentFeature) {
    302 	u.features[feature] = struct{}{}
    303 }
    304 
    305 // AddSDKAgentKey adds the component identified by name to the User-Agent string.
    306 func (u *RequestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) {
    307 	// TODO: should target sdkAgent
    308 	u.userAgent.AddKey(keyType.string() + "/" + strings.Map(rules, key))
    309 }
    310 
    311 // AddSDKAgentKeyValue adds the key identified by the given name and value to the User-Agent string.
    312 func (u *RequestUserAgent) AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) {
    313 	// TODO: should target sdkAgent
    314 	u.userAgent.AddKeyValue(keyType.string(), strings.Map(rules, key)+"#"+strings.Map(rules, value))
    315 }
    316 
    317 // AddCredentialsSource adds the credential source as a feature on the User-Agent string
    318 func (u *RequestUserAgent) AddCredentialsSource(source aws.CredentialSource) {
    319 	x, ok := credentialSourceToFeature[source]
    320 	if ok {
    321 		u.AddUserAgentFeature(x)
    322 	}
    323 }
    324 
    325 // ID the name of the middleware.
    326 func (u *RequestUserAgent) ID() string {
    327 	return "UserAgent"
    328 }
    329 
    330 // HandleBuild adds or appends the constructed user agent to the request.
    331 func (u *RequestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
    332 	out middleware.BuildOutput, metadata middleware.Metadata, err error,
    333 ) {
    334 	switch req := in.Request.(type) {
    335 	case *smithyhttp.Request:
    336 		u.addHTTPUserAgent(req)
    337 		// TODO: To be re-enabled
    338 		// u.addHTTPSDKAgent(req)
    339 	default:
    340 		return out, metadata, fmt.Errorf("unknown transport type %T", in)
    341 	}
    342 
    343 	return next.HandleBuild(ctx, in)
    344 }
    345 
    346 func (u *RequestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) {
    347 	const userAgent = "User-Agent"
    348 	if len(u.features) > 0 {
    349 		updateHTTPHeader(request, userAgent, buildFeatureMetrics(u.features))
    350 	}
    351 	updateHTTPHeader(request, userAgent, u.userAgent.Build())
    352 }
    353 
    354 func (u *RequestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) {
    355 	const sdkAgent = "X-Amz-User-Agent"
    356 	updateHTTPHeader(request, sdkAgent, u.sdkAgent.Build())
    357 }
    358 
    359 func updateHTTPHeader(request *smithyhttp.Request, header string, value string) {
    360 	var current string
    361 	if v := request.Header[header]; len(v) > 0 {
    362 		current = v[0]
    363 	}
    364 	if len(current) > 0 {
    365 		current = value + " " + current
    366 	} else {
    367 		current = value
    368 	}
    369 	request.Header[header] = append(request.Header[header][:0], current)
    370 }
    371 
    372 func rules(r rune) rune {
    373 	switch {
    374 	case r >= '0' && r <= '9':
    375 		return r
    376 	case r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z':
    377 		return r
    378 	case validChars[r]:
    379 		return r
    380 	default:
    381 		return '-'
    382 	}
    383 }
    384 
    385 func buildFeatureMetrics(features map[UserAgentFeature]struct{}) string {
    386 	fs := make([]string, 0, len(features))
    387 	for f := range features {
    388 		fs = append(fs, string(f))
    389 	}
    390 
    391 	sort.Strings(fs)
    392 	return fmt.Sprintf("%s/%s", FeatureMetadata2.string(), strings.Join(fs, ","))
    393 }