Nikita Zinkevich
8f7ccb0f62
All checks were successful
/ DCO (pull_request) Successful in 2m11s
/ Vulncheck (pull_request) Successful in 2m50s
/ Builds (pull_request) Successful in 2m23s
/ Lint (pull_request) Successful in 3m42s
/ Tests (pull_request) Successful in 2m26s
/ Vulncheck (push) Successful in 4m19s
/ Lint (push) Successful in 2m58s
/ Tests (push) Successful in 2m50s
/ Builds (push) Successful in 4m40s
Signed-off-by: Nikita Zinkevich <n.zinkevich@yadro.com>
259 lines
7.6 KiB
Go
259 lines
7.6 KiB
Go
package frostfs
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/hex"
|
|
"fmt"
|
|
"io"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/frostfs"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/frostfs/crdt"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
objectv2 "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/api/object"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/pkg/chain"
|
|
"git.frostfs.info/TrueCloudLab/policy-engine/schema/native"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
accessBoxCRDTNameAttr = "S3-Access-Box-CRDT-Name"
|
|
)
|
|
|
|
// AuthmateFrostFS is a mediator which implements authmate.FrostFS through pool.Pool.
|
|
type AuthmateFrostFS struct {
|
|
frostFS frostfs.FrostFS
|
|
log *zap.Logger
|
|
}
|
|
|
|
// NewAuthmateFrostFS creates new AuthmateFrostFS using provided pool.Pool.
|
|
func NewAuthmateFrostFS(frostFS frostfs.FrostFS, log *zap.Logger) *AuthmateFrostFS {
|
|
return &AuthmateFrostFS{frostFS: frostFS, log: log}
|
|
}
|
|
|
|
// ContainerExists implements authmate.FrostFS interface method.
|
|
func (x *AuthmateFrostFS) ContainerExists(ctx context.Context, idCnr cid.ID) error {
|
|
_, err := x.frostFS.Container(ctx, frostfs.PrmContainer{ContainerID: idCnr})
|
|
if err != nil {
|
|
return fmt.Errorf("get container via connection pool: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// TimeToEpoch implements authmate.FrostFS interface method.
|
|
func (x *AuthmateFrostFS) TimeToEpoch(ctx context.Context, futureTime time.Time) (uint64, uint64, error) {
|
|
return x.frostFS.TimeToEpoch(ctx, time.Now(), futureTime)
|
|
}
|
|
|
|
// CreateContainer implements authmate.FrostFS interface method.
|
|
func (x *AuthmateFrostFS) CreateContainer(ctx context.Context, prm authmate.PrmContainerCreate) (cid.ID, error) {
|
|
var owner user.ID
|
|
owner.SetScriptHash(prm.Owner.GetScriptHash())
|
|
|
|
res, err := x.frostFS.CreateContainer(ctx, frostfs.PrmContainerCreate{
|
|
Creator: owner,
|
|
Policy: prm.Policy,
|
|
Name: prm.FriendlyName,
|
|
})
|
|
if err != nil {
|
|
return cid.ID{}, err
|
|
}
|
|
|
|
ch := chain.Chain{
|
|
ID: chain.ID("authmate/" + owner.String()),
|
|
Rules: []chain.Rule{
|
|
{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Names: []string{"*"}},
|
|
Resources: chain.Resources{Names: []string{
|
|
fmt.Sprintf(native.ResourceFormatRootContainer, res.ContainerID),
|
|
fmt.Sprintf(native.ResourceFormatRootContainerObjects, res.ContainerID),
|
|
}},
|
|
Condition: []chain.Condition{{
|
|
Op: chain.CondStringEquals,
|
|
Kind: chain.KindRequest,
|
|
Key: native.PropertyKeyActorPublicKey,
|
|
Value: hex.EncodeToString(prm.Owner.Bytes()),
|
|
}},
|
|
},
|
|
{
|
|
Status: chain.Allow,
|
|
Actions: chain.Actions{Names: []string{
|
|
native.MethodGetContainer,
|
|
native.MethodGetObject,
|
|
native.MethodHeadObject,
|
|
native.MethodSearchObject,
|
|
native.MethodRangeObject,
|
|
native.MethodHashObject,
|
|
}},
|
|
Resources: chain.Resources{Names: []string{
|
|
fmt.Sprintf(native.ResourceFormatRootContainer, res.ContainerID),
|
|
fmt.Sprintf(native.ResourceFormatRootContainerObjects, res.ContainerID),
|
|
}},
|
|
},
|
|
},
|
|
}
|
|
|
|
err = x.frostFS.AddContainerPolicyChain(ctx, frostfs.PrmAddContainerPolicyChain{
|
|
ContainerID: res.ContainerID,
|
|
Chain: ch,
|
|
})
|
|
if err != nil {
|
|
return cid.ID{}, err
|
|
}
|
|
|
|
return res.ContainerID, nil
|
|
}
|
|
|
|
// GetCredsObject implements authmate.FrostFS interface method.
|
|
func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, prm tokens.PrmGetCredsObject) (obj *object.Object, err error) {
|
|
var readObjAddr *oid.Address
|
|
defer func() {
|
|
if prm.FallbackAddress == nil {
|
|
return
|
|
}
|
|
if err != nil && (readObjAddr == nil || !readObjAddr.Equals(*prm.FallbackAddress)) {
|
|
obj, err = x.readObject(ctx, *prm.FallbackAddress)
|
|
}
|
|
}()
|
|
|
|
versions, err := x.getCredVersions(ctx, prm.Container, prm.AccessKeyID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var addr oid.Address
|
|
isCustom := addr.DecodeString(strings.ReplaceAll(prm.AccessKeyID, "0", "/")) != nil
|
|
|
|
var credObjID oid.ID
|
|
if last := versions.GetLast(); last != nil {
|
|
credObjID = last.ObjID
|
|
} else if !isCustom {
|
|
credObjID = addr.Object()
|
|
} else {
|
|
return nil, fmt.Errorf("%w: '%s'", tokens.ErrCustomAccessKeyIDNotFound, prm.AccessKeyID)
|
|
}
|
|
|
|
readObjAddr = &oid.Address{}
|
|
readObjAddr.SetContainer(prm.Container)
|
|
readObjAddr.SetObject(credObjID)
|
|
|
|
return x.readObject(ctx, *readObjAddr)
|
|
}
|
|
|
|
func (x *AuthmateFrostFS) readObject(ctx context.Context, addr oid.Address) (*object.Object, error) {
|
|
res, err := x.frostFS.GetObject(ctx, frostfs.PrmObjectGet{
|
|
Container: addr.Container(),
|
|
Object: addr.Object(),
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if closeErr := res.Payload.Close(); closeErr != nil {
|
|
x.reqLogger(ctx).Warn(logs.CloseCredsObjectPayload, zap.Error(closeErr))
|
|
}
|
|
}()
|
|
|
|
data, err := io.ReadAll(res.Payload)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
res.Header.SetPayload(data)
|
|
|
|
return &res.Header, err
|
|
}
|
|
|
|
// CreateObject implements authmate.FrostFS interface method.
|
|
func (x *AuthmateFrostFS) CreateObject(ctx context.Context, prm tokens.PrmObjectCreate) (oid.ID, error) {
|
|
attributes := [][2]string{{objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)}}
|
|
|
|
if prm.NewVersionForAccessKeyID != "" {
|
|
versions, err := x.getCredVersions(ctx, prm.Container, prm.NewVersionForAccessKeyID)
|
|
if err != nil {
|
|
return oid.ID{}, err
|
|
}
|
|
|
|
if versions.GetLast() == nil {
|
|
var addr oid.Address
|
|
isCustom := addr.DecodeString(strings.ReplaceAll(prm.NewVersionForAccessKeyID, "0", "/")) != nil
|
|
|
|
if isCustom {
|
|
return oid.ID{}, fmt.Errorf("creds object for accessKeyId '%s' not found", prm.NewVersionForAccessKeyID)
|
|
}
|
|
|
|
versions.AppendVersion(&crdt.ObjectVersion{ObjID: addr.Object()})
|
|
}
|
|
|
|
for key, val := range versions.GetCRDTHeaders() {
|
|
attributes = append(attributes, [2]string{key, val})
|
|
}
|
|
|
|
attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, versions.Name()})
|
|
} else if prm.CustomAccessKey != "" {
|
|
attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, prm.CustomAccessKey})
|
|
}
|
|
|
|
for _, attr := range prm.CustomAttributes {
|
|
// we don't check attribute duplication since storage node does this
|
|
attributes = append(attributes, [2]string{attr.Key(), attr.Value()})
|
|
}
|
|
|
|
res, err := x.frostFS.CreateObject(ctx, frostfs.PrmObjectCreate{
|
|
Container: prm.Container,
|
|
Filepath: prm.Filepath,
|
|
Attributes: attributes,
|
|
Payload: bytes.NewReader(prm.Payload),
|
|
})
|
|
if err != nil {
|
|
return oid.ID{}, err
|
|
}
|
|
|
|
return res.ObjectID, nil
|
|
}
|
|
|
|
func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, cnrID cid.ID, accessKeyID string) (*crdt.ObjectVersions, error) {
|
|
credVersions, err := x.frostFS.SearchObjects(ctx, frostfs.PrmObjectSearch{
|
|
Container: cnrID,
|
|
ExactAttribute: [2]string{accessBoxCRDTNameAttr, accessKeyID},
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("search s3 access boxes: %w", err)
|
|
}
|
|
|
|
versions := crdt.NewObjectVersions(accessKeyID)
|
|
|
|
for _, id := range credVersions {
|
|
objVersion, err := x.frostFS.HeadObject(ctx, frostfs.PrmObjectHead{
|
|
Container: cnrID,
|
|
Object: id,
|
|
})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("head crdt access box '%s': %w", id.EncodeToString(), err)
|
|
}
|
|
|
|
versions.AppendVersion(crdt.NewObjectVersion(objVersion))
|
|
}
|
|
|
|
return versions, nil
|
|
}
|
|
|
|
func (x *AuthmateFrostFS) reqLogger(ctx context.Context) *zap.Logger {
|
|
reqLogger := middleware.GetReqLog(ctx)
|
|
if reqLogger != nil {
|
|
return reqLogger
|
|
}
|
|
return x.log
|
|
}
|