nspcc-dev / neofs-node

NeoFS is a decentralized distributed object storage integrated with the Neo blockchain
https://fs.neo.org
GNU General Public License v3.0
31 stars 38 forks source link

Private object is accessible through REST geteway when must not be #2790

Closed cthulhu-rider closed 3 months ago

cthulhu-rider commented 3 months ago

i was playing with NeoFS DevEnv and various access settings. I encountered situtation when private object is expectedly inacessible to anonymous subject incl. REST gateway, but can be downloaded throught REST gateway itself

Expected Behavior

$ curl http://rest.neofs.devenv:8090/v1/objects/A74xJsQNjnBpWTscB8kgEf3s2jZmgJJ5MDzPv8w7YmUu/5gHCdcBR5Lgnz3DGgbVWvU8m6bWC1WihpYFVEPxbypp9
{"code":2048,"message":"head object: status: code = 2048 message = access to object operation denied","type":"API"}

Current Behavior

$ curl http://rest.neofs.devenv:8090/v1/objects/EPx35S7TLMXm1ZgSjUZVzXdTtibjYGi5Je4JGrBonAw5/4smVsw84ADigT4Vcp9B2Fp3qn9a9yb4nwXk6hGuinLSd
{"attributes":[{"key":"FileName","value":"Makefile"},{"key":"Timestamp","value":"1711624178"}],"containerId":"EPx35S7TLMXm1ZgSjUZVzXdTtibjYGi5Je4JGrBonAw5","objectId":"4smVsw84ADigT4Vcp9B2Fp3qn9a9yb4nwXk6hGuinLSd","objectSize":4218,"ownerId":"Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn"

Possible Solution

1st i thought that maybe REST communicates with node that cached allowed access settings that hadnt expired yet. But then I polled all the storage nodes via CLI, and they all denied. And REST is still a success

seems like REST unintentionally found security loophole

Steps to Reproduce (for bugs)

  1. run NeoFS DevEnv with REST gateway (default)
  2. create public-read container
    $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json container create --policy 'REP 2 CBF 1' --basic-acl eacl-public-read  --await
  3. upload object into it
    $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json object put --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --file Makefile
    Enter password > 
    4218 / 4218 [============================================================================================================================================================================================================================================] 100.00% 0s
    [Makefile] Object successfully stored
    OID: CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6
    CID: FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3
  4. extend access rules to deny OTHERS access (create blank eACL and set it for the container)
    $ neofs-cli acl extended create --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 -r 'deny get others' --out eacl.json
    $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/chain/node-wallet.json container set-eacl --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3 --table eacl.json --await
    Enter password > 
    Checking the ability to modify access rights in the container...
    ACL extension is enabled in the container, continue processing.
    eACL modification request accepted for processing (the operation may not be completed yet)
    awaiting...
    EACL has been persisted on sidechain
  5. download object through CLI with REST account
    $ neofs-cli -r s01.neofs.devenv:8080 -w ../devenv/services/rest_gate/wallet.json object get --cid FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3  --oid CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6
    Enter password > 
    rpc error: init object reading on client: header: status: code = 2048 message = access to object operation denied: access to operation OBJECT_GET is denied by extended ACL check: denied by rule
  6. download object through REST gate
    $ curl http://rest.neofs.devenv:8090/v1/objects/FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3/CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6
Helper Go test
```go func TestDownload(t *testing.T) { const strCID = "FdFrW1Sv12yeWWVmYDP4kA9LBKS3xvz7MufgmWCUXyF3" const strOID = "CZxSG4keAyJqVAUa1ghBysryhZDKngEs7j3fxZ9ZsSG6" var cnr cid.ID require.NoError(t, cnr.DecodeString(strCID)) var obj oid.ID require.NoError(t, obj.DecodeString(strOID)) cl, err := client.New(client.PrmInit{}) require.NoError(t, err) var dialPrm client.PrmDial dialPrm.SetServerURI("s01.neofs.devenv:8080") require.NoError(t, cl.Dial(dialPrm)) t.Cleanup(func() { cl.Close() }) signer := user.NewSigner(test.RandomSigner(t), usertest.ID(t)) _, _, err = cl.ObjectGetInit(context.Background(), cnr, obj, signer, client.PrmObjectGet{}) require.Error(t, err) } ```

Regression

idk yet

Your Environment

roman-khimov commented 3 months ago

It works as expected in mainnet (0.40.1): https://rest.fs.neo.org/v1/objects/5fNrip1bbWxtvKTwUXGrVcPBVXB3FQZ9T6jYihzZi2q2/EW2fuWx6eSgNiXcGSyrTLmTccojyCN439HviJh3B2a4X

Either it's a regression or something more specific.

cthulhu-rider commented 3 months ago

Either it's a regression

node version is the same, which REST is served in the mainnet?

roman-khimov commented 3 months ago

The latest one, 0.8.3.

cthulhu-rider commented 3 months ago

if in step 4. add deny head others and/or deny getrange others - object becomes unavailable

seems like REST is smart enough to make do with HEAD and GETRANGE even w/o GET

cthulhu-rider commented 3 months ago

this turned out to be expected behavior for /objects/<cid>/<oid> cuz it calls HEAD and GETRANGE separately. Instead, /get/<cid>/<oid> calls GET, which fails as expected