forked from TrueCloudLab/frostfs-s3-gw
[#135] authmate: Support CRDT GSet for credentials
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
This commit is contained in:
parent
7a380fa46c
commit
84358f6742
7 changed files with 274 additions and 38 deletions
|
@ -46,6 +46,10 @@ func (m credentialsMock) Put(context.Context, cid.ID, user.ID, *accessbox.Access
|
|||
return oid.Address{}, nil
|
||||
}
|
||||
|
||||
func (m credentialsMock) Update(context.Context, oid.Address, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, error) {
|
||||
return oid.Address{}, nil
|
||||
}
|
||||
|
||||
func TestCheckSign(t *testing.T) {
|
||||
var accessKeyAddr oid.Address
|
||||
err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
|
||||
|
|
|
@ -105,6 +105,7 @@ type (
|
|||
Lifetime time.Duration
|
||||
AwsCliCredentialsFile string
|
||||
ContainerPolicies ContainerPolicies
|
||||
UpdateCreds *UpdateOptions
|
||||
}
|
||||
|
||||
// ContainerOptions groups parameters of auth container to put the secret into.
|
||||
|
@ -114,6 +115,12 @@ type (
|
|||
PlacementPolicy string
|
||||
}
|
||||
|
||||
// UpdateOptions groups parameters to update existing the secret into.
|
||||
UpdateOptions struct {
|
||||
Address oid.Address
|
||||
SecretAccessKey []byte
|
||||
}
|
||||
|
||||
// ObtainSecretOptions contains options for passing to Agent.ObtainSecret method.
|
||||
ObtainSecretOptions struct {
|
||||
SecretAddress string
|
||||
|
@ -129,11 +136,12 @@ type lifetimeOptions struct {
|
|||
|
||||
type (
|
||||
issuingResult struct {
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
OwnerPrivateKey string `json:"owner_private_key"`
|
||||
WalletPublicKey string `json:"wallet_public_key"`
|
||||
ContainerID string `json:"container_id"`
|
||||
AccessKeyID string `json:"access_key_id"`
|
||||
InitialAccessKeyID string `json:"initial_access_key_id"`
|
||||
SecretAccessKey string `json:"secret_access_key"`
|
||||
OwnerPrivateKey string `json:"owner_private_key"`
|
||||
WalletPublicKey string `json:"wallet_public_key"`
|
||||
ContainerID string `json:"container_id"`
|
||||
}
|
||||
|
||||
obtainingResult struct {
|
||||
|
@ -144,9 +152,14 @@ type (
|
|||
|
||||
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner user.ID) (cid.ID, error) {
|
||||
if !opts.ID.Equals(cid.ID{}) {
|
||||
a.log.Info("check container", zap.Stringer("cid", opts.ID))
|
||||
return opts.ID, a.frostFS.ContainerExists(ctx, opts.ID)
|
||||
}
|
||||
|
||||
a.log.Info("create container",
|
||||
zap.String("friendly_name", opts.FriendlyName),
|
||||
zap.String("placement_policy", opts.PlacementPolicy))
|
||||
|
||||
var prm PrmContainerCreate
|
||||
|
||||
err := prm.Policy.DecodeString(opts.PlacementPolicy)
|
||||
|
@ -224,7 +237,12 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
|||
return fmt.Errorf("create tokens: %w", err)
|
||||
}
|
||||
|
||||
box, secrets, err := accessbox.PackTokens(gatesData)
|
||||
var secret []byte
|
||||
if options.UpdateCreds != nil {
|
||||
secret = options.UpdateCreds.SecretAccessKey
|
||||
}
|
||||
|
||||
box, secrets, err := accessbox.PackTokens(gatesData, secret)
|
||||
if err != nil {
|
||||
return fmt.Errorf("pack tokens: %w", err)
|
||||
}
|
||||
|
@ -233,10 +251,6 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
|||
|
||||
var idOwner user.ID
|
||||
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
||||
|
||||
a.log.Info("check container or create", zap.Stringer("cid", options.Container.ID),
|
||||
zap.String("friendly_name", options.Container.FriendlyName),
|
||||
zap.String("placement_policy", options.Container.PlacementPolicy))
|
||||
id, err := a.checkContainer(ctx, options.Container, idOwner)
|
||||
if err != nil {
|
||||
return fmt.Errorf("check container: %w", err)
|
||||
|
@ -245,24 +259,30 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
|||
a.log.Info("store bearer token into FrostFS",
|
||||
zap.Stringer("owner_tkn", idOwner))
|
||||
|
||||
addr, err := tokens.
|
||||
New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log)).
|
||||
Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
|
||||
creds := tokens.New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log))
|
||||
|
||||
var addr oid.Address
|
||||
var oldAddr oid.Address
|
||||
if options.UpdateCreds != nil {
|
||||
oldAddr = options.UpdateCreds.Address
|
||||
addr, err = creds.Update(ctx, oldAddr, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
|
||||
} else {
|
||||
addr, err = creds.Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
|
||||
oldAddr = addr
|
||||
}
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to put bearer token: %w", err)
|
||||
return fmt.Errorf("failed to put creds: %w", err)
|
||||
}
|
||||
|
||||
objID := addr.Object()
|
||||
strIDObj := objID.EncodeToString()
|
||||
|
||||
accessKeyID := addr.Container().EncodeToString() + "0" + strIDObj
|
||||
accessKeyID := addr.Container().EncodeToString() + "0" + addr.Object().EncodeToString()
|
||||
|
||||
ir := &issuingResult{
|
||||
AccessKeyID: accessKeyID,
|
||||
SecretAccessKey: secrets.AccessKey,
|
||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
||||
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
||||
ContainerID: id.EncodeToString(),
|
||||
AccessKeyID: accessKeyID,
|
||||
InitialAccessKeyID: oldAddr.Container().EncodeToString() + "0" + oldAddr.Object().EncodeToString(),
|
||||
SecretAccessKey: secrets.AccessKey,
|
||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
||||
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
||||
ContainerID: id.EncodeToString(),
|
||||
}
|
||||
|
||||
enc := json.NewEncoder(w)
|
||||
|
@ -272,7 +292,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
|||
}
|
||||
|
||||
if options.AwsCliCredentialsFile != "" {
|
||||
profileName := "authmate_cred_" + strIDObj
|
||||
profileName := "authmate_cred_" + addr.Object().EncodeToString()
|
||||
if _, err = os.Stat(options.AwsCliCredentialsFile); os.IsNotExist(err) {
|
||||
profileName = "default"
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package main
|
|||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
|
@ -19,6 +20,7 @@ import (
|
|||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||
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/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||
|
@ -88,6 +90,7 @@ var (
|
|||
const (
|
||||
envWalletPassphrase = "wallet.passphrase"
|
||||
envWalletGatePassphrase = "wallet.gate.passphrase"
|
||||
envSecretAccessKey = "secret.access.key"
|
||||
)
|
||||
|
||||
var zapConfig = zap.Config{
|
||||
|
@ -229,6 +232,12 @@ func issueSecret() *cli.Command {
|
|||
Required: false,
|
||||
Destination: &containerIDFlag,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "access-key-id",
|
||||
Usage: "access key id for s3 (use this flag to update existing creds, if this flag is provided '--container-id', '--container-friendly-name' and '--container-placement-policy' are ineffective)",
|
||||
Required: false,
|
||||
Destination: &accessKeyIDFlag,
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "container-friendly-name",
|
||||
Usage: "friendly name of auth container to put the secret into",
|
||||
|
@ -333,6 +342,32 @@ It will be ceil rounded to the nearest amount of epoch.`,
|
|||
}
|
||||
}
|
||||
|
||||
var credsToUpdate *authmate.UpdateOptions
|
||||
if len(accessKeyIDFlag) > 0 {
|
||||
secretAccessKeyStr := wallet.GetPassword(viper.GetViper(), envSecretAccessKey)
|
||||
if secretAccessKeyStr == nil {
|
||||
return fmt.Errorf("you must provide AUTHMATE_SECRET_ACCESS_KEY env to update existing creds")
|
||||
}
|
||||
|
||||
secretAccessKey, err := hex.DecodeString(*secretAccessKeyStr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("access key must be hex encoded")
|
||||
}
|
||||
|
||||
var addr oid.Address
|
||||
credAddr := strings.Replace(accessKeyIDFlag, "0", "/", 1)
|
||||
if err = addr.DecodeString(credAddr); err != nil {
|
||||
return fmt.Errorf("failed to parse creds address: %w", err)
|
||||
}
|
||||
// we can create new creds version only in the same container
|
||||
containerID = addr.Container()
|
||||
|
||||
credsToUpdate = &authmate.UpdateOptions{
|
||||
Address: addr,
|
||||
SecretAccessKey: secretAccessKey,
|
||||
}
|
||||
}
|
||||
|
||||
var gatesPublicKeys []*keys.PublicKey
|
||||
for _, key := range gatesPublicKeysFlag.Value() {
|
||||
gpk, err := keys.NewPublicKeyFromString(key)
|
||||
|
@ -380,6 +415,7 @@ It will be ceil rounded to the nearest amount of epoch.`,
|
|||
ContainerPolicies: policies,
|
||||
Lifetime: lifetimeFlag,
|
||||
AwsCliCredentialsFile: awcCliCredFile,
|
||||
UpdateCreds: credsToUpdate,
|
||||
}
|
||||
|
||||
var tcancel context.CancelFunc
|
||||
|
|
|
@ -95,7 +95,8 @@ func (x *AccessBox) Unmarshal(data []byte) error {
|
|||
|
||||
// PackTokens adds bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
||||
// Session token can be nil.
|
||||
func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
|
||||
// Secret can be nil. In such case secret will be generated.
|
||||
func PackTokens(gatesData []*GateData, secret []byte) (*AccessBox, *Secrets, error) {
|
||||
box := &AccessBox{}
|
||||
ephemeralKey, err := keys.NewPrivateKey()
|
||||
if err != nil {
|
||||
|
@ -103,9 +104,11 @@ func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
|
|||
}
|
||||
box.OwnerPublicKey = ephemeralKey.PublicKey().Bytes()
|
||||
|
||||
secret, err := generateSecret()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
|
||||
if secret == nil {
|
||||
secret, err = generateSecret()
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
if err := box.addTokens(gatesData, ephemeralKey, secret); err != nil {
|
||||
|
|
|
@ -60,7 +60,7 @@ func TestBearerTokenInAccessBox(t *testing.T) {
|
|||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||
|
||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||
box, _, err = PackTokens([]*GateData{gate})
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := box.Marshal()
|
||||
|
@ -95,7 +95,7 @@ func TestSessionTokenInAccessBox(t *testing.T) {
|
|||
var newTkn bearer.Token
|
||||
gate := NewGateData(cred.PublicKey(), &newTkn)
|
||||
gate.SessionTokens = []*session.Container{tkn}
|
||||
box, _, err = PackTokens([]*GateData{gate})
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
data, err := box.Marshal()
|
||||
|
@ -135,7 +135,7 @@ func TestAccessboxMultipleKeys(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
box, _, err = PackTokens(gates)
|
||||
box, _, err = PackTokens(gates, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
for i, k := range privateKeys {
|
||||
|
@ -164,7 +164,7 @@ func TestUnknownKey(t *testing.T) {
|
|||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||
|
||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||
box, _, err = PackTokens([]*GateData{gate})
|
||||
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = box.GetTokens(wrongCred)
|
||||
|
|
|
@ -20,6 +20,7 @@ type (
|
|||
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 {
|
||||
|
@ -40,6 +41,10 @@ type PrmObjectCreate struct {
|
|||
// 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
|
||||
|
||||
|
@ -57,12 +62,13 @@ type FrostFS interface {
|
|||
// prevented the object from being created.
|
||||
CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
|
||||
|
||||
// ReadObjectPayload reads payload of the object from FrostFS network by address
|
||||
// into memory.
|
||||
// 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.
|
||||
ReadObjectPayload(context.Context, oid.Address) ([]byte, error)
|
||||
GetCredsPayload(context.Context, oid.Address) ([]byte, error)
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -103,7 +109,7 @@ func (c *cred) GetBox(ctx context.Context, addr oid.Address) (*accessbox.Box, er
|
|||
}
|
||||
|
||||
func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.AccessBox, error) {
|
||||
data, err := c.frostFS.ReadObjectPayload(ctx, addr)
|
||||
data, err := c.frostFS.GetCredsPayload(ctx, addr)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read payload: %w", err)
|
||||
}
|
||||
|
@ -118,6 +124,15 @@ func (c *cred) getAccessBox(ctx context.Context, addr oid.Address) (*accessbox.A
|
|||
}
|
||||
|
||||
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 {
|
||||
|
@ -130,9 +145,10 @@ func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *acces
|
|||
|
||||
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
|
||||
Creator: issuer,
|
||||
Container: idCnr,
|
||||
Container: cnrID,
|
||||
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
||||
ExpirationEpoch: expiration,
|
||||
NewVersionFor: newVersionFor,
|
||||
Payload: data,
|
||||
})
|
||||
if err != nil {
|
||||
|
@ -141,7 +157,7 @@ func (c *cred) Put(ctx context.Context, idCnr cid.ID, issuer user.ID, box *acces
|
|||
|
||||
var addr oid.Address
|
||||
addr.SetObject(idObj)
|
||||
addr.SetContainer(idCnr)
|
||||
addr.SetContainer(cnrID)
|
||||
|
||||
return addr, nil
|
||||
}
|
||||
|
|
157
internal/frostfs/authmate.go
Normal file
157
internal/frostfs/authmate.go
Normal file
|
@ -0,0 +1,157 @@
|
|||
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"
|
||||
)
|
||||
|
||||
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) *AuthmateFrostFS {
|
||||
return &AuthmateFrostFS{frostFS: NewFrostFS(p)}
|
||||
}
|
||||
|
||||
// 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)
|
||||
|
||||
return x.frostFS.CreateContainer(ctx, layer.PrmContainerCreate{
|
||||
Creator: prm.Owner,
|
||||
Policy: prm.Policy,
|
||||
Name: prm.FriendlyName,
|
||||
BasicACL: basicACL,
|
||||
})
|
||||
}
|
||||
|
||||
// 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()})
|
||||
}
|
||||
|
||||
return x.frostFS.CreateObject(ctx, layer.PrmObjectCreate{
|
||||
Creator: prm.Creator,
|
||||
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()
|
||||
}
|
Loading…
Reference in a new issue