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