frostfs-s3-gw/api/router.go

526 lines
21 KiB
Go
Raw Permalink Normal View History

package api
import (
"context"
"fmt"
"net/http"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/metrics"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/engine"
"github.com/go-chi/chi/v5"
"github.com/go-chi/chi/v5/middleware"
"go.uber.org/zap"
)
type (
// Handler is an S3 API handler interface.
Handler interface {
HeadObjectHandler(http.ResponseWriter, *http.Request)
GetObjectACLHandler(http.ResponseWriter, *http.Request)
PutObjectACLHandler(http.ResponseWriter, *http.Request)
GetObjectTaggingHandler(http.ResponseWriter, *http.Request)
PutObjectTaggingHandler(http.ResponseWriter, *http.Request)
DeleteObjectTaggingHandler(http.ResponseWriter, *http.Request)
SelectObjectContentHandler(http.ResponseWriter, *http.Request)
GetObjectRetentionHandler(http.ResponseWriter, *http.Request)
GetObjectLegalHoldHandler(http.ResponseWriter, *http.Request)
GetObjectHandler(http.ResponseWriter, *http.Request)
GetObjectAttributesHandler(http.ResponseWriter, *http.Request)
CopyObjectHandler(http.ResponseWriter, *http.Request)
PutObjectRetentionHandler(http.ResponseWriter, *http.Request)
PutObjectLegalHoldHandler(http.ResponseWriter, *http.Request)
PutObjectHandler(http.ResponseWriter, *http.Request)
DeleteObjectHandler(http.ResponseWriter, *http.Request)
GetBucketLocationHandler(http.ResponseWriter, *http.Request)
GetBucketPolicyStatusHandler(http.ResponseWriter, *http.Request)
GetBucketPolicyHandler(http.ResponseWriter, *http.Request)
GetBucketLifecycleHandler(http.ResponseWriter, *http.Request)
GetBucketEncryptionHandler(http.ResponseWriter, *http.Request)
GetBucketACLHandler(http.ResponseWriter, *http.Request)
PutBucketACLHandler(http.ResponseWriter, *http.Request)
GetBucketCorsHandler(http.ResponseWriter, *http.Request)
PutBucketCorsHandler(http.ResponseWriter, *http.Request)
DeleteBucketCorsHandler(http.ResponseWriter, *http.Request)
GetBucketWebsiteHandler(http.ResponseWriter, *http.Request)
GetBucketAccelerateHandler(http.ResponseWriter, *http.Request)
GetBucketRequestPaymentHandler(http.ResponseWriter, *http.Request)
GetBucketLoggingHandler(http.ResponseWriter, *http.Request)
GetBucketReplicationHandler(http.ResponseWriter, *http.Request)
GetBucketTaggingHandler(http.ResponseWriter, *http.Request)
DeleteBucketWebsiteHandler(http.ResponseWriter, *http.Request)
DeleteBucketTaggingHandler(http.ResponseWriter, *http.Request)
GetBucketObjectLockConfigHandler(http.ResponseWriter, *http.Request)
GetBucketVersioningHandler(http.ResponseWriter, *http.Request)
GetBucketNotificationHandler(http.ResponseWriter, *http.Request)
ListenBucketNotificationHandler(http.ResponseWriter, *http.Request)
ListObjectsV2MHandler(http.ResponseWriter, *http.Request)
ListObjectsV2Handler(http.ResponseWriter, *http.Request)
ListBucketObjectVersionsHandler(http.ResponseWriter, *http.Request)
ListObjectsV1Handler(http.ResponseWriter, *http.Request)
PutBucketLifecycleHandler(http.ResponseWriter, *http.Request)
PutBucketEncryptionHandler(http.ResponseWriter, *http.Request)
PutBucketPolicyHandler(http.ResponseWriter, *http.Request)
PutBucketObjectLockConfigHandler(http.ResponseWriter, *http.Request)
PutBucketTaggingHandler(http.ResponseWriter, *http.Request)
PutBucketVersioningHandler(http.ResponseWriter, *http.Request)
PutBucketNotificationHandler(http.ResponseWriter, *http.Request)
CreateBucketHandler(http.ResponseWriter, *http.Request)
HeadBucketHandler(http.ResponseWriter, *http.Request)
PostObject(http.ResponseWriter, *http.Request)
DeleteMultipleObjectsHandler(http.ResponseWriter, *http.Request)
DeleteBucketPolicyHandler(http.ResponseWriter, *http.Request)
DeleteBucketLifecycleHandler(http.ResponseWriter, *http.Request)
DeleteBucketEncryptionHandler(http.ResponseWriter, *http.Request)
DeleteBucketHandler(http.ResponseWriter, *http.Request)
ListBucketsHandler(http.ResponseWriter, *http.Request)
Preflight(w http.ResponseWriter, r *http.Request)
AppendCORSHeaders(w http.ResponseWriter, r *http.Request)
CreateMultipartUploadHandler(http.ResponseWriter, *http.Request)
UploadPartHandler(http.ResponseWriter, *http.Request)
UploadPartCopy(w http.ResponseWriter, r *http.Request)
CompleteMultipartUploadHandler(http.ResponseWriter, *http.Request)
AbortMultipartUploadHandler(http.ResponseWriter, *http.Request)
ListPartsHandler(w http.ResponseWriter, r *http.Request)
ListMultipartUploadsHandler(http.ResponseWriter, *http.Request)
PatchObjectHandler(http.ResponseWriter, *http.Request)
ResolveBucket(ctx context.Context, bucket string) (*data.BucketInfo, error)
ResolveCID(ctx context.Context, bucket string) (cid.ID, error)
}
)
type Settings interface {
s3middleware.RequestSettings
s3middleware.PolicySettings
s3middleware.MetricsSettings
s3middleware.VHSSettings
s3middleware.LogHTTPSettings
}
type FrostFSID interface {
s3middleware.FrostFSIDValidator
s3middleware.FrostFSIDInformer
}
type Config struct {
Throttle middleware.ThrottleOpts
Handler Handler
Center s3middleware.Center
Log *zap.Logger
Metrics *metrics.AppMetrics
MiddlewareSettings Settings
FrostfsID FrostFSID
FrostFSIDValidation bool
PolicyChecker engine.ChainRouter
XMLDecoder s3middleware.XMLDecoder
Tagging s3middleware.ResourceTagging
}
func NewRouter(cfg Config) *chi.Mux {
api := chi.NewRouter()
api.Use(
s3middleware.LogHTTP(cfg.Log, cfg.MiddlewareSettings),
s3middleware.Request(cfg.Log, cfg.MiddlewareSettings),
middleware.ThrottleWithOpts(cfg.Throttle),
middleware.Recoverer,
s3middleware.Metrics(cfg.Log, cfg.Handler.ResolveCID, cfg.Metrics, cfg.MiddlewareSettings),
s3middleware.LogSuccessResponse(cfg.Log),
s3middleware.Auth(cfg.Center, cfg.Log),
)
if cfg.FrostFSIDValidation {
api.Use(s3middleware.FrostfsIDValidation(cfg.FrostfsID, cfg.Log))
}
api.Use(s3middleware.PrepareAddressStyle(cfg.MiddlewareSettings, cfg.Log))
api.Use(s3middleware.PolicyCheck(s3middleware.PolicyConfig{
Storage: cfg.PolicyChecker,
FrostfsID: cfg.FrostfsID,
Settings: cfg.MiddlewareSettings,
Log: cfg.Log,
BucketResolver: cfg.Handler.ResolveBucket,
Decoder: cfg.XMLDecoder,
Tagging: cfg.Tagging,
}))
defaultRouter := chi.NewRouter()
defaultRouter.Mount("/{bucket}", bucketRouter(cfg.Handler))
defaultRouter.Get("/", named(s3middleware.ListBucketsOperation, cfg.Handler.ListBucketsHandler))
attachErrorHandler(defaultRouter)
vhsRouter := bucketRouter(cfg.Handler)
router := newGlobalRouter(defaultRouter, vhsRouter)
api.Mount("/", router)
attachErrorHandler(api)
return api
}
type globalRouter struct {
pathStyleRouter chi.Router
vhsRouter chi.Router
}
func newGlobalRouter(pathStyleRouter, vhsRouter chi.Router) *globalRouter {
return &globalRouter{
pathStyleRouter: pathStyleRouter,
vhsRouter: vhsRouter,
}
}
func (g *globalRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
router := g.pathStyleRouter
if reqInfo := s3middleware.GetReqInfo(r.Context()); reqInfo.RequestVHSEnabled {
router = g.vhsRouter
}
router.ServeHTTP(w, r)
}
func named(name string, handlerFunc http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
reqInfo := s3middleware.GetReqInfo(r.Context())
reqInfo.API = name
handlerFunc.ServeHTTP(w, r)
}
}
// If none of the http routes match respond with appropriate errors.
func errorResponseHandler(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqInfo := s3middleware.GetReqInfo(ctx)
desc := fmt.Sprintf("Unknown API request at %s", r.URL.Path)
_, wrErr := s3middleware.WriteErrorResponse(w, reqInfo, errors.Error{
Code: "UnknownAPIRequest",
Description: desc,
HTTPStatusCode: http.StatusBadRequest,
})
if log := s3middleware.GetReqLog(ctx); log != nil {
fields := []zap.Field{
zap.String("method", reqInfo.API),
zap.String("http method", r.Method),
zap.String("url", r.RequestURI),
}
if wrErr != nil {
fields = append(fields, zap.NamedError("write_response_error", wrErr))
}
log.Error(logs.RequestUnmatched, fields...)
}
}
func notSupportedHandler() http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
reqInfo := s3middleware.GetReqInfo(ctx)
_, wrErr := s3middleware.WriteErrorResponse(w, reqInfo, errors.GetAPIError(errors.ErrNotSupported))
if log := s3middleware.GetReqLog(ctx); log != nil {
fields := []zap.Field{
zap.String("http method", r.Method),
zap.String("url", r.RequestURI),
}
if wrErr != nil {
fields = append(fields, zap.NamedError("write_response_error", wrErr))
}
log.Error(logs.NotSupported, fields...)
}
}
}
// attachErrorHandler set NotFoundHandler and MethodNotAllowedHandler for chi.Router.
func attachErrorHandler(api *chi.Mux) {
errorHandler := http.HandlerFunc(errorResponseHandler)
// If none of the routes match, add default error handler routes
api.NotFound(named("NotFound", errorHandler))
api.MethodNotAllowed(named("MethodNotAllowed", errorHandler))
}
func bucketRouter(h Handler) chi.Router {
bktRouter := chi.NewRouter()
bktRouter.Use(
s3middleware.WrapHandler(h.AppendCORSHeaders),
)
bktRouter.Mount("/", objectRouter(h))
bktRouter.Options("/", named(s3middleware.OptionsBucketOperation, h.Preflight))
bktRouter.Head("/", named(s3middleware.HeadBucketOperation, h.HeadBucketHandler))
// GET method handlers
bktRouter.Group(func(r chi.Router) {
r.Method(http.MethodGet, "/", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.UploadsQuery).
Handler(named(s3middleware.ListMultipartUploadsOperation, h.ListMultipartUploadsHandler))).
Add(NewFilter().
Queries(s3middleware.LocationQuery).
Handler(named(s3middleware.GetBucketLocationOperation, h.GetBucketLocationHandler))).
Add(NewFilter().
Queries(s3middleware.PolicyStatusQuery).
Handler(named(s3middleware.GetBucketPolicyStatusOperation, h.GetBucketPolicyStatusHandler))).
Add(NewFilter().
Queries(s3middleware.PolicyQuery).
Handler(named(s3middleware.GetBucketPolicyOperation, h.GetBucketPolicyHandler))).
Add(NewFilter().
Queries(s3middleware.LifecycleQuery).
Handler(named(s3middleware.GetBucketLifecycleOperation, h.GetBucketLifecycleHandler))).
Add(NewFilter().
Queries(s3middleware.EncryptionQuery).
Handler(named(s3middleware.GetBucketEncryptionOperation, h.GetBucketEncryptionHandler))).
Add(NewFilter().
Queries(s3middleware.CorsQuery).
Handler(named(s3middleware.GetBucketCorsOperation, h.GetBucketCorsHandler))).
Add(NewFilter().
Queries(s3middleware.ACLQuery).
Handler(named(s3middleware.GetBucketACLOperation, h.GetBucketACLHandler))).
Add(NewFilter().
Queries(s3middleware.WebsiteQuery).
Handler(named(s3middleware.GetBucketWebsiteOperation, h.GetBucketWebsiteHandler))).
Add(NewFilter().
Queries(s3middleware.AccelerateQuery).
Handler(named(s3middleware.GetBucketAccelerateOperation, h.GetBucketAccelerateHandler))).
Add(NewFilter().
Queries(s3middleware.RequestPaymentQuery).
Handler(named(s3middleware.GetBucketRequestPaymentOperation, h.GetBucketRequestPaymentHandler))).
Add(NewFilter().
Queries(s3middleware.LoggingQuery).
Handler(named(s3middleware.GetBucketLoggingOperation, h.GetBucketLoggingHandler))).
Add(NewFilter().
Queries(s3middleware.ReplicationQuery).
Handler(named(s3middleware.GetBucketReplicationOperation, h.GetBucketReplicationHandler))).
Add(NewFilter().
Queries(s3middleware.TaggingQuery).
Handler(named(s3middleware.GetBucketTaggingOperation, h.GetBucketTaggingHandler))).
Add(NewFilter().
Queries(s3middleware.ObjectLockQuery).
Handler(named(s3middleware.GetBucketObjectLockConfigOperation, h.GetBucketObjectLockConfigHandler))).
Add(NewFilter().
Queries(s3middleware.VersioningQuery).
Handler(named(s3middleware.GetBucketVersioningOperation, h.GetBucketVersioningHandler))).
Add(NewFilter().
Queries(s3middleware.NotificationQuery).
Handler(named(s3middleware.GetBucketNotificationOperation, h.GetBucketNotificationHandler))).
Add(NewFilter().
Queries(s3middleware.EventsQuery).
Handler(named(s3middleware.ListenBucketNotificationOperation, h.ListenBucketNotificationHandler))).
Add(NewFilter().
QueriesMatch(s3middleware.ListTypeQuery, "2", s3middleware.MetadataQuery, "true").
Handler(named(s3middleware.ListObjectsV2MOperation, h.ListObjectsV2MHandler))).
Add(NewFilter().
QueriesMatch(s3middleware.ListTypeQuery, "2").
Handler(named(s3middleware.ListObjectsV2Operation, h.ListObjectsV2Handler))).
Add(NewFilter().
Queries(s3middleware.VersionsQuery).
Handler(named(s3middleware.ListBucketObjectVersionsOperation, h.ListBucketObjectVersionsHandler))).
Add(NewFilter().
AllowedQueries(s3middleware.QueryDelimiter, s3middleware.QueryMaxKeys, s3middleware.QueryPrefix,
s3middleware.QueryMarker, s3middleware.QueryEncodingType).
Handler(named(s3middleware.ListObjectsV1Operation, h.ListObjectsV1Handler))).
Add(NewFilter().
NoQueries().
Handler(listWrapper(h))).
DefaultHandler(notSupportedHandler()))
})
// PUT method handlers
bktRouter.Group(func(r chi.Router) {
r.Method(http.MethodPut, "/", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.CorsQuery).
Handler(named(s3middleware.PutBucketCorsOperation, h.PutBucketCorsHandler))).
Add(NewFilter().
Queries(s3middleware.ACLQuery).
Handler(named(s3middleware.PutBucketACLOperation, h.PutBucketACLHandler))).
Add(NewFilter().
Queries(s3middleware.LifecycleQuery).
Handler(named(s3middleware.PutBucketLifecycleOperation, h.PutBucketLifecycleHandler))).
Add(NewFilter().
Queries(s3middleware.EncryptionQuery).
Handler(named(s3middleware.PutBucketEncryptionOperation, h.PutBucketEncryptionHandler))).
Add(NewFilter().
Queries(s3middleware.PolicyQuery).
Handler(named(s3middleware.PutBucketPolicyOperation, h.PutBucketPolicyHandler))).
Add(NewFilter().
Queries(s3middleware.ObjectLockQuery).
Handler(named(s3middleware.PutBucketObjectLockConfigOperation, h.PutBucketObjectLockConfigHandler))).
Add(NewFilter().
Queries(s3middleware.TaggingQuery).
Handler(named(s3middleware.PutBucketTaggingOperation, h.PutBucketTaggingHandler))).
Add(NewFilter().
Queries(s3middleware.VersioningQuery).
Handler(named(s3middleware.PutBucketVersioningOperation, h.PutBucketVersioningHandler))).
Add(NewFilter().
Queries(s3middleware.NotificationQuery).
Handler(named(s3middleware.PutBucketNotificationOperation, h.PutBucketNotificationHandler))).
Add(NewFilter().
NoQueries().
Handler(named(s3middleware.CreateBucketOperation, h.CreateBucketHandler))).
DefaultHandler(notSupportedHandler()))
})
// POST method handlers
bktRouter.Group(func(r chi.Router) {
r.Method(http.MethodPost, "/", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.DeleteQuery).
Handler(named(s3middleware.DeleteMultipleObjectsOperation, h.DeleteMultipleObjectsHandler))).
// todo consider add filter to match header for defaultHandler: hdrContentType, "multipart/form-data*"
DefaultHandler(named(s3middleware.PostObjectOperation, h.PostObject)))
})
// DELETE method handlers
bktRouter.Group(func(r chi.Router) {
r.Method(http.MethodDelete, "/", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.CorsQuery).
Handler(named(s3middleware.DeleteBucketCorsOperation, h.DeleteBucketCorsHandler))).
Add(NewFilter().
Queries(s3middleware.WebsiteQuery).
Handler(named(s3middleware.DeleteBucketWebsiteOperation, h.DeleteBucketWebsiteHandler))).
Add(NewFilter().
Queries(s3middleware.TaggingQuery).
Handler(named(s3middleware.DeleteBucketTaggingOperation, h.DeleteBucketTaggingHandler))).
Add(NewFilter().
Queries(s3middleware.PolicyQuery).
Handler(named(s3middleware.DeleteBucketPolicyOperation, h.DeleteBucketPolicyHandler))).
Add(NewFilter().
Queries(s3middleware.LifecycleQuery).
Handler(named(s3middleware.DeleteBucketLifecycleOperation, h.DeleteBucketLifecycleHandler))).
Add(NewFilter().
Queries(s3middleware.EncryptionQuery).
Handler(named(s3middleware.DeleteBucketEncryptionOperation, h.DeleteBucketEncryptionHandler))).
Add(NewFilter().
NoQueries().
Handler(named(s3middleware.DeleteBucketOperation, h.DeleteBucketHandler))).
DefaultHandler(notSupportedHandler()))
})
attachErrorHandler(bktRouter)
return bktRouter
}
func listWrapper(h Handler) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if reqInfo := s3middleware.GetReqInfo(r.Context()); reqInfo.BucketName == "" {
reqInfo.API = s3middleware.ListBucketsOperation
h.ListBucketsHandler(w, r)
} else {
reqInfo.API = s3middleware.ListObjectsV1Operation
h.ListObjectsV1Handler(w, r)
}
}
}
func objectRouter(h Handler) chi.Router {
objRouter := chi.NewRouter()
objRouter.Options("/*", named(s3middleware.OptionsObjectOperation, h.Preflight))
objRouter.Head("/*", named(s3middleware.HeadObjectOperation, h.HeadObjectHandler))
objRouter.Patch("/*", named(s3middleware.PatchObjectOperation, h.PatchObjectHandler))
// GET method handlers
objRouter.Group(func(r chi.Router) {
r.Method(http.MethodGet, "/*", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.UploadIDQuery).
Handler(named(s3middleware.ListPartsOperation, h.ListPartsHandler))).
Add(NewFilter().
Queries(s3middleware.ACLQuery).
Handler(named(s3middleware.GetObjectACLOperation, h.GetObjectACLHandler))).
Add(NewFilter().
Queries(s3middleware.TaggingQuery).
Handler(named(s3middleware.GetObjectTaggingOperation, h.GetObjectTaggingHandler))).
Add(NewFilter().
Queries(s3middleware.RetentionQuery).
Handler(named(s3middleware.GetObjectRetentionOperation, h.GetObjectRetentionHandler))).
Add(NewFilter().
Queries(s3middleware.LegalHoldQuery).
Handler(named(s3middleware.GetObjectLegalHoldOperation, h.GetObjectLegalHoldHandler))).
Add(NewFilter().
Queries(s3middleware.AttributesQuery).
Handler(named(s3middleware.GetObjectAttributesOperation, h.GetObjectAttributesHandler))).
DefaultHandler(named(s3middleware.GetObjectOperation, h.GetObjectHandler)))
})
// PUT method handlers
objRouter.Group(func(r chi.Router) {
r.Method(http.MethodPut, "/*", NewHandlerFilter().
Add(NewFilter().
Headers(AmzCopySource).
Queries(s3middleware.PartNumberQuery, s3middleware.UploadIDQuery).
Handler(named(s3middleware.UploadPartCopyOperation, h.UploadPartCopy))).
Add(NewFilter().
Queries(s3middleware.PartNumberQuery, s3middleware.UploadIDQuery).
Handler(named(s3middleware.UploadPartOperation, h.UploadPartHandler))).
Add(NewFilter().
Queries(s3middleware.ACLQuery).
Handler(named(s3middleware.PutObjectACLOperation, h.PutObjectACLHandler))).
Add(NewFilter().
Queries(s3middleware.TaggingQuery).
Handler(named(s3middleware.PutObjectTaggingOperation, h.PutObjectTaggingHandler))).
Add(NewFilter().
Headers(AmzCopySource).
Handler(named(s3middleware.CopyObjectOperation, h.CopyObjectHandler))).
Add(NewFilter().
Queries(s3middleware.RetentionQuery).
Handler(named(s3middleware.PutObjectRetentionOperation, h.PutObjectRetentionHandler))).
Add(NewFilter().
Queries(s3middleware.LegalHoldQuery).
Handler(named(s3middleware.PutObjectLegalHoldOperation, h.PutObjectLegalHoldHandler))).
DefaultHandler(named(s3middleware.PutObjectOperation, h.PutObjectHandler)))
})
// POST method handlers
objRouter.Group(func(r chi.Router) {
r.Method(http.MethodPost, "/*", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.UploadIDQuery).
Handler(named(s3middleware.CompleteMultipartUploadOperation, h.CompleteMultipartUploadHandler))).
Add(NewFilter().
Queries(s3middleware.UploadsQuery).
Handler(named(s3middleware.CreateMultipartUploadOperation, h.CreateMultipartUploadHandler))).
DefaultHandler(named(s3middleware.SelectObjectContentOperation, h.SelectObjectContentHandler)))
})
// DELETE method handlers
objRouter.Group(func(r chi.Router) {
r.Method(http.MethodDelete, "/*", NewHandlerFilter().
Add(NewFilter().
Queries(s3middleware.UploadIDQuery).
Handler(named(s3middleware.AbortMultipartUploadOperation, h.AbortMultipartUploadHandler))).
Add(NewFilter().
Queries(s3middleware.TaggingQuery).
Handler(named(s3middleware.DeleteObjectTaggingOperation, h.DeleteObjectTaggingHandler))).
DefaultHandler(named(s3middleware.DeleteObjectOperation, h.DeleteObjectHandler)))
})
attachErrorHandler(objRouter)
return objRouter
}