s3api_bucket_handlers.go 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883
  1. package s3api
  2. import (
  3. "bytes"
  4. "context"
  5. "encoding/json"
  6. "encoding/xml"
  7. "errors"
  8. "fmt"
  9. "math"
  10. "net/http"
  11. "sort"
  12. "strings"
  13. "time"
  14. "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
  15. "github.com/seaweedfs/seaweedfs/weed/s3api/s3bucket"
  16. "github.com/seaweedfs/seaweedfs/weed/filer"
  17. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  18. "github.com/seaweedfs/seaweedfs/weed/storage/needle"
  19. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  20. "github.com/aws/aws-sdk-go/aws"
  21. "github.com/aws/aws-sdk-go/service/s3"
  22. "github.com/seaweedfs/seaweedfs/weed/glog"
  23. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  24. util_http "github.com/seaweedfs/seaweedfs/weed/util/http"
  25. )
  26. func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
  27. glog.V(3).Infof("ListBucketsHandler")
  28. var identity *Identity
  29. var s3Err s3err.ErrorCode
  30. if s3a.iam.isEnabled() {
  31. // Use authRequest instead of authUser for consistency with other endpoints
  32. // This ensures the same authentication flow and any fixes (like prefix handling) are applied
  33. identity, s3Err = s3a.iam.authRequest(r, s3_constants.ACTION_LIST)
  34. if s3Err != s3err.ErrNone {
  35. s3err.WriteErrorResponse(w, r, s3Err)
  36. return
  37. }
  38. }
  39. var response ListAllMyBucketsResult
  40. entries, _, err := s3a.list(s3a.option.BucketsPath, "", "", false, math.MaxInt32)
  41. if err != nil {
  42. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  43. return
  44. }
  45. identityId := r.Header.Get(s3_constants.AmzIdentityId)
  46. var listBuckets ListAllMyBucketsList
  47. for _, entry := range entries {
  48. if entry.IsDirectory {
  49. // Check permissions for each bucket
  50. if identity != nil {
  51. // For JWT-authenticated users, use IAM authorization
  52. sessionToken := r.Header.Get("X-SeaweedFS-Session-Token")
  53. if s3a.iam.iamIntegration != nil && sessionToken != "" {
  54. // Use IAM authorization for JWT users
  55. errCode := s3a.iam.authorizeWithIAM(r, identity, s3_constants.ACTION_LIST, entry.Name, "")
  56. if errCode != s3err.ErrNone {
  57. continue
  58. }
  59. } else {
  60. // Use legacy authorization for non-JWT users
  61. if !identity.canDo(s3_constants.ACTION_LIST, entry.Name, "") {
  62. continue
  63. }
  64. }
  65. }
  66. listBuckets.Bucket = append(listBuckets.Bucket, ListAllMyBucketsEntry{
  67. Name: entry.Name,
  68. CreationDate: time.Unix(entry.Attributes.Crtime, 0).UTC(),
  69. })
  70. }
  71. }
  72. response = ListAllMyBucketsResult{
  73. Owner: CanonicalUser{
  74. ID: identityId,
  75. DisplayName: identityId,
  76. },
  77. Buckets: listBuckets,
  78. }
  79. writeSuccessResponseXML(w, r, response)
  80. }
  81. func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
  82. // collect parameters
  83. bucket, _ := s3_constants.GetBucketAndObject(r)
  84. // validate the bucket name
  85. err := s3bucket.VerifyS3BucketName(bucket)
  86. if err != nil {
  87. glog.Errorf("put invalid bucket name: %v %v", bucket, err)
  88. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidBucketName)
  89. return
  90. }
  91. // avoid duplicated buckets
  92. errCode := s3err.ErrNone
  93. if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  94. if resp, err := client.CollectionList(context.Background(), &filer_pb.CollectionListRequest{
  95. IncludeEcVolumes: true,
  96. IncludeNormalVolumes: true,
  97. }); err != nil {
  98. glog.Errorf("list collection: %v", err)
  99. return fmt.Errorf("list collections: %w", err)
  100. } else {
  101. for _, c := range resp.Collections {
  102. if s3a.getCollectionName(bucket) == c.Name {
  103. errCode = s3err.ErrBucketAlreadyExists
  104. break
  105. }
  106. }
  107. }
  108. return nil
  109. }); err != nil {
  110. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  111. return
  112. }
  113. if exist, err := s3a.exists(s3a.option.BucketsPath, bucket, true); err == nil && exist {
  114. errCode = s3err.ErrBucketAlreadyExists
  115. }
  116. if errCode != s3err.ErrNone {
  117. s3err.WriteErrorResponse(w, r, errCode)
  118. return
  119. }
  120. fn := func(entry *filer_pb.Entry) {
  121. if identityId := r.Header.Get(s3_constants.AmzIdentityId); identityId != "" {
  122. if entry.Extended == nil {
  123. entry.Extended = make(map[string][]byte)
  124. }
  125. entry.Extended[s3_constants.AmzIdentityId] = []byte(identityId)
  126. }
  127. }
  128. // create the folder for bucket, but lazily create actual collection
  129. if err := s3a.mkdir(s3a.option.BucketsPath, bucket, fn); err != nil {
  130. glog.Errorf("PutBucketHandler mkdir: %v", err)
  131. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  132. return
  133. }
  134. // Check for x-amz-bucket-object-lock-enabled header (S3 standard compliance)
  135. if objectLockHeaderValue := r.Header.Get(s3_constants.AmzBucketObjectLockEnabled); strings.EqualFold(objectLockHeaderValue, "true") {
  136. glog.V(3).Infof("PutBucketHandler: enabling Object Lock and Versioning for bucket %s due to x-amz-bucket-object-lock-enabled header", bucket)
  137. // Atomically update the configuration of the specified bucket. See the updateBucketConfig
  138. // function definition for detailed documentation on parameters and behavior.
  139. errCode := s3a.updateBucketConfig(bucket, func(bucketConfig *BucketConfig) error {
  140. // Enable versioning (required for Object Lock)
  141. bucketConfig.Versioning = s3_constants.VersioningEnabled
  142. // Create basic Object Lock configuration (enabled without default retention)
  143. objectLockConfig := &ObjectLockConfiguration{
  144. ObjectLockEnabled: s3_constants.ObjectLockEnabled,
  145. }
  146. // Set the cached Object Lock configuration
  147. bucketConfig.ObjectLockConfig = objectLockConfig
  148. return nil
  149. })
  150. if errCode != s3err.ErrNone {
  151. glog.Errorf("PutBucketHandler: failed to enable Object Lock for bucket %s: %v", bucket, errCode)
  152. s3err.WriteErrorResponse(w, r, errCode)
  153. return
  154. }
  155. glog.V(3).Infof("PutBucketHandler: enabled Object Lock and Versioning for bucket %s", bucket)
  156. }
  157. w.Header().Set("Location", "/"+bucket)
  158. writeSuccessResponseEmpty(w, r)
  159. }
  160. func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
  161. bucket, _ := s3_constants.GetBucketAndObject(r)
  162. glog.V(3).Infof("DeleteBucketHandler %s", bucket)
  163. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  164. s3err.WriteErrorResponse(w, r, err)
  165. return
  166. }
  167. err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  168. if !s3a.option.AllowDeleteBucketNotEmpty {
  169. entries, _, err := s3a.list(s3a.option.BucketsPath+"/"+bucket, "", "", false, 2)
  170. if err != nil {
  171. return fmt.Errorf("failed to list bucket %s: %v", bucket, err)
  172. }
  173. for _, entry := range entries {
  174. if entry.Name != s3_constants.MultipartUploadsFolder {
  175. return errors.New(s3err.GetAPIError(s3err.ErrBucketNotEmpty).Code)
  176. }
  177. }
  178. }
  179. // delete collection
  180. deleteCollectionRequest := &filer_pb.DeleteCollectionRequest{
  181. Collection: s3a.getCollectionName(bucket),
  182. }
  183. glog.V(1).Infof("delete collection: %v", deleteCollectionRequest)
  184. if _, err := client.DeleteCollection(context.Background(), deleteCollectionRequest); err != nil {
  185. return fmt.Errorf("delete collection %s: %v", bucket, err)
  186. }
  187. return nil
  188. })
  189. if err != nil {
  190. s3ErrorCode := s3err.ErrInternalError
  191. if err.Error() == s3err.GetAPIError(s3err.ErrBucketNotEmpty).Code {
  192. s3ErrorCode = s3err.ErrBucketNotEmpty
  193. }
  194. s3err.WriteErrorResponse(w, r, s3ErrorCode)
  195. return
  196. }
  197. err = s3a.rm(s3a.option.BucketsPath, bucket, false, true)
  198. if err != nil {
  199. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  200. return
  201. }
  202. // Clean up bucket-related caches and locks after successful deletion
  203. s3a.invalidateBucketConfigCache(bucket)
  204. s3err.WriteEmptyResponse(w, r, http.StatusNoContent)
  205. }
  206. func (s3a *S3ApiServer) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
  207. bucket, _ := s3_constants.GetBucketAndObject(r)
  208. glog.V(3).Infof("HeadBucketHandler %s", bucket)
  209. if entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket); entry == nil || errors.Is(err, filer_pb.ErrNotFound) {
  210. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  211. return
  212. }
  213. writeSuccessResponseEmpty(w, r)
  214. }
  215. func (s3a *S3ApiServer) checkBucket(r *http.Request, bucket string) s3err.ErrorCode {
  216. entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  217. if entry == nil || errors.Is(err, filer_pb.ErrNotFound) {
  218. return s3err.ErrNoSuchBucket
  219. }
  220. //if iam is enabled, the access was already checked before
  221. if s3a.iam.isEnabled() {
  222. return s3err.ErrNone
  223. }
  224. if !s3a.hasAccess(r, entry) {
  225. return s3err.ErrAccessDenied
  226. }
  227. return s3err.ErrNone
  228. }
  229. func (s3a *S3ApiServer) hasAccess(r *http.Request, entry *filer_pb.Entry) bool {
  230. // Check if user is properly authenticated as admin through IAM system
  231. if s3a.isUserAdmin(r) {
  232. return true
  233. }
  234. if entry.Extended == nil {
  235. return true
  236. }
  237. identityId := r.Header.Get(s3_constants.AmzIdentityId)
  238. if id, ok := entry.Extended[s3_constants.AmzIdentityId]; ok {
  239. if identityId != string(id) {
  240. glog.V(3).Infof("hasAccess: %s != %s (entry.Extended = %v)", identityId, id, entry.Extended)
  241. return false
  242. }
  243. }
  244. return true
  245. }
  246. // isUserAdmin securely checks if the authenticated user is an admin
  247. // This validates admin status through proper IAM authentication, not spoofable headers
  248. func (s3a *S3ApiServer) isUserAdmin(r *http.Request) bool {
  249. // Use a minimal admin action to authenticate and check admin status
  250. adminAction := Action("Admin")
  251. identity, errCode := s3a.iam.authRequest(r, adminAction)
  252. if errCode != s3err.ErrNone {
  253. return false
  254. }
  255. // Check if the authenticated identity has admin privileges
  256. return identity != nil && identity.isAdmin()
  257. }
  258. // isBucketPublicRead checks if a bucket allows anonymous read access based on its cached ACL status
  259. func (s3a *S3ApiServer) isBucketPublicRead(bucket string) bool {
  260. // Get bucket configuration which contains cached public-read status
  261. config, errCode := s3a.getBucketConfig(bucket)
  262. if errCode != s3err.ErrNone {
  263. return false
  264. }
  265. // Return the cached public-read status (no JSON parsing needed)
  266. return config.IsPublicRead
  267. }
  268. // isPublicReadGrants checks if the grants allow public read access
  269. func isPublicReadGrants(grants []*s3.Grant) bool {
  270. for _, grant := range grants {
  271. if grant.Grantee != nil && grant.Grantee.URI != nil && grant.Permission != nil {
  272. // Check for AllUsers group with Read permission
  273. if *grant.Grantee.URI == s3_constants.GranteeGroupAllUsers &&
  274. (*grant.Permission == s3_constants.PermissionRead || *grant.Permission == s3_constants.PermissionFullControl) {
  275. return true
  276. }
  277. }
  278. }
  279. return false
  280. }
  281. // AuthWithPublicRead creates an auth wrapper that allows anonymous access for public-read buckets
  282. func (s3a *S3ApiServer) AuthWithPublicRead(handler http.HandlerFunc, action Action) http.HandlerFunc {
  283. return func(w http.ResponseWriter, r *http.Request) {
  284. bucket, _ := s3_constants.GetBucketAndObject(r)
  285. authType := getRequestAuthType(r)
  286. isAnonymous := authType == authTypeAnonymous
  287. // For anonymous requests, check if bucket allows public read
  288. if isAnonymous {
  289. isPublic := s3a.isBucketPublicRead(bucket)
  290. if isPublic {
  291. handler(w, r)
  292. return
  293. }
  294. }
  295. // For all authenticated requests and anonymous requests to non-public buckets,
  296. // use normal IAM auth to enforce policies
  297. s3a.iam.Auth(handler, action)(w, r)
  298. }
  299. }
  300. // GetBucketAclHandler Get Bucket ACL
  301. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketAcl.html
  302. func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Request) {
  303. // collect parameters
  304. bucket, _ := s3_constants.GetBucketAndObject(r)
  305. glog.V(3).Infof("GetBucketAclHandler %s", bucket)
  306. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  307. s3err.WriteErrorResponse(w, r, err)
  308. return
  309. }
  310. amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
  311. amzDisplayName := s3a.iam.GetAccountNameById(amzAccountId)
  312. response := AccessControlPolicy{
  313. Owner: CanonicalUser{
  314. ID: amzAccountId,
  315. DisplayName: amzDisplayName,
  316. },
  317. }
  318. response.AccessControlList.Grant = append(response.AccessControlList.Grant, Grant{
  319. Grantee: Grantee{
  320. ID: amzAccountId,
  321. DisplayName: amzDisplayName,
  322. Type: "CanonicalUser",
  323. XMLXSI: "CanonicalUser",
  324. XMLNS: "http://www.w3.org/2001/XMLSchema-instance"},
  325. Permission: s3.PermissionFullControl,
  326. })
  327. writeSuccessResponseXML(w, r, response)
  328. }
  329. // PutBucketAclHandler Put bucket ACL
  330. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html //
  331. func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Request) {
  332. // collect parameters
  333. bucket, _ := s3_constants.GetBucketAndObject(r)
  334. glog.V(3).Infof("PutBucketAclHandler %s", bucket)
  335. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  336. s3err.WriteErrorResponse(w, r, err)
  337. return
  338. }
  339. // Get account information for ACL processing
  340. amzAccountId := r.Header.Get(s3_constants.AmzAccountId)
  341. // Get bucket ownership settings (these would be used for ownership validation in a full implementation)
  342. bucketOwnership := "" // Default/simplified for now - in a full implementation this would be retrieved from bucket config
  343. bucketOwnerId := amzAccountId // Simplified - bucket owner is current account
  344. // Use the existing ACL parsing logic to handle both canned ACLs and XML body
  345. grants, errCode := ExtractAcl(r, s3a.iam, bucketOwnership, bucketOwnerId, amzAccountId, amzAccountId)
  346. if errCode != s3err.ErrNone {
  347. s3err.WriteErrorResponse(w, r, errCode)
  348. return
  349. }
  350. // Store the bucket ACL in bucket metadata
  351. errCode = s3a.updateBucketConfig(bucket, func(config *BucketConfig) error {
  352. if len(grants) > 0 {
  353. grantsBytes, err := json.Marshal(grants)
  354. if err != nil {
  355. glog.Errorf("PutBucketAclHandler: failed to marshal grants: %v", err)
  356. return err
  357. }
  358. config.ACL = grantsBytes
  359. // Cache the public-read status to avoid JSON parsing on every request
  360. config.IsPublicRead = isPublicReadGrants(grants)
  361. } else {
  362. config.ACL = nil
  363. config.IsPublicRead = false
  364. }
  365. config.Owner = amzAccountId
  366. return nil
  367. })
  368. if errCode != s3err.ErrNone {
  369. s3err.WriteErrorResponse(w, r, errCode)
  370. return
  371. }
  372. glog.V(3).Infof("PutBucketAclHandler: Successfully stored ACL for bucket %s with %d grants", bucket, len(grants))
  373. writeSuccessResponseEmpty(w, r)
  374. }
  375. // GetBucketLifecycleConfigurationHandler Get Bucket Lifecycle configuration
  376. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html
  377. func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
  378. // collect parameters
  379. bucket, _ := s3_constants.GetBucketAndObject(r)
  380. glog.V(3).Infof("GetBucketLifecycleConfigurationHandler %s", bucket)
  381. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  382. s3err.WriteErrorResponse(w, r, err)
  383. return
  384. }
  385. fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
  386. if err != nil {
  387. glog.Errorf("GetBucketLifecycleConfigurationHandler: %s", err)
  388. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  389. return
  390. }
  391. ttls := fc.GetCollectionTtls(s3a.getCollectionName(bucket))
  392. if len(ttls) == 0 {
  393. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchLifecycleConfiguration)
  394. return
  395. }
  396. response := Lifecycle{}
  397. // Sort locationPrefixes to ensure consistent ordering of lifecycle rules
  398. var locationPrefixes []string
  399. for locationPrefix := range ttls {
  400. locationPrefixes = append(locationPrefixes, locationPrefix)
  401. }
  402. sort.Strings(locationPrefixes)
  403. for _, locationPrefix := range locationPrefixes {
  404. internalTtl := ttls[locationPrefix]
  405. ttl, _ := needle.ReadTTL(internalTtl)
  406. days := int(ttl.Minutes() / 60 / 24)
  407. if days == 0 {
  408. continue
  409. }
  410. prefix, found := strings.CutPrefix(locationPrefix, fmt.Sprintf("%s/%s/", s3a.option.BucketsPath, bucket))
  411. if !found {
  412. continue
  413. }
  414. response.Rules = append(response.Rules, Rule{
  415. ID: prefix,
  416. Status: Enabled,
  417. Prefix: Prefix{val: prefix, set: true},
  418. Expiration: Expiration{Days: days, set: true},
  419. })
  420. }
  421. writeSuccessResponseXML(w, r, response)
  422. }
  423. // PutBucketLifecycleConfigurationHandler Put Bucket Lifecycle configuration
  424. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
  425. func (s3a *S3ApiServer) PutBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
  426. // collect parameters
  427. bucket, _ := s3_constants.GetBucketAndObject(r)
  428. glog.V(3).Infof("PutBucketLifecycleConfigurationHandler %s", bucket)
  429. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  430. s3err.WriteErrorResponse(w, r, err)
  431. return
  432. }
  433. lifeCycleConfig := Lifecycle{}
  434. if err := xmlDecoder(r.Body, &lifeCycleConfig, r.ContentLength); err != nil {
  435. glog.Warningf("PutBucketLifecycleConfigurationHandler xml decode: %s", err)
  436. s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML)
  437. return
  438. }
  439. fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
  440. if err != nil {
  441. glog.Errorf("PutBucketLifecycleConfigurationHandler read filer config: %s", err)
  442. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  443. return
  444. }
  445. collectionName := s3a.getCollectionName(bucket)
  446. collectionTtls := fc.GetCollectionTtls(collectionName)
  447. changed := false
  448. for _, rule := range lifeCycleConfig.Rules {
  449. if rule.Status != Enabled {
  450. continue
  451. }
  452. var rulePrefix string
  453. switch {
  454. case rule.Filter.Prefix.set:
  455. rulePrefix = rule.Filter.Prefix.val
  456. case rule.Prefix.set:
  457. rulePrefix = rule.Prefix.val
  458. case !rule.Expiration.Date.IsZero() || rule.Transition.Days > 0 || !rule.Transition.Date.IsZero():
  459. s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
  460. return
  461. }
  462. if rule.Expiration.Days == 0 {
  463. continue
  464. }
  465. locConf := &filer_pb.FilerConf_PathConf{
  466. LocationPrefix: fmt.Sprintf("%s/%s/%s", s3a.option.BucketsPath, bucket, rulePrefix),
  467. Collection: collectionName,
  468. Ttl: fmt.Sprintf("%dd", rule.Expiration.Days),
  469. }
  470. if ttl, ok := collectionTtls[locConf.LocationPrefix]; ok && ttl == locConf.Ttl {
  471. continue
  472. }
  473. if err := fc.AddLocationConf(locConf); err != nil {
  474. glog.Errorf("PutBucketLifecycleConfigurationHandler add location config: %s", err)
  475. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  476. return
  477. }
  478. changed = true
  479. }
  480. if changed {
  481. var buf bytes.Buffer
  482. if err := fc.ToText(&buf); err != nil {
  483. glog.Errorf("PutBucketLifecycleConfigurationHandler save config to text: %s", err)
  484. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  485. }
  486. if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  487. return filer.SaveInsideFiler(client, filer.DirectoryEtcSeaweedFS, filer.FilerConfName, buf.Bytes())
  488. }); err != nil {
  489. glog.Errorf("PutBucketLifecycleConfigurationHandler save config inside filer: %s", err)
  490. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  491. return
  492. }
  493. }
  494. writeSuccessResponseEmpty(w, r)
  495. }
  496. // DeleteBucketLifecycleHandler Delete Bucket Lifecycle
  497. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html
  498. func (s3a *S3ApiServer) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
  499. // collect parameters
  500. bucket, _ := s3_constants.GetBucketAndObject(r)
  501. glog.V(3).Infof("DeleteBucketLifecycleHandler %s", bucket)
  502. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  503. s3err.WriteErrorResponse(w, r, err)
  504. return
  505. }
  506. fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
  507. if err != nil {
  508. glog.Errorf("DeleteBucketLifecycleHandler read filer config: %s", err)
  509. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  510. return
  511. }
  512. collectionTtls := fc.GetCollectionTtls(s3a.getCollectionName(bucket))
  513. changed := false
  514. for prefix, ttl := range collectionTtls {
  515. bucketPrefix := fmt.Sprintf("%s/%s/", s3a.option.BucketsPath, bucket)
  516. if strings.HasPrefix(prefix, bucketPrefix) && strings.HasSuffix(ttl, "d") {
  517. pathConf, found := fc.GetLocationConf(prefix)
  518. if found {
  519. pathConf.Ttl = ""
  520. fc.SetLocationConf(pathConf)
  521. }
  522. changed = true
  523. }
  524. }
  525. if changed {
  526. var buf bytes.Buffer
  527. if err := fc.ToText(&buf); err != nil {
  528. glog.Errorf("DeleteBucketLifecycleHandler save config to text: %s", err)
  529. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  530. }
  531. if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  532. return filer.SaveInsideFiler(client, filer.DirectoryEtcSeaweedFS, filer.FilerConfName, buf.Bytes())
  533. }); err != nil {
  534. glog.Errorf("DeleteBucketLifecycleHandler save config inside filer: %s", err)
  535. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  536. return
  537. }
  538. }
  539. s3err.WriteEmptyResponse(w, r, http.StatusNoContent)
  540. }
  541. // GetBucketLocationHandler Get bucket location
  542. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html
  543. func (s3a *S3ApiServer) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
  544. bucket, _ := s3_constants.GetBucketAndObject(r)
  545. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  546. s3err.WriteErrorResponse(w, r, err)
  547. return
  548. }
  549. writeSuccessResponseXML(w, r, CreateBucketConfiguration{})
  550. }
  551. // GetBucketRequestPaymentHandler Get bucket location
  552. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketRequestPayment.html
  553. func (s3a *S3ApiServer) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
  554. writeSuccessResponseXML(w, r, RequestPaymentConfiguration{Payer: "BucketOwner"})
  555. }
  556. // PutBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketOwnershipControls.html
  557. func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
  558. bucket, _ := s3_constants.GetBucketAndObject(r)
  559. glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
  560. errCode := s3a.checkAccessByOwnership(r, bucket)
  561. if errCode != s3err.ErrNone {
  562. s3err.WriteErrorResponse(w, r, errCode)
  563. return
  564. }
  565. if r.Body == nil || r.Body == http.NoBody {
  566. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  567. return
  568. }
  569. var v s3.OwnershipControls
  570. defer util_http.CloseRequest(r)
  571. err := xmlutil.UnmarshalXML(&v, xml.NewDecoder(r.Body), "")
  572. if err != nil {
  573. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  574. return
  575. }
  576. if len(v.Rules) != 1 {
  577. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  578. return
  579. }
  580. printOwnership := true
  581. ownership := *v.Rules[0].ObjectOwnership
  582. switch ownership {
  583. case s3_constants.OwnershipObjectWriter:
  584. case s3_constants.OwnershipBucketOwnerPreferred:
  585. case s3_constants.OwnershipBucketOwnerEnforced:
  586. printOwnership = false
  587. default:
  588. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  589. return
  590. }
  591. // Check if ownership needs to be updated
  592. currentOwnership, errCode := s3a.getBucketOwnership(bucket)
  593. if errCode != s3err.ErrNone {
  594. s3err.WriteErrorResponse(w, r, errCode)
  595. return
  596. }
  597. if currentOwnership != ownership {
  598. errCode = s3a.setBucketOwnership(bucket, ownership)
  599. if errCode != s3err.ErrNone {
  600. s3err.WriteErrorResponse(w, r, errCode)
  601. return
  602. }
  603. }
  604. if printOwnership {
  605. result := &s3.PutBucketOwnershipControlsInput{
  606. OwnershipControls: &v,
  607. }
  608. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
  609. } else {
  610. writeSuccessResponseEmpty(w, r)
  611. }
  612. }
  613. // GetBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketOwnershipControls.html
  614. func (s3a *S3ApiServer) GetBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
  615. bucket, _ := s3_constants.GetBucketAndObject(r)
  616. glog.V(3).Infof("GetBucketOwnershipControls %s", bucket)
  617. errCode := s3a.checkAccessByOwnership(r, bucket)
  618. if errCode != s3err.ErrNone {
  619. s3err.WriteErrorResponse(w, r, errCode)
  620. return
  621. }
  622. // Get ownership using new bucket config system
  623. ownership, errCode := s3a.getBucketOwnership(bucket)
  624. if errCode == s3err.ErrNoSuchBucket {
  625. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  626. return
  627. } else if errCode != s3err.ErrNone {
  628. s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
  629. return
  630. }
  631. result := &s3.PutBucketOwnershipControlsInput{
  632. OwnershipControls: &s3.OwnershipControls{
  633. Rules: []*s3.OwnershipControlsRule{
  634. {
  635. ObjectOwnership: &ownership,
  636. },
  637. },
  638. },
  639. }
  640. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
  641. }
  642. // DeleteBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketOwnershipControls.html
  643. func (s3a *S3ApiServer) DeleteBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
  644. bucket, _ := s3_constants.GetBucketAndObject(r)
  645. glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
  646. errCode := s3a.checkAccessByOwnership(r, bucket)
  647. if errCode != s3err.ErrNone {
  648. s3err.WriteErrorResponse(w, r, errCode)
  649. return
  650. }
  651. bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  652. if err != nil {
  653. if errors.Is(err, filer_pb.ErrNotFound) {
  654. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  655. return
  656. }
  657. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  658. return
  659. }
  660. _, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
  661. if !ok {
  662. s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
  663. return
  664. }
  665. delete(bucketEntry.Extended, s3_constants.ExtOwnershipKey)
  666. err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
  667. if err != nil {
  668. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  669. return
  670. }
  671. emptyOwnershipControls := &s3.OwnershipControls{
  672. Rules: []*s3.OwnershipControlsRule{},
  673. }
  674. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, emptyOwnershipControls)
  675. }
  676. // GetBucketVersioningHandler Get Bucket Versioning status
  677. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketVersioning.html
  678. func (s3a *S3ApiServer) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
  679. bucket, _ := s3_constants.GetBucketAndObject(r)
  680. glog.V(3).Infof("GetBucketVersioning %s", bucket)
  681. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  682. s3err.WriteErrorResponse(w, r, err)
  683. return
  684. }
  685. // Get versioning status using new bucket config system
  686. versioningStatus, errCode := s3a.getBucketVersioningStatus(bucket)
  687. if errCode != s3err.ErrNone {
  688. s3err.WriteErrorResponse(w, r, errCode)
  689. return
  690. }
  691. // AWS S3 behavior: If versioning was never configured, don't return Status field
  692. var response *s3.PutBucketVersioningInput
  693. if versioningStatus == "" {
  694. // No versioning configuration - return empty response (no Status field)
  695. response = &s3.PutBucketVersioningInput{
  696. VersioningConfiguration: &s3.VersioningConfiguration{},
  697. }
  698. } else {
  699. // Versioning was explicitly configured - return the status
  700. response = &s3.PutBucketVersioningInput{
  701. VersioningConfiguration: &s3.VersioningConfiguration{
  702. Status: aws.String(versioningStatus),
  703. },
  704. }
  705. }
  706. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, response)
  707. }
  708. // PutBucketVersioningHandler Put bucket Versioning
  709. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketVersioning.html
  710. func (s3a *S3ApiServer) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) {
  711. bucket, _ := s3_constants.GetBucketAndObject(r)
  712. glog.V(3).Infof("PutBucketVersioning %s", bucket)
  713. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  714. s3err.WriteErrorResponse(w, r, err)
  715. return
  716. }
  717. if r.Body == nil || r.Body == http.NoBody {
  718. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  719. return
  720. }
  721. var versioningConfig s3.VersioningConfiguration
  722. defer util_http.CloseRequest(r)
  723. err := xmlutil.UnmarshalXML(&versioningConfig, xml.NewDecoder(r.Body), "")
  724. if err != nil {
  725. glog.Warningf("PutBucketVersioningHandler xml decode: %s", err)
  726. s3err.WriteErrorResponse(w, r, s3err.ErrMalformedXML)
  727. return
  728. }
  729. if versioningConfig.Status == nil {
  730. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  731. return
  732. }
  733. status := *versioningConfig.Status
  734. if status != s3_constants.VersioningEnabled && status != s3_constants.VersioningSuspended {
  735. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  736. return
  737. }
  738. // Check if trying to suspend versioning on a bucket with object lock enabled
  739. if status == s3_constants.VersioningSuspended {
  740. // Get bucket configuration to check for object lock
  741. bucketConfig, errCode := s3a.getBucketConfig(bucket)
  742. if errCode == s3err.ErrNone && bucketConfig.ObjectLockConfig != nil {
  743. // Object lock is enabled, cannot suspend versioning
  744. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidBucketState)
  745. return
  746. }
  747. }
  748. // Update bucket versioning configuration using new bucket config system
  749. if errCode := s3a.setBucketVersioningStatus(bucket, status); errCode != s3err.ErrNone {
  750. glog.Errorf("PutBucketVersioningHandler save config: %d", errCode)
  751. s3err.WriteErrorResponse(w, r, errCode)
  752. return
  753. }
  754. writeSuccessResponseEmpty(w, r)
  755. }