You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

215 lines
6.0 KiB

9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
5 months ago
9 months ago
9 months ago
  1. package s3api
  2. import (
  3. "crypto/md5"
  4. "encoding/json"
  5. "fmt"
  6. "github.com/pquerna/cachecontrol/cacheobject"
  7. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  8. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  9. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  10. "github.com/seaweedfs/seaweedfs/weed/security"
  11. "io"
  12. "net/http"
  13. "strings"
  14. "time"
  15. "github.com/seaweedfs/seaweedfs/weed/glog"
  16. weed_server "github.com/seaweedfs/seaweedfs/weed/server"
  17. )
  18. func (s3a *S3ApiServer) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
  19. // http://docs.aws.amazon.com/AmazonS3/latest/dev/UploadingObjects.html
  20. bucket, object := s3_constants.GetBucketAndObject(r)
  21. glog.V(3).Infof("PutObjectHandler %s %s", bucket, object)
  22. _, err := validateContentMd5(r.Header)
  23. if err != nil {
  24. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidDigest)
  25. return
  26. }
  27. if r.Header.Get("Cache-Control") != "" {
  28. if _, err = cacheobject.ParseRequestCacheControl(r.Header.Get("Cache-Control")); err != nil {
  29. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidDigest)
  30. return
  31. }
  32. }
  33. if r.Header.Get("Expires") != "" {
  34. if _, err = time.Parse(http.TimeFormat, r.Header.Get("Expires")); err != nil {
  35. s3err.WriteErrorResponse(w, r, s3err.ErrMalformedDate)
  36. return
  37. }
  38. }
  39. dataReader := r.Body
  40. rAuthType := getRequestAuthType(r)
  41. if s3a.iam.isEnabled() {
  42. var s3ErrCode s3err.ErrorCode
  43. var identity *Identity
  44. switch rAuthType {
  45. case authTypeStreamingSigned:
  46. dataReader, identity, s3ErrCode = s3a.iam.newSignV4ChunkedReader(r)
  47. case authTypeSignedV2, authTypePresignedV2:
  48. identity, s3ErrCode = s3a.iam.isReqAuthenticatedV2(r)
  49. case authTypePresigned, authTypeSigned:
  50. identity, s3ErrCode = s3a.iam.reqSignatureV4Verify(r)
  51. case authTypeAnonymous:
  52. identity = s3a.iam.identityAnonymous
  53. }
  54. if s3ErrCode != s3err.ErrNone {
  55. s3err.WriteErrorResponse(w, r, s3ErrCode)
  56. return
  57. }
  58. if identity != nil && identity.Account.Id != AccountAnonymous.Id {
  59. r.Header.Set(s3_constants.AmzAccountId, identity.Account.Id)
  60. }
  61. } else {
  62. if authTypeStreamingSigned == rAuthType {
  63. s3err.WriteErrorResponse(w, r, s3err.ErrAuthNotSetup)
  64. return
  65. }
  66. }
  67. defer dataReader.Close()
  68. errCode := s3a.CheckAccessForPutObject(r, bucket, object)
  69. if errCode != s3err.ErrNone {
  70. s3err.WriteErrorResponse(w, r, errCode)
  71. return
  72. }
  73. objectContentType := r.Header.Get("Content-Type")
  74. if strings.HasSuffix(object, "/") && r.ContentLength == 0 {
  75. if err := s3a.mkdir(
  76. s3a.option.BucketsPath, bucket+strings.TrimSuffix(object, "/"),
  77. func(entry *filer_pb.Entry) {
  78. if objectContentType == "" {
  79. objectContentType = "httpd/unix-directory"
  80. }
  81. entry.Attributes.Mime = objectContentType
  82. }); err != nil {
  83. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  84. return
  85. }
  86. } else {
  87. uploadUrl := s3a.toFilerUrl(bucket, object)
  88. if objectContentType == "" {
  89. dataReader = mimeDetect(r, dataReader)
  90. }
  91. etag, errCode := s3a.putToFiler(r, uploadUrl, dataReader, "", bucket)
  92. if errCode != s3err.ErrNone {
  93. s3err.WriteErrorResponse(w, r, errCode)
  94. return
  95. }
  96. setEtag(w, etag)
  97. }
  98. writeSuccessResponseEmpty(w, r)
  99. }
  100. func (s3a *S3ApiServer) putToFiler(r *http.Request, uploadUrl string, dataReader io.Reader, destination string, bucket string) (etag string, code s3err.ErrorCode) {
  101. hash := md5.New()
  102. var body = io.TeeReader(dataReader, hash)
  103. proxyReq, err := http.NewRequest(http.MethodPut, uploadUrl, body)
  104. if err != nil {
  105. glog.Errorf("NewRequest %s: %v", uploadUrl, err)
  106. return "", s3err.ErrInternalError
  107. }
  108. proxyReq.Header.Set("X-Forwarded-For", r.RemoteAddr)
  109. if destination != "" {
  110. proxyReq.Header.Set(s3_constants.SeaweedStorageDestinationHeader, destination)
  111. }
  112. if s3a.option.FilerGroup != "" {
  113. query := proxyReq.URL.Query()
  114. query.Add("collection", s3a.getCollectionName(bucket))
  115. proxyReq.URL.RawQuery = query.Encode()
  116. }
  117. for header, values := range r.Header {
  118. for _, value := range values {
  119. proxyReq.Header.Add(header, value)
  120. }
  121. }
  122. // ensure that the Authorization header is overriding any previous
  123. // Authorization header which might be already present in proxyReq
  124. s3a.maybeAddFilerJwtAuthorization(proxyReq, true)
  125. resp, postErr := s3a.client.Do(proxyReq)
  126. if postErr != nil {
  127. glog.Errorf("post to filer: %v", postErr)
  128. return "", s3err.ErrInternalError
  129. }
  130. defer resp.Body.Close()
  131. etag = fmt.Sprintf("%x", hash.Sum(nil))
  132. resp_body, ra_err := io.ReadAll(resp.Body)
  133. if ra_err != nil {
  134. glog.Errorf("upload to filer response read %d: %v", resp.StatusCode, ra_err)
  135. return etag, s3err.ErrInternalError
  136. }
  137. var ret weed_server.FilerPostResult
  138. unmarshal_err := json.Unmarshal(resp_body, &ret)
  139. if unmarshal_err != nil {
  140. glog.Errorf("failing to read upload to %s : %v", uploadUrl, string(resp_body))
  141. return "", s3err.ErrInternalError
  142. }
  143. if ret.Error != "" {
  144. glog.Errorf("upload to filer error: %v", ret.Error)
  145. return "", filerErrorToS3Error(ret.Error)
  146. }
  147. return etag, s3err.ErrNone
  148. }
  149. func setEtag(w http.ResponseWriter, etag string) {
  150. if etag != "" {
  151. if strings.HasPrefix(etag, "\"") {
  152. w.Header()["ETag"] = []string{etag}
  153. } else {
  154. w.Header()["ETag"] = []string{"\"" + etag + "\""}
  155. }
  156. }
  157. }
  158. func filerErrorToS3Error(errString string) s3err.ErrorCode {
  159. switch {
  160. case strings.HasPrefix(errString, "existing ") && strings.HasSuffix(errString, "is a directory"):
  161. return s3err.ErrExistingObjectIsDirectory
  162. case strings.HasSuffix(errString, "is a file"):
  163. return s3err.ErrExistingObjectIsFile
  164. default:
  165. return s3err.ErrInternalError
  166. }
  167. }
  168. func (s3a *S3ApiServer) maybeAddFilerJwtAuthorization(r *http.Request, isWrite bool) {
  169. encodedJwt := s3a.maybeGetFilerJwtAuthorizationToken(isWrite)
  170. if encodedJwt == "" {
  171. return
  172. }
  173. r.Header.Set("Authorization", "BEARER "+string(encodedJwt))
  174. }
  175. func (s3a *S3ApiServer) maybeGetFilerJwtAuthorizationToken(isWrite bool) string {
  176. var encodedJwt security.EncodedJwt
  177. if isWrite {
  178. encodedJwt = security.GenJwtForFilerServer(s3a.filerGuard.SigningKey, s3a.filerGuard.ExpiresAfterSec)
  179. } else {
  180. encodedJwt = security.GenJwtForFilerServer(s3a.filerGuard.ReadSigningKey, s3a.filerGuard.ReadExpiresAfterSec)
  181. }
  182. return string(encodedJwt)
  183. }