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 }