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 }