package frostfs import ( "bytes" "context" "fmt" "io" "strconv" "time" objectv2 "git.frostfs.info/TrueCloudLab/frostfs-api-go/v2/object" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer" "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-sdk-go/container/acl" 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/pool" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" ) const ( accessBoxCRDTNameAttr = "S3-Access-Box-CRDT-Name" ) // AuthmateFrostFS is a mediator which implements authmate.FrostFS through pool.Pool. type AuthmateFrostFS struct { frostFS *FrostFS } // NewAuthmateFrostFS creates new AuthmateFrostFS using provided pool.Pool. func NewAuthmateFrostFS(p *pool.Pool, key *keys.PrivateKey) *AuthmateFrostFS { return &AuthmateFrostFS{frostFS: NewFrostFS(p, key)} } // ContainerExists implements authmate.FrostFS interface method. func (x *AuthmateFrostFS) ContainerExists(ctx context.Context, idCnr cid.ID) error { _, err := x.frostFS.Container(ctx, 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) { basicACL := acl.Private // allow reading objects to OTHERS in order to provide read access to S3 gateways basicACL.AllowOp(acl.OpObjectGet, acl.RoleOthers) basicACL.AllowOp(acl.OpObjectHead, acl.RoleOthers) basicACL.AllowOp(acl.OpObjectSearch, acl.RoleOthers) res, err := x.frostFS.CreateContainer(ctx, layer.PrmContainerCreate{ Creator: prm.Owner, Policy: prm.Policy, Name: prm.FriendlyName, BasicACL: basicACL, }) if err != nil { return cid.ID{}, err } return res.ContainerID, nil } // GetCredsPayload implements authmate.FrostFS interface method. func (x *AuthmateFrostFS) GetCredsPayload(ctx context.Context, addr oid.Address) ([]byte, error) { versions, err := x.getCredVersions(ctx, addr) if err != nil { return nil, err } credObjID := addr.Object() if last := versions.GetLast(); last != nil { credObjID = last.OjbID } res, err := x.frostFS.ReadObject(ctx, layer.PrmObjectRead{ Container: addr.Container(), Object: credObjID, WithPayload: true, }) if err != nil { return nil, err } defer res.Payload.Close() return io.ReadAll(res.Payload) } // 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.NewVersionFor != nil { var addr oid.Address addr.SetContainer(prm.Container) addr.SetObject(*prm.NewVersionFor) versions, err := x.getCredVersions(ctx, addr) if err != nil { return oid.ID{}, err } if versions.GetLast() == nil { versions.AppendVersion(&crdt.ObjectVersion{OjbID: addr.Object()}) } for key, val := range versions.GetCRDTHeaders() { attributes = append(attributes, [2]string{key, val}) } attributes = append(attributes, [2]string{accessBoxCRDTNameAttr, versions.Name()}) } 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()}) } return x.frostFS.CreateObject(ctx, layer.PrmObjectCreate{ Container: prm.Container, Filepath: prm.Filepath, Attributes: attributes, Payload: bytes.NewReader(prm.Payload), }) } func (x *AuthmateFrostFS) getCredVersions(ctx context.Context, addr oid.Address) (*crdt.ObjectVersions, error) { objCredSystemName := credVersionSysName(addr.Container(), addr.Object()) credVersions, err := x.frostFS.SearchObjects(ctx, layer.PrmObjectSearch{ Container: addr.Container(), ExactAttribute: [2]string{accessBoxCRDTNameAttr, objCredSystemName}, }) if err != nil { return nil, fmt.Errorf("search s3 access boxes: %w", err) } versions := crdt.NewObjectVersions(objCredSystemName) for _, id := range credVersions { objVersion, err := x.frostFS.ReadObject(ctx, layer.PrmObjectRead{ Container: addr.Container(), Object: id, WithHeader: true, }) if err != nil { return nil, fmt.Errorf("head crdt access box '%s': %w", id.EncodeToString(), err) } versions.AppendVersion(crdt.NewObjectVersion(objVersion.Head)) } return versions, nil } func credVersionSysName(cnrID cid.ID, objID oid.ID) string { return cnrID.EncodeToString() + "0" + objID.EncodeToString() }