Merged in NFSSVC-23 (pull request #4)

[WIP] Isolate a new auth scheme within a subrouter
This commit is contained in:
Pavel Korotkov 2020-07-21 10:50:37 +00:00
commit 4098bfda9c
14 changed files with 401 additions and 232 deletions

210
auth/center.go Normal file
View file

@ -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<access_key_id>[^/]+)/(?P<date>[^/]+)/(?P<region>[^/]*)/(?P<service>[^/]+)/aws4_request, SignedHeaders=(?P<signed_header_fields>.*), Signature=(?P<v4_signature>.*)`)
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: &regexpSubmatcher{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)
}

19
auth/regexp-utils.go Normal file
View file

@ -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
}

42
auth/rsa-utils.go Normal file
View file

@ -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)
}

24
cmd/gate/app-new-auth.go Normal file
View file

@ -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)
}

View file

@ -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)

View file

@ -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)

1
go.mod
View file

@ -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
)

5
go.sum
View file

@ -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=

View file

@ -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())

View file

@ -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:

View file

@ -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

View file

@ -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)
}

View file

@ -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
}

View file

@ -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 {