github.com/aavshr/aws-sdk-go@v1.41.3/aws/endpoints/v3model.go (about) 1 package endpoints 2 3 import ( 4 "fmt" 5 "regexp" 6 "strconv" 7 "strings" 8 ) 9 10 const ( 11 ec2MetadataEndpointIPv6 = "http://[fd00:ec2::254]/latest" 12 ec2MetadataEndpointIPv4 = "http://169.254.169.254/latest" 13 ) 14 15 var regionValidationRegex = regexp.MustCompile(`^[[:alnum:]]([[:alnum:]\-]*[[:alnum:]])?$`) 16 17 type partitions []partition 18 19 func (ps partitions) EndpointFor(service, region string, opts ...func(*Options)) (ResolvedEndpoint, error) { 20 var opt Options 21 opt.Set(opts...) 22 23 for i := 0; i < len(ps); i++ { 24 if !ps[i].canResolveEndpoint(service, region, opt.StrictMatching) { 25 continue 26 } 27 28 return ps[i].EndpointFor(service, region, opts...) 29 } 30 31 // If loose matching fallback to first partition format to use 32 // when resolving the endpoint. 33 if !opt.StrictMatching && len(ps) > 0 { 34 return ps[0].EndpointFor(service, region, opts...) 35 } 36 37 return ResolvedEndpoint{}, NewUnknownEndpointError("all partitions", service, region, []string{}) 38 } 39 40 // Partitions satisfies the EnumPartitions interface and returns a list 41 // of Partitions representing each partition represented in the SDK's 42 // endpoints model. 43 func (ps partitions) Partitions() []Partition { 44 parts := make([]Partition, 0, len(ps)) 45 for i := 0; i < len(ps); i++ { 46 parts = append(parts, ps[i].Partition()) 47 } 48 49 return parts 50 } 51 52 type partition struct { 53 ID string `json:"partition"` 54 Name string `json:"partitionName"` 55 DNSSuffix string `json:"dnsSuffix"` 56 RegionRegex regionRegex `json:"regionRegex"` 57 Defaults endpoint `json:"defaults"` 58 Regions regions `json:"regions"` 59 Services services `json:"services"` 60 } 61 62 func (p partition) Partition() Partition { 63 return Partition{ 64 dnsSuffix: p.DNSSuffix, 65 id: p.ID, 66 p: &p, 67 } 68 } 69 70 func (p partition) canResolveEndpoint(service, region string, strictMatch bool) bool { 71 s, hasService := p.Services[service] 72 _, hasEndpoint := s.Endpoints[region] 73 74 if hasEndpoint && hasService { 75 return true 76 } 77 78 if strictMatch { 79 return false 80 } 81 82 return p.RegionRegex.MatchString(region) 83 } 84 85 func allowLegacyEmptyRegion(service string) bool { 86 legacy := map[string]struct{}{ 87 "budgets": {}, 88 "ce": {}, 89 "chime": {}, 90 "cloudfront": {}, 91 "ec2metadata": {}, 92 "iam": {}, 93 "importexport": {}, 94 "organizations": {}, 95 "route53": {}, 96 "sts": {}, 97 "support": {}, 98 "waf": {}, 99 } 100 101 _, allowed := legacy[service] 102 return allowed 103 } 104 105 func (p partition) EndpointFor(service, region string, opts ...func(*Options)) (resolved ResolvedEndpoint, err error) { 106 var opt Options 107 opt.Set(opts...) 108 109 s, hasService := p.Services[service] 110 111 if service == Ec2metadataServiceID && !hasService { 112 endpoint := getEC2MetadataEndpoint(p.ID, service, opt.EC2MetadataEndpointMode) 113 return endpoint, nil 114 } 115 116 if len(service) == 0 || !(hasService || opt.ResolveUnknownService) { 117 // Only return error if the resolver will not fallback to creating 118 // endpoint based on service endpoint ID passed in. 119 return resolved, NewUnknownServiceError(p.ID, service, serviceList(p.Services)) 120 } 121 122 if len(region) == 0 && allowLegacyEmptyRegion(service) && len(s.PartitionEndpoint) != 0 { 123 region = s.PartitionEndpoint 124 } 125 126 if (service == "sts" && opt.STSRegionalEndpoint != RegionalSTSEndpoint) || 127 (service == "s3" && opt.S3UsEast1RegionalEndpoint != RegionalS3UsEast1Endpoint) { 128 if _, ok := legacyGlobalRegions[service][region]; ok { 129 region = "aws-global" 130 } 131 } 132 133 e, hasEndpoint := s.endpointForRegion(region) 134 if len(region) == 0 || (!hasEndpoint && opt.StrictMatching) { 135 return resolved, NewUnknownEndpointError(p.ID, service, region, endpointList(s.Endpoints)) 136 } 137 138 defs := []endpoint{p.Defaults, s.Defaults} 139 140 return e.resolve(service, p.ID, region, p.DNSSuffix, defs, opt) 141 } 142 143 func getEC2MetadataEndpoint(partitionID, service string, mode EC2IMDSEndpointModeState) ResolvedEndpoint { 144 switch mode { 145 case EC2IMDSEndpointModeStateIPv6: 146 return ResolvedEndpoint{ 147 URL: ec2MetadataEndpointIPv6, 148 PartitionID: partitionID, 149 SigningRegion: "aws-global", 150 SigningName: service, 151 SigningNameDerived: true, 152 SigningMethod: "v4", 153 } 154 case EC2IMDSEndpointModeStateIPv4: 155 fallthrough 156 default: 157 return ResolvedEndpoint{ 158 URL: ec2MetadataEndpointIPv4, 159 PartitionID: partitionID, 160 SigningRegion: "aws-global", 161 SigningName: service, 162 SigningNameDerived: true, 163 SigningMethod: "v4", 164 } 165 } 166 } 167 168 func serviceList(ss services) []string { 169 list := make([]string, 0, len(ss)) 170 for k := range ss { 171 list = append(list, k) 172 } 173 return list 174 } 175 func endpointList(es endpoints) []string { 176 list := make([]string, 0, len(es)) 177 for k := range es { 178 list = append(list, k) 179 } 180 return list 181 } 182 183 type regionRegex struct { 184 *regexp.Regexp 185 } 186 187 func (rr *regionRegex) UnmarshalJSON(b []byte) (err error) { 188 // Strip leading and trailing quotes 189 regex, err := strconv.Unquote(string(b)) 190 if err != nil { 191 return fmt.Errorf("unable to strip quotes from regex, %v", err) 192 } 193 194 rr.Regexp, err = regexp.Compile(regex) 195 if err != nil { 196 return fmt.Errorf("unable to unmarshal region regex, %v", err) 197 } 198 return nil 199 } 200 201 type regions map[string]region 202 203 type region struct { 204 Description string `json:"description"` 205 } 206 207 type services map[string]service 208 209 type service struct { 210 PartitionEndpoint string `json:"partitionEndpoint"` 211 IsRegionalized boxedBool `json:"isRegionalized,omitempty"` 212 Defaults endpoint `json:"defaults"` 213 Endpoints endpoints `json:"endpoints"` 214 } 215 216 func (s *service) endpointForRegion(region string) (endpoint, bool) { 217 if e, ok := s.Endpoints[region]; ok { 218 return e, true 219 } 220 221 if s.IsRegionalized == boxedFalse { 222 return s.Endpoints[s.PartitionEndpoint], region == s.PartitionEndpoint 223 } 224 225 // Unable to find any matching endpoint, return 226 // blank that will be used for generic endpoint creation. 227 return endpoint{}, false 228 } 229 230 type endpoints map[string]endpoint 231 232 type endpoint struct { 233 Hostname string `json:"hostname"` 234 Protocols []string `json:"protocols"` 235 CredentialScope credentialScope `json:"credentialScope"` 236 237 // Custom fields not modeled 238 HasDualStack boxedBool `json:"-"` 239 DualStackHostname string `json:"-"` 240 241 // Signature Version not used 242 SignatureVersions []string `json:"signatureVersions"` 243 244 // SSLCommonName not used. 245 SSLCommonName string `json:"sslCommonName"` 246 } 247 248 const ( 249 defaultProtocol = "https" 250 defaultSigner = "v4" 251 ) 252 253 var ( 254 protocolPriority = []string{"https", "http"} 255 signerPriority = []string{"v4", "v2"} 256 ) 257 258 func getByPriority(s []string, p []string, def string) string { 259 if len(s) == 0 { 260 return def 261 } 262 263 for i := 0; i < len(p); i++ { 264 for j := 0; j < len(s); j++ { 265 if s[j] == p[i] { 266 return s[j] 267 } 268 } 269 } 270 271 return s[0] 272 } 273 274 func (e endpoint) resolve(service, partitionID, region, dnsSuffix string, defs []endpoint, opts Options) (ResolvedEndpoint, error) { 275 var merged endpoint 276 for _, def := range defs { 277 merged.mergeIn(def) 278 } 279 merged.mergeIn(e) 280 e = merged 281 282 signingRegion := e.CredentialScope.Region 283 if len(signingRegion) == 0 { 284 signingRegion = region 285 } 286 287 signingName := e.CredentialScope.Service 288 var signingNameDerived bool 289 if len(signingName) == 0 { 290 signingName = service 291 signingNameDerived = true 292 } 293 294 hostname := e.Hostname 295 // Offset the hostname for dualstack if enabled 296 if opts.UseDualStack && e.HasDualStack == boxedTrue { 297 hostname = e.DualStackHostname 298 region = signingRegion 299 } 300 301 if !validateInputRegion(region) { 302 return ResolvedEndpoint{}, fmt.Errorf("invalid region identifier format provided") 303 } 304 305 u := strings.Replace(hostname, "{service}", service, 1) 306 u = strings.Replace(u, "{region}", region, 1) 307 u = strings.Replace(u, "{dnsSuffix}", dnsSuffix, 1) 308 309 scheme := getEndpointScheme(e.Protocols, opts.DisableSSL) 310 u = fmt.Sprintf("%s://%s", scheme, u) 311 312 return ResolvedEndpoint{ 313 URL: u, 314 PartitionID: partitionID, 315 SigningRegion: signingRegion, 316 SigningName: signingName, 317 SigningNameDerived: signingNameDerived, 318 SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner), 319 }, nil 320 } 321 322 func getEndpointScheme(protocols []string, disableSSL bool) string { 323 if disableSSL { 324 return "http" 325 } 326 327 return getByPriority(protocols, protocolPriority, defaultProtocol) 328 } 329 330 func (e *endpoint) mergeIn(other endpoint) { 331 if len(other.Hostname) > 0 { 332 e.Hostname = other.Hostname 333 } 334 if len(other.Protocols) > 0 { 335 e.Protocols = other.Protocols 336 } 337 if len(other.SignatureVersions) > 0 { 338 e.SignatureVersions = other.SignatureVersions 339 } 340 if len(other.CredentialScope.Region) > 0 { 341 e.CredentialScope.Region = other.CredentialScope.Region 342 } 343 if len(other.CredentialScope.Service) > 0 { 344 e.CredentialScope.Service = other.CredentialScope.Service 345 } 346 if len(other.SSLCommonName) > 0 { 347 e.SSLCommonName = other.SSLCommonName 348 } 349 if other.HasDualStack != boxedBoolUnset { 350 e.HasDualStack = other.HasDualStack 351 } 352 if len(other.DualStackHostname) > 0 { 353 e.DualStackHostname = other.DualStackHostname 354 } 355 } 356 357 type credentialScope struct { 358 Region string `json:"region"` 359 Service string `json:"service"` 360 } 361 362 type boxedBool int 363 364 func (b *boxedBool) UnmarshalJSON(buf []byte) error { 365 v, err := strconv.ParseBool(string(buf)) 366 if err != nil { 367 return err 368 } 369 370 if v { 371 *b = boxedTrue 372 } else { 373 *b = boxedFalse 374 } 375 376 return nil 377 } 378 379 const ( 380 boxedBoolUnset boxedBool = iota 381 boxedFalse 382 boxedTrue 383 ) 384 385 func validateInputRegion(region string) bool { 386 return regionValidationRegex.MatchString(region) 387 }