package tokens import ( "context" "encoding/hex" "errors" "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" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/stretchr/testify/require" "go.uber.org/zap/zaptest" ) type frostfsMock struct { objects map[oid.Address][]*object.Object errors map[oid.Address]error } func newFrostfsMock() *frostfsMock { return &frostfsMock{ objects: map[oid.Address][]*object.Object{}, errors: map[oid.Address]error{}, } } func (f *frostfsMock) CreateObject(_ context.Context, prm PrmObjectCreate) (oid.ID, error) { var obj object.Object obj.SetPayload(prm.Payload) obj.SetOwnerID(prm.Creator) 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.NewVersionFor != nil { var addr oid.Address addr.SetObject(*prm.NewVersionFor) addr.SetContainer(prm.Container) _, ok := f.objects[addr] if !ok { return oid.ID{}, errors.New("not found") } objID := oidtest.ID() obj.SetID(objID) f.objects[addr] = append(f.objects[addr], &obj) return objID, nil } objID := oidtest.ID() obj.SetID(objID) var addr oid.Address addr.SetObject(objID) addr.SetContainer(prm.Container) f.objects[addr] = []*object.Object{&obj} return objID, nil } func (f *frostfsMock) GetCredsObject(_ context.Context, address oid.Address) (*object.Object, error) { if err := f.errors[address]; err != nil { return nil, err } objects, ok := f.objects[address] if !ok { return nil, errors.New("not found") } 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) 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()) frostfs := &frostfsMock{ objects: map[oid.Address][]*object.Object{addr: {&obj}}, errors: map[oid.Address]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) require.NoError(t, err) frostfs.errors[addr] = errors.New("network error") _, _, err = creds.GetBox(ctx, addr) require.NoError(t, err) frostfs.errors[addr] = &apistatus.ObjectAlreadyRemoved{} _, _, err = creds.GetBox(ctx, addr) 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, _, err := accessbox.PackTokens(gateData, secret) require.NoError(t, err) 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) { frostfs := newFrostfsMock() cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = time.Hour cfg.Key = key creds := New(cfg) cnrID := cidtest.ID() addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) require.NoError(t, err) _, _, err = creds.GetBox(ctx, addr) require.NoError(t, err) frostfs.errors[addr] = &apistatus.ObjectAlreadyRemoved{} _, _, err = creds.GetBox(ctx, addr) require.NoError(t, err) }) t.Run("error while getting box from frostfs", func(t *testing.T) { frostfs := newFrostfsMock() cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = key creds := New(cfg) cnrID := cidtest.ID() addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) require.NoError(t, err) frostfs.errors[addr] = errors.New("network error") _, _, err = creds.GetBox(ctx, addr) require.Error(t, err) }) t.Run("invalid key", func(t *testing.T) { frostfs := newFrostfsMock() var obj object.Object obj.SetPayload(data) addr := oidtest.Address() frostfs.objects[addr] = []*object.Object{&obj} cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = &keys.PrivateKey{} creds := New(cfg) _, _, err = creds.GetBox(ctx, addr) require.Error(t, err) }) t.Run("invalid payload", func(t *testing.T) { frostfs := newFrostfsMock() var obj object.Object obj.SetPayload([]byte("invalid")) addr := oidtest.Address() frostfs.objects[addr] = []*object.Object{&obj} cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = key creds := New(cfg) _, _, err = creds.GetBox(ctx, addr) require.Error(t, err) }) t.Run("check attributes update", func(t *testing.T) { frostfs := newFrostfsMock() cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = key creds := New(cfg) cnrID := cidtest.ID() addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) require.NoError(t, err) _, boxAttrs, err := creds.GetBox(ctx, addr) require.NoError(t, err) _, err = creds.Update(ctx, addr, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox, CustomAttributes: attrs}) require.NoError(t, err) _, newBoxAttrs, err := creds.GetBox(ctx, addr) require.NoError(t, err) require.Equal(t, len(boxAttrs)+1, len(newBoxAttrs)) }) t.Run("check accessbox update", func(t *testing.T) { frostfs := newFrostfsMock() cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = key creds := New(cfg) cnrID := cidtest.ID() addr, err := creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) require.NoError(t, err) box, _, err := creds.GetBox(ctx, addr) 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) require.NoError(t, err) _, err = creds.Update(ctx, addr, CredentialsParam{Keys: keys.PublicKeys{newKey.PublicKey()}, AccessBox: newAccessBox}) require.NoError(t, err) _, _, err = creds.GetBox(ctx, addr) require.Error(t, err) cfg.Key = newKey newCreds := New(cfg) box, _, err = newCreds.GetBox(ctx, addr) require.NoError(t, err) require.Equal(t, hex.EncodeToString(newSecret), box.Gate.SecretKey) }) t.Run("empty keys", func(t *testing.T) { frostfs := newFrostfsMock() cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = key creds := New(cfg) cnrID := cidtest.ID() _, err = creds.Put(ctx, cnrID, CredentialsParam{AccessBox: accessBox}) require.ErrorIs(t, err, ErrEmptyPublicKeys) }) t.Run("empty accessbox", func(t *testing.T) { frostfs := newFrostfsMock() cfg.FrostFS = frostfs cfg.RemovingCheckAfterDurations = 0 cfg.Key = key creds := New(cfg) cnrID := cidtest.ID() _, err = creds.Put(ctx, cnrID, CredentialsParam{Keys: keys.PublicKeys{key.PublicKey()}}) require.ErrorIs(t, err, ErrEmptyBearerToken) }) }