endpoints.go 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302
  1. package endpoints
  2. import (
  3. "fmt"
  4. "github.com/aws/smithy-go/logging"
  5. "regexp"
  6. "strings"
  7. "github.com/aws/aws-sdk-go-v2/aws"
  8. )
  9. // DefaultKey is a compound map key of a variant and other values.
  10. type DefaultKey struct {
  11. Variant EndpointVariant
  12. ServiceVariant ServiceVariant
  13. }
  14. // EndpointKey is a compound map key of a region and associated variant value.
  15. type EndpointKey struct {
  16. Region string
  17. Variant EndpointVariant
  18. ServiceVariant ServiceVariant
  19. }
  20. // EndpointVariant is a bit field to describe the endpoints attributes.
  21. type EndpointVariant uint64
  22. const (
  23. // FIPSVariant indicates that the endpoint is FIPS capable.
  24. FIPSVariant EndpointVariant = 1 << (64 - 1 - iota)
  25. // DualStackVariant indicates that the endpoint is DualStack capable.
  26. DualStackVariant
  27. )
  28. // ServiceVariant is a bit field to describe the service endpoint attributes.
  29. type ServiceVariant uint64
  30. const (
  31. defaultProtocol = "https"
  32. defaultSigner = "v4"
  33. )
  34. var (
  35. protocolPriority = []string{"https", "http"}
  36. signerPriority = []string{"v4", "s3v4"}
  37. )
  38. // Options provide configuration needed to direct how endpoints are resolved.
  39. type Options struct {
  40. // Logger is a logging implementation that log events should be sent to.
  41. Logger logging.Logger
  42. // LogDeprecated indicates that deprecated endpoints should be logged to the provided logger.
  43. LogDeprecated bool
  44. // ResolvedRegion is the resolved region string. If provided (non-zero length) it takes priority
  45. // over the region name passed to the ResolveEndpoint call.
  46. ResolvedRegion string
  47. // Disable usage of HTTPS (TLS / SSL)
  48. DisableHTTPS bool
  49. // Instruct the resolver to use a service endpoint that supports dual-stack.
  50. // If a service does not have a dual-stack endpoint an error will be returned by the resolver.
  51. UseDualStackEndpoint aws.DualStackEndpointState
  52. // Instruct the resolver to use a service endpoint that supports FIPS.
  53. // If a service does not have a FIPS endpoint an error will be returned by the resolver.
  54. UseFIPSEndpoint aws.FIPSEndpointState
  55. // ServiceVariant is a bitfield of service specified endpoint variant data.
  56. ServiceVariant ServiceVariant
  57. }
  58. // GetEndpointVariant returns the EndpointVariant for the variant associated options.
  59. func (o Options) GetEndpointVariant() (v EndpointVariant) {
  60. if o.UseDualStackEndpoint == aws.DualStackEndpointStateEnabled {
  61. v |= DualStackVariant
  62. }
  63. if o.UseFIPSEndpoint == aws.FIPSEndpointStateEnabled {
  64. v |= FIPSVariant
  65. }
  66. return v
  67. }
  68. // Partitions is a slice of partition
  69. type Partitions []Partition
  70. // ResolveEndpoint resolves a service endpoint for the given region and options.
  71. func (ps Partitions) ResolveEndpoint(region string, opts Options) (aws.Endpoint, error) {
  72. if len(ps) == 0 {
  73. return aws.Endpoint{}, fmt.Errorf("no partitions found")
  74. }
  75. if opts.Logger == nil {
  76. opts.Logger = logging.Nop{}
  77. }
  78. if len(opts.ResolvedRegion) > 0 {
  79. region = opts.ResolvedRegion
  80. }
  81. for i := 0; i < len(ps); i++ {
  82. if !ps[i].canResolveEndpoint(region, opts) {
  83. continue
  84. }
  85. return ps[i].ResolveEndpoint(region, opts)
  86. }
  87. // fallback to first partition format to use when resolving the endpoint.
  88. return ps[0].ResolveEndpoint(region, opts)
  89. }
  90. // Partition is an AWS partition description for a service and its' region endpoints.
  91. type Partition struct {
  92. ID string
  93. RegionRegex *regexp.Regexp
  94. PartitionEndpoint string
  95. IsRegionalized bool
  96. Defaults map[DefaultKey]Endpoint
  97. Endpoints Endpoints
  98. }
  99. func (p Partition) canResolveEndpoint(region string, opts Options) bool {
  100. _, ok := p.Endpoints[EndpointKey{
  101. Region: region,
  102. Variant: opts.GetEndpointVariant(),
  103. }]
  104. return ok || p.RegionRegex.MatchString(region)
  105. }
  106. // ResolveEndpoint resolves and service endpoint for the given region and options.
  107. func (p Partition) ResolveEndpoint(region string, options Options) (resolved aws.Endpoint, err error) {
  108. if len(region) == 0 && len(p.PartitionEndpoint) != 0 {
  109. region = p.PartitionEndpoint
  110. }
  111. endpoints := p.Endpoints
  112. variant := options.GetEndpointVariant()
  113. serviceVariant := options.ServiceVariant
  114. defaults := p.Defaults[DefaultKey{
  115. Variant: variant,
  116. ServiceVariant: serviceVariant,
  117. }]
  118. return p.endpointForRegion(region, variant, serviceVariant, endpoints).resolve(p.ID, region, defaults, options)
  119. }
  120. func (p Partition) endpointForRegion(region string, variant EndpointVariant, serviceVariant ServiceVariant, endpoints Endpoints) Endpoint {
  121. key := EndpointKey{
  122. Region: region,
  123. Variant: variant,
  124. }
  125. if e, ok := endpoints[key]; ok {
  126. return e
  127. }
  128. if !p.IsRegionalized {
  129. return endpoints[EndpointKey{
  130. Region: p.PartitionEndpoint,
  131. Variant: variant,
  132. ServiceVariant: serviceVariant,
  133. }]
  134. }
  135. // Unable to find any matching endpoint, return
  136. // blank that will be used for generic endpoint creation.
  137. return Endpoint{}
  138. }
  139. // Endpoints is a map of service config regions to endpoints
  140. type Endpoints map[EndpointKey]Endpoint
  141. // CredentialScope is the credential scope of a region and service
  142. type CredentialScope struct {
  143. Region string
  144. Service string
  145. }
  146. // Endpoint is a service endpoint description
  147. type Endpoint struct {
  148. // True if the endpoint cannot be resolved for this partition/region/service
  149. Unresolveable aws.Ternary
  150. Hostname string
  151. Protocols []string
  152. CredentialScope CredentialScope
  153. SignatureVersions []string
  154. // Indicates that this endpoint is deprecated.
  155. Deprecated aws.Ternary
  156. }
  157. // IsZero returns whether the endpoint structure is an empty (zero) value.
  158. func (e Endpoint) IsZero() bool {
  159. switch {
  160. case e.Unresolveable != aws.UnknownTernary:
  161. return false
  162. case len(e.Hostname) != 0:
  163. return false
  164. case len(e.Protocols) != 0:
  165. return false
  166. case e.CredentialScope != (CredentialScope{}):
  167. return false
  168. case len(e.SignatureVersions) != 0:
  169. return false
  170. }
  171. return true
  172. }
  173. func (e Endpoint) resolve(partition, region string, def Endpoint, options Options) (aws.Endpoint, error) {
  174. var merged Endpoint
  175. merged.mergeIn(def)
  176. merged.mergeIn(e)
  177. e = merged
  178. if e.IsZero() {
  179. return aws.Endpoint{}, fmt.Errorf("unable to resolve endpoint for region: %v", region)
  180. }
  181. var u string
  182. if e.Unresolveable != aws.TrueTernary {
  183. // Only attempt to resolve the endpoint if it can be resolved.
  184. hostname := strings.Replace(e.Hostname, "{region}", region, 1)
  185. scheme := getEndpointScheme(e.Protocols, options.DisableHTTPS)
  186. u = scheme + "://" + hostname
  187. }
  188. signingRegion := e.CredentialScope.Region
  189. if len(signingRegion) == 0 {
  190. signingRegion = region
  191. }
  192. signingName := e.CredentialScope.Service
  193. if e.Deprecated == aws.TrueTernary && options.LogDeprecated {
  194. options.Logger.Logf(logging.Warn, "endpoint identifier %q, url %q marked as deprecated", region, u)
  195. }
  196. return aws.Endpoint{
  197. URL: u,
  198. PartitionID: partition,
  199. SigningRegion: signingRegion,
  200. SigningName: signingName,
  201. SigningMethod: getByPriority(e.SignatureVersions, signerPriority, defaultSigner),
  202. }, nil
  203. }
  204. func (e *Endpoint) mergeIn(other Endpoint) {
  205. if other.Unresolveable != aws.UnknownTernary {
  206. e.Unresolveable = other.Unresolveable
  207. }
  208. if len(other.Hostname) > 0 {
  209. e.Hostname = other.Hostname
  210. }
  211. if len(other.Protocols) > 0 {
  212. e.Protocols = other.Protocols
  213. }
  214. if len(other.CredentialScope.Region) > 0 {
  215. e.CredentialScope.Region = other.CredentialScope.Region
  216. }
  217. if len(other.CredentialScope.Service) > 0 {
  218. e.CredentialScope.Service = other.CredentialScope.Service
  219. }
  220. if len(other.SignatureVersions) > 0 {
  221. e.SignatureVersions = other.SignatureVersions
  222. }
  223. if other.Deprecated != aws.UnknownTernary {
  224. e.Deprecated = other.Deprecated
  225. }
  226. }
  227. func getEndpointScheme(protocols []string, disableHTTPS bool) string {
  228. if disableHTTPS {
  229. return "http"
  230. }
  231. return getByPriority(protocols, protocolPriority, defaultProtocol)
  232. }
  233. func getByPriority(s []string, p []string, def string) string {
  234. if len(s) == 0 {
  235. return def
  236. }
  237. for i := 0; i < len(p); i++ {
  238. for j := 0; j < len(s); j++ {
  239. if s[j] == p[i] {
  240. return s[j]
  241. }
  242. }
  243. }
  244. return s[0]
  245. }