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
import (
"crypto/ecdsa"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/pem"
"io/ioutil"
"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"
)
// Center is a central app's authentication/authorization management unit.
type Center struct {
enclave *secureEnclave
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(pathToRSAKey, pathToECDSAKey string) (*Center, error) {
func NewCenter() *Center {
zstdEncoder, _ := zstd.NewWriter(nil)
zstdDecoder, _ := zstd.NewReader(nil)
enclave, err := newSecureEnclave(pathToRSAKey, pathToECDSAKey)
if err != nil {
return nil, errors.Wrap(err, "failed to create secure enclave")
}
center := &Center{
enclave: enclave,
return &Center{
zstdEncoder: zstdEncoder,
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) {
@ -36,7 +85,7 @@ func (center *Center) PackBearerToken(bearerToken *service.BearerTokenMsg) ([]by
if err != nil {
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 {
return nil, errors.Wrap(err, "")
}
@ -49,7 +98,7 @@ func (center *Center) UnpackBearerToken(packedBearerToken []byte) (*service.Bear
if err != nil {
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 {
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
}
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)
}
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/elliptic"
"crypto/rand"
"crypto/rsa"
"fmt"
"io"
"os"
@ -11,19 +12,18 @@ import (
"strings"
"time"
s3auth "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,37 @@ 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) (*s3auth.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 = 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 {
@ -145,22 +152,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,13 +7,12 @@ import (
"os"
"time"
s3auth "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/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"
@ -21,11 +20,12 @@ import (
type (
App struct {
cli pool.Pool
log *zap.Logger
cfg *viper.Viper
tls *tlsConfig
obj minio.ObjectLayer
center *s3auth.Center
cli pool.Pool
log *zap.Logger
cfg *viper.Viper
tls *tlsConfig
obj minio.ObjectLayer
conTimeout time.Duration
reqTimeout time.Duration
@ -44,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),
@ -82,7 +83,7 @@ func newApp(l *zap.Logger, v *viper.Viper) *App {
Peers: fetchPeers(l, v),
Logger: l,
PrivateKey: key,
PrivateKey: center.GetNeoFSKeyPrivateKey(),
GRPCLogger: gRPCLogger(l),
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 {
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
@ -112,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))
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),