storj/satellite/console/consolewasm/permission_test.go
Cameron 98fed4bc30 {satellite/console,web/satellite}: get project salt from satellite
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
2022-10-12 19:06:27 +00:00

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)
})
}