[#498] middleware: Add spans to detail the trace

Spans are added only to the following middleware:
* PolicyCheck
* Auth
* FrostfsIDValidation

This is done this way because these middleware are basic and
they interact with frostfs-storage.

Signed-off-by: Roman Loginov <r.loginov@yadro.com>
This commit is contained in:
Roman Loginov 2024-11-25 16:28:18 +03:00
parent e757f48be9
commit 3c9659cbde
6 changed files with 42 additions and 26 deletions

View file

@ -120,7 +120,7 @@ func IsStandardContentSHA256(key string) bool {
return ok return ok
} }
func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) { func (c *Center) Authenticate(ctx context.Context, r *http.Request) (*middleware.Box, error) {
var ( var (
err error err error
authHdr *AuthHeader authHdr *AuthHeader
@ -152,7 +152,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
authHeaderField := r.Header[AuthorizationHdr] authHeaderField := r.Header[AuthorizationHdr]
if len(authHeaderField) != 1 { if len(authHeaderField) != 1 {
if strings.HasPrefix(r.Header.Get(ContentTypeHdr), "multipart/form-data") { if strings.HasPrefix(r.Header.Get(ContentTypeHdr), "multipart/form-data") {
return c.checkFormData(r) return c.checkFormData(ctx, r)
} }
return nil, fmt.Errorf("%w: %v", middleware.ErrNoAuthorizationHeader, authHeaderField) return nil, fmt.Errorf("%w: %v", middleware.ErrNoAuthorizationHeader, authHeaderField)
} }
@ -178,7 +178,7 @@ func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
return nil, err return nil, err
} }
box, attrs, err := c.cli.GetBox(r.Context(), cnrID, authHdr.AccessKeyID) box, attrs, err := c.cli.GetBox(ctx, cnrID, authHdr.AccessKeyID)
if err != nil { if err != nil {
return nil, fmt.Errorf("get box by access key '%s': %w", authHdr.AccessKeyID, err) return nil, fmt.Errorf("get box by access key '%s': %w", authHdr.AccessKeyID, err)
} }
@ -251,7 +251,7 @@ func (c Center) checkAccessKeyID(accessKeyID string) error {
return fmt.Errorf("%w: accesskeyID prefix isn't allowed", apierr.GetAPIError(apierr.ErrAccessDenied)) return fmt.Errorf("%w: accesskeyID prefix isn't allowed", apierr.GetAPIError(apierr.ErrAccessDenied))
} }
func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) { func (c *Center) checkFormData(ctx context.Context, r *http.Request) (*middleware.Box, error) {
if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil { if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil {
return nil, fmt.Errorf("%w: parse multipart form with max size %d", apierr.GetAPIError(apierr.ErrInvalidArgument), maxFormSizeMemory) return nil, fmt.Errorf("%w: parse multipart form with max size %d", apierr.GetAPIError(apierr.ErrInvalidArgument), maxFormSizeMemory)
} }
@ -283,7 +283,7 @@ func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
return nil, err return nil, err
} }
box, attrs, err := c.cli.GetBox(r.Context(), cnrID, accessKeyID) box, attrs, err := c.cli.GetBox(ctx, cnrID, accessKeyID)
if err != nil { if err != nil {
return nil, fmt.Errorf("get box by accessKeyID '%s': %w", accessKeyID, err) return nil, fmt.Errorf("get box by accessKeyID '%s': %w", accessKeyID, err)
} }

View file

@ -385,7 +385,7 @@ func TestAuthenticate(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
creds := tokens.New(bigConfig) creds := tokens.New(bigConfig)
cntr := New(creds, tc.prefixes, &centerSettingsMock{}) cntr := New(creds, tc.prefixes, &centerSettingsMock{})
box, err := cntr.Authenticate(tc.request) box, err := cntr.Authenticate(context.Background(), tc.request)
if tc.err { if tc.err {
require.Error(t, err) require.Error(t, err)
@ -563,7 +563,7 @@ func TestHTTPPostAuthenticate(t *testing.T) {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
creds := tokens.New(bigConfig) creds := tokens.New(bigConfig)
cntr := New(creds, tc.prefixes, &centerSettingsMock{}) cntr := New(creds, tc.prefixes, &centerSettingsMock{})
box, err := cntr.Authenticate(tc.request) box, err := cntr.Authenticate(context.Background(), tc.request)
if tc.err { if tc.err {
require.Error(t, err) require.Error(t, err)

View file

@ -89,7 +89,7 @@ func TestCheckSign(t *testing.T) {
postReg: NewRegexpMatcher(postPolicyCredentialRegexp), postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
settings: &centerSettingsMock{}, settings: &centerSettingsMock{},
} }
box, err := c.Authenticate(req) box, err := c.Authenticate(context.Background(), req)
require.NoError(t, err) require.NoError(t, err)
require.EqualValues(t, expBox, box.AccessBox) require.EqualValues(t, expBox, box.AccessBox)
} }

View file

@ -1,6 +1,7 @@
package middleware package middleware
import ( import (
"context"
"crypto/elliptic" "crypto/elliptic"
"errors" "errors"
"fmt" "fmt"
@ -8,6 +9,7 @@ import (
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl" "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/acl"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
@ -30,7 +32,9 @@ type (
Center interface { Center interface {
// Authenticate validate and authenticate request. // Authenticate validate and authenticate request.
// Must return ErrNoAuthorizationHeader if auth header is missed. // Must return ErrNoAuthorizationHeader if auth header is missed.
Authenticate(request *http.Request) (*Box, error) // Authenticate uses a separate context so that the authorization
// span middleware does not contain all subsequent spans.
Authenticate(ctx context.Context, request *http.Request) (*Box, error)
} }
//nolint:revive //nolint:revive
@ -47,34 +51,38 @@ var ErrNoAuthorizationHeader = errors.New("no authorization header")
func Auth(center Center, log *zap.Logger) Func { func Auth(center Center, log *zap.Logger) Func {
return func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() reqCtx := r.Context()
reqInfo := GetReqInfo(ctx) ctx, span := tracing.StartSpanFromContext(reqCtx, "middleware.Auth")
reqInfo := GetReqInfo(reqCtx)
reqInfo.User = "anon" reqInfo.User = "anon"
box, err := center.Authenticate(r) box, err := center.Authenticate(ctx, r)
if err != nil { if err != nil {
if errors.Is(err, ErrNoAuthorizationHeader) { if errors.Is(err, ErrNoAuthorizationHeader) {
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err)) reqLogOrDefault(reqCtx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed, zap.Error(err))
} else { } else {
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err)) reqLogOrDefault(reqCtx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))
err = apierr.TransformToS3Error(err) err = apierr.TransformToS3Error(err)
if err.(apierr.Error).ErrCode == apierr.ErrInternalError { if err.(apierr.Error).ErrCode == apierr.ErrInternalError {
err = apierr.GetAPIError(apierr.ErrAccessDenied) err = apierr.GetAPIError(apierr.ErrAccessDenied)
} }
if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil { if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil {
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr)) reqLogOrDefault(reqCtx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
} }
span.End()
return return
} }
} else { } else {
ctx = SetBox(ctx, box) reqCtx = SetBox(reqCtx, box)
if box.AccessBox.Gate.BearerToken != nil { if box.AccessBox.Gate.BearerToken != nil {
reqInfo.User = bearer.ResolveIssuer(*box.AccessBox.Gate.BearerToken).String() reqInfo.User = bearer.ResolveIssuer(*box.AccessBox.Gate.BearerToken).String()
} }
reqLogOrDefault(ctx, log).Debug(logs.SuccessfulAuth, zap.String("accessKeyID", box.AuthHeaders.AccessKeyID)) reqLogOrDefault(reqCtx, log).Debug(logs.SuccessfulAuth, zap.String("accessKeyID", box.AuthHeaders.AccessKeyID))
} }
h.ServeHTTP(w, r.WithContext(ctx)) span.End()
h.ServeHTTP(w, r.WithContext(reqCtx))
}) })
} }
} }
@ -86,22 +94,26 @@ type FrostFSIDValidator interface {
func FrostfsIDValidation(frostfsID FrostFSIDValidator, log *zap.Logger) Func { func FrostfsIDValidation(frostfsID FrostFSIDValidator, log *zap.Logger) Func {
return func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx, span := tracing.StartSpanFromContext(r.Context(), "middleware.FrostfsIDValidation")
bd, err := GetBoxData(ctx) bd, err := GetBoxData(ctx)
if err != nil || bd.Gate.BearerToken == nil { if err != nil || bd.Gate.BearerToken == nil {
reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipFrostfsIDValidation) reqLogOrDefault(ctx, log).Debug(logs.AnonRequestSkipFrostfsIDValidation)
span.End()
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
return return
} }
if err = validateBearerToken(frostfsID, bd.Gate.BearerToken); err != nil { if err = validateBearerToken(frostfsID, bd.Gate.BearerToken); err != nil {
reqLogOrDefault(ctx, log).Error(logs.FrostfsIDValidationFailed, zap.Error(err)) reqLogOrDefault(ctx, log).Error(logs.FrostfsIDValidationFailed, zap.Error(err))
if _, wrErr := WriteErrorResponse(w, GetReqInfo(r.Context()), err); wrErr != nil { if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr)) reqLogOrDefault(ctx, log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
} }
span.End()
return return
} }
span.End()
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
}) })
} }

View file

@ -10,6 +10,7 @@ import (
"net/url" "net/url"
"strings" "strings"
"git.frostfs.info/TrueCloudLab/frostfs-observability/tracing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" apierr "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
@ -85,23 +86,26 @@ type PolicyConfig struct {
func PolicyCheck(cfg PolicyConfig) Func { func PolicyCheck(cfg PolicyConfig) Func {
return func(h http.Handler) http.Handler { return func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context() ctx, span := tracing.StartSpanFromContext(r.Context(), "middleware.PolicyCheck")
if err := policyCheck(r, cfg); err != nil {
if err := policyCheck(ctx, r, cfg); err != nil {
reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err)) reqLogOrDefault(ctx, cfg.Log).Error(logs.PolicyValidationFailed, zap.Error(err))
err = apierr.TransformToS3Error(err) err = apierr.TransformToS3Error(err)
if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil { if _, wrErr := WriteErrorResponse(w, GetReqInfo(ctx), err); wrErr != nil {
reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr)) reqLogOrDefault(ctx, cfg.Log).Error(logs.FailedToWriteResponse, zap.Error(wrErr))
} }
span.End()
return return
} }
span.End()
h.ServeHTTP(w, r) h.ServeHTTP(w, r)
}) })
} }
} }
func policyCheck(r *http.Request, cfg PolicyConfig) error { func policyCheck(ctx context.Context, r *http.Request, cfg PolicyConfig) error {
reqInfo := GetReqInfo(r.Context()) reqInfo := GetReqInfo(ctx)
req, userKey, userGroups, err := getPolicyRequest(r, cfg, reqInfo.RequestType, reqInfo.BucketName, reqInfo.ObjectName) req, userKey, userGroups, err := getPolicyRequest(r, cfg, reqInfo.RequestType, reqInfo.BucketName, reqInfo.ObjectName)
if err != nil { if err != nil {
@ -110,7 +114,7 @@ func policyCheck(r *http.Request, cfg PolicyConfig) error {
var bktInfo *data.BucketInfo var bktInfo *data.BucketInfo
if reqInfo.RequestType != noneType && !strings.HasSuffix(req.Operation(), CreateBucketOperation) { if reqInfo.RequestType != noneType && !strings.HasSuffix(req.Operation(), CreateBucketOperation) {
bktInfo, err = cfg.BucketResolver(r.Context(), reqInfo.BucketName) bktInfo, err = cfg.BucketResolver(ctx, reqInfo.BucketName)
if err != nil { if err != nil {
return err return err
} }

View file

@ -45,7 +45,7 @@ type centerMock struct {
key *keys.PrivateKey key *keys.PrivateKey
} }
func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) { func (c *centerMock) Authenticate(context.Context, *http.Request) (*middleware.Box, error) {
if c.noAuthHeader { if c.noAuthHeader {
return nil, middleware.ErrNoAuthorizationHeader return nil, middleware.ErrNoAuthorizationHeader
} }