package handler import ( "bytes" "context" "encoding/base64" "encoding/json" "fmt" "io" "net/http" "strings" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "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" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" s3common "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/pkg/policy-engine/common" "git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain" "go.uber.org/zap" ) func (h *handler) PutPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) { ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutPublicAccessBlock") defer span.End() var buf bytes.Buffer tee := io.TeeReader(r.Body, &buf) reqInfo := middleware.GetReqInfo(ctx) cfg := new(data.PublicAccessBlockConfiguration) if err := h.cfg.NewXMLDecoder(tee, r.UserAgent()).Decode(cfg); err != nil { h.logAndSendError(ctx, w, "could not decode body", reqInfo, fmt.Errorf("%w: %s", apierr.GetAPIError(apierr.ErrMalformedXML), err.Error())) return } if _, ok := r.Header[api.ContentMD5]; ok { headerMD5, err := base64.StdEncoding.DecodeString(r.Header.Get(api.ContentMD5)) if err != nil { h.logAndSendError(ctx, w, "invalid Content-MD5", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest)) return } bodyMD5, err := getContentMD5(&buf) if err != nil { h.logAndSendError(ctx, w, "could not get content md5", reqInfo, err) return } if !bytes.Equal(headerMD5, bodyMD5) { h.logAndSendError(ctx, w, "Content-MD5 does not match", reqInfo, apierr.GetAPIError(apierr.ErrInvalidDigest)) return } } bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err) return } settings, err := h.obj.GetBucketSettings(ctx, bktInfo) if err != nil { h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err) return } // Delete ACL chains if IgnorePublicAcls is set to true if (settings.PublicAccessBlock == nil || !settings.PublicAccessBlock.IgnorePublicAcls) && cfg.IgnorePublicAcls && settings.CannedACL != basicACLPrivate { if err = h.policyEngine.APE.DeleteACLChains(bktInfo.CID.EncodeToString(), []chain.ID{ getBucketCannedChainID(chain.S3, bktInfo.CID), getBucketCannedChainID(chain.Ingress, bktInfo.CID), }); err != nil { h.logAndSendError(ctx, w, "failed to delete morph rule chains", reqInfo, err) return } } // Set ACL chains if IgnorePublicAcls is set to false if settings.PublicAccessBlock != nil && settings.PublicAccessBlock.IgnorePublicAcls && !cfg.IgnorePublicAcls { chainRules := bucketCannedACLToAPERules(settings.CannedACL, reqInfo, bktInfo.CID) if err = h.policyEngine.APE.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil { h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err) return } } newSettings := *settings newSettings.PublicAccessBlock = cfg sp := &layer.PutSettingsParams{ BktInfo: bktInfo, Settings: &newSettings, } if err = h.obj.PutBucketSettings(ctx, sp); err != nil { h.logAndSendError(ctx, w, "couldn't save bucket settings", reqInfo, err, zap.String("container_id", bktInfo.CID.EncodeToString())) return } w.WriteHeader(http.StatusOK) } func (h *handler) GetPublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) { ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetPublicAccessBlock") defer span.End() reqInfo := middleware.GetReqInfo(ctx) bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err) return } settings, err := h.obj.GetBucketSettings(ctx, bktInfo) if err != nil { h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err) return } if settings.PublicAccessBlock == nil { h.logAndSendError(ctx, w, "no public access block", reqInfo, apierr.GetAPIError(apierr.ErrNoSuchPublicAccessBlockConfiguration)) return } if err = middleware.EncodeToResponse(w, settings.PublicAccessBlock); err != nil { h.logAndSendError(ctx, w, "something went wrong", reqInfo, err) return } } func (h *handler) DeletePublicAccessBlockHandler(w http.ResponseWriter, r *http.Request) { ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.DeletePublicAccessBlock") defer span.End() reqInfo := middleware.GetReqInfo(ctx) bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err) return } settings, err := h.obj.GetBucketSettings(ctx, bktInfo) if err != nil { h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err) return } if settings.PublicAccessBlock == nil { w.WriteHeader(http.StatusNoContent) return } // Set ACL chains if IgnorePublicAcls was set to true if settings.PublicAccessBlock.IgnorePublicAcls { chainRules := bucketCannedACLToAPERules(settings.CannedACL, reqInfo, bktInfo.CID) if err = h.policyEngine.APE.SaveACLChains(bktInfo.CID.EncodeToString(), chainRules); err != nil { h.logAndSendError(ctx, w, "failed to add morph rule chains", reqInfo, err) return } } newSettings := *settings newSettings.PublicAccessBlock = nil sp := &layer.PutSettingsParams{ BktInfo: bktInfo, Settings: &newSettings, } if err = h.obj.PutBucketSettings(ctx, sp); err != nil { h.logAndSendError(ctx, w, "couldn't save bucket settings", reqInfo, err, zap.String("container_id", bktInfo.CID.EncodeToString())) return } w.WriteHeader(http.StatusNoContent) } func (h *handler) CheckRestrictPublicBuckets(ctx context.Context) error { reqInfo := middleware.GetReqInfo(ctx) bktInfo, err := h.obj.GetBucketInfo(ctx, reqInfo.BucketName) if err != nil { return fmt.Errorf("get bucket info: %w", err) } settings, err := h.obj.GetBucketSettings(ctx, bktInfo) if err != nil { return fmt.Errorf("get bucket settings: %w", err) } if settings.PublicAccessBlock != nil && settings.PublicAccessBlock.RestrictPublicBuckets { jsonPolicy, err := h.policyEngine.APE.GetBucketPolicy(reqInfo.Namespace, bktInfo.CID) if err != nil { if strings.Contains(err.Error(), "not found") { return nil } return fmt.Errorf("get bucket policy: %w", err) } var bktPolicy s3common.Policy if err = json.Unmarshal(jsonPolicy, &bktPolicy); err != nil { return fmt.Errorf("unmarshal bucket policy: %w", err) } // Check whether bucket policy is public and namespaces of bucket and user are equal if getPolicyStatus(bktPolicy) == PolicyStatusIsPublicTrue && (reqInfo.UserNamespace == nil || *reqInfo.UserNamespace != reqInfo.Namespace) { return fmt.Errorf("public buckets are restricted: %w", apierr.GetAPIError(apierr.ErrAccessDenied)) } } return nil }