package layer import ( "bytes" "context" "encoding/xml" "errors" "fmt" "io" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" "go.uber.org/zap" ) const wildcard = "*" var supportedMethods = map[string]struct{}{"GET": {}, "HEAD": {}, "POST": {}, "PUT": {}, "DELETE": {}} func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error { ctx, span := tracing.StartSpanFromContext(ctx, "layer.PutBucketCORS") defer span.End() var ( buf bytes.Buffer tee = io.TeeReader(p.Reader, &buf) cors = &data.CORSConfiguration{} ) if err := p.NewDecoder(tee, p.UserAgent).Decode(cors); err != nil { return fmt.Errorf("xml decode cors: %w", err) } if cors.CORSRules == nil { return apierr.GetAPIError(apierr.ErrMalformedXML) } if err := checkCORS(cors); err != nil { return err } corsVersions, err := n.getCORSVersions(ctx, p.BktInfo) if err != nil { n.reqLogger(ctx).Error(logs.CouldntGetCORSObjectVersions, zap.Error(err), logs.TagField(logs.TagExternalStorage)) } prm := frostfs.PrmObjectCreate{ Container: n.corsCnrInfo.CID, Payload: &buf, Filepath: p.BktInfo.CORSObjectFilePath(), CreationTime: TimeNow(ctx), CopiesNumber: p.CopiesNumbers, PrmAuth: frostfs.PrmAuth{ PrivateKey: &n.gateKey.PrivateKey, }, } _, err = n.objectPutAndHash(ctx, prm, n.corsCnrInfo) if err != nil { return fmt.Errorf("put cors object: %w", err) } n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors) objs, err := n.treeService.DeleteBucketCORS(ctx, p.BktInfo) objNotFound := errors.Is(err, tree.ErrNoNodeToRemove) if err != nil && !objNotFound { n.reqLogger(ctx).Error(logs.CouldntDeleteBucketCORS, zap.Error(err), logs.TagField(logs.TagExternalStorageTree)) return nil } if !objNotFound { for _, addr := range objs { n.deleteCORSObject(ctx, p.BktInfo, addr) } } if corsVersions != nil { var addr oid.Address addr.SetContainer(n.corsCnrInfo.CID) for _, obj := range corsVersions.GetObjects() { addr.SetObject(obj.ObjID) n.deleteCORSObject(ctx, p.BktInfo, addr) } } return nil } // deleteCORSObject removes object and logs in case of error. func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) { var prmAuth frostfs.PrmAuth corsBkt := bktInfo if !addr.Container().Equals(bktInfo.CID) && !addr.Container().Equals(cid.ID{}) { corsBkt = &data.BucketInfo{CID: addr.Container()} prmAuth.PrivateKey = &n.gateKey.PrivateKey } if err := n.objectDeleteWithAuth(ctx, corsBkt, addr.Object(), prmAuth); err != nil { n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err), zap.String("cnrID", corsBkt.CID.EncodeToString()), zap.String("objID", addr.Object().EncodeToString()), logs.TagField(logs.TagExternalStorage)) } } func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo, decoder func(io.Reader, string) *xml.Decoder) (*data.CORSConfiguration, error) { ctx, span := tracing.StartSpanFromContext(ctx, "layer.GetBucketCORS") defer span.End() cors, err := n.getCORS(ctx, bktInfo, decoder) if err != nil { return nil, err } return cors, nil } func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error { ctx, span := tracing.StartSpanFromContext(ctx, "layer.DeleteBucketCORS") defer span.End() corsVersions, err := n.getCORSVersions(ctx, bktInfo) if err != nil { return fmt.Errorf("get cors versions: %w", err) } sortedVersions := corsVersions.GetSorted() for _, version := range sortedVersions { if err = n.objectDeleteWithAuth(ctx, n.corsCnrInfo, version.ObjID, frostfs.PrmAuth{PrivateKey: &n.gateKey.PrivateKey}); err != nil { return fmt.Errorf("delete cors object '%s': %w", version.VersionID(), err) } } objs, err := n.treeService.DeleteBucketCORS(ctx, bktInfo) objNotFound := errors.Is(err, tree.ErrNoNodeToRemove) if err != nil && !objNotFound { return fmt.Errorf("delete cors from tree: %w", err) } if !objNotFound { for _, addr := range objs { n.deleteCORSObject(ctx, bktInfo, addr) } } n.cache.DeleteCORS(bktInfo) return nil } func (n *Layer) deleteCORSVersions(ctx context.Context, bktInfo *data.BucketInfo) { corsVersions, err := n.getCORSVersions(ctx, bktInfo) if err != nil { n.reqLogger(ctx).Error(logs.CouldntGetCORSObjectVersions, zap.Error(err), logs.TagField(logs.TagExternalStorage)) return } var addr oid.Address addr.SetContainer(n.corsCnrInfo.CID) sortedVersions := corsVersions.GetSorted() for _, version := range sortedVersions { addr.SetObject(version.ObjID) n.deleteCORSObject(ctx, bktInfo, addr) } } func checkCORS(cors *data.CORSConfiguration) error { for _, r := range cors.CORSRules { for _, m := range r.AllowedMethods { if _, ok := supportedMethods[m]; !ok { return apierr.GetAPIErrorWithError(apierr.ErrCORSUnsupportedMethod, fmt.Errorf("unsupported method is %s", m)) } } for _, h := range r.ExposeHeaders { if h == wildcard { return apierr.GetAPIError(apierr.ErrCORSWildcardExposeHeaders) } } } return nil }