frostfs-s3-gw/internal/frostfs/authmate.go

191 lines
5.6 KiB
Go
Raw Permalink Normal View History

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/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"
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/acl"
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"
"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 layer.FrostFS
log *zap.Logger
}
// NewAuthmateFrostFS creates new AuthmateFrostFS using provided pool.Pool.
func NewAuthmateFrostFS(frostFS layer.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, layer.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) {
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
}
// GetCredsObject implements authmate.FrostFS interface method.
func (x *AuthmateFrostFS) GetCredsObject(ctx context.Context, addr oid.Address) (*object.Object, error) {
versions, err := x.getCredVersions(ctx, addr)
if err != nil {
return nil, err
}
credObjID := addr.Object()
if last := versions.GetLast(); last != nil {
credObjID = last.ObjID
}
res, err := x.frostFS.GetObject(ctx, layer.PrmObjectGet{
Container: addr.Container(),
Object: credObjID,
})
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.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{ObjID: 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()})
}
res, err := x.frostFS.CreateObject(ctx, layer.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, 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.HeadObject(ctx, layer.PrmObjectHead{
Container: addr.Container(),
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
}
func credVersionSysName(cnrID cid.ID, objID oid.ID) string {
return cnrID.EncodeToString() + "0" + objID.EncodeToString()
}