frostfs-s3-gw/creds/tokens/credentials_test.go
Denis Kirillov 3cf27d281d [#509] Support fallback address when getting box
Signed-off-by: Denis Kirillov <d.kirillov@yadro.com>
2024-10-23 15:01:31 +03:00

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")
}