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
|
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) {
|
func TestCheckSign(t *testing.T) {
|
||||||
var accessKeyAddr oid.Address
|
var accessKeyAddr oid.Address
|
||||||
err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
|
err := accessKeyAddr.DecodeString("8N7CYBY74kxZXoyvA5UNdmovaXqFpwNfvEPsqaN81es2/3tDwq5tR8fByrJcyJwyiuYX7Dae8tyDT7pd8oaL1MBto")
|
||||||
|
|
|
@ -105,6 +105,7 @@ type (
|
||||||
Lifetime time.Duration
|
Lifetime time.Duration
|
||||||
AwsCliCredentialsFile string
|
AwsCliCredentialsFile string
|
||||||
ContainerPolicies ContainerPolicies
|
ContainerPolicies ContainerPolicies
|
||||||
|
UpdateCreds *UpdateOptions
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContainerOptions groups parameters of auth container to put the secret into.
|
// ContainerOptions groups parameters of auth container to put the secret into.
|
||||||
|
@ -114,6 +115,12 @@ type (
|
||||||
PlacementPolicy string
|
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 contains options for passing to Agent.ObtainSecret method.
|
||||||
ObtainSecretOptions struct {
|
ObtainSecretOptions struct {
|
||||||
SecretAddress string
|
SecretAddress string
|
||||||
|
@ -130,6 +137,7 @@ type lifetimeOptions struct {
|
||||||
type (
|
type (
|
||||||
issuingResult struct {
|
issuingResult struct {
|
||||||
AccessKeyID string `json:"access_key_id"`
|
AccessKeyID string `json:"access_key_id"`
|
||||||
|
InitialAccessKeyID string `json:"initial_access_key_id"`
|
||||||
SecretAccessKey string `json:"secret_access_key"`
|
SecretAccessKey string `json:"secret_access_key"`
|
||||||
OwnerPrivateKey string `json:"owner_private_key"`
|
OwnerPrivateKey string `json:"owner_private_key"`
|
||||||
WalletPublicKey string `json:"wallet_public_key"`
|
WalletPublicKey string `json:"wallet_public_key"`
|
||||||
|
@ -144,9 +152,14 @@ type (
|
||||||
|
|
||||||
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner user.ID) (cid.ID, error) {
|
func (a *Agent) checkContainer(ctx context.Context, opts ContainerOptions, idOwner user.ID) (cid.ID, error) {
|
||||||
if !opts.ID.Equals(cid.ID{}) {
|
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)
|
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
|
var prm PrmContainerCreate
|
||||||
|
|
||||||
err := prm.Policy.DecodeString(opts.PlacementPolicy)
|
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)
|
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 {
|
if err != nil {
|
||||||
return fmt.Errorf("pack tokens: %w", err)
|
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
|
var idOwner user.ID
|
||||||
user.IDFromKey(&idOwner, options.FrostFSKey.PrivateKey.PublicKey)
|
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)
|
id, err := a.checkContainer(ctx, options.Container, idOwner)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("check container: %w", err)
|
return fmt.Errorf("check container: %w", err)
|
||||||
|
@ -245,20 +259,26 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
||||||
a.log.Info("store bearer token into FrostFS",
|
a.log.Info("store bearer token into FrostFS",
|
||||||
zap.Stringer("owner_tkn", idOwner))
|
zap.Stringer("owner_tkn", idOwner))
|
||||||
|
|
||||||
addr, err := tokens.
|
creds := tokens.New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log))
|
||||||
New(a.frostFS, secrets.EphemeralKey, cache.DefaultAccessBoxConfig(a.log)).
|
|
||||||
Put(ctx, id, idOwner, box, lifetime.Exp, options.GatesPublicKeys...)
|
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 {
|
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()
|
accessKeyID := addr.Container().EncodeToString() + "0" + addr.Object().EncodeToString()
|
||||||
strIDObj := objID.EncodeToString()
|
|
||||||
|
|
||||||
accessKeyID := addr.Container().EncodeToString() + "0" + strIDObj
|
|
||||||
|
|
||||||
ir := &issuingResult{
|
ir := &issuingResult{
|
||||||
AccessKeyID: accessKeyID,
|
AccessKeyID: accessKeyID,
|
||||||
|
InitialAccessKeyID: oldAddr.Container().EncodeToString() + "0" + oldAddr.Object().EncodeToString(),
|
||||||
SecretAccessKey: secrets.AccessKey,
|
SecretAccessKey: secrets.AccessKey,
|
||||||
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
OwnerPrivateKey: hex.EncodeToString(secrets.EphemeralKey.Bytes()),
|
||||||
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
WalletPublicKey: hex.EncodeToString(options.FrostFSKey.PublicKey().Bytes()),
|
||||||
|
@ -272,7 +292,7 @@ func (a *Agent) IssueSecret(ctx context.Context, w io.Writer, options *IssueSecr
|
||||||
}
|
}
|
||||||
|
|
||||||
if options.AwsCliCredentialsFile != "" {
|
if options.AwsCliCredentialsFile != "" {
|
||||||
profileName := "authmate_cred_" + strIDObj
|
profileName := "authmate_cred_" + addr.Object().EncodeToString()
|
||||||
if _, err = os.Stat(options.AwsCliCredentialsFile); os.IsNotExist(err) {
|
if _, err = os.Stat(options.AwsCliCredentialsFile); os.IsNotExist(err) {
|
||||||
profileName = "default"
|
profileName = "default"
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"crypto/ecdsa"
|
"crypto/ecdsa"
|
||||||
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
@ -19,6 +20,7 @@ import (
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/version"
|
||||||
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/wallet"
|
||||||
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
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"
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/pool"
|
||||||
"github.com/aws/aws-sdk-go/aws"
|
"github.com/aws/aws-sdk-go/aws"
|
||||||
"github.com/aws/aws-sdk-go/aws/credentials"
|
"github.com/aws/aws-sdk-go/aws/credentials"
|
||||||
|
@ -88,6 +90,7 @@ var (
|
||||||
const (
|
const (
|
||||||
envWalletPassphrase = "wallet.passphrase"
|
envWalletPassphrase = "wallet.passphrase"
|
||||||
envWalletGatePassphrase = "wallet.gate.passphrase"
|
envWalletGatePassphrase = "wallet.gate.passphrase"
|
||||||
|
envSecretAccessKey = "secret.access.key"
|
||||||
)
|
)
|
||||||
|
|
||||||
var zapConfig = zap.Config{
|
var zapConfig = zap.Config{
|
||||||
|
@ -229,6 +232,12 @@ func issueSecret() *cli.Command {
|
||||||
Required: false,
|
Required: false,
|
||||||
Destination: &containerIDFlag,
|
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{
|
&cli.StringFlag{
|
||||||
Name: "container-friendly-name",
|
Name: "container-friendly-name",
|
||||||
Usage: "friendly name of auth container to put the secret into",
|
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
|
var gatesPublicKeys []*keys.PublicKey
|
||||||
for _, key := range gatesPublicKeysFlag.Value() {
|
for _, key := range gatesPublicKeysFlag.Value() {
|
||||||
gpk, err := keys.NewPublicKeyFromString(key)
|
gpk, err := keys.NewPublicKeyFromString(key)
|
||||||
|
@ -380,6 +415,7 @@ It will be ceil rounded to the nearest amount of epoch.`,
|
||||||
ContainerPolicies: policies,
|
ContainerPolicies: policies,
|
||||||
Lifetime: lifetimeFlag,
|
Lifetime: lifetimeFlag,
|
||||||
AwsCliCredentialsFile: awcCliCredFile,
|
AwsCliCredentialsFile: awcCliCredFile,
|
||||||
|
UpdateCreds: credsToUpdate,
|
||||||
}
|
}
|
||||||
|
|
||||||
var tcancel context.CancelFunc
|
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.
|
// PackTokens adds bearer and session tokens to BearerTokens and SessionToken lists respectively.
|
||||||
// Session token can be nil.
|
// 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{}
|
box := &AccessBox{}
|
||||||
ephemeralKey, err := keys.NewPrivateKey()
|
ephemeralKey, err := keys.NewPrivateKey()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -103,10 +104,12 @@ func PackTokens(gatesData []*GateData) (*AccessBox, *Secrets, error) {
|
||||||
}
|
}
|
||||||
box.OwnerPublicKey = ephemeralKey.PublicKey().Bytes()
|
box.OwnerPublicKey = ephemeralKey.PublicKey().Bytes()
|
||||||
|
|
||||||
secret, err := generateSecret()
|
if secret == nil {
|
||||||
|
secret, err = generateSecret()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
|
return nil, nil, fmt.Errorf("failed to generate accessKey as hex: %w", err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if err := box.addTokens(gatesData, ephemeralKey, secret); err != nil {
|
if err := box.addTokens(gatesData, ephemeralKey, secret); err != nil {
|
||||||
return nil, nil, fmt.Errorf("failed to add tokens to accessbox: %w", err)
|
return nil, nil, fmt.Errorf("failed to add tokens to accessbox: %w", err)
|
||||||
|
|
|
@ -60,7 +60,7 @@ func TestBearerTokenInAccessBox(t *testing.T) {
|
||||||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||||
|
|
||||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||||
box, _, err = PackTokens([]*GateData{gate})
|
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := box.Marshal()
|
data, err := box.Marshal()
|
||||||
|
@ -95,7 +95,7 @@ func TestSessionTokenInAccessBox(t *testing.T) {
|
||||||
var newTkn bearer.Token
|
var newTkn bearer.Token
|
||||||
gate := NewGateData(cred.PublicKey(), &newTkn)
|
gate := NewGateData(cred.PublicKey(), &newTkn)
|
||||||
gate.SessionTokens = []*session.Container{tkn}
|
gate.SessionTokens = []*session.Container{tkn}
|
||||||
box, _, err = PackTokens([]*GateData{gate})
|
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
data, err := box.Marshal()
|
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)
|
require.NoError(t, err)
|
||||||
|
|
||||||
for i, k := range privateKeys {
|
for i, k := range privateKeys {
|
||||||
|
@ -164,7 +164,7 @@ func TestUnknownKey(t *testing.T) {
|
||||||
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
require.NoError(t, tkn.Sign(sec.PrivateKey))
|
||||||
|
|
||||||
gate := NewGateData(cred.PublicKey(), &tkn)
|
gate := NewGateData(cred.PublicKey(), &tkn)
|
||||||
box, _, err = PackTokens([]*GateData{gate})
|
box, _, err = PackTokens([]*GateData{gate}, nil)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
_, err = box.GetTokens(wrongCred)
|
_, err = box.GetTokens(wrongCred)
|
||||||
|
|
|
@ -20,6 +20,7 @@ type (
|
||||||
Credentials interface {
|
Credentials interface {
|
||||||
GetBox(context.Context, oid.Address) (*accessbox.Box, error)
|
GetBox(context.Context, oid.Address) (*accessbox.Box, error)
|
||||||
Put(context.Context, cid.ID, user.ID, *accessbox.AccessBox, uint64, ...*keys.PublicKey) (oid.Address, 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 {
|
cred struct {
|
||||||
|
@ -40,6 +41,10 @@ type PrmObjectCreate struct {
|
||||||
// File path.
|
// File path.
|
||||||
Filepath string
|
Filepath string
|
||||||
|
|
||||||
|
// Optional.
|
||||||
|
// If provided cred object will be created using crdt approach.
|
||||||
|
NewVersionFor *oid.ID
|
||||||
|
|
||||||
// Last FrostFS epoch of the object lifetime.
|
// Last FrostFS epoch of the object lifetime.
|
||||||
ExpirationEpoch uint64
|
ExpirationEpoch uint64
|
||||||
|
|
||||||
|
@ -57,12 +62,13 @@ type FrostFS interface {
|
||||||
// prevented the object from being created.
|
// prevented the object from being created.
|
||||||
CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
|
CreateObject(context.Context, PrmObjectCreate) (oid.ID, error)
|
||||||
|
|
||||||
// ReadObjectPayload reads payload of the object from FrostFS network by address
|
// GetCredsPayload gets payload of the credential object from FrostFS network.
|
||||||
// into memory.
|
// 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
|
// It returns exactly one non-nil value. It returns any error encountered which
|
||||||
// prevented the object payload from being read.
|
// prevented the object payload from being read.
|
||||||
ReadObjectPayload(context.Context, oid.Address) ([]byte, error)
|
GetCredsPayload(context.Context, oid.Address) ([]byte, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
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) {
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("read payload: %w", err)
|
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) {
|
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 {
|
if len(keys) == 0 {
|
||||||
return oid.Address{}, ErrEmptyPublicKeys
|
return oid.Address{}, ErrEmptyPublicKeys
|
||||||
} else if box == nil {
|
} 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{
|
idObj, err := c.frostFS.CreateObject(ctx, PrmObjectCreate{
|
||||||
Creator: issuer,
|
Creator: issuer,
|
||||||
Container: idCnr,
|
Container: cnrID,
|
||||||
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
Filepath: strconv.FormatInt(time.Now().Unix(), 10) + "_access.box",
|
||||||
ExpirationEpoch: expiration,
|
ExpirationEpoch: expiration,
|
||||||
|
NewVersionFor: newVersionFor,
|
||||||
Payload: data,
|
Payload: data,
|
||||||
})
|
})
|
||||||
if err != nil {
|
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
|
var addr oid.Address
|
||||||
addr.SetObject(idObj)
|
addr.SetObject(idObj)
|
||||||
addr.SetContainer(idCnr)
|
addr.SetContainer(cnrID)
|
||||||
|
|
||||||
return addr, nil
|
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