host_style_bucket.go 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173
  1. package s3
  2. import (
  3. "fmt"
  4. "net/url"
  5. "regexp"
  6. "strings"
  7. "github.com/aws/aws-sdk-go/aws"
  8. "github.com/aws/aws-sdk-go/aws/awserr"
  9. "github.com/aws/aws-sdk-go/aws/awsutil"
  10. "github.com/aws/aws-sdk-go/aws/request"
  11. )
  12. // an operationBlacklist is a list of operation names that should a
  13. // request handler should not be executed with.
  14. type operationBlacklist []string
  15. // Continue will return true of the Request's operation name is not
  16. // in the blacklist. False otherwise.
  17. func (b operationBlacklist) Continue(r *request.Request) bool {
  18. for i := 0; i < len(b); i++ {
  19. if b[i] == r.Operation.Name {
  20. return false
  21. }
  22. }
  23. return true
  24. }
  25. var accelerateOpBlacklist = operationBlacklist{
  26. opListBuckets, opCreateBucket, opDeleteBucket,
  27. }
  28. // Request handler to automatically add the bucket name to the endpoint domain
  29. // if possible. This style of bucket is valid for all bucket names which are
  30. // DNS compatible and do not contain "."
  31. func updateEndpointForS3Config(r *request.Request) {
  32. forceHostStyle := aws.BoolValue(r.Config.S3ForcePathStyle)
  33. accelerate := aws.BoolValue(r.Config.S3UseAccelerate)
  34. useDualStack := aws.BoolValue(r.Config.UseDualStack)
  35. if useDualStack && accelerate {
  36. r.Error = awserr.New("InvalidParameterException",
  37. fmt.Sprintf("configuration aws.Config.UseDualStack is not compatible with aws.Config.Accelerate"),
  38. nil)
  39. return
  40. }
  41. if accelerate && accelerateOpBlacklist.Continue(r) {
  42. if forceHostStyle {
  43. if r.Config.Logger != nil {
  44. r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
  45. }
  46. }
  47. updateEndpointForAccelerate(r)
  48. } else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
  49. updateEndpointForHostStyle(r)
  50. }
  51. }
  52. func updateEndpointForHostStyle(r *request.Request) {
  53. bucket, ok := bucketNameFromReqParams(r.Params)
  54. if !ok {
  55. // Ignore operation requests if the bucketname was not provided
  56. // if this is an input validation error the validation handler
  57. // will report it.
  58. return
  59. }
  60. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  61. // bucket name must be valid to put into the host
  62. return
  63. }
  64. moveBucketToHost(r.HTTPRequest.URL, bucket)
  65. }
  66. func updateEndpointForAccelerate(r *request.Request) {
  67. bucket, ok := bucketNameFromReqParams(r.Params)
  68. if !ok {
  69. // Ignore operation requests if the bucketname was not provided
  70. // if this is an input validation error the validation handler
  71. // will report it.
  72. return
  73. }
  74. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  75. r.Error = awserr.New("InvalidParameterException",
  76. fmt.Sprintf("bucket name %s is not compatibile with S3 Accelerate", bucket),
  77. nil)
  78. return
  79. }
  80. // Change endpoint from s3(-[a-z0-1-])?.amazonaws.com to s3-accelerate.amazonaws.com
  81. r.HTTPRequest.URL.Host = replaceHostRegion(r.HTTPRequest.URL.Host, "accelerate")
  82. moveBucketToHost(r.HTTPRequest.URL, bucket)
  83. }
  84. // Attempts to retrieve the bucket name from the request input parameters.
  85. // If no bucket is found, or the field is empty "", false will be returned.
  86. func bucketNameFromReqParams(params interface{}) (string, bool) {
  87. b, _ := awsutil.ValuesAtPath(params, "Bucket")
  88. if len(b) == 0 {
  89. return "", false
  90. }
  91. if bucket, ok := b[0].(*string); ok {
  92. if bucketStr := aws.StringValue(bucket); bucketStr != "" {
  93. return bucketStr, true
  94. }
  95. }
  96. return "", false
  97. }
  98. // hostCompatibleBucketName returns true if the request should
  99. // put the bucket in the host. This is false if S3ForcePathStyle is
  100. // explicitly set or if the bucket is not DNS compatible.
  101. func hostCompatibleBucketName(u *url.URL, bucket string) bool {
  102. // Bucket might be DNS compatible but dots in the hostname will fail
  103. // certificate validation, so do not use host-style.
  104. if u.Scheme == "https" && strings.Contains(bucket, ".") {
  105. return false
  106. }
  107. // if the bucket is DNS compatible
  108. return dnsCompatibleBucketName(bucket)
  109. }
  110. var reDomain = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
  111. var reIPAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
  112. // dnsCompatibleBucketName returns true if the bucket name is DNS compatible.
  113. // Buckets created outside of the classic region MUST be DNS compatible.
  114. func dnsCompatibleBucketName(bucket string) bool {
  115. return reDomain.MatchString(bucket) &&
  116. !reIPAddress.MatchString(bucket) &&
  117. !strings.Contains(bucket, "..")
  118. }
  119. // moveBucketToHost moves the bucket name from the URI path to URL host.
  120. func moveBucketToHost(u *url.URL, bucket string) {
  121. u.Host = bucket + "." + u.Host
  122. u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
  123. if u.Path == "" {
  124. u.Path = "/"
  125. }
  126. }
  127. const s3HostPrefix = "s3"
  128. // replaceHostRegion replaces the S3 region string in the host with the
  129. // value provided. If v is empty the host prefix returned will be s3.
  130. func replaceHostRegion(host, v string) string {
  131. if !strings.HasPrefix(host, s3HostPrefix) {
  132. return host
  133. }
  134. suffix := host[len(s3HostPrefix):]
  135. for i := len(s3HostPrefix); i < len(host); i++ {
  136. if host[i] == '.' {
  137. // Trim until '.' leave the it in place.
  138. suffix = host[i:]
  139. break
  140. }
  141. }
  142. if len(v) == 0 {
  143. return fmt.Sprintf("s3%s", suffix)
  144. }
  145. return fmt.Sprintf("s3-%s%s", v, suffix)
  146. }