// Copyright (C) 2020 Storj Labs, Inc. // See LICENSE for copying information. package consolewasm_test import ( "context" "errors" "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.String() 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) restrictedAccessGrant, err := consolewasm.GenAccessGrant(satelliteNodeURL, restrictedKey.Serialize(), passphrase, projectID) 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)) withAccessKey(ctx, t, planet, passphrase, "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, "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, "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, "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, "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, "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, "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 context.Context, t *testing.T, planet *testplanet.Planet, passphrase string, 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, upl.Projects[0].ID.String()) 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) }) }