diff --git a/api/auth/center.go b/api/auth/center.go index 0d0d35f4..da4945e5 100644 --- a/api/auth/center.go +++ b/api/auth/center.go @@ -12,12 +12,12 @@ import ( "github.com/aws/aws-sdk-go/aws/credentials" v4 "github.com/aws/aws-sdk-go/aws/signer/v4" - sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/bearer" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/s3" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/token" + sdk "github.com/nspcc-dev/neofs-http-gw/neofs" + "github.com/nspcc-dev/neofs-s3-gw/authmate" + "github.com/nspcc-dev/neofs-s3-gw/creds/bearer" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" "go.uber.org/zap" ) @@ -36,7 +36,7 @@ type ( // Params stores node connection parameters. Params struct { - Client sdk.Client + Client sdk.ClientPlant Logger *zap.Logger Credential hcs.Credentials } @@ -55,7 +55,7 @@ func (p prs) Seek(_ int64, _ int) (int64, error) { var _ io.ReadSeeker = prs(0) // New creates an instance of AuthCenter. -func New(obj sdk.ObjectClient, key hcs.PrivateKey) Center { +func New(obj sdk.ClientPlant, key hcs.PrivateKey) Center { return ¢er{ cli: bearer.New(obj, key), reg: ®expSubmatcher{re: authorizationFieldRegexp}, @@ -100,7 +100,7 @@ func (c *center) Authenticate(r *http.Request) (*token.BearerToken, error) { return nil, err } - secret, err := s3.SecretAccessKey(tkn) + secret, err := authmate.BearerToAccessKey(tkn) if err != nil { return nil, err } diff --git a/api/layer/container.go b/api/layer/container.go index 439d91dc..524da132 100644 --- a/api/layer/container.go +++ b/api/layer/container.go @@ -42,7 +42,15 @@ func (n *layer) containerInfo(ctx context.Context, cid *container.ID) (*BucketIn } ) - if res, err = n.cli.Container().Get(ctx, cid); err != nil { + conn, _, err := n.cli.ConnectionArtifacts() + if err != nil { + n.log.Error("failed to get connection from the pool", + zap.String("request_id", rid), + zap.Error(err)) + return nil, err + } + res, err = conn.GetContainer(ctx, cid) + if err != nil { n.log.Error("could not fetch container", zap.Stringer("cid", cid), zap.String("request_id", rid), @@ -84,7 +92,15 @@ func (n *layer) containerList(ctx context.Context) ([]*BucketInfo, error) { rid = api.GetRequestID(ctx) ) - if res, err = n.cli.Container().List(ctx, own); err != nil { + conn, _, err := n.cli.ConnectionArtifacts() + if err != nil { + n.log.Error("failed to get connection from the pool", + zap.String("request_id", rid), + zap.Error(err)) + return nil, err + } + res, err = conn.ListContainers(ctx, own) + if err != nil { n.log.Error("could not fetch container", zap.String("request_id", rid), zap.Error(err)) diff --git a/api/layer/layer.go b/api/layer/layer.go index c32711a4..2838b448 100644 --- a/api/layer/layer.go +++ b/api/layer/layer.go @@ -8,13 +8,15 @@ import ( "net/url" "time" - sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" - "github.com/nspcc-dev/cdn-sdk/pool" + "github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/owner" + "github.com/nspcc-dev/neofs-api-go/pkg/token" + "github.com/nspcc-dev/neofs-http-gw/connections" + sdk "github.com/nspcc-dev/neofs-http-gw/neofs" "github.com/nspcc-dev/neofs-s3-gw/api" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "go.uber.org/zap" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" @@ -22,13 +24,13 @@ import ( type ( layer struct { - cli sdk.Client + cli sdk.ClientPlant log *zap.Logger } // Params stores basic API parameters. Params struct { - Pool pool.Client + Pool connections.Pool Logger *zap.Logger Timeout time.Duration Credential neofs.Credentials @@ -96,7 +98,7 @@ var ( // NewLayer creates instance of layer. It checks credentials // and establishes gRPC connection with node. -func NewLayer(log *zap.Logger, cli sdk.Client) Client { +func NewLayer(log *zap.Logger, cli sdk.ClientPlant) Client { return &layer{ cli: cli, log: log, @@ -105,16 +107,21 @@ func NewLayer(log *zap.Logger, cli sdk.Client) Client { // Owner returns owner id from BearerToken (context) or from client owner. func (n *layer) Owner(ctx context.Context) *owner.ID { - if tkn, err := sdk.BearerToken(ctx); err != nil && tkn != nil { + if tkn, ok := ctx.Value(api.BearerTokenKey).(*token.BearerToken); ok && tkn != nil { return tkn.Issuer() } - return n.cli.Owner() + return n.cli.OwnerID() } // Get NeoFS Object by refs.Address (should be used by auth.Center). func (n *layer) Get(ctx context.Context, address *object.Address) (*object.Object, error) { - return n.cli.Object().Get(ctx, address) + conn, tok, err := n.cli.ConnectionArtifacts() + if err != nil { + return nil, err + } + ops := new(client.GetObjectParams).WithAddress(address) + return conn.GetObject(ctx, ops, client.WithSession(tok)) } // GetBucketInfo returns bucket info by name. diff --git a/api/layer/object.go b/api/layer/object.go index c1232420..c24e0e86 100644 --- a/api/layer/object.go +++ b/api/layer/object.go @@ -8,7 +8,7 @@ import ( "strconv" "time" - sdk "github.com/nspcc-dev/cdn-sdk" + "github.com/nspcc-dev/neofs-api-go/pkg/client" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-s3-gw/api" @@ -33,16 +33,20 @@ type ( // objectSearch returns all available objects by search params. func (n *layer) objectSearch(ctx context.Context, p *findParams) ([]*object.ID, error) { - opts := []sdk.ObjectSearchOption{ - sdk.SearchRootObjects(), - } + var opts object.SearchFilters + + opts.AddRootFilter() if filename, err := url.QueryUnescape(p.val); err != nil { return nil, err } else if filename != "" { - opts = append(opts, sdk.SearchByFilename(filename)) + opts.AddFilter(object.AttributeFileName, filename, object.MatchStringEqual) } - return n.cli.Object().Search(ctx, p.cid, opts...) + conn, _, err := n.cli.ConnectionArtifacts() + if err != nil { + return nil, err + } + return conn.SearchObject(ctx, new(client.SearchObjectParams).WithContainerID(p.cid).WithSearchFilters(opts)) } // objectFindID returns object id (uuid) based on it's nice name in s3. If @@ -61,14 +65,24 @@ func (n *layer) objectFindID(ctx context.Context, p *findParams) (*object.ID, er // objectHead returns all object's headers. func (n *layer) objectHead(ctx context.Context, address *object.Address) (*object.Object, error) { - return n.cli.Object().Head(ctx, address, sdk.WithFullHeaders()) + conn, _, err := n.cli.ConnectionArtifacts() + if err != nil { + return nil, err + } + ops := new(client.ObjectHeaderParams).WithAddress(address).WithAllFields() + return conn.GetObjectHeader(ctx, ops) } // objectGet and write it into provided io.Reader. func (n *layer) objectGet(ctx context.Context, p *getParams) (*object.Object, error) { + conn, tok, err := n.cli.ConnectionArtifacts() + if err != nil { + return nil, err + } // prepare length/offset writer w := newWriter(p.Writer, p.offset, p.length) - return n.cli.Object().Get(ctx, p.address, sdk.WithGetWriter(w)) + ops := new(client.GetObjectParams).WithAddress(p.address).WithPayloadWriter(w) + return conn.GetObject(ctx, ops, client.WithSession(tok)) } // objectPut into NeoFS, took payload from io.Reader. @@ -78,8 +92,6 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo, obj string bkt *BucketInfo own = n.Owner(ctx) - - address *object.Address ) if obj, err = url.QueryUnescape(p.Object); err != nil { @@ -123,12 +135,23 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo, raw.SetAttributes(attributes...) r := newDetector(p.Reader) - if address, err = n.cli.Object().Put(ctx, raw.Object(), sdk.WithPutReader(r)); err != nil { + conn, tok, err := n.cli.ConnectionArtifacts() + if err != nil { + return nil, err + } + + ops := new(client.PutObjectParams).WithObject(raw.Object()).WithPayloadReader(r) + oid, err := conn.PutObject( + ctx, + ops, + client.WithSession(tok), + ) + if err != nil { return nil, err } return &ObjectInfo{ - id: address.ObjectID(), + id: oid, Owner: own, Bucket: p.Bucket, @@ -142,5 +165,11 @@ func (n *layer) objectPut(ctx context.Context, p *PutObjectParams) (*ObjectInfo, // objectDelete puts tombstone object into neofs. func (n *layer) objectDelete(ctx context.Context, address *object.Address) error { - return n.cli.Object().Delete(ctx, address) + conn, _, err := n.cli.ConnectionArtifacts() + if err != nil { + return err + } + dop := new(client.DeleteObjectParams) + dop.WithAddress(address) + return conn.DeleteObject(ctx, dop) } diff --git a/api/user-auth.go b/api/user-auth.go index 4e00112a..cb530e7a 100644 --- a/api/user-auth.go +++ b/api/user-auth.go @@ -1,14 +1,20 @@ package api import ( + "context" "net/http" "github.com/gorilla/mux" - sdk "github.com/nspcc-dev/cdn-sdk" "github.com/nspcc-dev/neofs-s3-gw/api/auth" "go.uber.org/zap" ) +// KeyWrapper is wrapper for context keys. +type KeyWrapper string + +// BearerTokenKey is an ID used to store bearer token in a context. +var BearerTokenKey = KeyWrapper("__context_bearer_token_key") + // AttachUserAuth adds user authentication via center to router using log for logging. func AttachUserAuth(router *mux.Router, center auth.Center, log *zap.Logger) { router.Use(func(h http.Handler) http.Handler { @@ -21,7 +27,7 @@ func AttachUserAuth(router *mux.Router, center auth.Center, log *zap.Logger) { } h.ServeHTTP(w, r.WithContext( - sdk.SetBearerToken(r.Context(), token))) + context.WithValue(r.Context(), BearerTokenKey, token))) }) }) } diff --git a/authmate/authmate.go b/authmate/authmate.go index 0f00cd32..1872d39c 100644 --- a/authmate/authmate.go +++ b/authmate/authmate.go @@ -3,6 +3,8 @@ package authmate import ( "context" "crypto/ecdsa" + "crypto/sha256" + "encoding/hex" "encoding/json" "fmt" "io" @@ -10,31 +12,34 @@ import ( "strconv" "time" - sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/bearer" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" - "github.com/nspcc-dev/cdn-sdk/creds/s3" "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" "github.com/nspcc-dev/neofs-api-go/pkg/container" "github.com/nspcc-dev/neofs-api-go/pkg/netmap" "github.com/nspcc-dev/neofs-api-go/pkg/object" "github.com/nspcc-dev/neofs-api-go/pkg/owner" "github.com/nspcc-dev/neofs-api-go/pkg/token" + sdk "github.com/nspcc-dev/neofs-http-gw/neofs" "github.com/nspcc-dev/neofs-node/pkg/policy" + "github.com/nspcc-dev/neofs-s3-gw/creds/bearer" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "go.uber.org/zap" ) -const defaultAuthContainerBasicACL uint32 = 0b00111100100011001000110011001100 +const ( + defaultAuthContainerBasicACL uint32 = 0b00111100100011001000110011001100 + containerCreationTimeout = 120 * time.Second + containerPollInterval = 5 * time.Second +) // Agent contains client communicating with NeoFS and logger. type Agent struct { - cli sdk.Client + cli sdk.ClientPlant log *zap.Logger } // New creates an object of type Agent that consists of Client and logger. -func New(log *zap.Logger, client sdk.Client) *Agent { +func New(log *zap.Logger, client sdk.ClientPlant) *Agent { return &Agent{log: log, cli: client} } @@ -70,9 +75,14 @@ type ( ) func (a *Agent) checkContainer(ctx context.Context, cid *container.ID, friendlyName string) (*container.ID, error) { + conn, _, err := a.cli.ConnectionArtifacts() + if err != nil { + return nil, err + } + if cid != nil { // check that container exists - _, err := a.cli.Container().Get(ctx, cid) + _, err = conn.GetContainer(ctx, cid) return cid, err } @@ -87,9 +97,31 @@ func (a *Agent) checkContainer(ctx context.Context, cid *container.ID, friendlyN container.WithAttribute(container.AttributeName, friendlyName), container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10))) - return a.cli.Container().Put(ctx, cnr, - sdk.ContainerPutAndWait(), - sdk.ContainerPutWithTimeout(120*time.Second)) + cid, err = conn.PutContainer(ctx, cnr) + if err != nil { + return nil, err + } + + wctx, cancel := context.WithTimeout(ctx, containerCreationTimeout) + defer cancel() + ticker := time.NewTimer(containerPollInterval) + defer ticker.Stop() + wdone := wctx.Done() + done := ctx.Done() + for { + select { + case <-done: + return nil, ctx.Err() + case <-wdone: + return nil, wctx.Err() + case <-ticker.C: + _, err = conn.GetContainer(ctx, cid) + if err == nil { + return cid, nil + } + ticker.Reset(containerPollInterval) + } + } } // IssueSecret creates an auth token, puts it in the NeoFS network and writes to io.Writer a new secret access key. @@ -121,13 +153,13 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr zap.Stringer("owner_tkn", tkn.Issuer())) address, err := bearer. - New(a.cli.Object(), options.OwnerPrivateKey). + New(a.cli, options.OwnerPrivateKey). Put(ctx, cid, tkn, options.GatesPublicKeys...) if err != nil { return fmt.Errorf("failed to put bearer token: %w", err) } - secret, err := s3.SecretAccessKey(tkn) + secret, err := BearerToAccessKey(tkn) if err != nil { return fmt.Errorf("failed to get bearer token secret key: %w", err) } @@ -146,7 +178,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr // ObtainSecret receives an existing secret access key from NeoFS and // writes to io.Writer the secret access key. func (a *Agent) ObtainSecret(ctx context.Context, w io.Writer, options *ObtainSecretOptions) error { - bearerCreds := bearer.New(a.cli.Object(), options.GatePrivateKey) + bearerCreds := bearer.New(a.cli, options.GatePrivateKey) address := object.NewAddress() if err := address.Parse(options.SecretAddress); err != nil { return fmt.Errorf("failed to parse secret address: %w", err) @@ -157,7 +189,7 @@ func (a *Agent) ObtainSecret(ctx context.Context, w io.Writer, options *ObtainSe return fmt.Errorf("failed to get bearer token: %w", err) } - secret, err := s3.SecretAccessKey(tkn) + secret, err := BearerToAccessKey(tkn) if err != nil { return fmt.Errorf("failed to get bearer token secret key: %w", err) } @@ -234,3 +266,14 @@ func buildBearerToken(key *ecdsa.PrivateKey, oid *owner.ID, table *eacl.Table) ( return bearerToken, bearerToken.SignToken(key) } + +// BearerToAccessKey returns secret access key generated from given BearerToken. +func BearerToAccessKey(tkn *token.BearerToken) (string, error) { + data, err := tkn.Marshal() + if err != nil { + return "", err + } + + hash := sha256.Sum256(data) + return hex.EncodeToString(hash[:]), nil +} diff --git a/cmd/authmate/main.go b/cmd/authmate/main.go index af0a526d..6787289b 100644 --- a/cmd/authmate/main.go +++ b/cmd/authmate/main.go @@ -6,15 +6,16 @@ import ( "encoding/json" "fmt" "os" + "os/signal" + "syscall" "time" - sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" - "github.com/nspcc-dev/cdn-sdk/grace" - "github.com/nspcc-dev/cdn-sdk/pool" "github.com/nspcc-dev/neofs-api-go/pkg/container" + "github.com/nspcc-dev/neofs-http-gw/connections" + sdk "github.com/nspcc-dev/neofs-http-gw/neofs" "github.com/nspcc-dev/neofs-s3-gw/authmate" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "github.com/nspcc-dev/neofs-s3-gw/internal/version" "github.com/urfave/cli/v2" "go.uber.org/zap" @@ -64,12 +65,13 @@ var zapConfig = zap.Config{ func prepare() (context.Context, *zap.Logger) { var ( - err error - log = zap.NewNop() + err error + log = zap.NewNop() + ctx, _ = signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) ) if !logEnabledFlag { - return grace.Context(log), log + return ctx, log } else if logDebugEnabledFlag { zapConfig.Level = zap.NewAtomicLevelAt(zapcore.DebugLevel) } @@ -78,7 +80,7 @@ func prepare() (context.Context, *zap.Logger) { panic(err) } - return grace.Context(log), log + return ctx, log } func main() { @@ -363,25 +365,23 @@ func fetchHCSCredentials(val string) (hcs.Credentials, error) { return hcs.NewCredentials(val) } -func createSDKClient(ctx context.Context, log *zap.Logger, neofsCreds neofs.Credentials, peerAddress string) (sdk.Client, error) { +func createSDKClient(ctx context.Context, log *zap.Logger, neofsCreds neofs.Credentials, peerAddress string) (sdk.ClientPlant, error) { log.Debug("prepare connection pool") - p, err := pool.New(ctx, - pool.WithLogger(log), - pool.WithAddress(peerAddress), - pool.WithCredentials(neofsCreds), - pool.WithAPIPreparer(sdk.APIPreparer), - pool.WithConnectTimeout(poolConnectTimeout), - pool.WithRequestTimeout(poolRequestTimeout)) + pb := new(connections.PoolBuilder) + pb.AddNode(peerAddress, 1) + + opts := &connections.PoolBuilderOptions{ + Key: neofsCreds.PrivateKey(), + NodeConnectionTimeout: poolConnectTimeout, + NodeRequestTimeout: poolRequestTimeout, + } + pool, err := pb.Build(ctx, opts) if err != nil { return nil, fmt.Errorf("failed to create connection pool: %w", err) } log.Debug("prepare sdk client") - return sdk.New(ctx, - sdk.WithLogger(log), - sdk.WithCredentials(neofsCreds), - sdk.WithConnectionPool(p), - sdk.WithAPIPreparer(sdk.APIPreparer)) + return sdk.NewClientPlant(ctx, pool, neofsCreds) } diff --git a/cmd/s3-gw/app-healthy.go b/cmd/s3-gw/app-healthy.go index ca487a93..61294ed9 100644 --- a/cmd/s3-gw/app-healthy.go +++ b/cmd/s3-gw/app-healthy.go @@ -18,6 +18,7 @@ const ( defaultContentType = "text/plain; charset=utf-8" ) +//nolint:deadcode,unused // TODO func attachHealthy(r *mux.Router, h Healthy) { healthy := r.PathPrefix(systemPath + "/-"). Subrouter(). diff --git a/cmd/s3-gw/app-settings.go b/cmd/s3-gw/app-settings.go index 0ab9638f..dac553b2 100644 --- a/cmd/s3-gw/app-settings.go +++ b/cmd/s3-gw/app-settings.go @@ -9,6 +9,7 @@ import ( "strings" "time" + "github.com/nspcc-dev/neofs-http-gw/connections" "github.com/nspcc-dev/neofs-s3-gw/internal/version" "github.com/spf13/pflag" "github.com/spf13/viper" @@ -110,8 +111,8 @@ var ignore = map[string]struct{}{ func (empty) Read([]byte) (int, error) { return 0, io.EOF } -func fetchPeers(l *zap.Logger, v *viper.Viper) map[string]float64 { - peers := make(map[string]float64) +func fetchPeers(l *zap.Logger, v *viper.Viper) *connections.PoolBuilder { + pb := new(connections.PoolBuilder) for i := 0; ; i++ { key := cfgPeers + "." + strconv.Itoa(i) + "." @@ -122,14 +123,17 @@ func fetchPeers(l *zap.Logger, v *viper.Viper) map[string]float64 { l.Warn("skip, empty address") break } + if weight <= 0 { // unspecified or wrong + weight = 1 + } + pb.AddNode(address, weight) - peers[address] = weight - l.Info("add connection peer", + l.Info("added connection peer", zap.String("address", address), zap.Float64("weight", weight)) } - return peers + return pb } func fetchDomains(v *viper.Viper) []string { diff --git a/cmd/s3-gw/app.go b/cmd/s3-gw/app.go index 2b9c5fc9..6736fc73 100644 --- a/cmd/s3-gw/app.go +++ b/cmd/s3-gw/app.go @@ -2,29 +2,26 @@ package main import ( "context" - "errors" + "math" "net" "net/http" - "os" - sdk "github.com/nspcc-dev/cdn-sdk" - "github.com/nspcc-dev/cdn-sdk/creds/hcs" - "github.com/nspcc-dev/cdn-sdk/creds/neofs" - "github.com/nspcc-dev/cdn-sdk/pool" + "github.com/nspcc-dev/neofs-http-gw/connections" + sdk "github.com/nspcc-dev/neofs-http-gw/neofs" "github.com/nspcc-dev/neofs-s3-gw/api" "github.com/nspcc-dev/neofs-s3-gw/api/auth" "github.com/nspcc-dev/neofs-s3-gw/api/handler" "github.com/nspcc-dev/neofs-s3-gw/api/layer" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/nspcc-dev/neofs-s3-gw/creds/neofs" "github.com/spf13/viper" "go.uber.org/zap" - "google.golang.org/grpc" - "google.golang.org/grpc/keepalive" ) type ( // App is the main application structure. App struct { - cli pool.Client + cli sdk.ClientPlant ctr auth.Center log *zap.Logger cfg *viper.Viper @@ -46,10 +43,10 @@ type ( func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { var ( + pool connections.Pool err error tls *tlsConfig - cli sdk.Client - con pool.Client + cli sdk.ClientPlant caller api.Handler ctr auth.Center obj layer.Client @@ -57,7 +54,7 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { hcsCred hcs.Credentials nfsCred neofs.Credentials - peers = fetchPeers(l, v) + poolPeers = fetchPeers(l, v) reBalance = defaultRebalanceTimer conTimeout = defaultConnectTimeout @@ -109,56 +106,30 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { zap.String("HCS", hcsCredential), zap.String("NeoFS", nfsCredential)) - poolOptions := []pool.Option{ - pool.WithLogger(l), - pool.WithWeightPool(peers), - pool.WithCredentials(nfsCred), - pool.WithTickerTimeout(reBalance), - pool.WithConnectTimeout(conTimeout), - pool.WithRequestTimeout(reqTimeout), - pool.WithAPIPreparer(sdk.APIPreparer), - pool.WithGRPCOptions( - grpc.WithBlock(), - grpc.WithInsecure(), - grpc.WithKeepaliveParams(keepalive.ClientParameters{ - Time: v.GetDuration(cfgKeepaliveTime), - Timeout: v.GetDuration(cfgKeepaliveTimeout), - PermitWithoutStream: v.GetBool(cfgKeepalivePermitWithoutStream), - }))} - - if con, err = pool.New(ctx, poolOptions...); err != nil { - l.Fatal("could not prepare pool connections", zap.Error(err)) + opts := &connections.PoolBuilderOptions{ + Key: nfsCred.PrivateKey(), + NodeConnectionTimeout: conTimeout, + NodeRequestTimeout: reqTimeout, + ClientRebalanceInterval: reBalance, + SessionExpirationEpoch: math.MaxUint64, + KeepaliveTime: v.GetDuration(cfgKeepaliveTime), + KeepaliveTimeout: v.GetDuration(cfgKeepaliveTimeout), + KeepalivePermitWoStream: v.GetBool(cfgKeepalivePermitWithoutStream), } - - { // should establish connection with NeoFS Storage Nodes - ctx, cancel := context.WithTimeout(ctx, conTimeout) - defer cancel() - - if _, err = con.Connection(ctx); err != nil { - if errors.Is(err, context.Canceled) { - l.Info("connection canceled") - os.Exit(0) - } - - l.Fatal("could not establish connection", - zap.Error(err)) - } + pool, err = poolPeers.Build(ctx, opts) + if err != nil { + l.Fatal("failed to create connection pool", zap.Error(err)) } - - if cli, err = sdk.New(ctx, - sdk.WithLogger(l), - sdk.WithConnectionPool(con), - sdk.WithCredentials(nfsCred), - sdk.WithAPIPreparer(sdk.APIPreparer)); err != nil { - l.Fatal("could not prepare sdk client", - zap.Error(err)) + cli, err = sdk.NewClientPlant(ctx, pool, nfsCred) + if err != nil { + l.Fatal("failed to create neofs client plant") } // prepare object layer obj = layer.NewLayer(l, cli) // prepare auth center - ctr = auth.New(cli.Object(), hcsCred.PrivateKey()) + ctr = auth.New(cli, hcsCred.PrivateKey()) if caller, err = handler.New(l, obj); err != nil { l.Fatal("could not initialize API handler", zap.Error(err)) @@ -166,7 +137,7 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { return &App{ ctr: ctr, - cli: con, + cli: cli, log: l, cfg: v, obj: obj, @@ -184,12 +155,7 @@ func newApp(ctx context.Context, l *zap.Logger, v *viper.Viper) *App { func (a *App) Wait() { a.log.Info("application started") - select { - case <-a.wrkDone: // wait for worker is stopped - <-a.webDone - case <-a.webDone: // wait for web-server is stopped - <-a.wrkDone - } + <-a.webDone // wait for web-server to be stopped a.log.Info("application finished") } @@ -212,7 +178,7 @@ func (a *App) Server(ctx context.Context) { router := newS3Router() // Attach app-specific routes: - attachHealthy(router, a.cli) + // attachHealthy(router, a.cli) attachMetrics(router, a.cfg, a.log) attachProfiler(router, a.cfg, a.log) @@ -258,10 +224,3 @@ func (a *App) Server(ctx context.Context) { close(a.webDone) } - -// Worker runs client worker. -func (a *App) Worker(ctx context.Context) { - a.cli.Worker(ctx) - a.log.Info("stopping worker") - close(a.wrkDone) -} diff --git a/cmd/s3-gw/main.go b/cmd/s3-gw/main.go index 7caede82..088e5635 100644 --- a/cmd/s3-gw/main.go +++ b/cmd/s3-gw/main.go @@ -1,8 +1,11 @@ package main import ( - "github.com/nspcc-dev/cdn-sdk/grace" - "github.com/nspcc-dev/cdn-sdk/logger" + "context" + "os/signal" + "syscall" + + "github.com/nspcc-dev/neofs-http-gw/logger" "github.com/spf13/viper" "go.uber.org/zap" ) @@ -39,14 +42,13 @@ func newLogger(v *viper.Viper) *zap.Logger { func main() { var ( - v = newSettings() - l = newLogger(v) - g = grace.Context(l) - a = newApp(g, l, v) + v = newSettings() + l = newLogger(v) + g, _ = signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM, syscall.SIGHUP) + a = newApp(g, l, v) ) go a.Server(g) - go a.Worker(g) a.Wait() } diff --git a/creds/accessbox/accessbox.go b/creds/accessbox/accessbox.go new file mode 100644 index 00000000..66bcd367 --- /dev/null +++ b/creds/accessbox/accessbox.go @@ -0,0 +1,29 @@ +package accessbox + +import "github.com/nspcc-dev/neofs-api-go/pkg/token" + +type ( + // Box provides marshalling/unmarshalling for the token. + Box interface { + Marshal() ([]byte, error) + Unmarshal([]byte) error + } + + // Encoder provides encoding method. + Encoder interface { + Encode(Box) error + } + + // Decoder provides decoding method. + Decoder interface { + Decode(Box) error + } + + // BearerTokenBox is a marshalling/unmarshalling bearer token wrapper. + BearerTokenBox interface { + Box + + Token() *token.BearerToken + SetToken(*token.BearerToken) + } +) diff --git a/creds/accessbox/bearer_token.go b/creds/accessbox/bearer_token.go new file mode 100644 index 00000000..8551a52b --- /dev/null +++ b/creds/accessbox/bearer_token.go @@ -0,0 +1,43 @@ +package accessbox + +import ( + "github.com/nspcc-dev/neofs-api-go/pkg/token" +) + +type bearerBox struct { + tkn *token.BearerToken +} + +// NewBearerBox wraps given bearer token into BearerTokenBox. +func NewBearerBox(token *token.BearerToken) BearerTokenBox { + return &bearerBox{tkn: token} +} + +// Marshal serializes bearer token. +func (b *bearerBox) Marshal() ([]byte, error) { + return b.tkn.Marshal(nil) +} + +// Marshal initializes bearer box from its serialized representation. +func (b *bearerBox) Unmarshal(data []byte) error { + tkn := token.NewBearerToken() + + err := tkn.Unmarshal(data) + if err != nil { + return err + } + + b.SetToken(tkn) + + return nil +} + +// Token unwraps bearer token from the box. +func (b *bearerBox) Token() *token.BearerToken { + return b.tkn +} + +// SetToken sets new token in the box. +func (b *bearerBox) SetToken(tkn *token.BearerToken) { + b.tkn = tkn +} diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/bearer_token_test.go new file mode 100644 index 00000000..bcaba33f --- /dev/null +++ b/creds/accessbox/bearer_token_test.go @@ -0,0 +1,161 @@ +package accessbox + +import ( + "bytes" + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "encoding/binary" + "strconv" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl" + "github.com/nspcc-dev/neofs-api-go/pkg/token" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "github.com/stretchr/testify/require" +) + +func Test_encrypt_decrypt(t *testing.T) { + tkn := token.NewBearerToken() + box := NewBearerBox(tkn) + + sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + cred, err := hcs.Generate(rand.Reader) + require.NoError(t, err) + + tkn.SetEACLTable(eacl.NewTable()) + require.NoError(t, tkn.SignToken(sec)) + + data, err := box.Marshal() + require.NoError(t, err) + + encrypted, err := encrypt(cred.PrivateKey(), cred.PublicKey(), data) + require.NoError(t, err) + + decrypted, err := decrypt(cred.PrivateKey(), cred.PublicKey(), encrypted) + require.NoError(t, err) + + require.Equal(t, data, decrypted) +} + +func Test_encrypt_decrypt_step_by_step(t *testing.T) { + tkn := token.NewBearerToken() + box := NewBearerBox(tkn) + + sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + cred, err := hcs.Generate(rand.Reader) + require.NoError(t, err) + + tkn.SetEACLTable(eacl.NewTable()) + require.NoError(t, tkn.SignToken(sec)) + + data, err := box.Marshal() + require.NoError(t, err) + + buf := new(bytes.Buffer) + _, err = cred.PublicKey().WriteTo(buf) + require.NoError(t, err) + + encrypted, err := encrypt(cred.PrivateKey(), cred.PublicKey(), data) + require.NoError(t, err) + + length := len(encrypted) + temp := make([]byte, length+binary.MaxVarintLen64) + size := binary.PutVarint(temp, int64(length)) + copy(temp[size:], encrypted) + buf.Write(temp[:length+size]) + + sender, err := hcs.NewPublicKeyFromReader(buf) + require.NoError(t, err) + + require.Equal(t, cred.PublicKey(), sender) + + ln, err := binary.ReadVarint(buf) + require.NoError(t, err) + require.Equal(t, int64(length), ln) + + enc := make([]byte, ln) + n, err := buf.Read(enc) + require.NoError(t, err) + require.Equal(t, length, n) + require.Equal(t, encrypted, enc) + + decrypted, err := decrypt(cred.PrivateKey(), sender, enc) + require.NoError(t, err) + require.Equal(t, data, decrypted) +} + +func TestSingleKey_AccessBox(t *testing.T) { + tkn := token.NewBearerToken() + expect := NewBearerBox(tkn) + actual := NewBearerBox(nil) + + sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + cred, err := hcs.Generate(rand.Reader) + require.NoError(t, err) + + tkn.SetEACLTable(eacl.NewTable()) + require.NoError(t, tkn.SignToken(sec)) + + data, err := Encode(expect, cred.PrivateKey(), cred.PublicKey()) + require.NoError(t, err) + + require.NoError(t, Decode(data, actual, cred.PrivateKey())) + require.Equal(t, expect, actual) +} + +func TestBearerToken_AccessBox(t *testing.T) { + tkn := token.NewBearerToken() + box := NewBearerBox(tkn) + sec, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + cred, err := hcs.Generate(rand.Reader) + require.NoError(t, err) + + tkn.SetEACLTable(eacl.NewTable()) + require.NoError(t, tkn.SignToken(sec)) + + count := 10 + pubs := make([]hcs.PublicKey, 0, count) + keys := make([]hcs.PrivateKey, 0, count) + { // generate keys + for i := 0; i < count; i++ { + cred, err := hcs.Generate(rand.Reader) + require.NoError(t, err) + + pubs = append(pubs, cred.PublicKey()) + keys = append(keys, cred.PrivateKey()) + } + } + + buf := new(bytes.Buffer) + require.NoError(t, NewEncoder(buf, cred.PrivateKey(), pubs...).Encode(box)) + + data := buf.Bytes() + + for i := range keys { + key := keys[i] + t.Run("try with key "+strconv.Itoa(i), func(t *testing.T) { + r := bytes.NewReader(data) + nbx := NewBearerBox(nil) + require.NoError(t, NewDecoder(r, key).Decode(nbx)) + require.Equal(t, tkn, nbx.Token()) + }) + } + + t.Run("should fail for unknown key", func(t *testing.T) { + cred, err = hcs.Generate(rand.Reader) + require.NoError(t, err) + + r := bytes.NewReader(data) + nbx := NewBearerBox(nil) + require.EqualError(t, NewDecoder(r, cred.PrivateKey()).Decode(nbx), "chacha20poly1305: message authentication failed") + }) +} diff --git a/creds/accessbox/decoder.go b/creds/accessbox/decoder.go new file mode 100644 index 00000000..059a2972 --- /dev/null +++ b/creds/accessbox/decoder.go @@ -0,0 +1,88 @@ +package accessbox + +import ( + "bufio" + "bytes" + "encoding/binary" + "fmt" + "io" + + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/curve25519" +) + +type decoder struct { + *bufio.Reader + + key hcs.PrivateKey +} + +// NewDecoder returns new private key decoder. +func NewDecoder(r io.Reader, key hcs.PrivateKey) Decoder { + return &decoder{Reader: bufio.NewReader(r), key: key} +} + +func decrypt(owner hcs.PrivateKey, sender hcs.PublicKey, data []byte) ([]byte, error) { + sb := sender.Bytes() + + key, err := curve25519.X25519(owner.Bytes(), sb) + if err != nil { + return nil, err + } + + dec, err := chacha20poly1305.NewX(key) + if err != nil { + return nil, err + } + + if ld, ns := len(data), dec.NonceSize(); ld < ns { + return nil, fmt.Errorf("wrong data size (%d), should be greater than %d", ld, ns) + } + + nonce, cypher := data[:dec.NonceSize()], data[dec.NonceSize():] + return dec.Open(nil, nonce, cypher, nil) +} + +func (d *decoder) Decode(box Box) error { + sender, err := hcs.NewPublicKeyFromReader(d) + if err != nil { + return err + } + + var lastErr error + + for { + size, err := binary.ReadVarint(d) + if err == io.EOF { + break + } else if err != nil { + return err + } + + data := make([]byte, size) + + if ln, err := d.Read(data); err != nil { + lastErr = err + continue + } else if ln != int(size) { + lastErr = fmt.Errorf("expect %d bytes, but read only %d bytes", size, ln) + continue + } else if decoded, err := decrypt(d.key, sender, data); err != nil { + lastErr = err + continue + } else if err = box.Unmarshal(decoded); err != nil { + lastErr = err + continue + } + + return nil + } + + return lastErr +} + +// Decode unwraps serialized bearer token from data into box using owner key. +func Decode(data []byte, box Box, owner hcs.PrivateKey) error { + return NewDecoder(bytes.NewBuffer(data), owner).Decode(box) +} diff --git a/creds/accessbox/encoder.go b/creds/accessbox/encoder.go new file mode 100644 index 00000000..d46b875b --- /dev/null +++ b/creds/accessbox/encoder.go @@ -0,0 +1,85 @@ +package accessbox + +import ( + "bytes" + "crypto/rand" + "encoding/binary" + "fmt" + "io" + + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" + "golang.org/x/crypto/chacha20poly1305" + "golang.org/x/crypto/curve25519" +) + +type encoder struct { + io.Writer + + owner hcs.PrivateKey + keys []hcs.PublicKey +} + +// NewEncoder creates encoder. +func NewEncoder(w io.Writer, owner hcs.PrivateKey, keys ...hcs.PublicKey) Encoder { + return &encoder{ + Writer: w, + owner: owner, + keys: keys, + } +} + +func encrypt(owner hcs.PrivateKey, sender hcs.PublicKey, data []byte) ([]byte, error) { + key, err := curve25519.X25519(owner.Bytes(), sender.Bytes()) + if err != nil { + return nil, err + } + + enc, err := chacha20poly1305.NewX(key) + if err != nil { + return nil, err + } + + nonce := make([]byte, enc.NonceSize(), enc.NonceSize()+len(data)+enc.Overhead()) + if _, err := rand.Read(nonce); err != nil { + return nil, err + } + + return enc.Seal(nonce, nonce, data, nil), nil +} + +// Encode and encrypt box through owner private key and public keys. +func (e *encoder) Encode(box Box) error { + data, err := box.Marshal() + if err != nil { + return err + } + + // write owner public key + if _, err = e.owner.PublicKey().WriteTo(e); err != nil { + return err + } + + for i, sender := range e.keys { + encrypted, err := encrypt(e.owner, sender, data) + if err != nil { + return fmt.Errorf("%w, sender = %d", err, i) + } + + ln := len(encrypted) + temp := make([]byte, ln+binary.MaxVarintLen64) + size := binary.PutVarint(temp, int64(ln)) + copy(temp[size:], encrypted) + if _, err := e.Write(temp[:size+ln]); err != nil { + return fmt.Errorf("%w, sender = %d", err, i) + } + } + + return nil +} + +// Encode and encrypt box through owner private key and public keys. +func Encode(box Box, owner hcs.PrivateKey, keys ...hcs.PublicKey) ([]byte, error) { + buf := new(bytes.Buffer) + err := NewEncoder(buf, owner, keys...).Encode(box) + return buf.Bytes(), err +} diff --git a/creds/bearer/credentials.go b/creds/bearer/credentials.go new file mode 100644 index 00000000..cbdd9f72 --- /dev/null +++ b/creds/bearer/credentials.go @@ -0,0 +1,140 @@ +package bearer + +import ( + "bytes" + "context" + "errors" + "strconv" + "sync" + "time" + + "github.com/nspcc-dev/neofs-api-go/pkg/client" + "github.com/nspcc-dev/neofs-api-go/pkg/container" + "github.com/nspcc-dev/neofs-api-go/pkg/object" + "github.com/nspcc-dev/neofs-api-go/pkg/token" + sdk "github.com/nspcc-dev/neofs-http-gw/neofs" + "github.com/nspcc-dev/neofs-s3-gw/creds/accessbox" + "github.com/nspcc-dev/neofs-s3-gw/creds/hcs" +) + +type ( + // Credentials is a bearer token get/put interface. + Credentials interface { + Get(context.Context, *object.Address) (*token.BearerToken, error) + Put(context.Context, *container.ID, *token.BearerToken, ...hcs.PublicKey) (*object.Address, error) + } + + cred struct { + key hcs.PrivateKey + obj sdk.ClientPlant + } +) + +var ( + // ErrEmptyPublicKeys is returned when no HCS keys are provided. + ErrEmptyPublicKeys = errors.New("HCS public keys could not be empty") + // ErrEmptyBearerToken is returned when no bearer token is provided. + ErrEmptyBearerToken = errors.New("Bearer token could not be empty") +) + +var bufferPool = sync.Pool{ + New: func() interface{} { + return new(bytes.Buffer) + }, +} + +var _ = New + +// New creates new Credentials instance using given cli and key. +func New(cli sdk.ClientPlant, key hcs.PrivateKey) Credentials { + return &cred{obj: cli, key: key} +} + +func (c *cred) acquireBuffer() *bytes.Buffer { + return bufferPool.Get().(*bytes.Buffer) +} + +func (c *cred) releaseBuffer(buf *bytes.Buffer) { + buf.Reset() + bufferPool.Put(buf) +} + +func (c *cred) Get(ctx context.Context, address *object.Address) (*token.BearerToken, error) { + buf := c.acquireBuffer() + defer c.releaseBuffer(buf) + + box := accessbox.NewBearerBox(nil) + + conn, tok, err := c.obj.ConnectionArtifacts() + if err != nil { + return nil, err + } + ops := new(client.GetObjectParams).WithAddress(address).WithPayloadWriter(buf) + + _, err = conn.GetObject( + ctx, + ops, + client.WithSession(tok), + ) + if err != nil { + return nil, err + } + + err = accessbox.NewDecoder(buf, c.key).Decode(box) + if err != nil { + return nil, err + } + + return box.Token(), nil +} + +func (c *cred) Put(ctx context.Context, cid *container.ID, tkn *token.BearerToken, keys ...hcs.PublicKey) (*object.Address, error) { + var ( + err error + buf = c.acquireBuffer() + box = accessbox.NewBearerBox(tkn) + + created = strconv.FormatInt(time.Now().Unix(), 10) + ) + + defer c.releaseBuffer(buf) + + if len(keys) == 0 { + return nil, ErrEmptyPublicKeys + } else if tkn == nil { + return nil, ErrEmptyBearerToken + } else if err = accessbox.NewEncoder(buf, c.key, keys...).Encode(box); err != nil { + return nil, err + } + + conn, tok, err := c.obj.ConnectionArtifacts() + if err != nil { + return nil, err + } + timestamp := object.NewAttribute() + timestamp.SetKey(object.AttributeTimestamp) + timestamp.SetValue(created) + + filename := object.NewAttribute() + filename.SetKey(object.AttributeFileName) + filename.SetValue(created + "_access.box") + + raw := object.NewRaw() + raw.SetContainerID(cid) + raw.SetOwnerID(tkn.Issuer()) + raw.SetAttributes(filename, timestamp) + + ops := new(client.PutObjectParams).WithObject(raw.Object()).WithPayloadReader(buf) + oid, err := conn.PutObject( + ctx, + ops, + client.WithSession(tok), + ) + if err != nil { + return nil, err + } + address := object.NewAddress() + address.SetObjectID(oid) + address.SetContainerID(cid) + return address, nil +} diff --git a/creds/hcs/credentials.go b/creds/hcs/credentials.go new file mode 100644 index 00000000..5e3009f7 --- /dev/null +++ b/creds/hcs/credentials.go @@ -0,0 +1,90 @@ +package hcs + +import ( + "errors" + "io" + + "golang.org/x/crypto/curve25519" +) + +type ( + // Credentials is an HCS interface (private/public key). + Credentials interface { + PublicKey() PublicKey + PrivateKey() PrivateKey + } + + keyer interface { + io.WriterTo + + Bytes() []byte + String() string + } + + // PublicKey is a public key wrapper providing useful methods. + PublicKey interface { + keyer + } + + // PrivateKey is private key wrapper providing useful methods. + PrivateKey interface { + keyer + + PublicKey() PublicKey + } + + credentials struct { + public PublicKey + secret PrivateKey + } + + public []byte + secret []byte +) + +// ErrEmptyCredentials is returned when no credentials are provided. +var ErrEmptyCredentials = errors.New("empty credentials") + +var _ = NewCredentials + +// Generate generates new key pair using given source of randomness. +func Generate(r io.Reader) (Credentials, error) { + buf := make([]byte, curve25519.ScalarSize) + + if _, err := r.Read(buf); err != nil { + return nil, err + } + + sk := secret(buf) + return &credentials{ + secret: &sk, + public: sk.PublicKey(), + }, nil +} + +// NewCredentials loads private key from the string given and returns Credentials wrapper. +func NewCredentials(val string) (Credentials, error) { + if val == "" { + return nil, ErrEmptyCredentials + } + + sk, err := loadPrivateKey(val) + if err != nil { + return nil, err + } + + return &credentials{ + secret: sk, + public: sk.PublicKey(), + }, nil +} + +// PublicKey returns public key. +func (c *credentials) PublicKey() PublicKey { + return c.public +} + +// PrivateKey returns private key. +func (c *credentials) PrivateKey() PrivateKey { + return c.secret +} diff --git a/creds/hcs/public.go b/creds/hcs/public.go new file mode 100644 index 00000000..9a72551c --- /dev/null +++ b/creds/hcs/public.go @@ -0,0 +1,65 @@ +package hcs + +import ( + "encoding/hex" + "io" + "io/ioutil" + "os" + + "golang.org/x/crypto/curve25519" +) + +func (p *public) Bytes() []byte { + buf := make([]byte, curve25519.PointSize) + copy(buf, *p) + return buf +} + +func (p *public) String() string { + buf := p.Bytes() + return hex.EncodeToString(buf) +} + +func (p *public) WriteTo(w io.Writer) (int64, error) { + pb := p.Bytes() + pl, err := w.Write(pb) + return int64(pl), err +} + +func publicKeyFromBytes(v []byte) (PublicKey, error) { + pub := public(v) + return &pub, nil +} + +func publicKeyFromString(val string) (PublicKey, error) { + v, err := hex.DecodeString(val) + if err != nil { + return nil, err + } + + return publicKeyFromBytes(v) +} + +// NewPublicKeyFromReader reads new public key from given reader. +func NewPublicKeyFromReader(r io.Reader) (PublicKey, error) { + data := make([]byte, curve25519.PointSize) + if _, err := r.Read(data); err != nil { + return nil, err + } + + return publicKeyFromBytes(data) +} + +// LoadPublicKey loads public key from given file or (serialized) string. +func LoadPublicKey(val string) (PublicKey, error) { + data, err := ioutil.ReadFile(val) + if err != nil { + if os.IsNotExist(err) { + return publicKeyFromString(val) + } + + return nil, err + } + + return publicKeyFromBytes(data) +} diff --git a/creds/hcs/secret.go b/creds/hcs/secret.go new file mode 100644 index 00000000..1236c42d --- /dev/null +++ b/creds/hcs/secret.go @@ -0,0 +1,60 @@ +package hcs + +import ( + "encoding/hex" + "io" + "io/ioutil" + "os" + + "golang.org/x/crypto/curve25519" +) + +func (s *secret) Bytes() []byte { + buf := make([]byte, curve25519.ScalarSize) + copy(buf, *s) + return buf +} + +func (s *secret) String() string { + buf := s.Bytes() + return hex.EncodeToString(buf) +} + +func (s *secret) PublicKey() PublicKey { + sk := s.Bytes() + + pb, _ := curve25519.X25519(sk, curve25519.Basepoint) + pk := public(pb) + return &pk +} + +func (s *secret) WriteTo(w io.Writer) (int64, error) { + sb := s.Bytes() + sl, err := w.Write(sb) + return int64(sl), err +} + +func privateKeyFromBytes(val []byte) (PrivateKey, error) { + sk := secret(val) + return &sk, nil +} + +func privateKeyFromString(val string) (PrivateKey, error) { + data, err := hex.DecodeString(val) + if err != nil { + return nil, err + } + + return privateKeyFromBytes(data) +} + +func loadPrivateKey(val string) (PrivateKey, error) { + data, err := ioutil.ReadFile(val) + if os.IsNotExist(err) { + return privateKeyFromString(val) + } else if err != nil { + return nil, err + } + + return privateKeyFromBytes(data) +} diff --git a/creds/neofs/credentials.go b/creds/neofs/credentials.go new file mode 100644 index 00000000..fa25864b --- /dev/null +++ b/creds/neofs/credentials.go @@ -0,0 +1,71 @@ +package neofs + +import ( + "crypto/ecdsa" + + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + crypto "github.com/nspcc-dev/neofs-crypto" +) + +type ( + // Credentials contains methods that needed to work with NeoFS. + Credentials interface { + WIF() string + Owner() *owner.ID + PublicKey() *ecdsa.PublicKey + PrivateKey() *ecdsa.PrivateKey + } + + cred struct { + key *ecdsa.PrivateKey + owner *owner.ID + wif string + } +) + +// New creates an instance of Credentials through string representation of secret. +// It allows passing WIF, path, hex-encoded and others. +func New(secret string) (Credentials, error) { + key, err := crypto.LoadPrivateKey(secret) + if err != nil { + return nil, err + } + + return setFromPrivateKey(key) +} + +// PrivateKey returns ecdsa.PrivateKey. +func (c *cred) PrivateKey() *ecdsa.PrivateKey { + return c.key +} + +// PublicKey returns ecdsa.PublicKey. +func (c *cred) PublicKey() *ecdsa.PublicKey { + return &c.key.PublicKey +} + +// Owner returns owner.ID. +func (c *cred) Owner() *owner.ID { + return c.owner +} + +// WIF returns string representation of WIF. +func (c *cred) WIF() string { + return c.wif +} + +func setFromPrivateKey(key *ecdsa.PrivateKey) (*cred, error) { + wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) + if err != nil { + return nil, err + } + + ownerID := owner.NewIDFromNeo3Wallet(wallet) + + wif, err := crypto.WIFEncode(key) + if err != nil { + return nil, err + } + + return &cred{key: key, owner: ownerID, wif: wif}, nil +} diff --git a/creds/neofs/credentials_test.go b/creds/neofs/credentials_test.go new file mode 100644 index 00000000..82e15ae1 --- /dev/null +++ b/creds/neofs/credentials_test.go @@ -0,0 +1,40 @@ +package neofs + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "testing" + + "github.com/nspcc-dev/neofs-api-go/pkg/owner" + crypto "github.com/nspcc-dev/neofs-crypto" + "github.com/stretchr/testify/require" +) + +func TestNew(t *testing.T) { + t.Run("should fail", func(t *testing.T) { + cred, err := New("") + require.Nil(t, cred) + require.Error(t, err) + }) + + t.Run("should work as expected", func(t *testing.T) { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + require.NoError(t, err) + + wif, err := crypto.WIFEncode(key) + require.NoError(t, err) + + wallet, err := owner.NEO3WalletFromPublicKey(&key.PublicKey) + require.NoError(t, err) + + own := owner.NewIDFromNeo3Wallet(wallet) + + cred, err := New(wif) + require.NoError(t, err) + require.Equal(t, cred.WIF(), wif) + require.Equal(t, cred.Owner(), own) + require.Equal(t, cred.PrivateKey(), key) + require.Equal(t, cred.PublicKey(), &key.PublicKey) + }) +} diff --git a/go.mod b/go.mod index d163875e..0b40bd8d 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,14 @@ module github.com/nspcc-dev/neofs-s3-gw -go 1.14 +go 1.16 require ( github.com/aws/aws-sdk-go v1.37.9 github.com/google/uuid v1.2.0 github.com/gorilla/mux v1.8.0 - github.com/nspcc-dev/cdn-sdk v0.3.4 - github.com/nspcc-dev/neofs-api-go v1.23.0 + github.com/nspcc-dev/neofs-api-go v1.26.1 + github.com/nspcc-dev/neofs-crypto v0.3.0 + github.com/nspcc-dev/neofs-http-gw v0.15.1 github.com/nspcc-dev/neofs-node v1.22.0 github.com/prometheus/client_golang v1.9.0 github.com/spf13/pflag v1.0.5 @@ -15,5 +16,6 @@ require ( github.com/stretchr/testify v1.7.0 github.com/urfave/cli/v2 v2.3.0 go.uber.org/zap v1.16.0 - google.golang.org/grpc v1.35.0 + golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 + google.golang.org/grpc v1.36.1 ) diff --git a/go.sum b/go.sum index d05cdf9a..3ba68e3c 100644 --- a/go.sum +++ b/go.sum @@ -40,6 +40,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= +github.com/andybalholm/brotli v1.0.0/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= @@ -122,6 +124,7 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= +github.com/fasthttp/router v1.3.5/go.mod h1:BylQKgvh6YQkR0mvL60+HJyTaGwcn5d8UFNweOb/Nw8= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/flynn-archive/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:rZfgFAXFS/z/lEd6LJmf9HVZ1LkgYiHx5pHhV5DR16M= github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= @@ -256,7 +259,9 @@ github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8 github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kkdai/bstream v0.0.0-20161212061736-f391b8402d23/go.mod h1:J+Gs4SYgM6CZQHDETBtE9HaSEkGmuNXF86RwHhHUvq4= +github.com/klauspost/compress v1.10.7/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= +github.com/klauspost/compress v1.11.8/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= @@ -317,27 +322,30 @@ github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzE github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nspcc-dev/cdn-sdk v0.3.4 h1:RtYWuF9xDWrkVwu6sFRWlyZ+ToYM7Y9h8B93Fg4CPEA= -github.com/nspcc-dev/cdn-sdk v0.3.4/go.mod h1:JC4dT16H5HilyZcb8sTxL/TMC1FSEKMuFAqRsmAPoAk= github.com/nspcc-dev/dbft v0.0.0-20191205084618-dacb1a30c254/go.mod h1:w1Ln2aT+dBlPhLnuZhBV+DfPEdS2CHWWLp5JTScY3bw= github.com/nspcc-dev/dbft v0.0.0-20191209120240-0d6b7568d9ae/go.mod h1:3FjXOoHmA51EGfb5GS/HOv7VdmngNRTssSeQ729dvGY= github.com/nspcc-dev/dbft v0.0.0-20200117124306-478e5cfbf03a/go.mod h1:/YFK+XOxxg0Bfm6P92lY5eDSLYfp06XOdL8KAVgXjVk= github.com/nspcc-dev/dbft v0.0.0-20200219114139-199d286ed6c1/go.mod h1:O0qtn62prQSqizzoagHmuuKoz8QMkU3SzBoKdEvm3aQ= github.com/nspcc-dev/dbft v0.0.0-20200711144034-c526ccc6f570/go.mod h1:1FYQXSbb6/9HQIkoF8XO7W/S8N7AZRkBsgwbcXRvk0E= github.com/nspcc-dev/dbft v0.0.0-20201221101812-e13a1a1c3cb2/go.mod h1:I5D0W3tu3epdt2RMCTxS//HDr4S+OHRqajouQTOAHI8= +github.com/nspcc-dev/dbft v0.0.0-20210302103605-cc75991b7cfb/go.mod h1:U8MSnEShH+o5hexfWJdze6uMFJteP0ko7J2frO7Yu1Y= github.com/nspcc-dev/hrw v1.0.9 h1:17VcAuTtrstmFppBjfRiia4K2wA/ukXZhLFS8Y8rz5Y= github.com/nspcc-dev/hrw v1.0.9/go.mod h1:l/W2vx83vMQo6aStyx2AuZrJ+07lGv2JQGlVkPG06MU= github.com/nspcc-dev/neo-go v0.73.1-pre.0.20200303142215-f5a1b928ce09/go.mod h1:pPYwPZ2ks+uMnlRLUyXOpLieaDQSEaf4NM3zHVbRjmg= github.com/nspcc-dev/neo-go v0.91.0/go.mod h1:G6HdOWvzQ6tlvFdvFSN/PgCzLPN/X/X4d5hTjFRUDcc= -github.com/nspcc-dev/neo-go v0.92.0 h1:iKHpKLzpwE6RSXnQb0BoYWi+H1P/hNyQbMpPG0mY57Q= github.com/nspcc-dev/neo-go v0.92.0/go.mod h1:L7PyTzjK1j/PCAxvbKiVFkCMZDvsv82JbXlPxaH1t0Q= +github.com/nspcc-dev/neo-go v0.95.0 h1:bttArYkIuhBJWSZsZ1xVW8MJsj5SvZwAhqVN3HZPNbo= +github.com/nspcc-dev/neo-go v0.95.0/go.mod h1:bW07ge1WFXsBgqrcPpLUr6OcyQxHqM26MZNesWMdH0c= github.com/nspcc-dev/neofs-api-go v1.22.0/go.mod h1:G7dqincfdjBrAbL5nxVp82emF05fSVEqe59ICsoRDI8= -github.com/nspcc-dev/neofs-api-go v1.23.0 h1:t4FB5uVY99UkYR0Hiyi1SHjZuqzf4qicw7tf7BBnkHk= -github.com/nspcc-dev/neofs-api-go v1.23.0/go.mod h1:G7dqincfdjBrAbL5nxVp82emF05fSVEqe59ICsoRDI8= +github.com/nspcc-dev/neofs-api-go v1.24.0/go.mod h1:G7dqincfdjBrAbL5nxVp82emF05fSVEqe59ICsoRDI8= +github.com/nspcc-dev/neofs-api-go v1.26.1 h1:GMIuEB6Hv9IXP9SJd/1f8Df6gRriPkSplpmpJXgQ/1I= +github.com/nspcc-dev/neofs-api-go v1.26.1/go.mod h1:SHuH1Ba3U/h3j+8HHbb3Cns1LfMlEb88guWog9Qi68Y= github.com/nspcc-dev/neofs-crypto v0.2.0/go.mod h1:F/96fUzPM3wR+UGsPi3faVNmFlA9KAEAUQR7dMxZmNA= github.com/nspcc-dev/neofs-crypto v0.2.3/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= github.com/nspcc-dev/neofs-crypto v0.3.0 h1:zlr3pgoxuzrmGCxc5W8dGVfA9Rro8diFvVnBg0L4ifM= github.com/nspcc-dev/neofs-crypto v0.3.0/go.mod h1:8w16GEJbH6791ktVqHN9YRNH3s9BEEKYxGhlFnp0cDw= +github.com/nspcc-dev/neofs-http-gw v0.15.1 h1:0T0g3LeFw5XsxKeWSASNtPOpz0eblhdXmx8coIES5gw= +github.com/nspcc-dev/neofs-http-gw v0.15.1/go.mod h1:DWrjzP7ozKolQkRKYeXrWPzQjrh+GxkFwc4N0yfE/zg= github.com/nspcc-dev/neofs-node v1.22.0 h1:TJ4d5zopItYYWMEajegVWBgAw8HjZFe12IkNm3Tt+rk= github.com/nspcc-dev/neofs-node v1.22.0/go.mod h1:ecpXrzIe1vcp5FBjPsIaHKVIVvxsv4GVBCw21WYcY3c= github.com/nspcc-dev/rfc6979 v0.1.0/go.mod h1:exhIh1PdpDC5vQmyEsGvc4YDM/lyQp/452QxGq/UEso= @@ -427,6 +435,7 @@ github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0 github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= +github.com/savsgio/gotils v0.0.0-20210105085219-0567298fdcac/go.mod h1:TWNAOTaVzGOXq8RbEvHnhzA/A2sLZzgn0m6URjnukY8= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= @@ -484,6 +493,11 @@ github.com/urfave/cli v1.22.1 h1:+mkCCcOFKPnCmVYVcURKps1Xe+3zP90gSYGNfRkjoIY= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasthttp v1.19.0/go.mod h1:jjraHZVbKOXftJfsOYoAjaeygpj5hr8ermTRJNroD7A= +github.com/valyala/fasthttp v1.22.0/go.mod h1:0mw2RjXGOzxf4NL2jni3gUQ7LfjjUSiG5sskOUUSEpU= +github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= +github.com/virtuald/go-ordered-json v0.0.0-20170621173500-b18e6e673d74/go.mod h1:RmMWU37GKR2s6pgrIEB4ixgpVCt/cf7dnJv3fuH1J1c= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/gopher-lua v0.0.0-20190514113301-1cd887cd7036/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ= @@ -528,8 +542,9 @@ golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200117160349-530e935923ad/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0 h1:hb9wdF1z5waM+dSIICn1l0DkLVDT3hqhhQsDNUmHPRE= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 h1:It14KIkyBFYkHkwZ7k45minvA9aorojkyjGk9KJ5B/w= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -571,10 +586,12 @@ golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191105084925-a882066a44e0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b h1:uwuIcX0g4Yl1NC5XAz37xsr2lTtcqevgzYNVt49waME= +golang.org/x/net v0.0.0-20201016165138-7b1cca2348c0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226101413-39120d07d75e/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -609,24 +626,30 @@ golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191010194322-b09406accb47/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200420163511-1957bb5e6d1f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200602225109-6fdc65e7d980/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201024232916-9f70ab9862d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e h1:AyodaIpKjppX+cBfTASF2E1US3H2JFBj920Ot3rtDjs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201214210602-f9fddec55a1e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073 h1:8qxJSnu+7dRq6upnbntrmriWByIakBuct5OM/MdQC1M= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20201210144234-2321bbc49cbf/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5 h1:i6eZZ+zk0SOf0xgBpEpPD18qWcJda6q1sxt3S0kzyUQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -697,8 +720,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.35.0 h1:TwIQcH3es+MojMVojxxfQ3l3OF2KzlRxML2xZq0kRo8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.36.1 h1:cmUfbeGKnz9+2DD/UYsMQXeqbHZqZDs4eQwW0sFOpBY= +google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=