From 3ff7028229c22c0d82f64bc3e63309e337122120 Mon Sep 17 00:00:00 2001 From: Pavel Korotkov Date: Thu, 16 Jul 2020 18:33:47 +0300 Subject: [PATCH] Add early support of auth middleware --- auth/center.go | 45 ++++++++++++++++++++++++++++++++++-- cmd/gate/app-new-auth.go | 24 +++++++++++++++++++ cmd/gate/app.go | 4 ++-- legacy/api-router.go | 6 ++--- legacy/neofs-router.go | 1 + neofs/layer/gateway-neofs.go | 29 ++++------------------- 6 files changed, 78 insertions(+), 31 deletions(-) create mode 100644 cmd/gate/app-new-auth.go diff --git a/auth/center.go b/auth/center.go index 84b95c6..7821707 100644 --- a/auth/center.go +++ b/auth/center.go @@ -8,6 +8,8 @@ import ( "crypto/x509" "encoding/pem" "io/ioutil" + "net/http" + "regexp" "github.com/klauspost/compress/zstd" "github.com/nspcc-dev/neofs-api-go/refs" @@ -16,8 +18,11 @@ import ( "github.com/pkg/errors" ) +const authorizationFieldPattern = `AWS4-HMAC-SHA256 Credential=(?P[^/]+)/(?P[^/]+)/(?P[^/]*)/(?P[^/]+)/aws4_request, SignedHeaders=(?P.*), Signature=(?P.*)` + // Center is a central app's authentication/authorization management unit. type Center struct { + submatcher *regexpSubmatcher zstdEncoder *zstd.Encoder zstdDecoder *zstd.Decoder neofsKeys struct { @@ -37,6 +42,7 @@ func NewCenter() *Center { zstdEncoder, _ := zstd.NewWriter(nil) zstdDecoder, _ := zstd.NewReader(nil) return &Center{ + submatcher: ®expSubmatcher{re: regexp.MustCompile(authorizationFieldPattern)}, zstdEncoder: zstdEncoder, zstdDecoder: zstdDecoder, } @@ -80,7 +86,7 @@ func (center *Center) SetUserAuthKeys(key *rsa.PrivateKey) { center.userAuthKeys.PublicKey = &key.PublicKey } -func (center *Center) PackBearerToken(bearerToken *service.BearerTokenMsg) ([]byte, error) { +func (center *Center) packBearerToken(bearerToken *service.BearerTokenMsg) ([]byte, error) { data, err := bearerToken.Marshal() if err != nil { return nil, errors.Wrap(err, "failed to marshal bearer token") @@ -92,7 +98,7 @@ func (center *Center) PackBearerToken(bearerToken *service.BearerTokenMsg) ([]by return append(sha256Hash(data), encryptedKeyID...), nil } -func (center *Center) UnpackBearerToken(packedBearerToken []byte) (*service.BearerTokenMsg, error) { +func (center *Center) unpackBearerToken(packedBearerToken []byte) (*service.BearerTokenMsg, error) { compressedKeyID := packedBearerToken[32:] encryptedKeyID, err := center.decompress(compressedKeyID) if err != nil { @@ -109,6 +115,25 @@ func (center *Center) UnpackBearerToken(packedBearerToken []byte) (*service.Bear return bearerToken, nil } +func (center *Center) AuthenticationPassed(header http.Header) (*service.BearerTokenMsg, error) { + authHeaderField := header["Authorization"] + if len(authHeaderField) != 1 { + return nil, errors.New("wrong length of Authorization header field") + } + sms := center.submatcher.getSubmatches(authHeaderField[0]) + if len(sms) != 6 { + return nil, errors.New("bad Authorization header field") + } + akid := sms["access_key_id"] + bt, err := center.unpackBearerToken([]byte(akid)) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack bearer token") + } + // v4sig := sms["v4_signature"] + // TODO: Validate V4 signature. + return bt, nil +} + func (center *Center) compress(data []byte) []byte { center.zstdEncoder.Reset(nil) var compressedData []byte @@ -154,3 +179,19 @@ func ReadRSAPrivateKeyFromPEMFile(filePath string) (*rsa.PrivateKey, error) { } return rsaKey, nil } + +type regexpSubmatcher struct { + re *regexp.Regexp +} + +func (resm *regexpSubmatcher) getSubmatches(target string) map[string]string { + matches := resm.re.FindStringSubmatch(target) + l := len(matches) + submatches := make(map[string]string, l) + for i, name := range resm.re.SubexpNames() { + if i > 0 && i <= l { + submatches[name] = matches[i] + } + } + return submatches +} diff --git a/cmd/gate/app-new-auth.go b/cmd/gate/app-new-auth.go new file mode 100644 index 0000000..5e77184 --- /dev/null +++ b/cmd/gate/app-new-auth.go @@ -0,0 +1,24 @@ +package main + +import ( + "net/http" + + "github.com/gorilla/mux" + s3auth "github.com/minio/minio/auth" + "go.uber.org/zap" +) + +func attachNewUserAuth(router *mux.Router, center *s3auth.Center, log *zap.Logger) { + uamw := func(h http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + _, err := center.AuthenticationPassed(r.Header) + if err != nil { + log.Error("failed to pass authentication", zap.Error(err)) + } + // TODO: Handle any auth error by rejecting request. + h.ServeHTTP(w, r) + + }) + } + router.Use(uamw) +} diff --git a/cmd/gate/app.go b/cmd/gate/app.go index 317b1d2..a99c93e 100644 --- a/cmd/gate/app.go +++ b/cmd/gate/app.go @@ -12,7 +12,6 @@ import ( "github.com/minio/minio/legacy/config" "github.com/minio/minio/neofs/layer" "github.com/minio/minio/neofs/pool" - "github.com/minio/minio/pkg/auth" "github.com/spf13/viper" "go.uber.org/zap" "google.golang.org/grpc/keepalive" @@ -121,7 +120,7 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { l.Info("used credentials", zap.String("AccessKey", uid.String()), zap.String("SecretKey", wif)) } - if obj, err = layer.NewLayer(cli, l, auth.Credentials{AccessKey: uid.String(), SecretKey: wif}); err != nil { + if obj, err = layer.NewLayer(cli, l, center); err != nil { l.Fatal("could not prepare ObjectLayer", zap.Error(err)) } } @@ -174,6 +173,7 @@ func (a *App) Server(ctx context.Context) { router := newS3Router() // Attach app-specific routes: + attachNewUserAuth(router, a.center, a.log) attachHealthy(router, a.cli) attachMetrics(router, a.cfg, a.log) attachProfiler(router, a.cfg, a.log) diff --git a/legacy/api-router.go b/legacy/api-router.go index ffecfd7..ad1d0f3 100644 --- a/legacy/api-router.go +++ b/legacy/api-router.go @@ -77,13 +77,13 @@ func registerAPIRouter(router *mux.Router, encryptionEnabled, allowSSEKMS bool) return allowSSEKMS }, } - // API Router apiRouter := router.PathPrefix(SlashSeparator).Subrouter() var routers []*mux.Router for _, domainName := range globalDomainNames { - routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName).Subrouter()) - routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName+":{port:.*}").Subrouter()) + r1 := apiRouter.Host("{bucket:.+}." + domainName).Subrouter() + r2 := apiRouter.Host("{bucket:.+}." + domainName + ":{port:.*}").Subrouter() + routers = append(routers, []*mux.Router{r1, r2}...) } routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter()) diff --git a/legacy/neofs-router.go b/legacy/neofs-router.go index 5be6973..ac42143 100644 --- a/legacy/neofs-router.go +++ b/legacy/neofs-router.go @@ -10,6 +10,7 @@ func AttachS3API(r *mux.Router, obj ObjectLayer, l *zap.Logger) { // Initialize all help initHelp() + // TODO: If this name is actually stays unchanges, move it to constants. globalGatewayName = "NeoFS S3 Gate" // Set when gateway is enabled diff --git a/neofs/layer/gateway-neofs.go b/neofs/layer/gateway-neofs.go index 2f4c161..05898fe 100644 --- a/neofs/layer/gateway-neofs.go +++ b/neofs/layer/gateway-neofs.go @@ -6,13 +6,11 @@ import ( "math" "time" + s3auth "github.com/minio/minio/auth" minio "github.com/minio/minio/legacy" "github.com/minio/minio/neofs/pool" - "github.com/minio/minio/pkg/auth" - "github.com/nspcc-dev/neofs-api-go/chain" "github.com/nspcc-dev/neofs-api-go/refs" "github.com/nspcc-dev/neofs-api-go/service" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/pkg/errors" "go.uber.org/zap" ) @@ -42,41 +40,24 @@ type ( // NewGatewayLayer creates instance of neofsObject. It checks credentials // and establishes gRPC connection with node. -func NewLayer(cli pool.Client, log *zap.Logger, cred auth.Credentials) (minio.ObjectLayer, error) { - // check if wif is correct - key, err := crypto.WIFDecode(cred.SecretKey) - if err != nil { - return nil, errors.New("can't decode secret key, it must be WIF") - } - // check if wif corresponds wallet address - if cred.AccessKey != chain.KeysToAddress(&key.PublicKey) { - return nil, errors.New("wif and wallet are not corresponded") - } - // format public key into owner - owner, err := refs.NewOwnerID(&key.PublicKey) - if err != nil { - return nil, errors.New("can't create owner id from key") - } - +func NewLayer(cli pool.Client, log *zap.Logger, center *s3auth.Center) (minio.ObjectLayer, error) { // setup gRPC connection // todo: think about getting timeout parameters from cli args ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() - token, err := generateToken(ctx, tokenParams{ cli: cli, - key: key, + key: center.GetNeoFSKeyPrivateKey(), until: math.MaxInt64, }) if err != nil { return nil, errors.Wrap(err, "can't establish neofs session with remote host") } - return &neofsObject{ cli: cli, - key: key, + key: center.GetNeoFSKeyPrivateKey(), log: log, - owner: owner, + owner: center.GetOwnerID(), token: token, }, nil }