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