diff --git a/auth/center.go b/auth/center.go index 966177cc..84b95c62 100644 --- a/auth/center.go +++ b/auth/center.go @@ -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 +} diff --git a/auth/enclave.go b/auth/enclave.go deleted file mode 100644 index 16113fbf..00000000 --- a/auth/enclave.go +++ /dev/null @@ -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{}) -} diff --git a/cmd/gate/app-settings.go b/cmd/gate/app-settings.go index da3089ff..98422939 100644 --- a/cmd/gate/app-settings.go +++ b/cmd/gate/app-settings.go @@ -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) diff --git a/cmd/gate/app.go b/cmd/gate/app.go index a5c4021c..317b1d2b 100644 --- a/cmd/gate/app.go +++ b/cmd/gate/app.go @@ -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),