Remove enclove as a separate entity; move auth center to app settings

This commit is contained in:
Pavel Korotkov 2020-07-15 23:16:27 +03:00
parent a890d9142d
commit a43c596f49
4 changed files with 156 additions and 220 deletions

View file

@ -1,34 +1,83 @@
package auth package auth
import ( import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256" "crypto/sha256"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"github.com/klauspost/compress/zstd" "github.com/klauspost/compress/zstd"
"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"
) )
// Center is a central app's authentication/authorization management unit. // Center is a central app's authentication/authorization management unit.
type Center struct { type Center struct {
enclave *secureEnclave
zstdEncoder *zstd.Encoder zstdEncoder *zstd.Encoder
zstdDecoder *zstd.Decoder 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. // NewCenter creates an instance of AuthCenter.
func NewCenter(pathToRSAKey, pathToECDSAKey string) (*Center, error) { func NewCenter() *Center {
zstdEncoder, _ := zstd.NewWriter(nil) zstdEncoder, _ := zstd.NewWriter(nil)
zstdDecoder, _ := zstd.NewReader(nil) zstdDecoder, _ := zstd.NewReader(nil)
enclave, err := newSecureEnclave(pathToRSAKey, pathToECDSAKey) return &Center{
if err != nil {
return nil, errors.Wrap(err, "failed to create secure enclave")
}
center := &Center{
enclave: enclave,
zstdEncoder: zstdEncoder, zstdEncoder: zstdEncoder,
zstdDecoder: zstdDecoder, zstdDecoder: zstdDecoder,
} }
return center, 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) GetNeoFSKeyPrivateKey() *ecdsa.PrivateKey {
return center.neofsKeys.PrivateKey
}
func (center *Center) GetNeoFSKeyPublicKey() *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) ([]byte, error) { func (center *Center) PackBearerToken(bearerToken *service.BearerTokenMsg) ([]byte, error) {
@ -36,7 +85,7 @@ func (center *Center) PackBearerToken(bearerToken *service.BearerTokenMsg) ([]by
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to marshal bearer token") return nil, errors.Wrap(err, "failed to marshal bearer token")
} }
encryptedKeyID, err := center.enclave.Encrypt(gateUserAuthKey, center.compress(data)) encryptedKeyID, err := encrypt(center.userAuthKeys.PublicKey, center.compress(data))
if err != nil { if err != nil {
return nil, errors.Wrap(err, "") return nil, errors.Wrap(err, "")
} }
@ -49,7 +98,7 @@ func (center *Center) UnpackBearerToken(packedBearerToken []byte) (*service.Bear
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to decompress key ID") return nil, errors.Wrap(err, "failed to decompress key ID")
} }
keyID, err := center.enclave.Decrypt(gateUserAuthKey, encryptedKeyID) keyID, err := decrypt(center.userAuthKeys.PrivateKey, encryptedKeyID)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "failed to decrypt key ID") return nil, errors.Wrap(err, "failed to decrypt key ID")
} }
@ -76,8 +125,32 @@ func (center *Center) decompress(data []byte) ([]byte, error) {
return decompressedData, nil return decompressedData, 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 { func sha256Hash(data []byte) []byte {
hash := sha256.New() hash := sha256.New()
hash.Write(data) hash.Write(data)
return hash.Sum(nil) return hash.Sum(nil)
} }
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
}

View file

@ -1,130 +0,0 @@
package auth
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"os"
"github.com/pkg/errors"
)
const (
gatewayEncryptionKeySize = 4096
)
const (
_ encryptionKeyName = iota
// Indicates that the key is used to encrypt
// a bearer token to pass auth procedure.
gateUserAuthKey
)
const (
_ signatureKeyName = iota
// Indicates that the key is a NeoFS ECDSA key.
gateNeoFSECDSAKey
// Indicates that the key is a NeoFS Ed25519 key.
gateNeoFSEd25519Key
)
type (
signatureKeyName byte
encryptionKeyName byte
)
type (
signatureKeyPair struct {
PrivateKey *ecdsa.PrivateKey
PublicKey *ecdsa.PublicKey
}
encryptionKeyPair struct {
PrivateKey *rsa.PrivateKey
PublicKey *rsa.PublicKey
}
)
type secureEnclave struct {
signatureKeys map[signatureKeyName]signatureKeyPair
encryptionKeys map[encryptionKeyName]encryptionKeyPair
}
func newSecureEnclave(pathToRSAKey, pathToECDSAKey string) (*secureEnclave, error) {
var (
rsaKey *rsa.PrivateKey
ecdsaKey *ecdsa.PrivateKey
)
if key1bs, err := ioutil.ReadFile(pathToRSAKey); err != nil {
// No file found.
if os.IsNotExist(err) {
if rsaKey, err = rsa.GenerateKey(rand.Reader, gatewayEncryptionKeySize); err != nil {
return nil, errors.Wrap(err, "failed to generate RSA key")
}
key1bs := x509.MarshalPKCS1PrivateKey(rsaKey)
data := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Bytes: key1bs})
if err := ioutil.WriteFile(pathToRSAKey, data, 0o600); err != nil {
return nil, errors.Wrapf(err, "failed to write file %s", pathToRSAKey)
}
} else {
return nil, errors.Wrapf(err, "failed to open file %s", pathToRSAKey)
}
} else {
pemBlock, _ := pem.Decode(key1bs)
if pemBlock == nil {
return nil, errors.Errorf("failed to decode PEM data from file %s", pathToRSAKey)
}
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", pathToRSAKey)
}
}
if key2bs, err := ioutil.ReadFile(pathToECDSAKey); err != nil {
// No file found.
if os.IsNotExist(err) {
if ecdsaKey, err = ecdsa.GenerateKey(elliptic.P256(), rand.Reader); err != nil {
return nil, errors.Wrap(err, "failed to generate ECDSA key")
}
key2bs, err := x509.MarshalECPrivateKey(ecdsaKey)
if err != nil {
return nil, errors.New("failed to marshal ECDSA private key")
}
data := pem.EncodeToMemory(&pem.Block{Type: "EC PRIVATE KEY", Bytes: key2bs})
if err := ioutil.WriteFile(pathToECDSAKey, data, 0o600); err != nil {
return nil, errors.Wrapf(err, "failed to write file %s", pathToECDSAKey)
}
} else {
return nil, errors.Wrapf(err, "failed to open file %s", pathToECDSAKey)
}
} else {
pemBlock, _ := pem.Decode(key2bs)
if pemBlock == nil {
return nil, errors.Errorf("failed to decode PEM data from file %s", pathToECDSAKey)
}
ecdsaKey, err = x509.ParseECPrivateKey(pemBlock.Bytes)
if err != nil {
return nil, errors.Wrapf(err, "failed to parse private key bytes from pem data from file %s", pathToECDSAKey)
}
}
return &secureEnclave{
encryptionKeys: map[encryptionKeyName]encryptionKeyPair{
gateUserAuthKey: {rsaKey, &rsaKey.PublicKey},
},
signatureKeys: map[signatureKeyName]signatureKeyPair{
gateNeoFSECDSAKey: {ecdsaKey, &ecdsaKey.PublicKey},
},
}, nil
}
func (se *secureEnclave) Encrypt(keyName encryptionKeyName, data []byte) ([]byte, error) {
return rsa.EncryptOAEP(sha256.New(), rand.Reader, se.encryptionKeys[keyName].PublicKey, data, []byte{})
}
func (se *secureEnclave) Decrypt(keyName encryptionKeyName, data []byte) ([]byte, error) {
return rsa.DecryptOAEP(sha256.New(), rand.Reader, se.encryptionKeys[keyName].PrivateKey, data, []byte{})
}

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"
s3auth "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,37 @@ 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) (*s3auth.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 = s3auth.ReadRSAPrivateKeyFromPEMFile(uapk)
if err != nil {
return nil, errors.Wrap(err, "could not load UserAuth private key")
}
center := s3auth.NewCenter()
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 +152,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,13 +7,12 @@ import (
"os" "os"
"time" "time"
s3auth "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/pool" "github.com/minio/minio/neofs/pool"
"github.com/minio/minio/pkg/auth" "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"
@ -21,11 +20,12 @@ import (
type ( type (
App struct { App struct {
cli pool.Pool center *s3auth.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
@ -44,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),
@ -82,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.GetNeoFSKeyPrivateKey(),
GRPCLogger: gRPCLogger(l), GRPCLogger: gRPCLogger(l),
GRPCVerbose: v.GetBool(cfgGRPCVerbose), GRPCVerbose: v.GetBool(cfgGRPCVerbose),
@ -95,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
@ -112,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(cli, l, auth.Credentials{AccessKey: uid.String(), SecretKey: wif}); 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),