forked from TrueCloudLab/frostfs-s3-gw
464 lines
13 KiB
Go
464 lines
13 KiB
Go
package tokens
|
|
|
|
import (
|
|
"context"
|
|
"encoding/hex"
|
|
"errors"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/cache"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer"
|
|
apistatus "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/client/status"
|
|
cidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id/test"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
oidtest "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id/test"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-sdk-go/user"
|
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
|
"github.com/stretchr/testify/require"
|
|
"go.uber.org/zap/zaptest"
|
|
)
|
|
|
|
type frostfsMock struct {
|
|
key *keys.PrivateKey
|
|
objects map[string][]*object.Object
|
|
errors map[string]error
|
|
}
|
|
|
|
func newFrostfsMock(key *keys.PrivateKey) *frostfsMock {
|
|
return &frostfsMock{
|
|
objects: map[string][]*object.Object{},
|
|
errors: map[string]error{},
|
|
key: key,
|
|
}
|
|
}
|
|
|
|
func (f *frostfsMock) ownerID() user.ID {
|
|
if f.key == nil {
|
|
return user.ID{}
|
|
}
|
|
|
|
var ownerID user.ID
|
|
user.IDFromKey(&ownerID, f.key.PrivateKey.PublicKey)
|
|
return ownerID
|
|
}
|
|
|
|
func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.ID, error) {
|
|
var obj object.Object
|
|
obj.SetPayload(prm.Payload)
|
|
obj.SetOwnerID(f.ownerID())
|
|
obj.SetContainerID(prm.Container)
|
|
|
|
a := object.NewAttribute()
|
|
a.SetKey(object.AttributeFilePath)
|
|
a.SetValue(prm.Filepath)
|
|
prm.CustomAttributes = append(prm.CustomAttributes, *a)
|
|
obj.SetAttributes(prm.CustomAttributes...)
|
|
|
|
if prm.NewVersionForAccessKeyID != "" {
|
|
_, ok := f.objects[prm.NewVersionForAccessKeyID]
|
|
if !ok {
|
|
return oid.ID{}, errors.New("not found")
|
|
}
|
|
|
|
objID := oidtest.ID()
|
|
obj.SetID(objID)
|
|
f.objects[prm.NewVersionForAccessKeyID] = append(f.objects[prm.NewVersionForAccessKeyID], &obj)
|
|
|
|
return objID, nil
|
|
}
|
|
|
|
objID := oidtest.ID()
|
|
obj.SetID(objID)
|
|
|
|
accessKeyID := prm.CustomAccessKey
|
|
if accessKeyID == "" {
|
|
accessKeyID = prm.Container.EncodeToString() + "0" + objID.EncodeToString()
|
|
}
|
|
|
|
var addr oid.Address
|
|
addr.SetObject(objID)
|
|
addr.SetContainer(prm.Container)
|
|
f.objects[accessKeyID] = []*object.Object{&obj}
|
|
|
|
return objID, nil
|
|
}
|
|
|
|
func (f *frostfsMock) GetCredsObject(_ context.Context, prm PrmGetCredsObject) (*object.Object, error) {
|
|
if err := f.errors[prm.AccessKeyID]; err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
objects, ok := f.objects[prm.AccessKeyID]
|
|
if !ok {
|
|
return nil, ErrCustomAccessKeyIDNotFound
|
|
}
|
|
|
|
return objects[len(objects)-1], nil
|
|
}
|
|
|
|
func TestRemovingAccessBox(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
key, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
gateData := []*accessbox.GateData{{
|
|
BearerToken: &bearer.Token{},
|
|
GateKey: key.PublicKey(),
|
|
}}
|
|
|
|
secretKey := "713d0a0b9efc7d22923e17b0402a6a89b4273bc711c8bacb2da1b643d0006aeb"
|
|
sk, err := hex.DecodeString(secretKey)
|
|
require.NoError(t, err)
|
|
|
|
accessBox, _, err := accessbox.PackTokens(gateData, sk, false)
|
|
require.NoError(t, err)
|
|
data, err := accessBox.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
var obj object.Object
|
|
obj.SetPayload(data)
|
|
addr := oidtest.Address()
|
|
obj.SetID(addr.Object())
|
|
obj.SetContainerID(addr.Container())
|
|
|
|
accessKeyID := getAccessKeyID(addr)
|
|
|
|
accessBoxCustom, _, err := accessbox.PackTokens(gateData, []byte("secret"), true)
|
|
require.NoError(t, err)
|
|
dataCustom, err := accessBoxCustom.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
var objCustom object.Object
|
|
objCustom.SetPayload(dataCustom)
|
|
addrCustom := oidtest.Address()
|
|
objCustom.SetID(addrCustom.Object())
|
|
objCustom.SetContainerID(addrCustom.Container())
|
|
|
|
accessKeyIDCustom := "accessKeyID"
|
|
|
|
frostfs := &frostfsMock{
|
|
objects: map[string][]*object.Object{accessKeyID: {&obj}, accessKeyIDCustom: {&objCustom}},
|
|
errors: map[string]error{},
|
|
}
|
|
|
|
cfg := Config{
|
|
FrostFS: frostfs,
|
|
Key: key,
|
|
CacheConfig: &cache.Config{
|
|
Size: 10,
|
|
Lifetime: 24 * time.Hour,
|
|
Logger: zaptest.NewLogger(t),
|
|
},
|
|
RemovingCheckAfterDurations: 0, // means check always
|
|
}
|
|
|
|
creds := New(cfg)
|
|
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
|
require.NoError(t, err)
|
|
|
|
frostfs.errors[accessKeyID] = errors.New("network error")
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
frostfs.errors[accessKeyIDCustom] = errors.New("network error")
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
|
require.NoError(t, err)
|
|
|
|
frostfs.errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.Error(t, err)
|
|
frostfs.errors[accessKeyIDCustom] = &apistatus.ObjectAlreadyRemoved{}
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
|
require.Error(t, err)
|
|
|
|
frostfs.errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.Error(t, err)
|
|
frostfs.errors[accessKeyIDCustom] = &apistatus.ObjectAlreadyRemoved{}
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyIDCustom)
|
|
require.Error(t, err)
|
|
}
|
|
|
|
func TestGetBox(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
key, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
gateData := []*accessbox.GateData{{
|
|
BearerToken: &bearer.Token{},
|
|
GateKey: key.PublicKey(),
|
|
}}
|
|
|
|
secret := []byte("secret")
|
|
accessBox, secrets, err := accessbox.PackTokens(gateData, secret, false)
|
|
require.NoError(t, err)
|
|
require.Equal(t, hex.EncodeToString(secret), secrets.SecretKey)
|
|
data, err := accessBox.Marshal()
|
|
require.NoError(t, err)
|
|
|
|
var attr object.Attribute
|
|
attr.SetKey("key")
|
|
attr.SetValue("value")
|
|
attrs := []object.Attribute{attr}
|
|
|
|
cfg := Config{
|
|
CacheConfig: &cache.Config{
|
|
Size: 10,
|
|
Lifetime: 24 * time.Hour,
|
|
Logger: zaptest.NewLogger(t),
|
|
},
|
|
}
|
|
|
|
t.Run("no removing check, accessbox from cache", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, time.Hour)
|
|
|
|
cnrID := cidtest.ID()
|
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
|
require.NoError(t, err)
|
|
|
|
accessKeyID := getAccessKeyID(addr)
|
|
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
|
|
creds.(*cred).frostFS.(*frostfsMock).errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{}
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("error while getting box from frostfs", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
cnrID := cidtest.ID()
|
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
|
require.NoError(t, err)
|
|
|
|
accessKeyID := getAccessKeyID(addr)
|
|
creds.(*cred).frostFS.(*frostfsMock).errors[accessKeyID] = errors.New("network error")
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("invalid key", func(t *testing.T) {
|
|
frostfs := newFrostfsMock(key)
|
|
|
|
var obj object.Object
|
|
obj.SetPayload(data)
|
|
addr := oidtest.Address()
|
|
accessKeyID := getAccessKeyID(addr)
|
|
frostfs.objects[accessKeyID] = []*object.Object{&obj}
|
|
|
|
cfg.FrostFS = frostfs
|
|
cfg.RemovingCheckAfterDurations = 0
|
|
cfg.Key = &keys.PrivateKey{}
|
|
creds := New(cfg)
|
|
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("invalid payload", func(t *testing.T) {
|
|
frostfs := newFrostfsMock(key)
|
|
|
|
var obj object.Object
|
|
obj.SetPayload([]byte("invalid"))
|
|
addr := oidtest.Address()
|
|
accessKeyID := getAccessKeyID(addr)
|
|
frostfs.objects[accessKeyID] = []*object.Object{&obj}
|
|
|
|
cfg.FrostFS = frostfs
|
|
cfg.RemovingCheckAfterDurations = 0
|
|
cfg.Key = key
|
|
creds := New(cfg)
|
|
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
t.Run("check attributes update", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
cnrID := cidtest.ID()
|
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
|
require.NoError(t, err)
|
|
|
|
accessKeyID := getAccessKeyID(addr)
|
|
_, boxAttrs, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
|
|
prm := CredentialsParam{
|
|
Container: addr.Container(),
|
|
AccessKeyID: accessKeyID,
|
|
Keys: keys.PublicKeys{key.PublicKey()},
|
|
AccessBox: accessBox,
|
|
CustomAttributes: attrs,
|
|
}
|
|
_, err = creds.Update(ctx, prm)
|
|
require.NoError(t, err)
|
|
|
|
_, newBoxAttrs, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, len(boxAttrs)+1, len(newBoxAttrs))
|
|
})
|
|
|
|
t.Run("check accessbox update", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
cnrID := cidtest.ID()
|
|
addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox})
|
|
require.NoError(t, err)
|
|
|
|
accessKeyID := getAccessKeyID(addr)
|
|
|
|
box, _, err := creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey)
|
|
|
|
newKey, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
newGateData := []*accessbox.GateData{{
|
|
BearerToken: &bearer.Token{},
|
|
GateKey: newKey.PublicKey(),
|
|
}}
|
|
|
|
newSecret := []byte("new-secret")
|
|
newAccessBox, _, err := accessbox.PackTokens(newGateData, newSecret, false)
|
|
require.NoError(t, err)
|
|
|
|
prm := CredentialsParam{
|
|
Container: addr.Container(),
|
|
AccessKeyID: accessKeyID,
|
|
Keys: keys.PublicKeys{newKey.PublicKey()},
|
|
AccessBox: newAccessBox,
|
|
}
|
|
|
|
_, err = creds.Update(ctx, prm)
|
|
require.NoError(t, err)
|
|
|
|
_, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.Error(t, err)
|
|
|
|
newCfg := Config{
|
|
FrostFS: creds.(*cred).frostFS,
|
|
Key: newKey,
|
|
CacheConfig: cfg.CacheConfig,
|
|
}
|
|
newCreds := New(newCfg)
|
|
|
|
box, _, err = newCreds.GetBox(ctx, addr.Container(), accessKeyID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, hex.EncodeToString(newSecret), box.Gate.SecretKey)
|
|
})
|
|
|
|
t.Run("check access key id uniqueness", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
prm := CredentialsParam{
|
|
Container: cidtest.ID(),
|
|
AccessBox: accessBox,
|
|
Keys: keys.PublicKeys{key.PublicKey()},
|
|
}
|
|
|
|
_, err = creds.Put(ctx, prm)
|
|
require.NoError(t, err)
|
|
|
|
_, err = creds.Put(ctx, prm)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
t.Run("empty keys", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
cnrID := cidtest.ID()
|
|
_, err = creds.Put(ctx, CredentialsParam{Container: cnrID, AccessBox: accessBox})
|
|
require.ErrorIs(t, err, ErrEmptyPublicKeys)
|
|
})
|
|
|
|
t.Run("empty accessbox", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
cnrID := cidtest.ID()
|
|
_, err = creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}})
|
|
require.ErrorIs(t, err, ErrEmptyBearerToken)
|
|
})
|
|
}
|
|
|
|
func TestBoxWithCustomAccessKeyID(t *testing.T) {
|
|
ctx := context.Background()
|
|
key, err := keys.NewPrivateKey()
|
|
require.NoError(t, err)
|
|
|
|
gateData := []*accessbox.GateData{{
|
|
BearerToken: &bearer.Token{},
|
|
GateKey: key.PublicKey(),
|
|
}}
|
|
|
|
secret := []byte("secret")
|
|
accessBox, secrets, err := accessbox.PackTokens(gateData, secret, true)
|
|
require.NoError(t, err)
|
|
require.Equal(t, string(secret), secrets.SecretKey)
|
|
|
|
cfg := Config{
|
|
CacheConfig: &cache.Config{
|
|
Size: 10,
|
|
Lifetime: 24 * time.Hour,
|
|
Logger: zaptest.NewLogger(t),
|
|
},
|
|
}
|
|
|
|
t.Run("check secret format", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
prm := CredentialsParam{
|
|
Container: cidtest.ID(),
|
|
AccessKeyID: "custom-access-key-id",
|
|
AccessBox: accessBox,
|
|
Keys: keys.PublicKeys{key.PublicKey()},
|
|
}
|
|
|
|
_, err = creds.Put(ctx, prm)
|
|
require.NoError(t, err)
|
|
|
|
box, _, err := creds.GetBox(ctx, prm.Container, prm.AccessKeyID)
|
|
require.NoError(t, err)
|
|
require.Equal(t, string(secret), box.Gate.SecretKey)
|
|
})
|
|
|
|
t.Run("check custom access key id uniqueness", func(t *testing.T) {
|
|
creds := newCreds(key, cfg, 0)
|
|
|
|
prm := CredentialsParam{
|
|
Container: cidtest.ID(),
|
|
AccessKeyID: "custom-access-key-id",
|
|
AccessBox: accessBox,
|
|
Keys: keys.PublicKeys{key.PublicKey()},
|
|
}
|
|
|
|
_, err = creds.Put(ctx, prm)
|
|
require.NoError(t, err)
|
|
|
|
_, err = creds.Put(ctx, prm)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func newCreds(key *keys.PrivateKey, cfg Config, removingCheckDuration time.Duration) Credentials {
|
|
frostfs := newFrostfsMock(key)
|
|
cfg.FrostFS = frostfs
|
|
cfg.RemovingCheckAfterDurations = removingCheckDuration
|
|
cfg.Key = key
|
|
return New(cfg)
|
|
}
|
|
|
|
func getAccessKeyID(addr oid.Address) string {
|
|
return strings.ReplaceAll(addr.EncodeToString(), "/", "0")
|
|
}
|