Different owners error when use client cut object #117

Closed
opened 2023-07-11 13:39:16 +00:00 by dkirillov · 3 comments
Collaborator

Failed to put object using client cut.

Actually I'm not sure if problem in SDK, in storage node or in client code (Pool).

Probably I don't understand session token correctly:
why we check here signature for session key if client only has its public part.
And we cannot use owner id for object that different from one who real sign the request (we have bearer token)

Expected Behavior

Object is successfully created

Current Behavior

Got

2023-07-11T15:52:18.540+0300    error   handler/util.go:38      request failed  {"status": 500, "request_id": "99da0a56-14be-40eb-b9bf-f1c689dc1561", "method": "PutObject", "bucket": "dkirillov", "object": "tmp", "description": "could not upload object", "error": "save object via connection pool: init writing on API client: client failure: status: code = 1024 message = (*object.FormatValidator) different owner identifiers NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM/NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt", "body close errors": []}

Possible Solution

No idea.

Steps to Reproduce (for bugs)

this test should be placed and run here

func TestDifferentOwnersWithoutSession(t *testing.T) {
	const maxSize = 100

	tt := new(testTarget)

	ownerKey, err := keys.NewPrivateKey()
	require.NoError(t, err)

	gateKey, err := keys.NewPrivateKey()
	require.NoError(t, err)

	target := NewPayloadSizeLimiter(Params{
		Key:                    &gateKey.PrivateKey,
		NextTargetInit:         func() ObjectWriter { return tt },
		NetworkState:           dummyEpochSource(123),
		MaxSize:                maxSize,
		WithoutHomomorphicHash: true,
	})

	cnr := cidtest.ID()
	hdr := newObject(cnr)

	var owner user.ID
	user.IDFromKey(&owner, ownerKey.PrivateKey.PublicKey)
	hdr.SetOwnerID(&owner)

	expectedPayload := make([]byte, maxSize*2+maxSize/2)
	_, _ = rand.Read(expectedPayload)

	writeObject(t, context.Background(), target, hdr, expectedPayload)
	require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object

	for i := range tt.objects {
		err := validateSignatureKey(tt.objects[i])
		require.NoError(t, err)
	}
}

func TestDifferentOwnersWithSession(t *testing.T) {
	const maxSize = 100

	tt := new(testTarget)

	ownerKey, err := keys.NewPrivateKey()
	require.NoError(t, err)

	gateKey, err := keys.NewPrivateKey()
	require.NoError(t, err)

	sessionKey, err := keys.NewPrivateKey()
	require.NoError(t, err)

	var stoken session.Object
	stoken.SetID(uuid.New())
	psk := frostfsecdsa.PublicKey(sessionKey.PrivateKey.PublicKey)
	stoken.SetAuthKey(&psk)

	target := NewPayloadSizeLimiter(Params{
		Key:                    &gateKey.PrivateKey,
		NextTargetInit:         func() ObjectWriter { return tt },
		NetworkState:           dummyEpochSource(123),
		MaxSize:                maxSize,
		SessionToken:           &stoken,
		WithoutHomomorphicHash: true,
	})

	cnr := cidtest.ID()
	hdr := newObject(cnr)

	var owner user.ID
	user.IDFromKey(&owner, ownerKey.PrivateKey.PublicKey)
	hdr.SetOwnerID(&owner)

	expectedPayload := make([]byte, maxSize*2+maxSize/2)
	_, _ = rand.Read(expectedPayload)

	writeObject(t, context.Background(), target, hdr, expectedPayload)
	require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object

	for i := range tt.objects {
		err := validateSignatureKey(tt.objects[i])
		require.NoError(t, err)
	}
}

// this is the same as https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/a65e26878b287a304713845d23a9c4ae1516c8aa/pkg/core/object/fmt.go#L135
func validateSignatureKey(obj *objectSDK.Object) error {
	sig := obj.Signature()
	if sig == nil {
		// TODO(@cthulhu-rider): #468 use "const" error
		return errors.New("missing signature")
	}

	var sigV2 refs.Signature
	sig.WriteToV2(&sigV2)

	binKey := sigV2.GetKey()

	var key frostfsecdsa.PublicKey

	err := key.Decode(binKey)
	if err != nil {
		return fmt.Errorf("decode public key: %w", err)
	}

	token := obj.SessionToken()

	if token == nil || !token.AssertAuthKey(&key) {
		return checkOwnerKey(*obj.OwnerID(), key)
	}

	// FIXME: #1159 perform token verification

	return nil
}

// this is the same as https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/a65e26878b287a304713845d23a9c4ae1516c8aa/pkg/core/object/fmt.go#L165
func checkOwnerKey(id user.ID, key frostfsecdsa.PublicKey) error {
	var id2 user.ID
	user.IDFromKey(&id2, (ecdsa.PublicKey)(key))

	if !id.Equals(id2) {
		return fmt.Errorf("(*object.FormatValidator) different owner identifiers %s/%s", id, id2)
	}

	return nil
}

Context

Such behavior was noticed in scope of #114
So we try to put object using s3 gw

Your Environment

  • Version used: master
  • Server setup and configuration: devenv
Failed to put object using [client cut](https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/branch/master/client/object_put_transformer.go). Actually I'm not sure if problem in SDK, in storage node or in client code (Pool). Probably I don't understand session token correctly: why we check [here](https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/a65e26878b287a304713845d23a9c4ae1516c8aa/pkg/core/object/fmt.go#L156) signature for session key if client only has its public part. And we [cannot use](https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/a65e26878b287a304713845d23a9c4ae1516c8aa/pkg/core/object/fmt.go#L157) owner id for object that different from one who real sign the request (we have bearer token) ## Expected Behavior Object is successfully created ## Current Behavior Got ``` 2023-07-11T15:52:18.540+0300 error handler/util.go:38 request failed {"status": 500, "request_id": "99da0a56-14be-40eb-b9bf-f1c689dc1561", "method": "PutObject", "bucket": "dkirillov", "object": "tmp", "description": "could not upload object", "error": "save object via connection pool: init writing on API client: client failure: status: code = 1024 message = (*object.FormatValidator) different owner identifiers NbUgTSFvPmsRxmGeWpuuGeJUoRoi6PErcM/NUUb82KR2JrVByHs2YSKgtK29gKnF5q6Vt", "body close errors": []} ``` ## Possible Solution No idea. ## Steps to Reproduce (for bugs) this test should be placed and run [here](https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/src/branch/master/object/transformer/transformer_test.go) ```golang func TestDifferentOwnersWithoutSession(t *testing.T) { const maxSize = 100 tt := new(testTarget) ownerKey, err := keys.NewPrivateKey() require.NoError(t, err) gateKey, err := keys.NewPrivateKey() require.NoError(t, err) target := NewPayloadSizeLimiter(Params{ Key: &gateKey.PrivateKey, NextTargetInit: func() ObjectWriter { return tt }, NetworkState: dummyEpochSource(123), MaxSize: maxSize, WithoutHomomorphicHash: true, }) cnr := cidtest.ID() hdr := newObject(cnr) var owner user.ID user.IDFromKey(&owner, ownerKey.PrivateKey.PublicKey) hdr.SetOwnerID(&owner) expectedPayload := make([]byte, maxSize*2+maxSize/2) _, _ = rand.Read(expectedPayload) writeObject(t, context.Background(), target, hdr, expectedPayload) require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object for i := range tt.objects { err := validateSignatureKey(tt.objects[i]) require.NoError(t, err) } } func TestDifferentOwnersWithSession(t *testing.T) { const maxSize = 100 tt := new(testTarget) ownerKey, err := keys.NewPrivateKey() require.NoError(t, err) gateKey, err := keys.NewPrivateKey() require.NoError(t, err) sessionKey, err := keys.NewPrivateKey() require.NoError(t, err) var stoken session.Object stoken.SetID(uuid.New()) psk := frostfsecdsa.PublicKey(sessionKey.PrivateKey.PublicKey) stoken.SetAuthKey(&psk) target := NewPayloadSizeLimiter(Params{ Key: &gateKey.PrivateKey, NextTargetInit: func() ObjectWriter { return tt }, NetworkState: dummyEpochSource(123), MaxSize: maxSize, SessionToken: &stoken, WithoutHomomorphicHash: true, }) cnr := cidtest.ID() hdr := newObject(cnr) var owner user.ID user.IDFromKey(&owner, ownerKey.PrivateKey.PublicKey) hdr.SetOwnerID(&owner) expectedPayload := make([]byte, maxSize*2+maxSize/2) _, _ = rand.Read(expectedPayload) writeObject(t, context.Background(), target, hdr, expectedPayload) require.Equal(t, 4, len(tt.objects)) // 3 parts + linking object for i := range tt.objects { err := validateSignatureKey(tt.objects[i]) require.NoError(t, err) } } // this is the same as https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/a65e26878b287a304713845d23a9c4ae1516c8aa/pkg/core/object/fmt.go#L135 func validateSignatureKey(obj *objectSDK.Object) error { sig := obj.Signature() if sig == nil { // TODO(@cthulhu-rider): #468 use "const" error return errors.New("missing signature") } var sigV2 refs.Signature sig.WriteToV2(&sigV2) binKey := sigV2.GetKey() var key frostfsecdsa.PublicKey err := key.Decode(binKey) if err != nil { return fmt.Errorf("decode public key: %w", err) } token := obj.SessionToken() if token == nil || !token.AssertAuthKey(&key) { return checkOwnerKey(*obj.OwnerID(), key) } // FIXME: #1159 perform token verification return nil } // this is the same as https://git.frostfs.info/TrueCloudLab/frostfs-node/src/commit/a65e26878b287a304713845d23a9c4ae1516c8aa/pkg/core/object/fmt.go#L165 func checkOwnerKey(id user.ID, key frostfsecdsa.PublicKey) error { var id2 user.ID user.IDFromKey(&id2, (ecdsa.PublicKey)(key)) if !id.Equals(id2) { return fmt.Errorf("(*object.FormatValidator) different owner identifiers %s/%s", id, id2) } return nil } ``` ## Context Such behavior was noticed in scope of https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/issues/114 So we try to put object using s3 gw ## Your Environment <!--- Include as many relevant details about the environment you experienced the bug in --> * Version used: [master](https://git.frostfs.info/TrueCloudLab/frostfs-sdk-go/commit/c359a7465a7cc2674b07b7a4a6db8e90ccbdffd0) * Server setup and configuration: devenv
dkirillov added the
bug
label 2023-07-11 13:39:16 +00:00
dstepanov-yadro was assigned by fyrchik 2023-07-11 15:12:46 +00:00

@dkirillov test TestDifferentOwnersWithSession

Session token must be signed with owner's private key, have gate's public key as auth key and expiration time.

	var stoken session.Object
	stoken.SetID(uuid.New())
	psk := frostfsecdsa.PublicKey(gateKey.PrivateKey.PublicKey)
	stoken.Sign(ownerKey.PrivateKey)
	stoken.SetAuthKey(&psk)
	stoken.SetExp(200)
@dkirillov test `TestDifferentOwnersWithSession` Session token must be signed with owner's private key, have gate's public key as auth key and expiration time. ``` var stoken session.Object stoken.SetID(uuid.New()) psk := frostfsecdsa.PublicKey(gateKey.PrivateKey.PublicKey) stoken.Sign(ownerKey.PrivateKey) stoken.SetAuthKey(&psk) stoken.SetExp(200) ```
Poster
Collaborator

Actually we cannot use this approach in the pool. We don't have ownerKey. As workaround of course we can form session token for gateKey and sign it also by gateKey but it looks like a kludge a little.

But I agree that when we (as pool) was provided by session token, it should be formed with approach you sugessted.

Actually, I would like to be able set owner field for object that it can be different from request signer. It seems that this field has just informative function (@alexvanin @fyrchik am I right?)

Actually we cannot use this approach in the pool. We don't have `ownerKey`. As workaround of course we can form session token for `gateKey` and sign it also by `gateKey` but it looks like a kludge a little. But I agree that when we (as pool) was provided by session token, it should be formed with approach you sugessted. Actually, I would like to be able set owner field for object that it can be different from request signer. It seems that this field has just informative function (@alexvanin @fyrchik am I right?)

Fixed

Fixed
Sign in to join this conversation.
No Milestone
No Assignees
2 Participants
Notifications
Due Date
The due date is invalid or out of range. Please use the format 'yyyy-mm-dd'.

No due date set.

Dependencies

No dependencies set.

Reference: TrueCloudLab/frostfs-sdk-go#117
There is no content yet.