303 lines
7.9 KiB
Go
303 lines
7.9 KiB
Go
|
package endpoints
|
||
|
|
||
|
import (
|
||
|
"fmt"
|
||
|
"github.com/aws/smithy-go/logging"
|
||
|
"regexp"
|
||
|
"strings"
|
||
|
|
||
|
"github.com/aws/aws-sdk-go-v2/aws"
|
||
|
)
|
||
|
|
||
|
// DefaultKey is a compound map key of a variant and other values.
|
||
|
type DefaultKey struct {
|
||
|
Variant EndpointVariant
|
||
|
ServiceVariant ServiceVariant
|
||
|
}
|
||
|
|
||
|
// EndpointKey is a compound map key of a region and associated variant value.
|
||
|
type EndpointKey struct {
|
||
|
Region string
|
||
|
Variant EndpointVariant
|
||
|
ServiceVariant ServiceVariant
|
||
|
}
|
||
|
|
||
|
// EndpointVariant is a bit field to describe the endpoints attributes.
|
||
|
type EndpointVariant uint64
|
||
|
|
||
|
const (
|
||
|
// FIPSVariant indicates that the endpoint is FIPS capable.
|
||
|
FIPSVariant EndpointVariant = 1 << (64 - 1 - iota)
|
||
|
|
||
|
// DualStackVariant indicates that the endpoint is DualStack capable.
|
||
|
DualStackVariant
|
||
|
)
|
||
|
|
||
|
// ServiceVariant is a bit field to describe the service endpoint attributes.
|
||
|
type ServiceVariant uint64
|
||
|
|
||
|
const (
|
||
|
defaultProtocol = "https"
|
||
|
defaultSigner = "v4"
|
||
|
)
|
||
|
|
||
|
var (
|
||
|
protocolPriority = []string{"https", "http"}
|
||
|
signerPriority = []string{"v4", "s3v4"}
|
||
|
)
|
||
|
|
||
|
// Options provide configuration needed to direct how endpoints are resolved.
|
||
|
type Options struct {
|
||
|
// Logger is a logging implementation that log events should be sent to.
|
||
|
Logger logging.Logger
|
||
|
|
||
|
// LogDeprecated indicates that deprecated endpoints should be logged to the provided logger.
|
||
|
LogDeprecated bool
|
||
|
|
||
|
// ResolvedRegion is the resolved region string. If provided (non-zero length) it takes priority
|
||
|
// over the region name passed to the ResolveEndpoint call.
|
||
|
ResolvedRegion string
|
||
|
|
||
|
// Disable usage of HTTPS (TLS / SSL)
|
||
|
DisableHTTPS bool
|
||
|
|
||
|
// Instruct the resolver to use a service endpoint that supports dual-stack.
|
||
|
// If a service does not have a dual-stack endpoint an error will be returned by the resolver.
|
||
|
UseDualStackEndpoint aws.DualStackEndpointState
|
||
|
|
||
|
// Instruct the resolver to use a service endpoint that supports FIPS.
|
||
|
// If a service does not have a FIPS endpoint an error will be returned by the resolver.
|
||
|
UseFIPSEndpoint aws.FIPSEndpointState
|
||
|
|
||
|
// ServiceVariant is a bitfield of service specified endpoint variant data.
|
||
|
ServiceVariant ServiceVariant
|
||
|
}
|
||
|
|
||
|
// GetEndpointVariant returns the EndpointVariant for the variant associated options.
|
||
|
func (o Options) GetEndpointVariant() (v EndpointVariant) {
|
||
|
if o.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
|
||
|
v |= DualStackVariant
|
||
|
}
|
||
|
if o.UseFIPSEndpoint == aws.FIPSEndpointStateEnabled {
|
||
|
v |= FIPSVariant
|
||
|
}
|
||
|
return v
|
||
|
}
|
||
|
|
||
|
// Partitions is a slice of partition
|
||
|
type Partitions []Partition
|
||
|
|
||
|
// ResolveEndpoint resolves a service endpoint for the given region and options.
|
||
|
func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) {
|
||
|
if len(ps) == 0 {
|
||
|
return aws.Endpoint{}, fmt.Errorf("no partitions found")
|
||
|
}
|
||
|
|
||
|
if opts.Logger == nil {
|
||
|
opts.Logger = logging.Nop{}
|
||
|
}
|
||
|
|
||
|
if len(opts.ResolvedRegion) > 0 {
|
||
|
region = opts.ResolvedRegion
|
||
|
}
|
||
|
|
||
|
for i := 0; i < len(ps); i++ {
|
||
|
if !ps[i].canResolveEndpoint(region, opts) {
|
||
|
continue
|
||
|
}
|
||
|
|
||
|
return ps[i].ResolveEndpoint(region, opts)
|
||
|
}
|
||
|
|
||
|
// fallback to first partition format to use when resolving the endpoint.
|
||
|
return ps[0].ResolveEndpoint(region, opts)
|
||
|
}
|
||
|
|
||
|
// Partition is an AWS partition description for a service and its' region endpoints.
|
||
|
type Partition struct {
|
||
|
ID string
|
||
|
RegionRegex *regexp.Regexp
|
||
|
PartitionEndpoint string
|
||
|
IsRegionalized bool
|
||
|
Defaults map[DefaultKey]Endpoint
|
||
|
Endpoints Endpoints
|
||
|
}
|
||
|
|
||
|
func (p Partition) canResolveEndpoint(region string, opts Options) bool {
|
||
|
_, ok := p.Endpoints[EndpointKey{
|
||
|
Region: region,
|
||
|
Variant: opts.GetEndpointVariant(),
|
||
|
}]
|
||
|
return ok || p.RegionRegex.MatchString(region)
|
||
|
}
|
||
|
|
||
|
// ResolveEndpoint resolves and service endpoint for the given region and options.
|
||
|
func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) {
|
||
|
if len(region) == 0 && len(p.PartitionEndpoint) != 0 {
|
||
|
region = p.PartitionEndpoint
|
||
|
}
|
||
|
|
||
|
endpoints := p.Endpoints
|
||
|
|
||
|
variant := options.GetEndpointVariant()
|
||
|
serviceVariant := options.ServiceVariant
|
||
|
|
||
|
defaults := p.Defaults[DefaultKey{
|
||
|
Variant: variant,
|
||
|
ServiceVariant: serviceVariant,
|
||
|
}]
|
||
|
|
||
|
return p.endpointForRegion(region, variant, serviceVariant, endpoints).resolve(p.ID, region, defaults, options)
|
||
|
}
|
||
|
|
||
|
func (p Partition) endpointForRegion(region string, variant EndpointVariant, serviceVariant ServiceVariant, endpoints Endpoints) Endpoint {
|
||
|
key := EndpointKey{
|
||
|
Region: region,
|
||
|
Variant: variant,
|
||
|
}
|
||
|
|
||
|
if e, ok := endpoints[key]; ok {
|
||
|
return e
|
||
|
}
|
||
|
|
||
|
if !p.IsRegionalized {
|
||
|
return endpoints[EndpointKey{
|
||
|
Region: p.PartitionEndpoint,
|
||
|
Variant: variant,
|
||
|
ServiceVariant: serviceVariant,
|
||
|
}]
|
||
|
}
|
||
|
|
||
|
// Unable to find any matching endpoint, return
|
||
|
// blank that will be used for generic endpoint creation.
|
||
|
return Endpoint{}
|
||
|
}
|
||
|
|
||
|
// Endpoints is a map of service config regions to endpoints
|
||
|
type Endpoints map[EndpointKey]Endpoint
|
||
|
|
||
|
// CredentialScope is the credential scope of a region and service
|
||
|
type CredentialScope struct {
|
||
|
Region string
|
||
|
Service string
|
||
|
}
|
||
|
|
||
|
// Endpoint is a service endpoint description
|
||
|
type Endpoint struct {
|
||
|
// True if the endpoint cannot be resolved for this partition/region/service
|
||
|
Unresolveable aws.Ternary
|
||
|
|
||
|
Hostname string
|
||
|
Protocols []string
|
||
|
|
||
|
CredentialScope CredentialScope
|
||
|
|
||
|
SignatureVersions []string
|
||
|
|
||
|
// Indicates that this endpoint is deprecated.
|
||
|
Deprecated aws.Ternary
|
||
|
}
|
||
|
|
||
|
// IsZero returns whether the endpoint structure is an empty (zero) value.
|
||
|
func (e Endpoint) IsZero() bool {
|
||
|
switch {
|
||
|
case e.Unresolveable != aws.UnknownTernary:
|
||
|
return false
|
||
|
case len(e.Hostname) != 0:
|
||
|
return false
|
||
|
case len(e.Protocols) != 0:
|
||
|
return false
|
||
|
case e.CredentialScope != (CredentialScope{}):
|
||
|
return false
|
||
|
case len(e.SignatureVersions) != 0:
|
||
|
return false
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) (aws.Endpoint, error) {
|
||
|
var merged Endpoint
|
||
|
merged.mergeIn(def)
|
||
|
merged.mergeIn(e)
|
||
|
e = merged
|
||
|
|
||
|
if e.IsZero() {
|
||
|
return aws.Endpoint{}, fmt.Errorf("unable to resolve endpoint for region: %v", region)
|
||
|
}
|
||
|
|
||
|
var u string
|
||
|
if e.Unresolveable != aws.TrueTernary {
|
||
|
// Only attempt to resolve the endpoint if it can be resolved.
|
||
|
hostname := strings.Replace(e.Hostname, "{region}", region, 1)
|
||
|
|
||
|
scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS)
|
||
|
u = scheme + "://" + hostname
|
||
|
}
|
||
|
|
||
|
signingRegion := e.CredentialScope.Region
|
||
|
if len(signingRegion) == 0 {
|
||
|
signingRegion = region
|
||
|
}
|
||
|
signingName := e.CredentialScope.Service
|
||
|
|
||
|
if e.Deprecated == aws.TrueTernary && options.LogDeprecated {
|
||
|
options.Logger.Logf(logging.Warn, "endpoint identifier %q, url %q marked as deprecated", region, u)
|
||
|
}
|
||
|
|
||
|
return aws.Endpoint{
|
||
|
URL: u,
|
||
|
PartitionID: partition,
|
||
|
SigningRegion: signingRegion,
|
||
|
SigningName: signingName,
|
||
|
SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
|
||
|
}, nil
|
||
|
}
|
||
|
|
||
|
func (e *Endpoint) mergeIn(other Endpoint) {
|
||
|
if other.Unresolveable != aws.UnknownTernary {
|
||
|
e.Unresolveable = other.Unresolveable
|
||
|
}
|
||
|
if len(other.Hostname) > 0 {
|
||
|
e.Hostname = other.Hostname
|
||
|
}
|
||
|
if len(other.Protocols) > 0 {
|
||
|
e.Protocols = other.Protocols
|
||
|
}
|
||
|
if len(other.CredentialScope.Region) > 0 {
|
||
|
e.CredentialScope.Region = other.CredentialScope.Region
|
||
|
}
|
||
|
if len(other.CredentialScope.Service) > 0 {
|
||
|
e.CredentialScope.Service = other.CredentialScope.Service
|
||
|
}
|
||
|
if len(other.SignatureVersions) > 0 {
|
||
|
e.SignatureVersions = other.SignatureVersions
|
||
|
}
|
||
|
if other.Deprecated != aws.UnknownTernary {
|
||
|
e.Deprecated = other.Deprecated
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func getEndpointScheme(protocols []string, disableHTTPS bool) string {
|
||
|
if disableHTTPS {
|
||
|
return "http"
|
||
|
}
|
||
|
|
||
|
return getByPriority(protocols, protocolPriority, defaultProtocol)
|
||
|
}
|
||
|
|
||
|
func getByPriority(s []string, p []string, def string) string {
|
||
|
if len(s) == 0 {
|
||
|
return def
|
||
|
}
|
||
|
|
||
|
for i := 0; i < len(p); i++ {
|
||
|
for j := 0; j < len(s); j++ {
|
||
|
if s[j] == p[i] {
|
||
|
return s[j]
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return s[0]
|
||
|
}
|