endpoints.go (5041B)
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 }