host_style_bucket.go 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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. if accelerate && accelerateOpBlacklist.Continue(r) {
  35. if forceHostStyle {
  36. if r.Config.Logger != nil {
  37. r.Config.Logger.Log("ERROR: aws.Config.S3UseAccelerate is not compatible with aws.Config.S3ForcePathStyle, ignoring S3ForcePathStyle.")
  38. }
  39. }
  40. updateEndpointForAccelerate(r)
  41. } else if !forceHostStyle && r.Operation.Name != opGetBucketLocation {
  42. updateEndpointForHostStyle(r)
  43. }
  44. }
  45. func updateEndpointForHostStyle(r *request.Request) {
  46. bucket, ok := bucketNameFromReqParams(r.Params)
  47. if !ok {
  48. // Ignore operation requests if the bucketname was not provided
  49. // if this is an input validation error the validation handler
  50. // will report it.
  51. return
  52. }
  53. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  54. // bucket name must be valid to put into the host
  55. return
  56. }
  57. moveBucketToHost(r.HTTPRequest.URL, bucket)
  58. }
  59. var (
  60. accelElem = []byte("s3-accelerate.dualstack.")
  61. )
  62. func updateEndpointForAccelerate(r *request.Request) {
  63. bucket, ok := bucketNameFromReqParams(r.Params)
  64. if !ok {
  65. // Ignore operation requests if the bucketname was not provided
  66. // if this is an input validation error the validation handler
  67. // will report it.
  68. return
  69. }
  70. if !hostCompatibleBucketName(r.HTTPRequest.URL, bucket) {
  71. r.Error = awserr.New("InvalidParameterException",
  72. fmt.Sprintf("bucket name %s is not compatible with S3 Accelerate", bucket),
  73. nil)
  74. return
  75. }
  76. parts := strings.Split(r.HTTPRequest.URL.Host, ".")
  77. if len(parts) < 3 {
  78. r.Error = awserr.New("InvalidParameterExecption",
  79. fmt.Sprintf("unable to update endpoint host for S3 accelerate, hostname invalid, %s",
  80. r.HTTPRequest.URL.Host), nil)
  81. return
  82. }
  83. if parts[0] == "s3" || strings.HasPrefix(parts[0], "s3-") {
  84. parts[0] = "s3-accelerate"
  85. }
  86. for i := 1; i+1 < len(parts); i++ {
  87. if parts[i] == aws.StringValue(r.Config.Region) {
  88. parts = append(parts[:i], parts[i+1:]...)
  89. break
  90. }
  91. }
  92. r.HTTPRequest.URL.Host = strings.Join(parts, ".")
  93. moveBucketToHost(r.HTTPRequest.URL, bucket)
  94. }
  95. // Attempts to retrieve the bucket name from the request input parameters.
  96. // If no bucket is found, or the field is empty "", false will be returned.
  97. func bucketNameFromReqParams(params interface{}) (string, bool) {
  98. b, _ := awsutil.ValuesAtPath(params, "Bucket")
  99. if len(b) == 0 {
  100. return "", false
  101. }
  102. if bucket, ok := b[0].(*string); ok {
  103. if bucketStr := aws.StringValue(bucket); bucketStr != "" {
  104. return bucketStr, true
  105. }
  106. }
  107. return "", false
  108. }
  109. // hostCompatibleBucketName returns true if the request should
  110. // put the bucket in the host. This is false if S3ForcePathStyle is
  111. // explicitly set or if the bucket is not DNS compatible.
  112. func hostCompatibleBucketName(u *url.URL, bucket string) bool {
  113. // Bucket might be DNS compatible but dots in the hostname will fail
  114. // certificate validation, so do not use host-style.
  115. if u.Scheme == "https" && strings.Contains(bucket, ".") {
  116. return false
  117. }
  118. // if the bucket is DNS compatible
  119. return dnsCompatibleBucketName(bucket)
  120. }
  121. var reDomain = regexp.MustCompile(`^[a-z0-9][a-z0-9\.\-]{1,61}[a-z0-9]$`)
  122. var reIPAddress = regexp.MustCompile(`^(\d+\.){3}\d+$`)
  123. // dnsCompatibleBucketName returns true if the bucket name is DNS compatible.
  124. // Buckets created outside of the classic region MUST be DNS compatible.
  125. func dnsCompatibleBucketName(bucket string) bool {
  126. return reDomain.MatchString(bucket) &&
  127. !reIPAddress.MatchString(bucket) &&
  128. !strings.Contains(bucket, "..")
  129. }
  130. // moveBucketToHost moves the bucket name from the URI path to URL host.
  131. func moveBucketToHost(u *url.URL, bucket string) {
  132. u.Host = bucket + "." + u.Host
  133. u.Path = strings.Replace(u.Path, "/{Bucket}", "", -1)
  134. if u.Path == "" {
  135. u.Path = "/"
  136. }
  137. }