diff --git a/auth/center.go b/auth/center.go new file mode 100644 index 00000000..5400ec46 --- /dev/null +++ b/auth/center.go @@ -0,0 +1,210 @@ +package auth + +import ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/rsa" + "encoding/hex" + "io/ioutil" + "net/http" + "regexp" + "strings" + "time" + + "github.com/aws/aws-sdk-go/aws/credentials" + v4 "github.com/aws/aws-sdk-go/aws/signer/v4" + "github.com/klauspost/compress/zstd" + "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" +) + +var authorizationFieldRegexp = regexp.MustCompile(`AWS4-HMAC-SHA256 Credential=(?P[^/]+)/(?P[^/]+)/(?P[^/]*)/(?P[^/]+)/aws4_request, SignedHeaders=(?P.*), Signature=(?P.*)`) + +const emptyStringSHA256 = `e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855` + +// Center is a central app's authentication/authorization management unit. +type Center struct { + log *zap.Logger + submatcher *regexpSubmatcher + zstdEncoder *zstd.Encoder + zstdDecoder *zstd.Decoder + neofsKeys struct { + PrivateKey *ecdsa.PrivateKey + PublicKey *ecdsa.PublicKey + } + ownerID refs.OwnerID + wifString string + userAuthKeys struct { + PrivateKey *rsa.PrivateKey + PublicKey *rsa.PublicKey + } +} + +// NewCenter creates an instance of AuthCenter. +func NewCenter(log *zap.Logger) (*Center, error) { + zstdEncoder, err := zstd.NewWriter(nil) + if err != nil { + return nil, errors.Wrap(err, "failed to create zstd encoder") + } + zstdDecoder, err := zstd.NewReader(nil) + if err != nil { + return nil, errors.Wrap(err, "failed to create zstd decoder") + } + return &Center{ + log: log, + submatcher: ®expSubmatcher{re: authorizationFieldRegexp}, + zstdEncoder: zstdEncoder, + zstdDecoder: zstdDecoder, + }, nil +} + +func (center *Center) SetNeoFSKeys(key *ecdsa.PrivateKey) error { + publicKey := &key.PublicKey + oid, err := refs.NewOwnerID(publicKey) + if err != nil { + return errors.Wrap(err, "failed to get OwnerID") + } + center.neofsKeys.PrivateKey = key + wif, err := crypto.WIFEncode(key) + if err != nil { + return errors.Wrap(err, "failed to get WIF string from given key") + } + center.neofsKeys.PublicKey = publicKey + center.ownerID = oid + center.wifString = wif + return nil +} + +func (center *Center) GetNeoFSPrivateKey() *ecdsa.PrivateKey { + return center.neofsKeys.PrivateKey +} + +func (center *Center) GetNeoFSPublicKey() *ecdsa.PublicKey { + return center.neofsKeys.PublicKey +} + +func (center *Center) GetOwnerID() refs.OwnerID { + return center.ownerID +} + +func (center *Center) GetWIFString() string { + return center.wifString +} + +func (center *Center) SetUserAuthKeys(key *rsa.PrivateKey) { + center.userAuthKeys.PrivateKey = key + center.userAuthKeys.PublicKey = &key.PublicKey +} + +func (center *Center) packBearerToken(bearerToken *service.BearerTokenMsg) (string, string, error) { + data, err := bearerToken.Marshal() + if err != nil { + return "", "", errors.Wrap(err, "failed to marshal bearer token") + } + encryptedKeyID, err := encrypt(center.userAuthKeys.PublicKey, center.compress(data)) + if err != nil { + return "", "", errors.Wrap(err, "failed to encrypt bearer token bytes") + } + accessKeyID := hex.EncodeToString(encryptedKeyID) + secretAccessKey := hex.EncodeToString(sha256Hash(data)) + return accessKeyID, secretAccessKey, nil +} + +func (center *Center) unpackBearerToken(accessKeyID string) (*service.BearerTokenMsg, string, error) { + encryptedKeyID, err := hex.DecodeString(accessKeyID) + if err != nil { + return nil, "", errors.Wrap(err, "failed to decode HEX string") + } + compressedKeyID, err := decrypt(center.userAuthKeys.PrivateKey, encryptedKeyID) + if err != nil { + return nil, "", errors.Wrap(err, "failed to decrypt key ID") + } + data, err := center.decompress(compressedKeyID) + if err != nil { + return nil, "", errors.Wrap(err, "failed to decompress key ID") + } + bearerToken := new(service.BearerTokenMsg) + if err := bearerToken.Unmarshal(data); err != nil { + return nil, "", errors.Wrap(err, "failed to unmarshal embedded bearer token") + } + secretAccessKey := hex.EncodeToString(sha256Hash(data)) + return bearerToken, secretAccessKey, nil +} + +func (center *Center) AuthenticationPassed(request *http.Request) (*service.BearerTokenMsg, error) { + queryValues := request.URL.Query() + if queryValues.Get("X-Amz-Algorithm") == "AWS4-HMAC-SHA256" { + return nil, errors.New("pre-signed form of request is not supported") + } + authHeaderField := request.Header["Authorization"] + if len(authHeaderField) != 1 { + return nil, errors.New("unsupported request: wrong length of Authorization header field") + } + sms1 := center.submatcher.getSubmatches(authHeaderField[0]) + if len(sms1) != 6 { + return nil, errors.New("bad Authorization header field") + } + signedHeaderFieldsNames := strings.Split(sms1["signed_header_fields"], ";") + if len(signedHeaderFieldsNames) == 0 { + return nil, errors.New("wrong format of signed headers part") + } + signatureDateTime, err := time.Parse("20060102T150405Z", request.Header.Get("X-Amz-Date")) + if err != nil { + return nil, errors.Wrap(err, "failed to parse x-amz-date header field") + } + accessKeyID := sms1["access_key_id"] + bearerToken, secretAccessKey, err := center.unpackBearerToken(accessKeyID) + if err != nil { + return nil, errors.Wrap(err, "failed to unpack bearer token") + } + otherRequest := request.Clone(context.TODO()) + otherRequest.Header = map[string][]string{} + for hfn, hfvs := range request.Header { + for _, shfn := range signedHeaderFieldsNames { + if strings.EqualFold(hfn, shfn) { + otherRequest.Header[hfn] = hfvs + } + } + } + awsCreds := credentials.NewStaticCredentials(accessKeyID, secretAccessKey, "") + signer := v4.NewSigner(awsCreds) + body, err := readAndKeepBody(request) + if err != nil { + return nil, errors.Wrap(err, "failed to read out request body") + } + _, err = signer.Sign(otherRequest, body, sms1["service"], sms1["region"], signatureDateTime) + if err != nil { + return nil, errors.Wrap(err, "failed to sign temporary HTTP request") + } + sms2 := center.submatcher.getSubmatches(otherRequest.Header.Get("Authorization")) + if sms1["v4_signature"] != sms2["v4_signature"] { + return nil, errors.Wrap(err, "failed to pass authentication procedure") + } + return bearerToken, nil +} + +// TODO: Make this write into a smart buffer backed by a file on a fast drive. +func readAndKeepBody(request *http.Request) (*bytes.Reader, error) { + if request.Body == nil { + var r bytes.Reader + return &r, nil + } + payload, err := ioutil.ReadAll(request.Body) + if err != nil { + return nil, err + } + request.Body = ioutil.NopCloser(bytes.NewReader(payload)) + return bytes.NewReader(payload), nil +} + +func (center *Center) compress(data []byte) []byte { + return center.zstdEncoder.EncodeAll(data, make([]byte, 0, len(data))) +} + +func (center *Center) decompress(data []byte) ([]byte, error) { + return center.zstdDecoder.DecodeAll(data, nil) +} diff --git a/auth/regexp-utils.go b/auth/regexp-utils.go new file mode 100644 index 00000000..94ba85d1 --- /dev/null +++ b/auth/regexp-utils.go @@ -0,0 +1,19 @@ +package auth + +import "regexp" + +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/auth/rsa-utils.go b/auth/rsa-utils.go new file mode 100644 index 00000000..cd1e7562 --- /dev/null +++ b/auth/rsa-utils.go @@ -0,0 +1,42 @@ +package auth + +import ( + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + "io/ioutil" + + "github.com/pkg/errors" +) + +func ReadRSAPrivateKeyFromPEMFile(filePath string) (*rsa.PrivateKey, error) { + kbs, err := ioutil.ReadFile(filePath) + if err != nil { + return nil, errors.Wrapf(err, "failed to read file %s", filePath) + } + pemBlock, _ := pem.Decode(kbs) + if pemBlock == nil { + return nil, errors.Errorf("failed to decode PEM data from file %s", filePath) + } + rsaKey, err := x509.ParsePKCS1PrivateKey(pemBlock.Bytes) + if err != nil { + return nil, errors.Wrapf(err, "failed to parse private key bytes from pem data from file %s", filePath) + } + return rsaKey, nil +} + +func encrypt(key *rsa.PublicKey, data []byte) ([]byte, error) { + return rsa.EncryptOAEP(sha256.New(), rand.Reader, key, data, []byte{}) +} + +func decrypt(key *rsa.PrivateKey, data []byte) ([]byte, error) { + return rsa.DecryptOAEP(sha256.New(), rand.Reader, key, data, []byte{}) +} + +func sha256Hash(data []byte) []byte { + hash := sha256.New() + hash.Write(data) + return hash.Sum(nil) +} diff --git a/cmd/gate/app-new-auth.go b/cmd/gate/app-new-auth.go new file mode 100644 index 00000000..6410193e --- /dev/null +++ b/cmd/gate/app-new-auth.go @@ -0,0 +1,24 @@ +package main + +import ( + "net/http" + + "github.com/gorilla/mux" + "github.com/minio/minio/auth" + "go.uber.org/zap" +) + +func attachNewUserAuth(router *mux.Router, center *auth.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) + 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-settings.go b/cmd/gate/app-settings.go index da3089ff..323338f7 100644 --- a/cmd/gate/app-settings.go +++ b/cmd/gate/app-settings.go @@ -4,6 +4,7 @@ import ( "crypto/ecdsa" "crypto/elliptic" "crypto/rand" + "crypto/rsa" "fmt" "io" "os" @@ -11,19 +12,18 @@ import ( "strings" "time" + "github.com/minio/minio/auth" "github.com/minio/minio/neofs/pool" + "github.com/pkg/errors" "github.com/minio/minio/misc" - "github.com/nspcc-dev/neofs-api-go/refs" crypto "github.com/nspcc-dev/neofs-crypto" "github.com/spf13/pflag" "github.com/spf13/viper" "go.uber.org/zap" ) -type empty int - const ( devNull = empty(0) generated = "generated" @@ -55,7 +55,11 @@ const ( // settings cfgKeepaliveTimeout = "keepalive.timeout" cfgKeepalivePermitWithoutStream = "keepalive.permit_without_stream" - // HTTPS/TLS: + // Keys + cfgNeoFSPrivateKey = "neofs-ecdsa-key" + cfgUserAuthPrivateKey = "userauth-rsa-key" + + // HTTPS/TLS cfgTLSKeyFile = "tls.key_file" cfgTLSCertFile = "tls.cert_file" @@ -66,8 +70,7 @@ const ( // settings cfgRebalanceTimer = "rebalance_timer" // gRPC - cfgGRPCVerbose = "verbose" - cfgGRPCPrivateKey = "key" + cfgGRPCVerbose = "verbose" // Metrics / Profiler / Web cfgEnableMetrics = "metrics" @@ -80,33 +83,40 @@ const ( // settings cfgApplicationBuildTime = "app.build_time" ) +type empty int + func (empty) Read([]byte) (int, error) { return 0, io.EOF } -func fetchKey(l *zap.Logger, v *viper.Viper) *ecdsa.PrivateKey { - switch val := v.GetString("key"); val { +func fetchAuthCenter(l *zap.Logger, v *viper.Viper) (*auth.Center, error) { + var ( + err error + neofsPrivateKey *ecdsa.PrivateKey + userAuthPrivateKey *rsa.PrivateKey + ) + switch nfspk := v.GetString(cfgNeoFSPrivateKey); nfspk { case generated: - key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + neofsPrivateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil { - l.Fatal("could not generate private key", zap.Error(err)) + return nil, errors.Wrap(err, "could not generate NeoFS private key") } - - id, err := refs.NewOwnerID(&key.PublicKey) - l.Info("generate new key", - zap.Stringer("key", id), - zap.Error(err)) - - return key - default: - key, err := crypto.LoadPrivateKey(val) + neofsPrivateKey, err = crypto.LoadPrivateKey(nfspk) if err != nil { - l.Fatal("could not load private key", - zap.String("key", v.GetString("key")), - zap.Error(err)) + return nil, errors.Wrap(err, "could not load NeoFS private key") } - - return key } + uapk := v.GetString(cfgUserAuthPrivateKey) + userAuthPrivateKey, err = auth.ReadRSAPrivateKeyFromPEMFile(uapk) + if err != nil { + return nil, errors.Wrap(err, "could not load UserAuth private key") + } + center, err := auth.NewCenter(l) + if err != nil { + return nil, errors.Wrap(err, "failed to create auth center") + } + center.SetUserAuthKeys(userAuthPrivateKey) + center.SetNeoFSKeys(neofsPrivateKey) + return center, nil } func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.Peer { @@ -145,22 +155,23 @@ func newSettings() *viper.Viper { flags.SortFlags = false flags.Bool(cfgEnableProfiler, false, "enable pprof") - flags.Bool(cfgEnableMetrics, false, "enable prometheus") + flags.Bool(cfgEnableMetrics, false, "enable prometheus metrics") help := flags.BoolP("help", "h", false, "show help") version := flags.BoolP("version", "v", false, "show version") - flags.String(cfgGRPCPrivateKey, generated, `"`+generated+`" to generate key, path to private key file, hex string or wif`) + flags.String(cfgNeoFSPrivateKey, generated, fmt.Sprintf(`set value to hex string, WIF string, or path to NeoFS private key file (use "%s" to generate key)`, generated)) + flags.String(cfgUserAuthPrivateKey, "", "set path to file with private key to use in auth scheme") - flags.Bool(cfgGRPCVerbose, false, "debug gRPC connections") - flags.Duration(cfgRequestTimeout, defaultRequestTimeout, "gRPC request timeout") - flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "gRPC connect timeout") - flags.Duration(cfgRebalanceTimer, defaultRebalanceTimer, "gRPC connection rebalance timer") + flags.Bool(cfgGRPCVerbose, false, "set debug mode of gRPC connections") + flags.Duration(cfgRequestTimeout, defaultRequestTimeout, "set gRPC request timeout") + flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "set gRPC connect timeout") + flags.Duration(cfgRebalanceTimer, defaultRebalanceTimer, "set gRPC connection rebalance timer") - ttl := flags.DurationP(cfgConnectionTTL, "t", defaultTTL, "gRPC connection time to live") + ttl := flags.DurationP(cfgConnectionTTL, "t", defaultTTL, "set gRPC connection time to live") - flags.String(cfgListenAddress, "0.0.0.0:8080", "S3 Gateway listen address") - peers := flags.StringArrayP("peers", "p", nil, "NeoFS nodes") + flags.String(cfgListenAddress, "0.0.0.0:8080", "set address to listen") + peers := flags.StringArrayP("peers", "p", nil, "set NeoFS nodes") // set prefers: v.Set(cfgApplicationName, misc.ApplicationName) diff --git a/cmd/gate/app.go b/cmd/gate/app.go index 151ab1cb..f7c7c1c2 100644 --- a/cmd/gate/app.go +++ b/cmd/gate/app.go @@ -7,14 +7,12 @@ import ( "os" "time" + "github.com/minio/minio/auth" minio "github.com/minio/minio/legacy" "github.com/minio/minio/legacy/config" "github.com/minio/minio/neofs/layer" "github.com/minio/minio/neofs/metrics" "github.com/minio/minio/neofs/pool" - "github.com/minio/minio/pkg/auth" - "github.com/nspcc-dev/neofs-api-go/refs" - crypto "github.com/nspcc-dev/neofs-crypto" "github.com/spf13/viper" "go.uber.org/zap" "google.golang.org/grpc/keepalive" @@ -22,11 +20,12 @@ import ( type ( App struct { - cli pool.Pool - log *zap.Logger - cfg *viper.Viper - tls *tlsConfig - obj minio.ObjectLayer + center *auth.Center + cli pool.Pool + log *zap.Logger + cfg *viper.Viper + tls *tlsConfig + obj minio.ObjectLayer conTimeout time.Duration reqTimeout time.Duration @@ -45,21 +44,22 @@ type ( func newApp(l *zap.Logger, v *viper.Viper) *App { var ( - err error - wif string - cli pool.Pool - tls *tlsConfig - uid refs.OwnerID - obj minio.ObjectLayer - - key = fetchKey(l, v) - - reBalance = defaultRebalanceTimer - + err error + cli pool.Pool + tls *tlsConfig + obj minio.ObjectLayer + reBalance = defaultRebalanceTimer conTimeout = defaultConnectTimeout reqTimeout = defaultRequestTimeout ) + center, err := fetchAuthCenter(l, v) + if err != nil { + l.Fatal("failed to initialize auth center", zap.Error(err)) + } + uid := center.GetOwnerID() + wif := center.GetWIFString() + if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) { tls = &tlsConfig{ KeyFile: v.GetString(cfgTLSKeyFile), @@ -83,7 +83,7 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { Peers: fetchPeers(l, v), Logger: l, - PrivateKey: key, + PrivateKey: center.GetNeoFSPrivateKey(), GRPCLogger: gRPCLogger(l), GRPCVerbose: v.GetBool(cfgGRPCVerbose), @@ -96,8 +96,7 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { } if cli, err = pool.New(poolConfig); err != nil { - l.Fatal("could not prepare pool connections", - zap.Error(err)) + l.Fatal("could not prepare pool connections", zap.Error(err)) } { // should establish connection with NeoFS Storage Nodes @@ -113,42 +112,27 @@ func newApp(l *zap.Logger, v *viper.Viper) *App { } { // should prepare object layer - if uid, err = refs.NewOwnerID(&key.PublicKey); err != nil { - l.Fatal("could not fetch OwnerID", - zap.Error(err)) - } - - if wif, err = crypto.WIFEncode(key); err != nil { - l.Fatal("could not encode key to WIF", - zap.Error(err)) - } - { // Temporary solution, to resolve problems with MinIO GW access/secret keys: if err = os.Setenv(config.EnvAccessKey, uid.String()); err != nil { - l.Fatal("could not set "+config.EnvAccessKey, - zap.Error(err)) + l.Fatal("could not set "+config.EnvAccessKey, zap.Error(err)) } else if err = os.Setenv(config.EnvSecretKey, wif); err != nil { - l.Fatal("could not set "+config.EnvSecretKey, - zap.Error(err)) + l.Fatal("could not set "+config.EnvSecretKey, zap.Error(err)) } - - l.Info("used credentials", - zap.String("AccessKey", uid.String()), - zap.String("SecretKey", wif)) + 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 { - l.Fatal("could not prepare ObjectLayer", - zap.Error(err)) + if obj, err = layer.NewLayer(l, cli, center); err != nil { + l.Fatal("could not prepare ObjectLayer", zap.Error(err)) } } return &App{ - cli: cli, - log: l, - cfg: v, - obj: obj, - tls: tls, + center: center, + cli: cli, + log: l, + cfg: v, + obj: obj, + tls: tls, webDone: make(chan struct{}, 1), wrkDone: make(chan struct{}, 1), @@ -190,6 +174,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/go.mod b/go.mod index 6edcbe51..f01570da 100644 --- a/go.mod +++ b/go.mod @@ -110,4 +110,5 @@ require ( gopkg.in/olivere/elastic.v5 v5.0.80 gopkg.in/yaml.v2 v2.2.8 honnef.co/go/tools v0.0.1-2020.1.3 // indirect + github.com/aws/aws-sdk-go v1.33.8 ) diff --git a/go.sum b/go.sum index 0c76b481..bb5f39f6 100644 --- a/go.sum +++ b/go.sum @@ -47,6 +47,8 @@ github.com/armon/go-metrics v0.0.0-20190430140413-ec5e00d3c878/go.mod h1:3AMJUQh github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310 h1:t+qxRrRtwNiUYA+Xh2jSXhoG2grnMCMKX4Fg6lx9X1U= github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= +github.com/aws/aws-sdk-go v1.33.8 h1:2/sOfb9oPHTRZ0lxinoaTPDcYwNa1H/SpKP4nVRBwmg= +github.com/aws/aws-sdk-go v1.33.8/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM= github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M= github.com/beevik/ntp v0.2.0 h1:sGsd+kAXzT0bfVfzJfce04g+dSRfrs+tbQW8lweuYgw= @@ -259,6 +261,8 @@ github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf h1:WfD7V github.com/inconshreveable/go-update v0.0.0-20160112193335-8152e7eb6ccf/go.mod h1:hyb9oH7vZsitZCiBt0ZvifOrB+qc8PS5IiilCIb87rg= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM= github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jmespath/go-jmespath v0.3.0 h1:OS12ieG61fsCg5+qLJ+SsW9NicxNkg3b25OyT2yCeUc= +github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jonboulle/clockwork v0.1.0 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= @@ -599,6 +603,7 @@ golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200602114024-627f9648deb9 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM= golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= diff --git a/legacy/api-router.go b/legacy/api-router.go index ffecfd7a..ad1d0f31 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/auth-handler.go b/legacy/auth-handler.go index 0d66d768..29c98038 100644 --- a/legacy/auth-handler.go +++ b/legacy/auth-handler.go @@ -274,13 +274,18 @@ func checkRequestAuthType(ctx context.Context, r *http.Request, action policy.Ac return s3Err } +// FIXME: Remove this temporary stub to by-pass Minio auth procedure. +func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (string, bool, APIErrorCode) { + return "", true, ErrNone +} + // Check request auth type verifies the incoming http request // - validates the request signature // - validates the policy action if anonymous tests bucket policies if any, // for authenticated requests validates IAM policies. // returns APIErrorCode if any to be replied to the client. // Additionally returns the accessKey used in the request, and if this request is by an admin. -func checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (accessKey string, owner bool, s3Err APIErrorCode) { +func _checkRequestAuthTypeToAccessKey(ctx context.Context, r *http.Request, action policy.Action, bucketName, objectName string) (accessKey string, owner bool, s3Err APIErrorCode) { var cred auth.Credentials switch getRequestAuthType(r) { case authTypeUnknown, authTypeStreamingSigned: diff --git a/legacy/neofs-router.go b/legacy/neofs-router.go index 610c23c7..44cf08bb 100644 --- a/legacy/neofs-router.go +++ b/legacy/neofs-router.go @@ -10,7 +10,8 @@ func AttachS3API(r *mux.Router, obj ObjectLayer, l *zap.Logger) { // Initialize all help initHelp() - globalGatewayName = "NeoFS GW" + // TODO: If this name is actually stays unchanges, move it to constants. + globalGatewayName = "NeoFS S3 Gate" // Set when gateway is enabled globalIsGateway = true diff --git a/neofs/layer/auth.go b/neofs/layer/auth.go deleted file mode 100644 index 69702900..00000000 --- a/neofs/layer/auth.go +++ /dev/null @@ -1,117 +0,0 @@ -package layer - -import ( - "crypto/rand" - "crypto/rsa" - "crypto/sha256" - - "github.com/klauspost/compress/zstd" - "github.com/nspcc-dev/neofs-api-go/service" - "github.com/pkg/errors" -) - -const ( - GatewayKeySize = 2048 -) - -type keyPair struct { - PrivateKey *rsa.PrivateKey - PublicKey *rsa.PublicKey -} - -type AuthCenter struct { - gatewayKeys keyPair -} - -func NewAuthCenter() (*AuthCenter, error) { - var ( - err error - privateKey *rsa.PrivateKey - ) - privateKey, err = pullGatewayPrivateKey() - if err != nil { - return nil, errors.Wrap(err, "failed to pull gateway private key from trusted enclave") - } - if privateKey == nil { - if privateKey, err = rsa.GenerateKey(rand.Reader, GatewayKeySize); err != nil { - return nil, errors.Wrap(err, "failed to generate gateway private key") - } - if err = pushGatewayPrivateKey(privateKey); err != nil { - return nil, errors.Wrap(err, "failed to push gateway private key to trusted enclave") - } - } - ac := &AuthCenter{gatewayKeys: keyPair{ - PrivateKey: privateKey, - PublicKey: &privateKey.PublicKey, - }} - return ac, nil -} - -func (ac *AuthCenter) PackBearerToken(bearerToken *service.BearerTokenMsg) ([]byte, error) { - data, err := bearerToken.Marshal() - if err != nil { - return nil, errors.Wrap(err, "failed to marshal bearer token") - } - encryptedKeyID, err := ac.encrypt(compress(data)) - if err != nil { - return nil, errors.Wrap(err, "") - } - return append(sha256Hash(data), encryptedKeyID...), nil -} - -func (ac *AuthCenter) UnpackBearerToken(packedBearerToken []byte) (*service.BearerTokenMsg, error) { - compressedKeyID := packedBearerToken[32:] - encryptedKeyID, err := decompress(compressedKeyID) - if err != nil { - return nil, errors.Wrap(err, "failed to decompress key ID") - } - keyID, err := ac.decrypt(encryptedKeyID) - if err != nil { - return nil, errors.Wrap(err, "failed to decrypt key ID") - } - bearerToken := new(service.BearerTokenMsg) - if err := bearerToken.Unmarshal(keyID); err != nil { - return nil, errors.Wrap(err, "failed to unmarshal embedded bearer token") - } - return bearerToken, nil -} - -func pullGatewayPrivateKey() (*rsa.PrivateKey, error) { - // TODO: Pull the private key from a persistent and trusted enclave. - return nil, nil -} - -func pushGatewayPrivateKey(key *rsa.PrivateKey) error { - // TODO: Push the private key to a persistent and trusted enclave. - return nil -} - -func (ac *AuthCenter) encrypt(data []byte) ([]byte, error) { - return rsa.EncryptOAEP(sha256.New(), rand.Reader, ac.gatewayKeys.PublicKey, data, []byte{}) -} - -func (ac *AuthCenter) decrypt(data []byte) ([]byte, error) { - return rsa.DecryptOAEP(sha256.New(), rand.Reader, ac.gatewayKeys.PrivateKey, data, []byte{}) -} - -func compress(data []byte) []byte { - var compressedData []byte - zstdEncoder, _ := zstd.NewWriter(nil) - zstdEncoder.EncodeAll(data, compressedData) - return compressedData -} - -func decompress(data []byte) ([]byte, error) { - var decompressedData []byte - zstdDecoder, _ := zstd.NewReader(nil) - if _, err := zstdDecoder.DecodeAll(data, decompressedData); err != nil { - return nil, err - } - return decompressedData, nil -} - -func sha256Hash(data []byte) []byte { - hash := sha256.New() - hash.Write(data) - return hash.Sum(nil) -} diff --git a/neofs/layer/gateway-neofs.go b/neofs/layer/gateway-neofs.go index 2f4c161d..498d7b30 100644 --- a/neofs/layer/gateway-neofs.go +++ b/neofs/layer/gateway-neofs.go @@ -6,13 +6,11 @@ import ( "math" "time" + auth "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" ) @@ -23,11 +21,12 @@ type ( neofsObject struct { minio.GatewayUnsupported // placeholder for unimplemented functions - cli pool.Client - log *zap.Logger - key *ecdsa.PrivateKey - owner refs.OwnerID - token *service.Token + log *zap.Logger + cli pool.Client + key *ecdsa.PrivateKey + owner refs.OwnerID + token *service.Token + bearerToken *service.BearerTokenMsg // Concurrency must be resolved by creating one lock per object, but // it may be unnecessary in neofs, because objects are immutable. So @@ -42,41 +41,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(log *zap.Logger, cli pool.Client, center *auth.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.GetNeoFSPrivateKey(), 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.GetNeoFSPrivateKey(), log: log, - owner: owner, + owner: center.GetOwnerID(), token: token, }, nil } diff --git a/neofs/layer/neofs-container.go b/neofs/layer/neofs-container.go index e6a60bb3..fb43031f 100644 --- a/neofs/layer/neofs-container.go +++ b/neofs/layer/neofs-container.go @@ -16,6 +16,7 @@ func (n *neofsObject) containerList(ctx context.Context) ([]refs.CID, error) { req.OwnerID = n.owner req.SetTTL(service.SingleForwardingTTL) req.SetVersion(APIVersion) + req.SetBearer(nil) err := service.SignRequestData(n.key, req) if err != nil {