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/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)
|
||||||
|
|
|
@ -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
1
go.mod
|
@ -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
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/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=
|
||||||
|
|
|
@ -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())
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"
|
"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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue