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}