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.

570 lines
17 KiB

7 years ago
7 years ago
3 years ago
7 years ago
3 years ago
7 years ago
7 years ago
7 years ago
7 years ago
7 years ago
3 years ago
4 years ago
3 years ago
3 years ago
7 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
3 years ago
7 years ago
7 years ago
3 years ago
4 years ago
4 years ago
7 years ago
7 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
3 years ago
4 years ago
3 years ago
4 years ago
3 years ago
4 years ago
  1. package s3api
  2. import (
  3. "context"
  4. "encoding/xml"
  5. "errors"
  6. "fmt"
  7. "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil"
  8. "github.com/seaweedfs/seaweedfs/weed/s3api/s3account"
  9. "github.com/seaweedfs/seaweedfs/weed/s3api/s3acl"
  10. "github.com/seaweedfs/seaweedfs/weed/util"
  11. "math"
  12. "net/http"
  13. "time"
  14. "github.com/seaweedfs/seaweedfs/weed/filer"
  15. "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
  16. "github.com/seaweedfs/seaweedfs/weed/storage/needle"
  17. "github.com/seaweedfs/seaweedfs/weed/s3api/s3err"
  18. "github.com/aws/aws-sdk-go/aws"
  19. "github.com/aws/aws-sdk-go/service/s3"
  20. "github.com/seaweedfs/seaweedfs/weed/glog"
  21. "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  22. )
  23. type ListAllMyBucketsResult struct {
  24. XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ ListAllMyBucketsResult"`
  25. Owner *s3.Owner
  26. Buckets []*s3.Bucket `xml:"Buckets>Bucket"`
  27. }
  28. func (s3a *S3ApiServer) ListBucketsHandler(w http.ResponseWriter, r *http.Request) {
  29. glog.V(3).Infof("ListBucketsHandler")
  30. var identity *Identity
  31. var s3Err s3err.ErrorCode
  32. if s3a.iam.isEnabled() {
  33. identity, s3Err = s3a.iam.authUser(r)
  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 buckets []*s3.Bucket
  47. for _, entry := range entries {
  48. if entry.IsDirectory {
  49. if identity != nil && !identity.canDo(s3_constants.ACTION_LIST, entry.Name, "") {
  50. continue
  51. }
  52. buckets = append(buckets, &s3.Bucket{
  53. Name: aws.String(entry.Name),
  54. CreationDate: aws.Time(time.Unix(entry.Attributes.Crtime, 0).UTC()),
  55. })
  56. }
  57. }
  58. response = ListAllMyBucketsResult{
  59. Owner: &s3.Owner{
  60. ID: aws.String(identityId),
  61. DisplayName: aws.String(identityId),
  62. },
  63. Buckets: buckets,
  64. }
  65. writeSuccessResponseXML(w, r, response)
  66. }
  67. func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) {
  68. bucket, _ := s3_constants.GetBucketAndObject(r)
  69. glog.V(3).Infof("PutBucketHandler %s", bucket)
  70. // avoid duplicated buckets
  71. errCode := s3err.ErrNone
  72. if err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  73. if resp, err := client.CollectionList(context.Background(), &filer_pb.CollectionListRequest{
  74. IncludeEcVolumes: true,
  75. IncludeNormalVolumes: true,
  76. }); err != nil {
  77. glog.Errorf("list collection: %v", err)
  78. return fmt.Errorf("list collections: %v", err)
  79. } else {
  80. for _, c := range resp.Collections {
  81. if bucket == c.Name {
  82. errCode = s3err.ErrBucketAlreadyExists
  83. break
  84. }
  85. }
  86. }
  87. return nil
  88. }); err != nil {
  89. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  90. return
  91. }
  92. if exist, err := s3a.exists(s3a.option.BucketsPath, bucket, true); err == nil && exist {
  93. errCode = s3err.ErrBucketAlreadyExists
  94. }
  95. if errCode != s3err.ErrNone {
  96. s3err.WriteErrorResponse(w, r, errCode)
  97. return
  98. }
  99. if s3a.iam.isEnabled() {
  100. if _, errCode = s3a.iam.authRequest(r, s3_constants.ACTION_ADMIN); errCode != s3err.ErrNone {
  101. s3err.WriteErrorResponse(w, r, errCode)
  102. return
  103. }
  104. }
  105. objectOwnership := r.Header.Get("X-Amz-Object-Ownership")
  106. requestAccountId := s3acl.GetAccountId(r)
  107. grants, errCode := s3acl.ExtractBucketAcl(r, s3a.accountManager, objectOwnership, requestAccountId, requestAccountId, true)
  108. if errCode != s3err.ErrNone {
  109. s3err.WriteErrorResponse(w, r, errCode)
  110. return
  111. }
  112. fn := func(entry *filer_pb.Entry) {
  113. if identityId := r.Header.Get(s3_constants.AmzIdentityId); identityId != "" {
  114. if entry.Extended == nil {
  115. entry.Extended = make(map[string][]byte)
  116. }
  117. entry.Extended[s3_constants.AmzIdentityId] = []byte(identityId)
  118. }
  119. if objectOwnership != "" {
  120. if entry.Extended == nil {
  121. entry.Extended = make(map[string][]byte)
  122. }
  123. entry.Extended[s3_constants.ExtOwnershipKey] = []byte(objectOwnership)
  124. }
  125. s3acl.AssembleEntryWithAcp(entry, requestAccountId, grants)
  126. }
  127. // create the folder for bucket, but lazily create actual collection
  128. if err := s3a.mkdir(s3a.option.BucketsPath, bucket, fn); err != nil {
  129. glog.Errorf("PutBucketHandler mkdir: %v", err)
  130. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  131. return
  132. }
  133. w.Header().Set("Location", "/"+bucket)
  134. writeSuccessResponseEmpty(w, r)
  135. }
  136. func (s3a *S3ApiServer) DeleteBucketHandler(w http.ResponseWriter, r *http.Request) {
  137. bucket, _ := s3_constants.GetBucketAndObject(r)
  138. glog.V(3).Infof("DeleteBucketHandler %s", bucket)
  139. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  140. s3err.WriteErrorResponse(w, r, err)
  141. return
  142. }
  143. err := s3a.WithFilerClient(false, func(client filer_pb.SeaweedFilerClient) error {
  144. if !s3a.option.AllowDeleteBucketNotEmpty {
  145. entries, _, err := s3a.list(s3a.option.BucketsPath+"/"+bucket, "", "", false, 2)
  146. if err != nil {
  147. return fmt.Errorf("failed to list bucket %s: %v", bucket, err)
  148. }
  149. for _, entry := range entries {
  150. if entry.Name != s3_constants.MultipartUploadsFolder {
  151. return errors.New(s3err.GetAPIError(s3err.ErrBucketNotEmpty).Code)
  152. }
  153. }
  154. }
  155. // delete collection
  156. deleteCollectionRequest := &filer_pb.DeleteCollectionRequest{
  157. Collection: bucket,
  158. }
  159. glog.V(1).Infof("delete collection: %v", deleteCollectionRequest)
  160. if _, err := client.DeleteCollection(context.Background(), deleteCollectionRequest); err != nil {
  161. return fmt.Errorf("delete collection %s: %v", bucket, err)
  162. }
  163. return nil
  164. })
  165. if err != nil {
  166. s3ErrorCode := s3err.ErrInternalError
  167. if err.Error() == s3err.GetAPIError(s3err.ErrBucketNotEmpty).Code {
  168. s3ErrorCode = s3err.ErrBucketNotEmpty
  169. }
  170. s3err.WriteErrorResponse(w, r, s3ErrorCode)
  171. return
  172. }
  173. err = s3a.rm(s3a.option.BucketsPath, bucket, false, true)
  174. if err != nil {
  175. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  176. return
  177. }
  178. s3err.WriteEmptyResponse(w, r, http.StatusNoContent)
  179. }
  180. func (s3a *S3ApiServer) HeadBucketHandler(w http.ResponseWriter, r *http.Request) {
  181. bucket, _ := s3_constants.GetBucketAndObject(r)
  182. glog.V(3).Infof("HeadBucketHandler %s", bucket)
  183. _, errorCode := s3a.checkAccessForReadBucket(r, bucket, s3_constants.PermissionRead)
  184. if errorCode != s3err.ErrNone {
  185. s3err.WriteErrorResponse(w, r, errorCode)
  186. return
  187. }
  188. if entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket); entry == nil || err == filer_pb.ErrNotFound {
  189. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  190. return
  191. }
  192. writeSuccessResponseEmpty(w, r)
  193. }
  194. func (s3a *S3ApiServer) checkBucket(r *http.Request, bucket string) s3err.ErrorCode {
  195. entry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  196. if entry == nil || err == filer_pb.ErrNotFound {
  197. return s3err.ErrNoSuchBucket
  198. }
  199. if !s3a.hasAccess(r, entry) {
  200. return s3err.ErrAccessDenied
  201. }
  202. return s3err.ErrNone
  203. }
  204. func (s3a *S3ApiServer) hasAccess(r *http.Request, entry *filer_pb.Entry) bool {
  205. isAdmin := r.Header.Get(s3_constants.AmzIsAdmin) != ""
  206. if isAdmin {
  207. return true
  208. }
  209. if entry.Extended == nil {
  210. return true
  211. }
  212. identityId := r.Header.Get(s3_constants.AmzIdentityId)
  213. if id, ok := entry.Extended[s3_constants.AmzIdentityId]; ok {
  214. if identityId != string(id) {
  215. return false
  216. }
  217. }
  218. return true
  219. }
  220. // PutBucketAclHandler Put bucket ACL
  221. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketAcl.html
  222. func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Request) {
  223. bucket, _ := s3_constants.GetBucketAndObject(r)
  224. glog.V(3).Infof("PutBucketAclHandler %s", bucket)
  225. accountId := s3acl.GetAccountId(r)
  226. bucketMetadata, errorCode := s3a.checkAccessForPutBucketAcl(accountId, bucket)
  227. if errorCode != s3err.ErrNone {
  228. s3err.WriteErrorResponse(w, r, errorCode)
  229. return
  230. }
  231. grants, errCode := s3acl.ExtractBucketAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, accountId, false)
  232. if errCode != s3err.ErrNone {
  233. s3err.WriteErrorResponse(w, r, errCode)
  234. return
  235. }
  236. bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  237. if err != nil {
  238. glog.Warning(err)
  239. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  240. return
  241. }
  242. errCode = s3acl.AssembleEntryWithAcp(bucketEntry, *bucketMetadata.Owner.ID, grants)
  243. if errCode != s3err.ErrNone {
  244. s3err.WriteErrorResponse(w, r, errCode)
  245. return
  246. }
  247. err = updateBucketEntry(s3a, bucketEntry)
  248. if err != nil {
  249. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  250. return
  251. }
  252. //update local cache
  253. bucketMetadata.Acl = grants
  254. s3err.WriteEmptyResponse(w, r, http.StatusOK)
  255. }
  256. // GetBucketAclHandler Get Bucket ACL
  257. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketAcl.html
  258. func (s3a *S3ApiServer) GetBucketAclHandler(w http.ResponseWriter, r *http.Request) {
  259. // collect parameters
  260. bucket, _ := s3_constants.GetBucketAndObject(r)
  261. glog.V(3).Infof("GetBucketAclHandler %s", bucket)
  262. bucketMetadata, errorCode := s3a.checkAccessForReadBucket(r, bucket, s3_constants.PermissionReadAcp)
  263. if s3err.ErrNone != errorCode {
  264. s3err.WriteErrorResponse(w, r, errorCode)
  265. return
  266. }
  267. acp := &s3.PutBucketAclInput{
  268. AccessControlPolicy: &s3.AccessControlPolicy{
  269. Grants: bucketMetadata.Acl,
  270. Owner: bucketMetadata.Owner,
  271. },
  272. }
  273. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, acp)
  274. }
  275. // GetBucketLifecycleConfigurationHandler Get Bucket Lifecycle configuration
  276. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLifecycleConfiguration.html
  277. func (s3a *S3ApiServer) GetBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
  278. // collect parameters
  279. bucket, _ := s3_constants.GetBucketAndObject(r)
  280. glog.V(3).Infof("GetBucketLifecycleConfigurationHandler %s", bucket)
  281. if err := s3a.checkBucket(r, bucket); err != s3err.ErrNone {
  282. s3err.WriteErrorResponse(w, r, err)
  283. return
  284. }
  285. fc, err := filer.ReadFilerConf(s3a.option.Filer, s3a.option.GrpcDialOption, nil)
  286. if err != nil {
  287. glog.Errorf("GetBucketLifecycleConfigurationHandler: %s", err)
  288. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  289. return
  290. }
  291. ttls := fc.GetCollectionTtls(bucket)
  292. if len(ttls) == 0 {
  293. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchLifecycleConfiguration)
  294. return
  295. }
  296. response := Lifecycle{}
  297. for prefix, internalTtl := range ttls {
  298. ttl, _ := needle.ReadTTL(internalTtl)
  299. days := int(ttl.Minutes() / 60 / 24)
  300. if days == 0 {
  301. continue
  302. }
  303. response.Rules = append(response.Rules, Rule{
  304. Status: Enabled, Filter: Filter{
  305. Prefix: Prefix{string: prefix, set: true},
  306. set: true,
  307. },
  308. Expiration: Expiration{Days: days, set: true},
  309. })
  310. }
  311. writeSuccessResponseXML(w, r, response)
  312. }
  313. // PutBucketLifecycleConfigurationHandler Put Bucket Lifecycle configuration
  314. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketLifecycleConfiguration.html
  315. func (s3a *S3ApiServer) PutBucketLifecycleConfigurationHandler(w http.ResponseWriter, r *http.Request) {
  316. s3err.WriteErrorResponse(w, r, s3err.ErrNotImplemented)
  317. }
  318. // DeleteBucketMetricsConfiguration Delete Bucket Lifecycle
  319. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketLifecycle.html
  320. func (s3a *S3ApiServer) DeleteBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
  321. s3err.WriteEmptyResponse(w, r, http.StatusNoContent)
  322. }
  323. // GetBucketLocationHandler Get bucket location
  324. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketLocation.html
  325. func (s3a *S3ApiServer) GetBucketLocationHandler(w http.ResponseWriter, r *http.Request) {
  326. writeSuccessResponseXML(w, r, LocationConstraint{})
  327. }
  328. // GetBucketRequestPaymentHandler Get bucket location
  329. // https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketRequestPayment.html
  330. func (s3a *S3ApiServer) GetBucketRequestPaymentHandler(w http.ResponseWriter, r *http.Request) {
  331. writeSuccessResponseXML(w, r, RequestPaymentConfiguration{Payer: "BucketOwner"})
  332. }
  333. // PutBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutBucketOwnershipControls.html
  334. func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
  335. bucket, _ := s3_constants.GetBucketAndObject(r)
  336. glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
  337. errCode := s3a.checkAccessByOwnership(r, bucket)
  338. if errCode != s3err.ErrNone {
  339. s3err.WriteErrorResponse(w, r, errCode)
  340. return
  341. }
  342. if r.Body == nil || r.Body == http.NoBody {
  343. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  344. return
  345. }
  346. defer util.CloseRequest(r)
  347. var v s3.OwnershipControls
  348. err := xmlutil.UnmarshalXML(&v, xml.NewDecoder(r.Body), "")
  349. if err != nil {
  350. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  351. return
  352. }
  353. if len(v.Rules) != 1 {
  354. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  355. return
  356. }
  357. printOwnership := true
  358. newObjectOwnership := *v.Rules[0].ObjectOwnership
  359. switch newObjectOwnership {
  360. case s3_constants.OwnershipObjectWriter:
  361. case s3_constants.OwnershipBucketOwnerPreferred:
  362. case s3_constants.OwnershipBucketOwnerEnforced:
  363. default:
  364. s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest)
  365. return
  366. }
  367. bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  368. if err != nil {
  369. if err == filer_pb.ErrNotFound {
  370. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  371. return
  372. }
  373. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  374. return
  375. }
  376. oldOwnership, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
  377. if !ok || string(oldOwnership) != newObjectOwnership {
  378. // must reset bucket acl to default(bucket owner with full control permission) before setting ownership
  379. // to `OwnershipBucketOwnerEnforced` (bucket cannot have ACLs set with ObjectOwnership's BucketOwnerEnforced setting)
  380. if newObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced {
  381. acpGrants := s3acl.GetAcpGrants(nil, bucketEntry.Extended)
  382. if len(acpGrants) > 1 {
  383. s3err.WriteErrorResponse(w, r, s3err.InvalidBucketAclWithObjectOwnership)
  384. return
  385. } else if len(acpGrants) == 1 {
  386. bucketOwner := s3acl.GetAcpOwner(bucketEntry.Extended, s3account.AccountAdmin.Id)
  387. expectGrant := s3acl.GrantWithFullControl(bucketOwner)
  388. if !s3acl.GrantEquals(acpGrants[0], expectGrant) {
  389. s3err.WriteErrorResponse(w, r, s3err.InvalidBucketAclWithObjectOwnership)
  390. return
  391. }
  392. }
  393. }
  394. if bucketEntry.Extended == nil {
  395. bucketEntry.Extended = make(map[string][]byte)
  396. }
  397. //update local cache
  398. bucketMetadata, eCode := s3a.bucketRegistry.GetBucketMetadata(bucket)
  399. if eCode == s3err.ErrNone {
  400. bucketMetadata.ObjectOwnership = newObjectOwnership
  401. }
  402. bucketEntry.Extended[s3_constants.ExtOwnershipKey] = []byte(newObjectOwnership)
  403. err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
  404. if err != nil {
  405. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  406. return
  407. }
  408. }
  409. if printOwnership {
  410. result := &s3.PutBucketOwnershipControlsInput{
  411. OwnershipControls: &v,
  412. }
  413. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
  414. } else {
  415. writeSuccessResponseEmpty(w, r)
  416. }
  417. }
  418. // GetBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetBucketOwnershipControls.html
  419. func (s3a *S3ApiServer) GetBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
  420. bucket, _ := s3_constants.GetBucketAndObject(r)
  421. glog.V(3).Infof("GetBucketOwnershipControls %s", bucket)
  422. errCode := s3a.checkAccessByOwnership(r, bucket)
  423. if errCode != s3err.ErrNone {
  424. s3err.WriteErrorResponse(w, r, errCode)
  425. return
  426. }
  427. bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  428. if err != nil {
  429. if err == filer_pb.ErrNotFound {
  430. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  431. return
  432. }
  433. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  434. return
  435. }
  436. v, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
  437. if !ok {
  438. s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
  439. return
  440. }
  441. ownership := string(v)
  442. result := &s3.PutBucketOwnershipControlsInput{
  443. OwnershipControls: &s3.OwnershipControls{
  444. Rules: []*s3.OwnershipControlsRule{
  445. {
  446. ObjectOwnership: &ownership,
  447. },
  448. },
  449. },
  450. }
  451. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, result)
  452. }
  453. // DeleteBucketOwnershipControls https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucketOwnershipControls.html
  454. func (s3a *S3ApiServer) DeleteBucketOwnershipControls(w http.ResponseWriter, r *http.Request) {
  455. bucket, _ := s3_constants.GetBucketAndObject(r)
  456. glog.V(3).Infof("PutBucketOwnershipControls %s", bucket)
  457. errCode := s3a.checkAccessByOwnership(r, bucket)
  458. if errCode != s3err.ErrNone {
  459. s3err.WriteErrorResponse(w, r, errCode)
  460. return
  461. }
  462. bucketEntry, err := s3a.getEntry(s3a.option.BucketsPath, bucket)
  463. if err != nil {
  464. if err == filer_pb.ErrNotFound {
  465. s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchBucket)
  466. return
  467. }
  468. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  469. return
  470. }
  471. _, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey]
  472. if !ok {
  473. s3err.WriteErrorResponse(w, r, s3err.OwnershipControlsNotFoundError)
  474. return
  475. }
  476. delete(bucketEntry.Extended, s3_constants.ExtOwnershipKey)
  477. err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry)
  478. if err != nil {
  479. s3err.WriteErrorResponse(w, r, s3err.ErrInternalError)
  480. return
  481. }
  482. emptyOwnershipControls := &s3.OwnershipControls{
  483. Rules: []*s3.OwnershipControlsRule{},
  484. }
  485. s3err.WriteAwsXMLResponse(w, r, http.StatusOK, emptyOwnershipControls)
  486. }