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/ecdsa"
"crypto/elliptic" "crypto/elliptic"
"crypto/rand" "crypto/rand"
"crypto/rsa"
"fmt" "fmt"
"io" "io"
"os" "os"
@ -11,19 +12,18 @@ import (
"strings" "strings"
"time" "time"
"github.com/minio/minio/auth"
"github.com/minio/minio/neofs/pool" "github.com/minio/minio/neofs/pool"
"github.com/pkg/errors"
"github.com/minio/minio/misc" "github.com/minio/minio/misc"
"github.com/nspcc-dev/neofs-api-go/refs"
crypto "github.com/nspcc-dev/neofs-crypto" crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/spf13/pflag" "github.com/spf13/pflag"
"github.com/spf13/viper" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
) )
type empty int
const ( const (
devNull = empty(0) devNull = empty(0)
generated = "generated" generated = "generated"
@ -55,7 +55,11 @@ const ( // settings
cfgKeepaliveTimeout = "keepalive.timeout" cfgKeepaliveTimeout = "keepalive.timeout"
cfgKeepalivePermitWithoutStream = "keepalive.permit_without_stream" cfgKeepalivePermitWithoutStream = "keepalive.permit_without_stream"
// HTTPS/TLS: // Keys
cfgNeoFSPrivateKey = "neofs-ecdsa-key"
cfgUserAuthPrivateKey = "userauth-rsa-key"
// HTTPS/TLS
cfgTLSKeyFile = "tls.key_file" cfgTLSKeyFile = "tls.key_file"
cfgTLSCertFile = "tls.cert_file" cfgTLSCertFile = "tls.cert_file"
@ -66,8 +70,7 @@ const ( // settings
cfgRebalanceTimer = "rebalance_timer" cfgRebalanceTimer = "rebalance_timer"
// gRPC // gRPC
cfgGRPCVerbose = "verbose" cfgGRPCVerbose = "verbose"
cfgGRPCPrivateKey = "key"
// Metrics / Profiler / Web // Metrics / Profiler / Web
cfgEnableMetrics = "metrics" cfgEnableMetrics = "metrics"
@ -80,33 +83,40 @@ const ( // settings
cfgApplicationBuildTime = "app.build_time" cfgApplicationBuildTime = "app.build_time"
) )
type empty int
func (empty) Read([]byte) (int, error) { return 0, io.EOF } func (empty) Read([]byte) (int, error) { return 0, io.EOF }
func fetchKey(l *zap.Logger, v *viper.Viper) *ecdsa.PrivateKey { func fetchAuthCenter(l *zap.Logger, v *viper.Viper) (*auth.Center, error) {
switch val := v.GetString("key"); val { var (
err error
neofsPrivateKey *ecdsa.PrivateKey
userAuthPrivateKey *rsa.PrivateKey
)
switch nfspk := v.GetString(cfgNeoFSPrivateKey); nfspk {
case generated: case generated:
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) neofsPrivateKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil { 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: default:
key, err := crypto.LoadPrivateKey(val) neofsPrivateKey, err = crypto.LoadPrivateKey(nfspk)
if err != nil { if err != nil {
l.Fatal("could not load private key", return nil, errors.Wrap(err, "could not load NeoFS private key")
zap.String("key", v.GetString("key")),
zap.Error(err))
} }
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 { func fetchPeers(l *zap.Logger, v *viper.Viper) []pool.Peer {
@ -145,22 +155,23 @@ func newSettings() *viper.Viper {
flags.SortFlags = false flags.SortFlags = false
flags.Bool(cfgEnableProfiler, false, "enable pprof") 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") help := flags.BoolP("help", "h", false, "show help")
version := flags.BoolP("version", "v", false, "show version") 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.Bool(cfgGRPCVerbose, false, "set debug mode of gRPC connections")
flags.Duration(cfgRequestTimeout, defaultRequestTimeout, "gRPC request timeout") flags.Duration(cfgRequestTimeout, defaultRequestTimeout, "set gRPC request timeout")
flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "gRPC connect timeout") flags.Duration(cfgConnectTimeout, defaultConnectTimeout, "set gRPC connect timeout")
flags.Duration(cfgRebalanceTimer, defaultRebalanceTimer, "gRPC connection rebalance timer") 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") flags.String(cfgListenAddress, "0.0.0.0:8080", "set address to listen")
peers := flags.StringArrayP("peers", "p", nil, "NeoFS nodes") peers := flags.StringArrayP("peers", "p", nil, "set NeoFS nodes")
// set prefers: // set prefers:
v.Set(cfgApplicationName, misc.ApplicationName) v.Set(cfgApplicationName, misc.ApplicationName)

View file

@ -7,14 +7,12 @@ import (
"os" "os"
"time" "time"
"github.com/minio/minio/auth"
minio "github.com/minio/minio/legacy" minio "github.com/minio/minio/legacy"
"github.com/minio/minio/legacy/config" "github.com/minio/minio/legacy/config"
"github.com/minio/minio/neofs/layer" "github.com/minio/minio/neofs/layer"
"github.com/minio/minio/neofs/metrics" "github.com/minio/minio/neofs/metrics"
"github.com/minio/minio/neofs/pool" "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" "github.com/spf13/viper"
"go.uber.org/zap" "go.uber.org/zap"
"google.golang.org/grpc/keepalive" "google.golang.org/grpc/keepalive"
@ -22,11 +20,12 @@ import (
type ( type (
App struct { App struct {
cli pool.Pool center *auth.Center
log *zap.Logger cli pool.Pool
cfg *viper.Viper log *zap.Logger
tls *tlsConfig cfg *viper.Viper
obj minio.ObjectLayer tls *tlsConfig
obj minio.ObjectLayer
conTimeout time.Duration conTimeout time.Duration
reqTimeout time.Duration reqTimeout time.Duration
@ -45,21 +44,22 @@ type (
func newApp(l *zap.Logger, v *viper.Viper) *App { func newApp(l *zap.Logger, v *viper.Viper) *App {
var ( var (
err error err error
wif string cli pool.Pool
cli pool.Pool tls *tlsConfig
tls *tlsConfig obj minio.ObjectLayer
uid refs.OwnerID reBalance = defaultRebalanceTimer
obj minio.ObjectLayer
key = fetchKey(l, v)
reBalance = defaultRebalanceTimer
conTimeout = defaultConnectTimeout conTimeout = defaultConnectTimeout
reqTimeout = defaultRequestTimeout 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) { if v.IsSet(cfgTLSKeyFile) && v.IsSet(cfgTLSCertFile) {
tls = &tlsConfig{ tls = &tlsConfig{
KeyFile: v.GetString(cfgTLSKeyFile), KeyFile: v.GetString(cfgTLSKeyFile),
@ -83,7 +83,7 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
Peers: fetchPeers(l, v), Peers: fetchPeers(l, v),
Logger: l, Logger: l,
PrivateKey: key, PrivateKey: center.GetNeoFSPrivateKey(),
GRPCLogger: gRPCLogger(l), GRPCLogger: gRPCLogger(l),
GRPCVerbose: v.GetBool(cfgGRPCVerbose), 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 { if cli, err = pool.New(poolConfig); err != nil {
l.Fatal("could not prepare pool connections", l.Fatal("could not prepare pool connections", zap.Error(err))
zap.Error(err))
} }
{ // should establish connection with NeoFS Storage Nodes { // should establish connection with NeoFS Storage Nodes
@ -113,42 +112,27 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
} }
{ // should prepare object layer { // 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: { // Temporary solution, to resolve problems with MinIO GW access/secret keys:
if err = os.Setenv(config.EnvAccessKey, uid.String()); err != nil { if err = os.Setenv(config.EnvAccessKey, uid.String()); err != nil {
l.Fatal("could not set "+config.EnvAccessKey, l.Fatal("could not set "+config.EnvAccessKey, zap.Error(err))
zap.Error(err))
} else if err = os.Setenv(config.EnvSecretKey, wif); err != nil { } else if err = os.Setenv(config.EnvSecretKey, wif); err != nil {
l.Fatal("could not set "+config.EnvSecretKey, l.Fatal("could not set "+config.EnvSecretKey, zap.Error(err))
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 { if obj, err = layer.NewLayer(l, cli, center); err != nil {
l.Fatal("could not prepare ObjectLayer", l.Fatal("could not prepare ObjectLayer", zap.Error(err))
zap.Error(err))
} }
} }
return &App{ return &App{
cli: cli, center: center,
log: l, cli: cli,
cfg: v, log: l,
obj: obj, cfg: v,
tls: tls, obj: obj,
tls: tls,
webDone: make(chan struct{}, 1), webDone: make(chan struct{}, 1),
wrkDone: make(chan struct{}, 1), wrkDone: make(chan struct{}, 1),
@ -190,6 +174,7 @@ func (a *App) Server(ctx context.Context) {
router := newS3Router() router := newS3Router()
// Attach app-specific routes: // Attach app-specific routes:
attachNewUserAuth(router, a.center, a.log)
attachHealthy(router, a.cli) attachHealthy(router, a.cli)
attachMetrics(router, a.cfg, a.log) attachMetrics(router, a.cfg, a.log)
attachProfiler(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/olivere/elastic.v5 v5.0.80
gopkg.in/yaml.v2 v2.2.8 gopkg.in/yaml.v2 v2.2.8
honnef.co/go/tools v0.0.1-2020.1.3 // indirect 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/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 h1:t+qxRrRtwNiUYA+Xh2jSXhoG2grnMCMKX4Fg6lx9X1U=
github.com/awalterschulze/gographviz v0.0.0-20181013152038-b2885df04310/go.mod h1:GEV5wmg4YquNw7v1kkyoX9etIk8yVmXj+AkDHuuETHs= 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 h1:M+TYzBcNIRyzPRg66ndEqUMd7oWDmhvdQmaPC6EZNwM=
github.com/bcicen/jstream v0.0.0-20190220045926-16c1f8af81c2/go.mod h1:RDu/qcrnpEdJC/p8tx34+YBFqqX71lB7dOX9QE+ZC4M= 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= 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/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 h1:FUwcHNlEqkqLjLBdCp5PRlCFijNjvcYANOZXzCfXwCM=
github.com/jcmturner/gofork v0.0.0-20190328161633-dc7c13fece03/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= 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 h1:VKV+ZcuP6l3yW9doeqz6ziZGgcynBVQO+obU0+0hcPo=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= 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-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-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-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-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 h1:pNX+40auqi2JqRfOP1akLGtYcn15TUbkhwuCO3foqqM=
golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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 return allowSSEKMS
}, },
} }
// API Router // API Router
apiRouter := router.PathPrefix(SlashSeparator).Subrouter() apiRouter := router.PathPrefix(SlashSeparator).Subrouter()
var routers []*mux.Router var routers []*mux.Router
for _, domainName := range globalDomainNames { for _, domainName := range globalDomainNames {
routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName).Subrouter()) r1 := apiRouter.Host("{bucket:.+}." + domainName).Subrouter()
routers = append(routers, apiRouter.Host("{bucket:.+}."+domainName+":{port:.*}").Subrouter()) r2 := apiRouter.Host("{bucket:.+}." + domainName + ":{port:.*}").Subrouter()
routers = append(routers, []*mux.Router{r1, r2}...)
} }
routers = append(routers, apiRouter.PathPrefix("/{bucket}").Subrouter()) 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 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 // Check request auth type verifies the incoming http request
// - validates the request signature // - validates the request signature
// - validates the policy action if anonymous tests bucket policies if any, // - validates the policy action if anonymous tests bucket policies if any,
// for authenticated requests validates IAM policies. // for authenticated requests validates IAM policies.
// returns APIErrorCode if any to be replied to the client. // 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. // 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 var cred auth.Credentials
switch getRequestAuthType(r) { switch getRequestAuthType(r) {
case authTypeUnknown, authTypeStreamingSigned: case authTypeUnknown, authTypeStreamingSigned:

View file

@ -10,7 +10,8 @@ func AttachS3API(r *mux.Router, obj ObjectLayer, l *zap.Logger) {
// Initialize all help // Initialize all help
initHelp() 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 // Set when gateway is enabled
globalIsGateway = true 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" "math"
"time" "time"
auth "github.com/minio/minio/auth"
minio "github.com/minio/minio/legacy" minio "github.com/minio/minio/legacy"
"github.com/minio/minio/neofs/pool" "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/refs"
"github.com/nspcc-dev/neofs-api-go/service" "github.com/nspcc-dev/neofs-api-go/service"
crypto "github.com/nspcc-dev/neofs-crypto"
"github.com/pkg/errors" "github.com/pkg/errors"
"go.uber.org/zap" "go.uber.org/zap"
) )
@ -23,11 +21,12 @@ type (
neofsObject struct { neofsObject struct {
minio.GatewayUnsupported // placeholder for unimplemented functions minio.GatewayUnsupported // placeholder for unimplemented functions
cli pool.Client log *zap.Logger
log *zap.Logger cli pool.Client
key *ecdsa.PrivateKey key *ecdsa.PrivateKey
owner refs.OwnerID owner refs.OwnerID
token *service.Token token *service.Token
bearerToken *service.BearerTokenMsg
// Concurrency must be resolved by creating one lock per object, but // Concurrency must be resolved by creating one lock per object, but
// it may be unnecessary in neofs, because objects are immutable. So // it may be unnecessary in neofs, because objects are immutable. So
@ -42,41 +41,24 @@ type (
// NewGatewayLayer creates instance of neofsObject. It checks credentials // NewGatewayLayer creates instance of neofsObject. It checks credentials
// and establishes gRPC connection with node. // and establishes gRPC connection with node.
func NewLayer(cli pool.Client, log *zap.Logger, cred auth.Credentials) (minio.ObjectLayer, error) { func NewLayer(log *zap.Logger, cli pool.Client, center *auth.Center) (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")
}
// setup gRPC connection // setup gRPC connection
// todo: think about getting timeout parameters from cli args // todo: think about getting timeout parameters from cli args
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
token, err := generateToken(ctx, tokenParams{ token, err := generateToken(ctx, tokenParams{
cli: cli, cli: cli,
key: key, key: center.GetNeoFSPrivateKey(),
until: math.MaxInt64, until: math.MaxInt64,
}) })
if err != nil { if err != nil {
return nil, errors.Wrap(err, "can't establish neofs session with remote host") return nil, errors.Wrap(err, "can't establish neofs session with remote host")
} }
return &neofsObject{ return &neofsObject{
cli: cli, cli: cli,
key: key, key: center.GetNeoFSPrivateKey(),
log: log, log: log,
owner: owner, owner: center.GetOwnerID(),
token: token, token: token,
}, nil }, nil
} }

View file

@ -16,6 +16,7 @@ func (n *neofsObject) containerList(ctx context.Context) ([]refs.CID, error) {
req.OwnerID = n.owner req.OwnerID = n.owner
req.SetTTL(service.SingleForwardingTTL) req.SetTTL(service.SingleForwardingTTL)
req.SetVersion(APIVersion) req.SetVersion(APIVersion)
req.SetBearer(nil)
err := service.SignRequestData(n.key, req) err := service.SignRequestData(n.key, req)
if err != nil { if err != nil {