endpoints.go raw

   1  package endpoints
   2  
   3  import (
   4  	"fmt"
   5  	"regexp"
   6  	"strings"
   7  
   8  	"github.com/aws/aws-sdk-go-v2/aws"
   9  )
  10  
  11  const (
  12  	defaultProtocol = "https"
  13  	defaultSigner   = "v4"
  14  )
  15  
  16  var (
  17  	protocolPriority = []string{"https", "http"}
  18  	signerPriority   = []string{"v4"}
  19  )
  20  
  21  // Options provide configuration needed to direct how endpoints are resolved.
  22  type Options struct {
  23  	// Disable usage of HTTPS (TLS / SSL)
  24  	DisableHTTPS bool
  25  }
  26  
  27  // Partitions is a slice of partition
  28  type Partitions []Partition
  29  
  30  // ResolveEndpoint resolves a service endpoint for the given region and options.
  31  func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) {
  32  	if len(ps) == 0 {
  33  		return aws.Endpoint{}, fmt.Errorf("no partitions found")
  34  	}
  35  
  36  	for i := 0; i < len(ps); i++ {
  37  		if !ps[i].canResolveEndpoint(region) {
  38  			continue
  39  		}
  40  
  41  		return ps[i].ResolveEndpoint(region, opts)
  42  	}
  43  
  44  	// fallback to first partition format to use when resolving the endpoint.
  45  	return ps[0].ResolveEndpoint(region, opts)
  46  }
  47  
  48  // Partition is an AWS partition description for a service and its' region endpoints.
  49  type Partition struct {
  50  	ID                string
  51  	RegionRegex       *regexp.Regexp
  52  	PartitionEndpoint string
  53  	IsRegionalized    bool
  54  	Defaults          Endpoint
  55  	Endpoints         Endpoints
  56  }
  57  
  58  func (p Partition) canResolveEndpoint(region string) bool {
  59  	_, ok := p.Endpoints[region]
  60  	return ok || p.RegionRegex.MatchString(region)
  61  }
  62  
  63  // ResolveEndpoint resolves and service endpoint for the given region and options.
  64  func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) {
  65  	if len(region) == 0 && len(p.PartitionEndpoint) != 0 {
  66  		region = p.PartitionEndpoint
  67  	}
  68  
  69  	e, _ := p.endpointForRegion(region)
  70  
  71  	return e.resolve(p.ID, region, p.Defaults, options), nil
  72  }
  73  
  74  func (p Partition) endpointForRegion(region string) (Endpoint, bool) {
  75  	if e, ok := p.Endpoints[region]; ok {
  76  		return e, true
  77  	}
  78  
  79  	if !p.IsRegionalized {
  80  		return p.Endpoints[p.PartitionEndpoint], region == p.PartitionEndpoint
  81  	}
  82  
  83  	// Unable to find any matching endpoint, return
  84  	// blank that will be used for generic endpoint creation.
  85  	return Endpoint{}, false
  86  }
  87  
  88  // Endpoints is a map of service config regions to endpoints
  89  type Endpoints map[string]Endpoint
  90  
  91  // CredentialScope is the credential scope of a region and service
  92  type CredentialScope struct {
  93  	Region  string
  94  	Service string
  95  }
  96  
  97  // Endpoint is a service endpoint description
  98  type Endpoint struct {
  99  	// True if the endpoint cannot be resolved for this partition/region/service
 100  	Unresolveable aws.Ternary
 101  
 102  	Hostname  string
 103  	Protocols []string
 104  
 105  	CredentialScope CredentialScope
 106  
 107  	SignatureVersions []string `json:"signatureVersions"`
 108  }
 109  
 110  func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) aws.Endpoint {
 111  	var merged Endpoint
 112  	merged.mergeIn(def)
 113  	merged.mergeIn(e)
 114  	e = merged
 115  
 116  	var u string
 117  	if e.Unresolveable != aws.TrueTernary {
 118  		// Only attempt to resolve the endpoint if it can be resolved.
 119  		hostname := strings.Replace(e.Hostname, "{region}", region, 1)
 120  
 121  		scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS)
 122  		u = scheme + "://" + hostname
 123  	}
 124  
 125  	signingRegion := e.CredentialScope.Region
 126  	if len(signingRegion) == 0 {
 127  		signingRegion = region
 128  	}
 129  	signingName := e.CredentialScope.Service
 130  
 131  	return aws.Endpoint{
 132  		URL:           u,
 133  		PartitionID:   partition,
 134  		SigningRegion: signingRegion,
 135  		SigningName:   signingName,
 136  		SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
 137  	}
 138  }
 139  
 140  func (e *Endpoint) mergeIn(other Endpoint) {
 141  	if other.Unresolveable != aws.UnknownTernary {
 142  		e.Unresolveable = other.Unresolveable
 143  	}
 144  	if len(other.Hostname) > 0 {
 145  		e.Hostname = other.Hostname
 146  	}
 147  	if len(other.Protocols) > 0 {
 148  		e.Protocols = other.Protocols
 149  	}
 150  	if len(other.CredentialScope.Region) > 0 {
 151  		e.CredentialScope.Region = other.CredentialScope.Region
 152  	}
 153  	if len(other.CredentialScope.Service) > 0 {
 154  		e.CredentialScope.Service = other.CredentialScope.Service
 155  	}
 156  	if len(other.SignatureVersions) > 0 {
 157  		e.SignatureVersions = other.SignatureVersions
 158  	}
 159  }
 160  
 161  func getEndpointScheme(protocols []string, disableHTTPS bool) string {
 162  	if disableHTTPS {
 163  		return "http"
 164  	}
 165  
 166  	return getByPriority(protocols, protocolPriority, defaultProtocol)
 167  }
 168  
 169  func getByPriority(s []string, p []string, def string) string {
 170  	if len(s) == 0 {
 171  		return def
 172  	}
 173  
 174  	for i := 0; i < len(p); i++ {
 175  		for j := 0; j < len(s); j++ {
 176  			if s[j] == p[i] {
 177  				return s[j]
 178  			}
 179  		}
 180  	}
 181  
 182  	return s[0]
 183  }
 184  
 185  // MapFIPSRegion extracts the intrinsic AWS region from one that may have an
 186  // embedded FIPS microformat.
 187  func MapFIPSRegion(region string) string {
 188  	const fipsInfix = "-fips-"
 189  	const fipsPrefix = "fips-"
 190  	const fipsSuffix = "-fips"
 191  
 192  	if strings.Contains(region, fipsInfix) ||
 193  		strings.Contains(region, fipsPrefix) ||
 194  		strings.Contains(region, fipsSuffix) {
 195  		region = strings.ReplaceAll(region, fipsInfix, "-")
 196  		region = strings.ReplaceAll(region, fipsPrefix, "")
 197  		region = strings.ReplaceAll(region, fipsSuffix, "")
 198  	}
 199  
 200  	return region
 201  }
 202