[#260] Refactor api/auth/center.go

Move the Center interface to middleware package where it's used

Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
Denis Kirillov 2023-10-05 11:05:21 +03:00
parent 861454e499
commit cf7254f8cd
9 changed files with 61 additions and 60 deletions

View file

@ -5,7 +5,6 @@ import (
"crypto/hmac" "crypto/hmac"
"crypto/sha256" "crypto/sha256"
"encoding/hex" "encoding/hex"
"errors"
"fmt" "fmt"
"io" "io"
"mime/multipart" "mime/multipart"
@ -17,6 +16,7 @@ import (
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4" v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" apiErrors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"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/creds/tokens" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id" oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
@ -31,27 +31,13 @@ var authorizationFieldRegexp = regexp.MustCompile(`AWS4-HMAC-SHA256 Credential=(
var postPolicyCredentialRegexp = regexp.MustCompile(`(?P<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<region>[^/]*)/(?P<service>[^/]+)/aws4_request`) var postPolicyCredentialRegexp = regexp.MustCompile(`(?P<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<region>[^/]*)/(?P<service>[^/]+)/aws4_request`)
type ( type (
// Center is a user authentication interface. Center struct {
Center interface {
Authenticate(request *http.Request) (*Box, error)
}
// Box contains access box and additional info.
Box struct {
AccessBox *accessbox.Box
ClientTime time.Time
AuthHeaders *AuthHeader
}
center struct {
reg *RegexpSubmatcher reg *RegexpSubmatcher
postReg *RegexpSubmatcher postReg *RegexpSubmatcher
cli tokens.Credentials cli tokens.Credentials
allowedAccessKeyIDPrefixes []string // empty slice means all access key ids are allowed allowedAccessKeyIDPrefixes []string // empty slice means all access key ids are allowed
} }
prs int
//nolint:revive //nolint:revive
AuthHeader struct { AuthHeader struct {
AccessKeyID string AccessKeyID string
@ -97,22 +83,9 @@ var ContentSHA256HeaderStandardValue = map[string]struct{}{
StreamingContentECDSASHA256Trailer: {}, StreamingContentECDSASHA256Trailer: {},
} }
// ErrNoAuthorizationHeader is returned for unauthenticated requests.
var ErrNoAuthorizationHeader = errors.New("no authorization header")
func (p prs) Read(_ []byte) (n int, err error) {
panic("implement me")
}
func (p prs) Seek(_ int64, _ int) (int64, error) {
panic("implement me")
}
var _ io.ReadSeeker = prs(0)
// New creates an instance of AuthCenter. // New creates an instance of AuthCenter.
func New(frostFS tokens.FrostFS, key *keys.PrivateKey, prefixes []string, config *cache.Config) Center { func New(frostFS tokens.FrostFS, key *keys.PrivateKey, prefixes []string, config *cache.Config) *Center {
return &center{ return &Center{
cli: tokens.New(frostFS, key, config), cli: tokens.New(frostFS, key, config),
reg: NewRegexpMatcher(authorizationFieldRegexp), reg: NewRegexpMatcher(authorizationFieldRegexp),
postReg: NewRegexpMatcher(postPolicyCredentialRegexp), postReg: NewRegexpMatcher(postPolicyCredentialRegexp),
@ -120,7 +93,7 @@ func New(frostFS tokens.FrostFS, key *keys.PrivateKey, prefixes []string, config
} }
} }
func (c *center) parseAuthHeader(header string) (*AuthHeader, error) { func (c *Center) parseAuthHeader(header string) (*AuthHeader, error) {
submatches := c.reg.GetSubmatches(header) submatches := c.reg.GetSubmatches(header)
if len(submatches) != authHeaderPartsNum { if len(submatches) != authHeaderPartsNum {
return nil, apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed) return nil, apiErrors.GetAPIError(apiErrors.ErrAuthorizationHeaderMalformed)
@ -156,7 +129,7 @@ func IsStandardContentSHA256(key string) bool {
return ok return ok
} }
func (c *center) Authenticate(r *http.Request) (*Box, error) { func (c *Center) Authenticate(r *http.Request) (*middleware.Box, error) {
var ( var (
err error err error
authHdr *AuthHeader authHdr *AuthHeader
@ -190,7 +163,7 @@ func (c *center) Authenticate(r *http.Request) (*Box, error) {
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(r)
} }
return nil, ErrNoAuthorizationHeader return nil, middleware.ErrNoAuthorizationHeader
} }
authHdr, err = c.parseAuthHeader(authHeaderField[0]) authHdr, err = c.parseAuthHeader(authHeaderField[0])
if err != nil { if err != nil {
@ -228,9 +201,13 @@ func (c *center) Authenticate(r *http.Request) (*Box, error) {
return nil, err return nil, err
} }
result := &Box{ result := &middleware.Box{
AccessBox: box, AccessBox: box,
AuthHeaders: authHdr, AuthHeaders: &middleware.AuthHeader{
AccessKeyID: authHdr.AccessKeyID,
Region: authHdr.Region,
SignatureV4: authHdr.SignatureV4,
},
} }
if needClientTime { if needClientTime {
result.ClientTime = signatureDateTime result.ClientTime = signatureDateTime
@ -253,7 +230,7 @@ func checkFormatHashContentSHA256(hash string) error {
return nil return nil
} }
func (c center) checkAccessKeyID(accessKeyID string) error { func (c Center) checkAccessKeyID(accessKeyID string) error {
if len(c.allowedAccessKeyIDPrefixes) == 0 { if len(c.allowedAccessKeyIDPrefixes) == 0 {
return nil return nil
} }
@ -267,7 +244,7 @@ func (c center) checkAccessKeyID(accessKeyID string) error {
return apiErrors.GetAPIError(apiErrors.ErrAccessDenied) return apiErrors.GetAPIError(apiErrors.ErrAccessDenied)
} }
func (c *center) checkFormData(r *http.Request) (*Box, error) { func (c *Center) checkFormData(r *http.Request) (*middleware.Box, error) {
if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil { if err := r.ParseMultipartForm(maxFormSizeMemory); err != nil {
return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidArgument) return nil, apiErrors.GetAPIError(apiErrors.ErrInvalidArgument)
} }
@ -278,7 +255,7 @@ func (c *center) checkFormData(r *http.Request) (*Box, error) {
policy := MultipartFormValue(r, "policy") policy := MultipartFormValue(r, "policy")
if policy == "" { if policy == "" {
return nil, ErrNoAuthorizationHeader return nil, middleware.ErrNoAuthorizationHeader
} }
submatches := c.postReg.GetSubmatches(MultipartFormValue(r, "x-amz-credential")) submatches := c.postReg.GetSubmatches(MultipartFormValue(r, "x-amz-credential"))
@ -309,7 +286,7 @@ func (c *center) checkFormData(r *http.Request) (*Box, error) {
return nil, apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch) return nil, apiErrors.GetAPIError(apiErrors.ErrSignatureDoesNotMatch)
} }
return &Box{AccessBox: box}, nil return &middleware.Box{AccessBox: box}, nil
} }
func cloneRequest(r *http.Request, authHeader *AuthHeader) *http.Request { func cloneRequest(r *http.Request, authHeader *AuthHeader) *http.Request {
@ -333,7 +310,7 @@ func cloneRequest(r *http.Request, authHeader *AuthHeader) *http.Request {
return otherRequest return otherRequest
} }
func (c *center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error { func (c *Center) checkSign(authHeader *AuthHeader, box *accessbox.Box, request *http.Request, signatureDateTime time.Time) error {
awsCreds := credentials.NewStaticCredentials(authHeader.AccessKeyID, box.Gate.SecretKey, "") awsCreds := credentials.NewStaticCredentials(authHeader.AccessKeyID, box.Gate.SecretKey, "")
signer := v4.NewSigner(awsCreds) signer := v4.NewSigner(awsCreds)
signer.DisableURIPathEscaping = true signer.DisableURIPathEscaping = true

View file

@ -12,7 +12,7 @@ import (
func TestAuthHeaderParse(t *testing.T) { func TestAuthHeaderParse(t *testing.T) {
defaultHeader := "AWS4-HMAC-SHA256 Credential=oid0cid/20210809/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2811ccb9e242f41426738fb1f" defaultHeader := "AWS4-HMAC-SHA256 Credential=oid0cid/20210809/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=2811ccb9e242f41426738fb1f"
center := &center{ center := &Center{
reg: NewRegexpMatcher(authorizationFieldRegexp), reg: NewRegexpMatcher(authorizationFieldRegexp),
} }

View file

@ -84,7 +84,7 @@ func TestCheckSign(t *testing.T) {
mock := newTokensFrostfsMock() mock := newTokensFrostfsMock()
mock.addBox(accessKeyAddr, expBox) mock.addBox(accessKeyAddr, expBox)
c := &center{ c := &Center{
cli: mock, cli: mock,
reg: NewRegexpMatcher(authorizationFieldRegexp), reg: NewRegexpMatcher(authorizationFieldRegexp),
postReg: NewRegexpMatcher(postPolicyCredentialRegexp), postReg: NewRegexpMatcher(postPolicyCredentialRegexp),

View file

@ -17,7 +17,6 @@ import (
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4" v4 "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth/signer/v4"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" s3errors "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
@ -355,10 +354,9 @@ func getChunkedRequest(ctx context.Context, t *testing.T, bktName, objName strin
reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName}) reqInfo := middleware.NewReqInfo(w, req, middleware.ObjectRequest{Bucket: bktName, Object: objName})
req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo)) req = req.WithContext(middleware.SetReqInfo(ctx, reqInfo))
req = req.WithContext(middleware.SetClientTime(req.Context(), signTime)) req = req.WithContext(middleware.SetClientTime(req.Context(), signTime))
req = req.WithContext(middleware.SetAuthHeaders(req.Context(), &auth.AuthHeader{ req = req.WithContext(middleware.SetAuthHeaders(req.Context(), &middleware.AuthHeader{
AccessKeyID: AWSAccessKeyID, AccessKeyID: AWSAccessKeyID,
SignatureV4: "4f232c4386841ef735655705268965c44a0e4690baa4adea153f7db9fa80a0a9", SignatureV4: "4f232c4386841ef735655705268965c44a0e4690baa4adea153f7db9fa80a0a9",
Service: "s3",
Region: "us-east-1", Region: "us-east-1",
})) }))
req = req.WithContext(middleware.SetBoxData(req.Context(), &accessbox.Box{ req = req.WithContext(middleware.SetBoxData(req.Context(), &accessbox.Box{

View file

@ -1,21 +1,49 @@
package middleware package middleware
import ( import (
stderrors "errors"
"net/http" "net/http"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "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/internal/logs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
"go.uber.org/zap" "go.uber.org/zap"
) )
func Auth(center auth.Center, log *zap.Logger) Func { type (
// Box contains access box and additional info.
Box struct {
AccessBox *accessbox.Box
ClientTime time.Time
AuthHeaders *AuthHeader
}
// Center is a user authentication interface.
Center interface {
// Authenticate validate and authenticate request.
// Must return ErrNoAuthorizationHeader if auth header is missed.
Authenticate(request *http.Request) (*Box, error)
}
//nolint:revive
AuthHeader struct {
AccessKeyID string
Region string
SignatureV4 string
}
)
// ErrNoAuthorizationHeader is returned for unauthenticated requests.
var ErrNoAuthorizationHeader = stderrors.New("no authorization header")
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() ctx := r.Context()
box, err := center.Authenticate(r) box, err := center.Authenticate(r)
if err != nil { if err != nil {
if err == auth.ErrNoAuthorizationHeader { if err == ErrNoAuthorizationHeader {
reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed) reqLogOrDefault(ctx, log).Debug(logs.CouldntReceiveAccessBoxForGateKeyRandomKeyWillBeUsed)
} else { } else {
reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err)) reqLogOrDefault(ctx, log).Error(logs.FailedToPassAuthentication, zap.Error(err))

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"time" "time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
) )
@ -37,8 +36,8 @@ func GetBoxData(ctx context.Context) (*accessbox.Box, error) {
} }
// GetAuthHeaders extracts auth.AuthHeader from context. // GetAuthHeaders extracts auth.AuthHeader from context.
func GetAuthHeaders(ctx context.Context) (*auth.AuthHeader, error) { func GetAuthHeaders(ctx context.Context) (*AuthHeader, error) {
authHeaders, ok := ctx.Value(authHeadersKey).(*auth.AuthHeader) authHeaders, ok := ctx.Value(authHeadersKey).(*AuthHeader)
if !ok { if !ok {
return nil, fmt.Errorf("couldn't get auth headers from context") return nil, fmt.Errorf("couldn't get auth headers from context")
} }
@ -62,7 +61,7 @@ func SetBoxData(ctx context.Context, box *accessbox.Box) context.Context {
} }
// SetAuthHeaders sets auth.AuthHeader in the context. // SetAuthHeaders sets auth.AuthHeader in the context.
func SetAuthHeaders(ctx context.Context, header *auth.AuthHeader) context.Context { func SetAuthHeaders(ctx context.Context, header *AuthHeader) context.Context {
return context.WithValue(ctx, authHeadersKey, header) return context.WithValue(ctx, authHeadersKey, header)
} }

View file

@ -5,7 +5,6 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
@ -90,7 +89,7 @@ type (
} }
) )
func AttachChi(api *chi.Mux, domains []string, throttle middleware.ThrottleOpts, h Handler, center auth.Center, log *zap.Logger, appMetrics *metrics.AppMetrics) { func AttachChi(api *chi.Mux, domains []string, throttle middleware.ThrottleOpts, h Handler, center s3middleware.Center, log *zap.Logger, appMetrics *metrics.AppMetrics) {
api.Use( api.Use(
s3middleware.Request(log), s3middleware.Request(log),
middleware.ThrottleWithOpts(throttle), middleware.ThrottleWithOpts(throttle),

View file

@ -6,7 +6,6 @@ import (
"net/http" "net/http"
"testing" "testing"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/auth"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
@ -15,8 +14,8 @@ import (
type centerMock struct { type centerMock struct {
} }
func (c *centerMock) Authenticate(*http.Request) (*auth.Box, error) { func (c *centerMock) Authenticate(*http.Request) (*middleware.Box, error) {
return &auth.Box{}, nil return &middleware.Box{}, nil
} }
type handlerMock struct { type handlerMock struct {

View file

@ -21,6 +21,7 @@ import (
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/handler"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer"
s3middleware "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/notifications" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/notifications"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/resolver"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs"
@ -47,7 +48,7 @@ const awsDefaultNamespace = "http://s3.amazonaws.com/doc/2006-03-01/"
type ( type (
// App is the main application structure. // App is the main application structure.
App struct { App struct {
ctr auth.Center ctr s3middleware.Center
log *zap.Logger log *zap.Logger
cfg *viper.Viper cfg *viper.Viper
pool *pool.Pool pool *pool.Pool