98fed4bc30
Add getSalt to projects api. Add action, GET_SALT, on Store Projects module to make the api request and return the salt string everywhere in the web app that generates an access grant. The Wasm code which is used to create the access grant has been changed to decode the salt as a base64 encoded string. The names of the function calls in the changed Wasm code have also been changed to ensure that access grant creation fails if JS access grant worker code and Wasm code are not the same version. https://github.com/storj/storj-private/issues/64 Change-Id: Ia2bc4cbadad84b066ca1882b042a3f0bb13c783a
416 lines
16 KiB
Go
416 lines
16 KiB
Go
// Copyright (C) 2020 Storj Labs, Inc.
|
|
// See LICENSE for copying information.
|
|
|
|
package consolewasm_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"storj.io/common/testcontext"
|
|
"storj.io/storj/private/testplanet"
|
|
"storj.io/storj/satellite/console/consolewasm"
|
|
"storj.io/uplink"
|
|
)
|
|
|
|
func TestSetPermissionWithBuckets(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
satellitePeer := planet.Satellites[0]
|
|
satelliteNodeURL := satellitePeer.NodeURL().String()
|
|
uplinkPeer := planet.Uplinks[0]
|
|
APIKey := uplinkPeer.APIKey[satellitePeer.ID()]
|
|
apiKeyString := APIKey.Serialize()
|
|
projectID := uplinkPeer.Projects[0].ID
|
|
require.Equal(t, 1, len(uplinkPeer.Projects))
|
|
passphrase := "supersecretpassphrase"
|
|
|
|
// Create an access grant with the uplink API key. With that access grant, create 2 buckets and upload an object.
|
|
uplinkAccess, err := uplinkPeer.Config.RequestAccessWithPassphrase(ctx, satelliteNodeURL, apiKeyString, passphrase)
|
|
require.NoError(t, err)
|
|
uplinkPeer.Access[satellitePeer.ID()] = uplinkAccess
|
|
testbucket1 := "buckettest1"
|
|
testbucket2 := "buckettest2"
|
|
testfilename := "file.txt"
|
|
testdata := []byte("fun data")
|
|
require.NoError(t, uplinkPeer.CreateBucket(ctx, satellitePeer, testbucket1))
|
|
require.NoError(t, uplinkPeer.CreateBucket(ctx, satellitePeer, testbucket2))
|
|
require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket1, testfilename, testdata))
|
|
require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket2, testfilename, testdata))
|
|
data, err := uplinkPeer.Download(ctx, satellitePeer, testbucket1, testfilename)
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, testdata)
|
|
|
|
buckets := []string{testbucket1}
|
|
|
|
// Restrict the uplink access grant with read only permissions and only allows actions for 1 bucket.
|
|
var sharePrefixes []uplink.SharePrefix
|
|
for _, path := range buckets {
|
|
sharePrefixes = append(sharePrefixes, uplink.SharePrefix{
|
|
Bucket: path,
|
|
})
|
|
}
|
|
restrictedUplinkAccess, err := uplinkAccess.Share(uplink.ReadOnlyPermission(), sharePrefixes...)
|
|
require.NoError(t, err)
|
|
|
|
// Expect that we can download the object with the restricted access for the 1 allowed bucket.
|
|
uplinkPeer.Access[satellitePeer.ID()] = restrictedUplinkAccess
|
|
uplinkPeer.APIKey[satellitePeer.ID()] = APIKey
|
|
data, err = uplinkPeer.Download(ctx, satellitePeer, testbucket1, testfilename)
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, testdata)
|
|
err = uplinkPeer.Upload(ctx, satellitePeer, testbucket1, "file2", testdata)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
_, err = uplinkPeer.Download(ctx, satellitePeer, testbucket2, testfilename)
|
|
require.Error(t, err)
|
|
|
|
// Create restricted access with the console access grant code that allows full access to only 1 bucket.
|
|
readOnlyPermission := consolewasm.Permission{
|
|
AllowDownload: true,
|
|
AllowUpload: false,
|
|
AllowList: true,
|
|
AllowDelete: false,
|
|
NotBefore: time.Now().Add(-24 * time.Hour),
|
|
NotAfter: time.Now().Add(48 * time.Hour),
|
|
}
|
|
restrictedKey, err := consolewasm.SetPermission(apiKeyString, buckets, readOnlyPermission)
|
|
require.NoError(t, err)
|
|
|
|
client := newTestClient(t, ctx, planet)
|
|
user := client.defaultUser()
|
|
client.login(user.email, user.password)
|
|
|
|
resp, bodyString := client.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", projectID.String()), nil)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var b64Salt string
|
|
require.NoError(t, json.Unmarshal([]byte(bodyString), &b64Salt))
|
|
|
|
restrictedAccessGrant, err := consolewasm.GenAccessGrant(satelliteNodeURL, restrictedKey.Serialize(), passphrase, b64Salt)
|
|
require.NoError(t, err)
|
|
restrictedAccess, err := uplink.ParseAccess(restrictedAccessGrant)
|
|
require.NoError(t, err)
|
|
|
|
// Expect that we can download the object with the restricted access for the 1 allowed bucket.
|
|
uplinkPeer.APIKey[satellitePeer.ID()] = restrictedKey
|
|
uplinkPeer.Access[satellitePeer.ID()] = restrictedAccess
|
|
data, err = uplinkPeer.Download(ctx, satellitePeer, testbucket1, testfilename)
|
|
require.NoError(t, err)
|
|
require.Equal(t, data, testdata)
|
|
err = uplinkPeer.Upload(ctx, satellitePeer, testbucket1, "file2", testdata)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
_, err = uplinkPeer.Download(ctx, satellitePeer, testbucket2, testfilename)
|
|
require.Error(t, err)
|
|
})
|
|
}
|
|
|
|
func TestSetPermission_Uplink(t *testing.T) {
|
|
testplanet.Run(t, testplanet.Config{
|
|
SatelliteCount: 1, StorageNodeCount: 0, UplinkCount: 1,
|
|
}, func(t *testing.T, ctx *testcontext.Context, planet *testplanet.Planet) {
|
|
satellitePeer := planet.Satellites[0]
|
|
satelliteNodeURL := satellitePeer.NodeURL().String()
|
|
uplinkPeer := planet.Uplinks[0]
|
|
APIKey := uplinkPeer.APIKey[satellitePeer.ID()]
|
|
apiKeyString := APIKey.Serialize()
|
|
require.Equal(t, 1, len(uplinkPeer.Projects))
|
|
passphrase := "supersecretpassphrase"
|
|
|
|
// Create an access grant with the uplink API key. With that access grant, create 2 bucket and upload files to them
|
|
uplinkAccess, err := uplinkPeer.Config.RequestAccessWithPassphrase(ctx, satelliteNodeURL, apiKeyString, passphrase)
|
|
require.NoError(t, err)
|
|
uplinkPeer.Access[satellitePeer.ID()] = uplinkAccess
|
|
testbucket1 := "buckettest1"
|
|
testbucket2 := "buckettest2"
|
|
testbucket3 := "buckettest3"
|
|
testfilename1 := "file1.txt"
|
|
testfilename2 := "file2.txt"
|
|
testdata := []byte("fun data")
|
|
require.NoError(t, uplinkPeer.CreateBucket(ctx, satellitePeer, testbucket1))
|
|
require.NoError(t, uplinkPeer.CreateBucket(ctx, satellitePeer, testbucket2))
|
|
require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket1, testfilename1, testdata))
|
|
require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket2, testfilename1, testdata))
|
|
require.NoError(t, uplinkPeer.Upload(ctx, satellitePeer, testbucket2, testfilename2, testdata))
|
|
|
|
client := newTestClient(t, ctx, planet)
|
|
user := client.defaultUser()
|
|
client.login(user.email, user.password)
|
|
|
|
resp, bodyString := client.request(http.MethodGet, fmt.Sprintf("/projects/%s/salt", uplinkPeer.Projects[0].ID.String()), nil)
|
|
require.Equal(t, http.StatusOK, resp.StatusCode)
|
|
|
|
var b64Salt string
|
|
require.NoError(t, json.Unmarshal([]byte(bodyString), &b64Salt))
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "only delete", []string{}, consolewasm.Permission{AllowDelete: true}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation except delete should be restricted
|
|
_, err = project.CreateBucket(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket2, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DownloadObject(ctx, testbucket2, testfilename1, nil)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
// We can see buckets names, but not the files inside
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 2, len(buckets))
|
|
|
|
objects := getAllObjects(ctx, project, testbucket1)
|
|
require.Equal(t, 0, len(objects))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename2)
|
|
require.NoError(t, err)
|
|
|
|
// Current implementation needs also permission to Download/Read/List so having
|
|
// only Delete permission for DeleteBucketWithObjects won't work
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket2)
|
|
require.Error(t, err)
|
|
})
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "only list", []string{testbucket1}, consolewasm.Permission{AllowList: true}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation except list inside testbucket1 should be restricted
|
|
_, err = project.CreateBucket(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket1, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DownloadObject(ctx, testbucket1, testfilename1, nil)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
// Only one bucket should be visible
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 1, len(buckets))
|
|
|
|
objects := getAllObjects(ctx, project, testbucket1)
|
|
require.Equal(t, 1, len(objects))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
})
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "only upload", []string{testbucket1}, consolewasm.Permission{AllowUpload: true}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation except upload to the testbucket1 should be restricted
|
|
_, err = project.CreateBucket(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket1, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.NoError(t, err)
|
|
|
|
_, err = project.DownloadObject(ctx, testbucket1, testfilename1, nil)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
// Only one bucket should be visible
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 1, len(buckets))
|
|
|
|
objects := getAllObjects(ctx, project, testbucket1)
|
|
require.Equal(t, 0, len(objects))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
})
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "only download", []string{testbucket1}, consolewasm.Permission{AllowDownload: true}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation except download from testbucket1 should be restricted
|
|
_, err = project.CreateBucket(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket1, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
_, err = project.DownloadObject(ctx, testbucket1, testfilename1, nil)
|
|
require.NoError(t, err)
|
|
|
|
// Only one bucket should be visible
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 1, len(buckets))
|
|
|
|
objects := getAllObjects(ctx, project, testbucket1)
|
|
require.Equal(t, 0, len(objects))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
})
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "not after", []string{}, consolewasm.Permission{
|
|
AllowDownload: true,
|
|
AllowUpload: true,
|
|
AllowList: true,
|
|
AllowDelete: true,
|
|
NotAfter: time.Now().Add(-2 * time.Hour),
|
|
}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation should be restricted
|
|
_, err = project.CreateBucket(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket1, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DownloadObject(ctx, testbucket1, testfilename1, nil)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 0, len(buckets))
|
|
|
|
objects := getAllObjects(ctx, project, testbucket1)
|
|
require.Equal(t, 0, len(objects))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
})
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "not before", []string{}, consolewasm.Permission{
|
|
AllowDownload: true,
|
|
AllowUpload: true,
|
|
AllowList: true,
|
|
AllowDelete: true,
|
|
NotBefore: time.Now().Add(2 * time.Hour),
|
|
}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation should be restricted
|
|
_, err = project.CreateBucket(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket2, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DownloadObject(ctx, testbucket1, testfilename1, nil)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket2)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 0, len(buckets))
|
|
|
|
objects := getAllObjects(ctx, project, testbucket1)
|
|
require.Equal(t, 0, len(objects))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename1)
|
|
require.True(t, errors.Is(err, uplink.ErrPermissionDenied))
|
|
})
|
|
|
|
withAccessKey(ctx, t, planet, passphrase, b64Salt, "all", []string{}, consolewasm.Permission{
|
|
AllowDownload: true,
|
|
AllowUpload: true,
|
|
AllowList: true,
|
|
AllowDelete: true,
|
|
NotBefore: time.Now().Add(-24 * time.Hour),
|
|
NotAfter: time.Now().Add(24 * time.Hour),
|
|
}, func(t *testing.T, project *uplink.Project) {
|
|
// All operation allowed
|
|
_, err = project.CreateBucket(ctx, testbucket3)
|
|
require.NoError(t, err)
|
|
|
|
upload, err := project.UploadObject(ctx, testbucket3, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
_, err = upload.Write(testdata)
|
|
require.NoError(t, err)
|
|
err = upload.Commit()
|
|
require.NoError(t, err)
|
|
|
|
objects := getAllObjects(ctx, project, testbucket3)
|
|
require.Equal(t, 1, len(objects))
|
|
|
|
_, err = project.DownloadObject(ctx, testbucket3, testfilename2, nil)
|
|
require.NoError(t, err)
|
|
|
|
_, err = project.DeleteBucketWithObjects(ctx, testbucket3)
|
|
require.NoError(t, err)
|
|
|
|
buckets := getAllBuckets(ctx, project)
|
|
require.Equal(t, 2, len(buckets))
|
|
|
|
_, err = project.DeleteObject(ctx, testbucket1, testfilename1)
|
|
require.NoError(t, err)
|
|
})
|
|
|
|
})
|
|
}
|
|
|
|
func getAllObjects(ctx *testcontext.Context, project *uplink.Project, bucket string) []*uplink.Object {
|
|
var objects = []*uplink.Object{}
|
|
iter := project.ListObjects(ctx, bucket, nil)
|
|
for iter.Next() {
|
|
objects = append(objects, iter.Item())
|
|
}
|
|
return objects
|
|
}
|
|
|
|
func getAllBuckets(ctx *testcontext.Context, project *uplink.Project) []*uplink.Bucket {
|
|
var buckets = []*uplink.Bucket{}
|
|
iter := project.ListBuckets(ctx, nil)
|
|
for iter.Next() {
|
|
buckets = append(buckets, iter.Item())
|
|
}
|
|
return buckets
|
|
}
|
|
|
|
func withAccessKey(ctx *testcontext.Context, t *testing.T, planet *testplanet.Planet, passphrase, salt, testname string, bucket []string, permissions consolewasm.Permission, fn func(t *testing.T, uplink *uplink.Project)) {
|
|
t.Run(testname, func(t *testing.T) {
|
|
upl := planet.Uplinks[0]
|
|
sat := planet.Satellites[0]
|
|
|
|
apikey := upl.APIKey[sat.ID()]
|
|
restrictedKey, err := consolewasm.SetPermission(apikey.Serialize(), bucket, permissions)
|
|
require.NoError(t, err)
|
|
|
|
restrictedGrant, err := consolewasm.GenAccessGrant(sat.NodeURL().String(), restrictedKey.Serialize(), passphrase, salt)
|
|
require.NoError(t, err)
|
|
|
|
access, err := uplink.ParseAccess(restrictedGrant)
|
|
require.NoError(t, err)
|
|
|
|
project, err := uplink.OpenProject(ctx, access)
|
|
require.NoError(t, err)
|
|
|
|
defer func() { require.NoError(t, project.Close()) }()
|
|
fn(t, project)
|
|
})
|
|
}
|