diff --git a/creds/accessbox/bearer_token_test.go b/creds/accessbox/accessbox_test.go similarity index 91% rename from creds/accessbox/bearer_token_test.go rename to creds/accessbox/accessbox_test.go index e00d8c6..27842a3 100644 --- a/creds/accessbox/bearer_token_test.go +++ b/creds/accessbox/accessbox_test.go @@ -224,14 +224,27 @@ func TestGetBox(t *testing.T) { var tkn bearer.Token gate := NewGateData(cred.PublicKey(), &tkn) - secret := []byte("secret") - accessBox, _, err := PackTokens([]*GateData{gate}, secret, false) - require.NoError(t, err) - box, err := accessBox.GetBox(cred, false) - require.NoError(t, err) - require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey) + t.Run("regular secret", func(t *testing.T) { + accessBox, secrets, err := PackTokens([]*GateData{gate}, secret, false) + require.NoError(t, err) + require.Equal(t, hex.EncodeToString(secret), secrets.SecretKey) + + box, err := accessBox.GetBox(cred, false) + require.NoError(t, err) + require.Equal(t, hex.EncodeToString(secret), box.Gate.SecretKey) + }) + + t.Run("custom secret", func(t *testing.T) { + accessBox, secrets, err := PackTokens([]*GateData{gate}, secret, true) + require.NoError(t, err) + require.Equal(t, string(secret), secrets.SecretKey) + + box, err := accessBox.GetBox(cred, true) + require.NoError(t, err) + require.Equal(t, string(secret), box.Gate.SecretKey) + }) } func TestAccessBox(t *testing.T) { diff --git a/creds/tokens/credentials_test.go b/creds/tokens/credentials_test.go index 9ac4b79..95935be 100644 --- a/creds/tokens/credentials_test.go +++ b/creds/tokens/credentials_test.go @@ -16,27 +16,40 @@ import ( "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() *frostfsMock { +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(prm.Creator) + obj.SetOwnerID(f.ownerID()) obj.SetContainerID(prm.Container) a := object.NewAttribute() @@ -81,7 +94,7 @@ func (f *frostfsMock) GetCredsObject(_ context.Context, prm PrmGetCredsObject) ( objects, ok := f.objects[prm.AccessKeyID] if !ok { - return nil, errors.New("not found") + return nil, ErrCustomAccessKeyIDNotFound } return objects[len(objects)-1], nil @@ -157,8 +170,9 @@ func TestGetBox(t *testing.T) { }} secret := []byte("secret") - accessBox, _, err := accessbox.PackTokens(gateData, secret, false) + 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) @@ -176,11 +190,7 @@ func TestGetBox(t *testing.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) + creds := newCreds(key, cfg, time.Hour) cnrID := cidtest.ID() addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) @@ -191,30 +201,26 @@ func TestGetBox(t *testing.T) { _, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID) require.NoError(t, err) - frostfs.errors[accessKeyID] = &apistatus.ObjectAlreadyRemoved{} + 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) { - frostfs := newFrostfsMock() - cfg.FrostFS = frostfs - cfg.RemovingCheckAfterDurations = 0 - cfg.Key = key - creds := New(cfg) + 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) - frostfs.errors[accessKeyID] = errors.New("network error") + 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() + frostfs := newFrostfsMock(key) var obj object.Object obj.SetPayload(data) @@ -232,7 +238,7 @@ func TestGetBox(t *testing.T) { }) t.Run("invalid payload", func(t *testing.T) { - frostfs := newFrostfsMock() + frostfs := newFrostfsMock(key) var obj object.Object obj.SetPayload([]byte("invalid")) @@ -250,11 +256,7 @@ func TestGetBox(t *testing.T) { }) t.Run("check attributes update", func(t *testing.T) { - frostfs := newFrostfsMock() - cfg.FrostFS = frostfs - cfg.RemovingCheckAfterDurations = 0 - cfg.Key = key - creds := New(cfg) + creds := newCreds(key, cfg, 0) cnrID := cidtest.ID() addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) @@ -280,11 +282,7 @@ func TestGetBox(t *testing.T) { }) t.Run("check accessbox update", func(t *testing.T) { - frostfs := newFrostfsMock() - cfg.FrostFS = frostfs - cfg.RemovingCheckAfterDurations = 0 - cfg.Key = key - creds := New(cfg) + creds := newCreds(key, cfg, 0) cnrID := cidtest.ID() addr, err := creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}, AccessBox: accessBox}) @@ -321,20 +319,36 @@ func TestGetBox(t *testing.T) { _, _, err = creds.GetBox(ctx, addr.Container(), accessKeyID) require.Error(t, err) - cfg.Key = newKey - newCreds := New(cfg) + 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) { - frostfs := newFrostfsMock() - cfg.FrostFS = frostfs - cfg.RemovingCheckAfterDurations = 0 - cfg.Key = key - creds := New(cfg) + creds := newCreds(key, cfg, 0) cnrID := cidtest.ID() _, err = creds.Put(ctx, CredentialsParam{Container: cnrID, AccessBox: accessBox}) @@ -342,11 +356,7 @@ func TestGetBox(t *testing.T) { }) t.Run("empty accessbox", func(t *testing.T) { - frostfs := newFrostfsMock() - cfg.FrostFS = frostfs - cfg.RemovingCheckAfterDurations = 0 - cfg.Key = key - creds := New(cfg) + creds := newCreds(key, cfg, 0) cnrID := cidtest.ID() _, err = creds.Put(ctx, CredentialsParam{Container: cnrID, Keys: keys.PublicKeys{key.PublicKey()}}) @@ -354,6 +364,73 @@ func TestGetBox(t *testing.T) { }) } +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") } diff --git a/internal/frostfs/authmate_test.go b/internal/frostfs/authmate_test.go index 0f0f167..c21b587 100644 --- a/internal/frostfs/authmate_test.go +++ b/internal/frostfs/authmate_test.go @@ -2,23 +2,27 @@ package frostfs import ( "context" + "strconv" "strings" "testing" + 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/api/middleware" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/authmate" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/accessbox" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/creds/tokens" "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/bearer" + "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" ) -func TestGetCredsObject(t *testing.T) { +func TestCredsObject(t *testing.T) { ctx, bktName, payload, newPayload := context.Background(), "bucket", []byte("payload"), []byte("new-payload") key, err := keys.NewPrivateKey() @@ -45,38 +49,166 @@ func TestGetCredsObject(t *testing.T) { }) require.NoError(t, err) - objID, err := frostfs.CreateObject(ctx, tokens.PrmObjectCreate{ - Container: cnrID, - Payload: payload, + t.Run("regular access key", func(t *testing.T) { + attr1 := object.NewAttribute() + attr1.SetKey("attr1") + attr1.SetValue("val1") + + prm := tokens.PrmObjectCreate{ + Container: cnrID, + Filepath: "regular-obj", + ExpirationEpoch: 10, + Payload: payload, + CustomAttributes: []object.Attribute{*attr1}, + } + + objID, err := frostfs.CreateObject(ctx, prm) + require.NoError(t, err) + + accessKeyID := cnrID.EncodeToString() + "0" + objID.EncodeToString() + + obj, err := frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID}) + require.NoError(t, err) + assertParamsSet(t, prm, obj, userID) + + t.Run("update existing", func(t *testing.T) { + attr2 := object.NewAttribute() + attr2.SetKey("attr2") + attr2.SetValue("val2") + + prmNew := tokens.PrmObjectCreate{ + Container: cnrID, + Filepath: "regular-obj-new", + ExpirationEpoch: 11, + Payload: newPayload, + CustomAttributes: []object.Attribute{*attr2}, + NewVersionForAccessKeyID: accessKeyID, + } + + _, err = frostfs.CreateObject(ctx, prmNew) + require.NoError(t, err) + + obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID}) + require.NoError(t, err) + assertParamsSet(t, prmNew, obj, userID) + }) + + t.Run("update not existing", func(t *testing.T) { + attr2 := object.NewAttribute() + attr2.SetKey("attr2") + attr2.SetValue("val2") + + addr := oidtest.Address() + accessKeyIDNotExisting := getAccessKeyID(addr) + + prmNew := tokens.PrmObjectCreate{ + Container: cnrID, + Filepath: "regular-obj-new", + ExpirationEpoch: 11, + Payload: newPayload, + CustomAttributes: []object.Attribute{*attr2}, + NewVersionForAccessKeyID: accessKeyIDNotExisting, + } + + _, err = frostfs.CreateObject(ctx, prmNew) + require.NoError(t, err) + + obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyIDNotExisting}) + require.NoError(t, err) + assertParamsSet(t, prmNew, obj, userID) + }) }) - require.NoError(t, err) - var addr oid.Address - addr.SetContainer(cnrID) - addr.SetObject(objID) + t.Run("custom access key", func(t *testing.T) { + attr1 := object.NewAttribute() + attr1.SetKey("attr1") + attr1.SetValue("val1") - accessKeyID := getAccessKeyID(addr) + accessKeyID := "custom-access-key-id" - obj, err := frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{ - Container: cnrID, - AccessKeyID: accessKeyID, + prm := tokens.PrmObjectCreate{ + Container: cnrID, + Filepath: "custom-obj", + ExpirationEpoch: 10, + Payload: payload, + CustomAccessKey: accessKeyID, + CustomAttributes: []object.Attribute{*attr1}, + } + + _, err = frostfs.CreateObject(ctx, prm) + require.NoError(t, err) + + obj, err := frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID}) + require.NoError(t, err) + assertParamsSet(t, prm, obj, userID) + + t.Run("update", func(t *testing.T) { + attr2 := object.NewAttribute() + attr2.SetKey("attr2") + attr2.SetValue("val2") + + prmNew := tokens.PrmObjectCreate{ + Container: cnrID, + Filepath: "custom-obj-new", + ExpirationEpoch: 11, + Payload: newPayload, + CustomAttributes: []object.Attribute{*attr2}, + NewVersionForAccessKeyID: accessKeyID, + } + + _, err = frostfs.CreateObject(ctx, prmNew) + require.NoError(t, err) + + obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{Container: cnrID, AccessKeyID: accessKeyID}) + require.NoError(t, err) + assertParamsSet(t, prmNew, obj, userID) + }) + + t.Run("update not existing", func(t *testing.T) { + accessKeyIDNotExisting := "unknown" + + prmNew := tokens.PrmObjectCreate{ + Container: cnrID, + Payload: newPayload, + NewVersionForAccessKeyID: accessKeyIDNotExisting, + } + + _, err = frostfs.CreateObject(ctx, prmNew) + require.Error(t, err) + }) }) - require.NoError(t, err) - require.Equal(t, payload, obj.Payload()) +} - _, err = frostfs.CreateObject(ctx, tokens.PrmObjectCreate{ - Container: cnrID, - Payload: newPayload, - NewVersionForAccessKeyID: accessKeyID, - }) - require.NoError(t, err) +func assertParamsSet(t *testing.T, prm tokens.PrmObjectCreate, obj *object.Object, userID user.ID) { + require.Equal(t, prm.Payload, obj.Payload()) + require.True(t, userID.Equals(obj.OwnerID()), "owners not matched") - obj, err = frostfs.GetCredsObject(ctx, tokens.PrmGetCredsObject{ - Container: cnrID, - AccessKeyID: getAccessKeyID(addr), - }) - require.NoError(t, err) - require.Equal(t, newPayload, obj.Payload()) + require.True(t, containerAttribute(obj.Attributes(), object.AttributeFilePath, prm.Filepath), "missing FilePath") + require.True(t, containerAttribute(obj.Attributes(), objectv2.SysAttributeExpEpoch, strconv.FormatUint(prm.ExpirationEpoch, 10)), "missing expiration epoch") + + var crdtName string + if prm.CustomAccessKey != "" { + crdtName = prm.CustomAccessKey + } else if prm.NewVersionForAccessKeyID != "" { + crdtName = prm.NewVersionForAccessKeyID + } + if crdtName != "" { + require.Truef(t, containerAttribute(obj.Attributes(), accessBoxCRDTNameAttr, crdtName), "wrong crdt name '%s'", crdtName) + } + + for _, attr := range prm.CustomAttributes { + require.True(t, containerAttribute(obj.Attributes(), attr.Key(), attr.Value()), "missing custom attribute") + } +} + +func containerAttribute(attrs []object.Attribute, key, val string) bool { + for _, attr := range attrs { + if attr.Key() == key && attr.Value() == val { + return true + } + } + + return false } func getAccessKeyID(addr oid.Address) string {