endpoints.go (8011B)
1 package endpoints 2 3 import ( 4 "fmt" 5 "regexp" 6 "strings" 7 8 "github.com/aws/smithy-go/logging" 9 10 "github.com/aws/aws-sdk-go-v2/aws" 11 ) 12 13 // DefaultKey is a compound map key of a variant and other values. 14 type DefaultKey struct { 15 Variant EndpointVariant 16 ServiceVariant ServiceVariant 17 } 18 19 // EndpointKey is a compound map key of a region and associated variant value. 20 type EndpointKey struct { 21 Region string 22 Variant EndpointVariant 23 ServiceVariant ServiceVariant 24 } 25 26 // EndpointVariant is a bit field to describe the endpoints attributes. 27 type EndpointVariant uint64 28 29 const ( 30 // FIPSVariant indicates that the endpoint is FIPS capable. 31 FIPSVariant EndpointVariant = 1 << (64 - 1 - iota) 32 33 // DualStackVariant indicates that the endpoint is DualStack capable. 34 DualStackVariant 35 ) 36 37 // ServiceVariant is a bit field to describe the service endpoint attributes. 38 type ServiceVariant uint64 39 40 const ( 41 defaultProtocol = "https" 42 defaultSigner = "v4" 43 ) 44 45 var ( 46 protocolPriority = []string{"https", "http"} 47 signerPriority = []string{"v4", "s3v4"} 48 ) 49 50 // Options provide configuration needed to direct how endpoints are resolved. 51 type Options struct { 52 // Logger is a logging implementation that log events should be sent to. 53 Logger logging.Logger 54 55 // LogDeprecated indicates that deprecated endpoints should be logged to the provided logger. 56 LogDeprecated bool 57 58 // ResolvedRegion is the resolved region string. If provided (non-zero length) it takes priority 59 // over the region name passed to the ResolveEndpoint call. 60 ResolvedRegion string 61 62 // Disable usage of HTTPS (TLS / SSL) 63 DisableHTTPS bool 64 65 // Instruct the resolver to use a service endpoint that supports dual-stack. 66 // If a service does not have a dual-stack endpoint an error will be returned by the resolver. 67 UseDualStackEndpoint aws.DualStackEndpointState 68 69 // Instruct the resolver to use a service endpoint that supports FIPS. 70 // If a service does not have a FIPS endpoint an error will be returned by the resolver. 71 UseFIPSEndpoint aws.FIPSEndpointState 72 73 // ServiceVariant is a bitfield of service specified endpoint variant data. 74 ServiceVariant ServiceVariant 75 } 76 77 // GetEndpointVariant returns the EndpointVariant for the variant associated options. 78 func (o Options) GetEndpointVariant() (v EndpointVariant) { 79 if o.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled { 80 v |= DualStackVariant 81 } 82 if o.UseFIPSEndpoint == aws.FIPSEndpointStateEnabled { 83 v |= FIPSVariant 84 } 85 return v 86 } 87 88 // Partitions is a slice of partition 89 type Partitions []Partition 90 91 // ResolveEndpoint resolves a service endpoint for the given region and options. 92 func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) { 93 if len(ps) == 0 { 94 return aws.Endpoint{}, fmt.Errorf("no partitions found") 95 } 96 97 if opts.Logger == nil { 98 opts.Logger = logging.Nop{} 99 } 100 101 if len(opts.ResolvedRegion) > 0 { 102 region = opts.ResolvedRegion 103 } 104 105 for i := range ps { 106 if !ps[i].canResolveEndpoint(region, opts) { 107 continue 108 } 109 110 return ps[i].ResolveEndpoint(region, opts) 111 } 112 113 // fallback to first partition format to use when resolving the endpoint. 114 return ps[0].ResolveEndpoint(region, opts) 115 } 116 117 // Partition is an AWS partition description for a service and its' region endpoints. 118 type Partition struct { 119 ID string 120 RegionRegex *regexp.Regexp 121 PartitionEndpoint string 122 IsRegionalized bool 123 Defaults map[DefaultKey]Endpoint 124 Endpoints Endpoints 125 } 126 127 func (p Partition) canResolveEndpoint(region string, opts Options) bool { 128 _, ok := p.Endpoints[EndpointKey{ 129 Region: region, 130 Variant: opts.GetEndpointVariant(), 131 }] 132 return ok || p.RegionRegex.MatchString(region) 133 } 134 135 // ResolveEndpoint resolves and service endpoint for the given region and options. 136 func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) { 137 if len(region) == 0 && len(p.PartitionEndpoint) != 0 { 138 region = p.PartitionEndpoint 139 } 140 141 endpoints := p.Endpoints 142 143 variant := options.GetEndpointVariant() 144 serviceVariant := options.ServiceVariant 145 146 defaults := p.Defaults[DefaultKey{ 147 Variant: variant, 148 ServiceVariant: serviceVariant, 149 }] 150 151 return p.endpointForRegion(region, variant, serviceVariant, endpoints).resolve(p.ID, region, defaults, options) 152 } 153 154 func (p Partition) endpointForRegion(region string, variant EndpointVariant, serviceVariant ServiceVariant, endpoints Endpoints) Endpoint { 155 key := EndpointKey{ 156 Region: region, 157 Variant: variant, 158 } 159 160 if e, ok := endpoints[key]; ok { 161 return e 162 } 163 164 if !p.IsRegionalized { 165 return endpoints[EndpointKey{ 166 Region: p.PartitionEndpoint, 167 Variant: variant, 168 ServiceVariant: serviceVariant, 169 }] 170 } 171 172 // Unable to find any matching endpoint, return 173 // blank that will be used for generic endpoint creation. 174 return Endpoint{} 175 } 176 177 // Endpoints is a map of service config regions to endpoints 178 type Endpoints map[EndpointKey]Endpoint 179 180 // CredentialScope is the credential scope of a region and service 181 type CredentialScope struct { 182 Region string 183 Service string 184 } 185 186 // Endpoint is a service endpoint description 187 type Endpoint struct { 188 // True if the endpoint cannot be resolved for this partition/region/service 189 Unresolveable aws.Ternary 190 191 Hostname string 192 Protocols []string 193 194 CredentialScope CredentialScope 195 196 SignatureVersions []string 197 198 // Indicates that this endpoint is deprecated. 199 Deprecated aws.Ternary 200 } 201 202 // IsZero returns whether the endpoint structure is an empty (zero) value. 203 func (e Endpoint) IsZero() bool { 204 switch { 205 case e.Unresolveable != aws.UnknownTernary: 206 return false 207 case len(e.Hostname) != 0: 208 return false 209 case len(e.Protocols) != 0: 210 return false 211 case e.CredentialScope != (CredentialScope{}): 212 return false 213 case len(e.SignatureVersions) != 0: 214 return false 215 } 216 return true 217 } 218 219 func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) (aws.Endpoint, error) { 220 var merged Endpoint 221 merged.mergeIn(def) 222 merged.mergeIn(e) 223 e = merged 224 225 if e.IsZero() { 226 return aws.Endpoint{}, fmt.Errorf("unable to resolve endpoint for region: %v", region) 227 } 228 229 var u string 230 if e.Unresolveable != aws.TrueTernary { 231 // Only attempt to resolve the endpoint if it can be resolved. 232 hostname := strings.Replace(e.Hostname, "{region}", region, 1) 233 234 scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS) 235 u = scheme + "://" + hostname 236 } 237 238 signingRegion := e.CredentialScope.Region 239 if len(signingRegion) == 0 { 240 signingRegion = region 241 } 242 signingName := e.CredentialScope.Service 243 244 if e.Deprecated == aws.TrueTernary && options.LogDeprecated { 245 options.Logger.Logf(logging.Warn, "endpoint identifier %q, url %q marked as deprecated", region, u) 246 } 247 248 return aws.Endpoint{ 249 URL: u, 250 PartitionID: partition, 251 SigningRegion: signingRegion, 252 SigningName: signingName, 253 SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner), 254 }, nil 255 } 256 257 func (e *Endpoint) mergeIn(other Endpoint) { 258 if other.Unresolveable != aws.UnknownTernary { 259 e.Unresolveable = other.Unresolveable 260 } 261 if len(other.Hostname) > 0 { 262 e.Hostname = other.Hostname 263 } 264 if len(other.Protocols) > 0 { 265 e.Protocols = other.Protocols 266 } 267 if len(other.CredentialScope.Region) > 0 { 268 e.CredentialScope.Region = other.CredentialScope.Region 269 } 270 if len(other.CredentialScope.Service) > 0 { 271 e.CredentialScope.Service = other.CredentialScope.Service 272 } 273 if len(other.SignatureVersions) > 0 { 274 e.SignatureVersions = other.SignatureVersions 275 } 276 if other.Deprecated != aws.UnknownTernary { 277 e.Deprecated = other.Deprecated 278 } 279 } 280 281 func getEndpointScheme(protocols []string, disableHTTPS bool) string { 282 if disableHTTPS { 283 return "http" 284 } 285 286 return getByPriority(protocols, protocolPriority, defaultProtocol) 287 } 288 289 func getByPriority(s []string, p []string, def string) string { 290 if len(s) == 0 { 291 return def 292 } 293 294 for i := range p { 295 for j := range s { 296 if s[j] == p[i] { 297 return s[j] 298 } 299 } 300 } 301 302 return s[0] 303 }