forked from TrueCloudLab/frostfs-s3-gw
Merged in NFSSVC-23 (pull request #4)
[WIP] Isolate a new auth scheme within a subrouter
This commit is contained in:
commit
4098bfda9c
14 changed files with 401 additions and 232 deletions
210
auth/center.go
Normal file
210
auth/center.go
Normal 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: ®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)
|
||||
}
|
19
auth/regexp-utils.go
Normal file
19
auth/regexp-utils.go
Normal 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
42
auth/rsa-utils.go
Normal 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
24
cmd/gate/app-new-auth.go
Normal 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)
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
1
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
|
||||
)
|
||||
|
|
5
go.sum
5
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=
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in a new issue