storj/satellite/console/consolewasm/permission_test.go
Egon Elbre 049953a7ce go.mod: bump storj.io/uplink
Change-Id: Ib535cb50195c5a0e6197e83e2e05865ba95bcaf8
2023-06-26 13:51:13 +00:00

419 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))
download, err := project.DownloadObject(ctx, testbucket1, testfilename1, nil)
require.NoError(t, err)
require.NoError(t, download.Close())
// 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))
download, err := project.DownloadObject(ctx, testbucket3, testfilename2, nil)
require.NoError(t, err)
require.NoError(t, download.Close())
_, 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)
})
}