1package endpoints
2
3import (
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.
13type DefaultKey struct {
14 Variant EndpointVariant
15 ServiceVariant ServiceVariant
16}
17
18// EndpointKey is a compound map key of a region and associated variant value.
19type EndpointKey struct {
20 Region string
21 Variant EndpointVariant
22 ServiceVariant ServiceVariant
23}
24
25// EndpointVariant is a bit field to describe the endpoints attributes.
26type EndpointVariant uint64
27
28const (
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.
37type ServiceVariant uint64
38
39const (
40 defaultProtocol = "https"
41 defaultSigner = "v4"
42)
43
44var (
45 protocolPriority = []string{"https", "http"}
46 signerPriority = []string{"v4", "s3v4"}
47)
48
49// Options provide configuration needed to direct how endpoints are resolved.
50type 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.
77func (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
88type Partitions []Partition
89
90// ResolveEndpoint resolves a service endpoint for the given region and options.
91func (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.
117type 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
126func (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.
135func (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
153func (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
177type Endpoints map[EndpointKey]Endpoint
178
179// CredentialScope is the credential scope of a region and service
180type CredentialScope struct {
181 Region string
182 Service string
183}
184
185// Endpoint is a service endpoint description
186type 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.
202func (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
218func (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
256func (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
280func getEndpointScheme(protocols []string, disableHTTPS bool) string {
281 if disableHTTPS {
282 return "http"
283 }
284
285 return getByPriority(protocols, protocolPriority, defaultProtocol)
286}
287
288func 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}