frostfs-s3-gw/creds/tokens/credentials.go

164 lines
5.0 KiB
Go

package tokens
import (
"context"
"errors"
"fmt"
"strconv"
"time"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
)
type (
// Credentials is a bearer token get/put interface.
Credentials interface {
GetBox(context.Context, oid.Address) (*accessbox.Box, error)
Put(context.Context, cid.ID, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error)
Update(context.Context, oid.Address, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error)
}
cred struct {
key *keys.PrivateKey
frostFS FrostFS
cache *cache.AccessBoxCache
}
)
// PrmObjectCreate groups parameters of objects created by credential tool.
type PrmObjectCreate struct {
// FrostFS identifier of the object creator.
Creator user.ID
// FrostFS container to store the object.
Container cid.ID
// File path.
Filepath string
// Optional.
// If provided cred object will be created using crdt approach.
NewVersionFor *oid.ID
// Last FrostFS epoch of the object lifetime.
ExpirationEpoch uint64
// Object payload.
Payload []byte
}
// FrostFS represents virtual connection to FrostFS network.
type FrostFS interface {
// CreateObject creates and saves a parameterized object in the specified
// FrostFS container from a specific user. It sets 'Timestamp' attribute to the current time.
// It returns the ID of the saved object.
//
// It returns exactly one non-nil value. It returns any error encountered which
// prevented the object from being created.
CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
// GetCredsPayload gets payload of the credential object from FrostFS network.
// It uses search by system name and select using CRDT 2PSet. In case of absence CRDT header
// it heads object by address.
//
// It returns exactly one non-nil value. It returns any error encountered which
// prevented the object payload from being read.
GetCredsPayload(context.Context, oid.Address) ([]byte, error)
}
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 _ = New
// New creates a new Credentials instance using the given cli and key.
func New(frostFS FrostFS, key *keys.PrivateKey, config *cache.Config) Credentials {
return &cred{frostFS: frostFS, key: key, cache: cache.NewAccessBoxCache(config)}
}
func (c *cred) GetBox(ctx context.Context, addr oid.Address) (*accessbox.Box, error) {
cachedBox := c.cache.Get(addr)
if cachedBox != nil {
return cachedBox, nil
}
box, err := c.getAccessBox(ctx, addr)
if err != nil {
return nil, fmt.Errorf("get access box: %w", err)
}
cachedBox, err = box.GetBox(c.key)
if err != nil {
return nil, fmt.Errorf("get box: %w", err)
}
if err = c.cache.Put(addr, cachedBox); err != nil {
return nil, fmt.Errorf("put box into cache: %w", err)
}
return cachedBox, nil
}
func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.AccessBox, error) {
data, err := c.frostFS.GetCredsPayload(ctx, addr)
if err != nil {
return nil, fmt.Errorf("read payload: %w", err)
}
// decode access box
var box accessbox.AccessBox
if err = box.Unmarshal(data); err != nil {
return nil, fmt.Errorf("unmarhal access box: %w", err)
}
return &box, nil
}
func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
return c.createObject(ctx, idCnr, nil, issuer, box, expiration, keys...)
}
func (c *cred) Update(ctx context.Context, addr oid.Address, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
objID := addr.Object()
return c.createObject(ctx, addr.Container(), &objID, issuer, box, expiration, keys...)
}
func (c *cred) createObject(ctx context.Context, cnrID cid.ID, newVersionFor *oid.ID, issuer user.ID, box *accessbox.AccessBox, expiration uint64, keys ...*keys.PublicKey) (oid.Address, error) {
if len(keys) == 0 {
return oid.Address{}, ErrEmptyPublicKeys
} else if box == nil {
return oid.Address{}, ErrEmptyBearerToken
}
data, err := box.Marshal()
if err != nil {
return oid.Address{}, fmt.Errorf("marshall box: %w", err)
}
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
Creator: issuer,
Container: cnrID,
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
ExpirationEpoch: expiration,
NewVersionFor: newVersionFor,
Payload: data,
})
if err != nil {
return oid.Address{}, fmt.Errorf("create object: %w", err)
}
var addr oid.Address
addr.SetObject(idObj)
addr.SetContainer(cnrID)
return addr, nil
}