src

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

user_agent.go (13854B)


      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 	UserAgentFeatureCredentialsProfileLogin = "AC"
    142 	UserAgentFeatureCredentialsLogin        = "AD"
    143 )
    144 
    145 var credentialSourceToFeature = map[aws.CredentialSource]UserAgentFeature{
    146 	aws.CredentialSourceCode:                 UserAgentFeatureCredentialsCode,
    147 	aws.CredentialSourceEnvVars:              UserAgentFeatureCredentialsEnvVars,
    148 	aws.CredentialSourceEnvVarsSTSWebIDToken: UserAgentFeatureCredentialsEnvVarsStsWebIDToken,
    149 	aws.CredentialSourceSTSAssumeRole:        UserAgentFeatureCredentialsStsAssumeRole,
    150 	aws.CredentialSourceSTSAssumeRoleSaml:    UserAgentFeatureCredentialsStsAssumeRoleSaml,
    151 	aws.CredentialSourceSTSAssumeRoleWebID:   UserAgentFeatureCredentialsStsAssumeRoleWebID,
    152 	aws.CredentialSourceSTSFederationToken:   UserAgentFeatureCredentialsStsFederationToken,
    153 	aws.CredentialSourceSTSSessionToken:      UserAgentFeatureCredentialsStsSessionToken,
    154 	aws.CredentialSourceProfile:              UserAgentFeatureCredentialsProfile,
    155 	aws.CredentialSourceProfileSourceProfile: UserAgentFeatureCredentialsProfileSourceProfile,
    156 	aws.CredentialSourceProfileNamedProvider: UserAgentFeatureCredentialsProfileNamedProvider,
    157 	aws.CredentialSourceProfileSTSWebIDToken: UserAgentFeatureCredentialsProfileStsWebIDToken,
    158 	aws.CredentialSourceProfileSSO:           UserAgentFeatureCredentialsProfileSso,
    159 	aws.CredentialSourceSSO:                  UserAgentFeatureCredentialsSso,
    160 	aws.CredentialSourceProfileSSOLegacy:     UserAgentFeatureCredentialsProfileSsoLegacy,
    161 	aws.CredentialSourceSSOLegacy:            UserAgentFeatureCredentialsSsoLegacy,
    162 	aws.CredentialSourceProfileProcess:       UserAgentFeatureCredentialsProfileProcess,
    163 	aws.CredentialSourceProcess:              UserAgentFeatureCredentialsProcess,
    164 	aws.CredentialSourceHTTP:                 UserAgentFeatureCredentialsHTTP,
    165 	aws.CredentialSourceIMDS:                 UserAgentFeatureCredentialsIMDS,
    166 	aws.CredentialSourceProfileLogin:         UserAgentFeatureCredentialsProfileLogin,
    167 	aws.CredentialSourceLogin:                UserAgentFeatureCredentialsLogin,
    168 }
    169 
    170 // RequestUserAgent is a build middleware that set the User-Agent for the request.
    171 type RequestUserAgent struct {
    172 	sdkAgent, userAgent *smithyhttp.UserAgentBuilder
    173 	features            map[UserAgentFeature]struct{}
    174 }
    175 
    176 // NewRequestUserAgent returns a new requestUserAgent which will set the User-Agent and X-Amz-User-Agent for the
    177 // request.
    178 //
    179 // User-Agent example:
    180 //
    181 //	aws-sdk-go-v2/1.2.3
    182 //
    183 // X-Amz-User-Agent example:
    184 //
    185 //	aws-sdk-go-v2/1.2.3 md/GOOS/linux md/GOARCH/amd64 lang/go/1.15
    186 func NewRequestUserAgent() *RequestUserAgent {
    187 	userAgent, sdkAgent := smithyhttp.NewUserAgentBuilder(), smithyhttp.NewUserAgentBuilder()
    188 	addProductName(userAgent)
    189 	addUserAgentMetadata(userAgent)
    190 	addProductName(sdkAgent)
    191 
    192 	r := &RequestUserAgent{
    193 		sdkAgent:  sdkAgent,
    194 		userAgent: userAgent,
    195 		features:  map[UserAgentFeature]struct{}{},
    196 	}
    197 
    198 	addSDKMetadata(r)
    199 
    200 	return r
    201 }
    202 
    203 func addSDKMetadata(r *RequestUserAgent) {
    204 	r.AddSDKAgentKey(OperatingSystemMetadata, getNormalizedOSName())
    205 	r.AddSDKAgentKeyValue(LanguageMetadata, "go", languageVersion)
    206 	r.AddSDKAgentKeyValue(AdditionalMetadata, "GOOS", runtime.GOOS)
    207 	r.AddSDKAgentKeyValue(AdditionalMetadata, "GOARCH", runtime.GOARCH)
    208 	if ev := os.Getenv(execEnvVar); len(ev) > 0 {
    209 		r.AddSDKAgentKey(EnvironmentMetadata, ev)
    210 	}
    211 }
    212 
    213 func addProductName(builder *smithyhttp.UserAgentBuilder) {
    214 	builder.AddKeyValue(aws.SDKName, aws.SDKVersion)
    215 }
    216 
    217 func addUserAgentMetadata(builder *smithyhttp.UserAgentBuilder) {
    218 	builder.AddKey(uaMetadata)
    219 }
    220 
    221 // AddUserAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
    222 func AddUserAgentKey(key string) func(*middleware.Stack) error {
    223 	return func(stack *middleware.Stack) error {
    224 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    225 		if err != nil {
    226 			return err
    227 		}
    228 		requestUserAgent.AddUserAgentKey(key)
    229 		return nil
    230 	}
    231 }
    232 
    233 // AddUserAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one.
    234 func AddUserAgentKeyValue(key, value string) func(*middleware.Stack) error {
    235 	return func(stack *middleware.Stack) error {
    236 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    237 		if err != nil {
    238 			return err
    239 		}
    240 		requestUserAgent.AddUserAgentKeyValue(key, value)
    241 		return nil
    242 	}
    243 }
    244 
    245 // AddSDKAgentKey retrieves a requestUserAgent from the provided stack, or initializes one.
    246 func AddSDKAgentKey(keyType SDKAgentKeyType, key string) func(*middleware.Stack) error {
    247 	return func(stack *middleware.Stack) error {
    248 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    249 		if err != nil {
    250 			return err
    251 		}
    252 		requestUserAgent.AddSDKAgentKey(keyType, key)
    253 		return nil
    254 	}
    255 }
    256 
    257 // AddSDKAgentKeyValue retrieves a requestUserAgent from the provided stack, or initializes one.
    258 func AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) func(*middleware.Stack) error {
    259 	return func(stack *middleware.Stack) error {
    260 		requestUserAgent, err := getOrAddRequestUserAgent(stack)
    261 		if err != nil {
    262 			return err
    263 		}
    264 		requestUserAgent.AddSDKAgentKeyValue(keyType, key, value)
    265 		return nil
    266 	}
    267 }
    268 
    269 // AddRequestUserAgentMiddleware registers a requestUserAgent middleware on the stack if not present.
    270 func AddRequestUserAgentMiddleware(stack *middleware.Stack) error {
    271 	_, err := getOrAddRequestUserAgent(stack)
    272 	return err
    273 }
    274 
    275 func getOrAddRequestUserAgent(stack *middleware.Stack) (*RequestUserAgent, error) {
    276 	id := (*RequestUserAgent)(nil).ID()
    277 	bm, ok := stack.Build.Get(id)
    278 	if !ok {
    279 		bm = NewRequestUserAgent()
    280 		err := stack.Build.Add(bm, middleware.After)
    281 		if err != nil {
    282 			return nil, err
    283 		}
    284 	}
    285 
    286 	requestUserAgent, ok := bm.(*RequestUserAgent)
    287 	if !ok {
    288 		return nil, fmt.Errorf("%T for %s middleware did not match expected type", bm, id)
    289 	}
    290 
    291 	return requestUserAgent, nil
    292 }
    293 
    294 // AddUserAgentKey adds the component identified by name to the User-Agent string.
    295 func (u *RequestUserAgent) AddUserAgentKey(key string) {
    296 	u.userAgent.AddKey(strings.Map(rules, key))
    297 }
    298 
    299 // AddUserAgentKeyValue adds the key identified by the given name and value to the User-Agent string.
    300 func (u *RequestUserAgent) AddUserAgentKeyValue(key, value string) {
    301 	u.userAgent.AddKeyValue(strings.Map(rules, key), strings.Map(rules, value))
    302 }
    303 
    304 // AddUserAgentFeature adds the feature ID to the tracking list to be emitted
    305 // in the final User-Agent string.
    306 func (u *RequestUserAgent) AddUserAgentFeature(feature UserAgentFeature) {
    307 	u.features[feature] = struct{}{}
    308 }
    309 
    310 // AddSDKAgentKey adds the component identified by name to the User-Agent string.
    311 func (u *RequestUserAgent) AddSDKAgentKey(keyType SDKAgentKeyType, key string) {
    312 	// TODO: should target sdkAgent
    313 	u.userAgent.AddKey(keyType.string() + "/" + strings.Map(rules, key))
    314 }
    315 
    316 // AddSDKAgentKeyValue adds the key identified by the given name and value to the User-Agent string.
    317 func (u *RequestUserAgent) AddSDKAgentKeyValue(keyType SDKAgentKeyType, key, value string) {
    318 	// TODO: should target sdkAgent
    319 	u.userAgent.AddKeyValue(keyType.string(), strings.Map(rules, key)+"#"+strings.Map(rules, value))
    320 }
    321 
    322 // AddCredentialsSource adds the credential source as a feature on the User-Agent string
    323 func (u *RequestUserAgent) AddCredentialsSource(source aws.CredentialSource) {
    324 	x, ok := credentialSourceToFeature[source]
    325 	if ok {
    326 		u.AddUserAgentFeature(x)
    327 	}
    328 }
    329 
    330 // ID the name of the middleware.
    331 func (u *RequestUserAgent) ID() string {
    332 	return "UserAgent"
    333 }
    334 
    335 // HandleBuild adds or appends the constructed user agent to the request.
    336 func (u *RequestUserAgent) HandleBuild(ctx context.Context, in middleware.BuildInput, next middleware.BuildHandler) (
    337 	out middleware.BuildOutput, metadata middleware.Metadata, err error,
    338 ) {
    339 	switch req := in.Request.(type) {
    340 	case *smithyhttp.Request:
    341 		u.addHTTPUserAgent(req)
    342 		// TODO: To be re-enabled
    343 		// u.addHTTPSDKAgent(req)
    344 	default:
    345 		return out, metadata, fmt.Errorf("unknown transport type %T", in)
    346 	}
    347 
    348 	return next.HandleBuild(ctx, in)
    349 }
    350 
    351 func (u *RequestUserAgent) addHTTPUserAgent(request *smithyhttp.Request) {
    352 	const userAgent = "User-Agent"
    353 	if len(u.features) > 0 {
    354 		updateHTTPHeader(request, userAgent, buildFeatureMetrics(u.features))
    355 	}
    356 	updateHTTPHeader(request, userAgent, u.userAgent.Build())
    357 }
    358 
    359 func (u *RequestUserAgent) addHTTPSDKAgent(request *smithyhttp.Request) {
    360 	const sdkAgent = "X-Amz-User-Agent"
    361 	updateHTTPHeader(request, sdkAgent, u.sdkAgent.Build())
    362 }
    363 
    364 func updateHTTPHeader(request *smithyhttp.Request, header string, value string) {
    365 	var current string
    366 	if v := request.Header[header]; len(v) > 0 {
    367 		current = v[0]
    368 	}
    369 	if len(current) > 0 {
    370 		current = value + " " + current
    371 	} else {
    372 		current = value
    373 	}
    374 	request.Header[header] = append(request.Header[header][:0], current)
    375 }
    376 
    377 func rules(r rune) rune {
    378 	switch {
    379 	case r >= '0' && r <= '9':
    380 		return r
    381 	case r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z':
    382 		return r
    383 	case validChars[r]:
    384 		return r
    385 	default:
    386 		return '-'
    387 	}
    388 }
    389 
    390 func buildFeatureMetrics(features map[UserAgentFeature]struct{}) string {
    391 	fs := make([]string, 0, len(features))
    392 	for f := range features {
    393 		fs = append(fs, string(f))
    394 	}
    395 
    396 	sort.Strings(fs)
    397 	return fmt.Sprintf("%s/%s", FeatureMetadata2.string(), strings.Join(fs, ","))
    398 }