[#346] *: Refactor communication with NeoFS at the protocol level

Make `tokens`, `authmate` and `layer` packages to depend from locally
defined `NeoFS` interface of the virtual connection to NeoFS network.
Create internal `neofs` package and implement these interfaces through
`pool.Pool` there. Implement mediators between `NeoFS` interfaces and
`neofs.NeoFS` implementation.

Signed-off-by: Leonard Lyubich <leonard@nspcc.ru>
This commit is contained in:
Leonard Lyubich 2022-03-01 22:02:24 +03:00 committed by LeL
parent 34a221c5c9
commit cd64f41ce8
14 changed files with 1348 additions and 606 deletions

View file

@ -3,14 +3,12 @@ package authmate
import (
"context"
"crypto/ecdsa"
"encoding/binary"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"math"
"os"
"strconv"
"time"
"github.com/google/uuid"
@ -18,35 +16,75 @@ import (
"github.com/nspcc-dev/neofs-s3-gw/api/cache"
"github.com/nspcc-dev/neofs-s3-gw/creds/accessbox"
"github.com/nspcc-dev/neofs-s3-gw/creds/tokens"
"github.com/nspcc-dev/neofs-sdk-go/acl"
"github.com/nspcc-dev/neofs-sdk-go/client"
apistatus "github.com/nspcc-dev/neofs-sdk-go/client/status"
"github.com/nspcc-dev/neofs-sdk-go/container"
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
"github.com/nspcc-dev/neofs-sdk-go/eacl"
"github.com/nspcc-dev/neofs-sdk-go/netmap"
"github.com/nspcc-dev/neofs-sdk-go/object/address"
"github.com/nspcc-dev/neofs-sdk-go/owner"
"github.com/nspcc-dev/neofs-sdk-go/policy"
"github.com/nspcc-dev/neofs-sdk-go/pool"
"github.com/nspcc-dev/neofs-sdk-go/session"
"github.com/nspcc-dev/neofs-sdk-go/token"
"go.uber.org/zap"
)
const (
defaultAuthContainerBasicACL acl.BasicACL = 0b00111100100011001000110011001110 // 0x3C8C8CCE - private container with only GET allowed to others
)
// PrmContainerCreate groups parameters of containers created by authmate.
type PrmContainerCreate struct {
// NeoFS identifier of the container creator.
Owner owner.ID
// Container placement policy.
Policy netmap.PlacementPolicy
// Friendly name for the container (optional).
FriendlyName string
}
// NetworkState represents NeoFS network state which is needed for authmate processing.
type NetworkState struct {
// Current NeoFS time.
Epoch uint64
// Duration of the Morph chain block in ms.
BlockDuration int64
// Duration of the NeoFS epoch in Morph chain blocks.
EpochDuration uint64
}
// NeoFS represents virtual connection to NeoFS network.
type NeoFS interface {
// NeoFS interface required by credential tool.
tokens.NeoFS
// ContainerExists checks container presence in NeoFS by identifier.
// Returns nil iff container exists.
ContainerExists(context.Context, cid.ID) error
// CreateContainer creates and saves parameterized container in NeoFS.
// Returns ID of the saved container.
//
// The container must be private with GET access of OTHERS group.
// Creation time should also be stamped.
//
// Returns exactly one non-nil value. Returns any error encountered which
// prevented the container to be created.
CreateContainer(context.Context, PrmContainerCreate) (*cid.ID, error)
// NetworkState returns current state of the NeoFS network.
// Returns any error encountered which prevented state to be read.
//
// Returns exactly one non-nil value. Returns any error encountered which
// prevented the state to be read.
NetworkState(context.Context) (*NetworkState, error)
}
// Agent contains client communicating with NeoFS and logger.
type Agent struct {
pool pool.Pool
log *zap.Logger
neoFS NeoFS
log *zap.Logger
}
// New creates an object of type Agent that consists of Client and logger.
func New(log *zap.Logger, conns pool.Pool) *Agent {
return &Agent{log: log, pool: conns}
func New(log *zap.Logger, neoFS NeoFS) *Agent {
return &Agent{log: log, neoFS: neoFS}
}
type (
@ -85,12 +123,6 @@ type lifetimeOptions struct {
Exp uint64
}
type epochDurations struct {
currentEpoch uint64
msPerBlock int64
blocksInEpoch uint64
}
type (
issuingResult struct {
AccessKeyID string `json:"access_key_id"`
@ -108,8 +140,7 @@ type (
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner *owner.ID) (*cid.ID, error) {
if opts.ID != nil {
// check that container exists
_, err := a.pool.GetContainer(ctx, opts.ID)
return opts.ID, err
return opts.ID, a.neoFS.ContainerExists(ctx, *opts.ID)
}
pp, err := policy.Parse(opts.PlacementPolicy)
@ -117,62 +148,18 @@ func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwn
return nil, fmt.Errorf("failed to build placement policy: %w", err)
}
cnrOptions := []container.Option{
container.WithPolicy(pp),
container.WithCustomBasicACL(defaultAuthContainerBasicACL),
container.WithAttribute(container.AttributeTimestamp, strconv.FormatInt(time.Now().Unix(), 10)),
container.WithOwnerID(idOwner),
}
if opts.FriendlyName != "" {
cnrOptions = append(cnrOptions, container.WithAttribute(container.AttributeName, opts.FriendlyName))
}
cnr := container.New(cnrOptions...)
if opts.FriendlyName != "" {
container.SetNativeName(cnr, opts.FriendlyName)
}
cnrID, err := a.pool.PutContainer(ctx, cnr)
cnrID, err := a.neoFS.CreateContainer(ctx, PrmContainerCreate{
Owner: *idOwner,
Policy: *pp,
FriendlyName: opts.FriendlyName,
})
if err != nil {
return nil, err
}
if err := a.pool.WaitForContainerPresence(ctx, cnrID, pool.DefaultPollingParams()); err != nil {
return nil, err
}
return cnrID, nil
}
func (a *Agent) getEpochDurations(ctx context.Context) (*epochDurations, error) {
if conn, _, err := a.pool.Connection(); err != nil {
return nil, err
} else if networkInfoRes, err := conn.NetworkInfo(ctx, client.PrmNetworkInfo{}); err != nil {
return nil, err
} else if err = apistatus.ErrFromStatus(networkInfoRes.Status()); err != nil {
return nil, err
} else {
networkInfo := networkInfoRes.Info()
res := &epochDurations{
currentEpoch: networkInfo.CurrentEpoch(),
msPerBlock: networkInfo.MsPerBlock(),
}
networkInfo.NetworkConfig().IterateParameters(func(parameter *netmap.NetworkParameter) bool {
if string(parameter.Key()) == "EpochDuration" {
data := make([]byte, 8)
copy(data, parameter.Value())
res.blocksInEpoch = binary.LittleEndian.Uint64(data)
return true
}
return false
})
if res.blocksInEpoch == 0 {
return nil, fmt.Errorf("not found param: EpochDuration")
}
return res, nil
}
}
func checkPolicy(policyString string) (*netmap.PlacementPolicy, error) {
result, err := policy.Parse(policyString)
if err == nil {
@ -226,12 +213,12 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
return err
}
durations, err := a.getEpochDurations(ctx)
netState, err := a.neoFS.NetworkState(ctx)
if err != nil {
return err
}
lifetime.Iat = durations.currentEpoch
msPerEpoch := durations.blocksInEpoch * uint64(durations.msPerBlock)
lifetime.Iat = netState.Epoch
msPerEpoch := netState.EpochDuration * uint64(netState.BlockDuration)
epochLifetime := uint64(options.Lifetime.Milliseconds()) / msPerEpoch
if uint64(options.Lifetime.Milliseconds())%msPerEpoch != 0 {
epochLifetime++
@ -268,7 +255,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
zap.Stringer("owner_tkn", idOwner))
addr, err := tokens.
New(a.pool, secrets.EphemeralKey, cache.DefaultAccessBoxConfig()).
New(a.neoFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig()).
Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
if err != nil {
return fmt.Errorf("failed to put bearer token: %w", err)
@ -310,7 +297,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 := tokens.New(a.pool, options.GatePrivateKey, cache.DefaultAccessBoxConfig())
bearerCreds := tokens.New(a.neoFS, options.GatePrivateKey, cache.DefaultAccessBoxConfig())
addr := address.NewAddress()
if err := addr.Parse(options.SecretAddress); err != nil {
return fmt.Errorf("failed to parse secret address: %w", err)