1489 lines
41 KiB
Go
1489 lines
41 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: MPL-2.0
|
|
|
|
package storage
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"context"
|
|
"crypto/sha256"
|
|
"fmt"
|
|
"os"
|
|
"path"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
"github.com/google/uuid"
|
|
internaltest "github.com/hashicorp/boundary-plugin-minio/internal/testing"
|
|
"github.com/hashicorp/boundary/sdk/pbs/controller/api/resources/storagebuckets"
|
|
"github.com/hashicorp/boundary/sdk/pbs/plugin"
|
|
"github.com/minio/madmin-go/v3"
|
|
"github.com/minio/minio-go/v7"
|
|
"github.com/minio/minio-go/v7/pkg/credentials"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"google.golang.org/grpc/codes"
|
|
"google.golang.org/grpc/status"
|
|
"google.golang.org/protobuf/testing/protocmp"
|
|
"google.golang.org/protobuf/types/known/structpb"
|
|
)
|
|
|
|
var (
|
|
policyDenyPutObject = []byte(`{
|
|
"Statement": [
|
|
{
|
|
"Action": [ "s3:*" ],
|
|
"Effect": "Allow",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [ "s3:PutObject" ],
|
|
"Effect": "Deny",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [
|
|
"admin:CreateServiceAccount",
|
|
"admin:RemoveServiceAccount"
|
|
],
|
|
"Effect": "Allow"
|
|
}
|
|
],
|
|
"Version": "2012-10-17"
|
|
}`)
|
|
policyDenyStatObject = []byte(`{
|
|
"Statement": [
|
|
{
|
|
"Action": [ "s3:*" ],
|
|
"Effect": "Allow",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [
|
|
"s3:GetObject",
|
|
"s3:GetObjectVersion",
|
|
"s3:GetObjectAttributes",
|
|
"s3:GetObjectVersionAttributes"
|
|
],
|
|
"Effect": "Deny",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [
|
|
"admin:CreateServiceAccount",
|
|
"admin:RemoveServiceAccount"
|
|
],
|
|
"Effect": "Allow"
|
|
}
|
|
],
|
|
"Version": "2012-10-17"
|
|
}`)
|
|
policyDenyListBucket = []byte(`{
|
|
"Statement": [
|
|
{
|
|
"Action": [ "s3:*" ],
|
|
"Effect": "Allow",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [ "s3:ListBucket" ],
|
|
"Effect": "Deny",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [
|
|
"admin:CreateServiceAccount",
|
|
"admin:RemoveServiceAccount"
|
|
],
|
|
"Effect": "Allow"
|
|
}
|
|
],
|
|
"Version": "2012-10-17"
|
|
}`)
|
|
policyDenyDeleteObject = []byte(`{
|
|
"Statement": [
|
|
{
|
|
"Action": [ "s3:*" ],
|
|
"Effect": "Allow",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [ "s3:DeleteObject" ],
|
|
"Effect": "Deny",
|
|
"Resource": "arn:aws:s3:::*"
|
|
},
|
|
{
|
|
"Action": [
|
|
"admin:CreateServiceAccount",
|
|
"admin:RemoveServiceAccount"
|
|
],
|
|
"Effect": "Allow"
|
|
}
|
|
],
|
|
"Version": "2012-10-17"
|
|
}`)
|
|
)
|
|
|
|
func TestOnCreateStorageBucket(t *testing.T) {
|
|
ctx := context.Background()
|
|
server := internaltest.NewMinioServer(t)
|
|
|
|
bucketName := "test-bucket"
|
|
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
minioClFn func(t *testing.T) *minio.Client
|
|
req *plugin.OnCreateStorageBucketRequest
|
|
expRsp *plugin.OnCreateStorageBucketResponse
|
|
expErrMsg string
|
|
}{
|
|
{
|
|
name: "nilBucket",
|
|
req: &plugin.OnCreateStorageBucketRequest{Bucket: nil},
|
|
expErrMsg: "no storage bucket information found",
|
|
},
|
|
{
|
|
name: "noBucketName",
|
|
req: &plugin.OnCreateStorageBucketRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: "",
|
|
},
|
|
},
|
|
expErrMsg: "storage bucket name is required",
|
|
},
|
|
{
|
|
name: "badStorageAttributes",
|
|
req: &plugin.OnCreateStorageBucketRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
},
|
|
},
|
|
expErrMsg: "empty attributes input",
|
|
},
|
|
{
|
|
name: "badStorageSecrets",
|
|
req: &plugin.OnCreateStorageBucketRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://foo"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expErrMsg: "empty secrets input",
|
|
},
|
|
{
|
|
name: "usingNonServiceAccount",
|
|
req: &plugin.OnCreateStorageBucketRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.RootUsername),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.RootPassword),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expErrMsg: "The specified service account is not found (Specified service account does not exist)",
|
|
},
|
|
{
|
|
name: "dryRunFail",
|
|
req: &plugin.OnCreateStorageBucketRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: func() map[string]*structpb.Value {
|
|
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyPutObject,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
|
|
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
|
|
}
|
|
}(),
|
|
},
|
|
},
|
|
},
|
|
expErrMsg: "failed to verify provided minio environment: failed to put object",
|
|
},
|
|
{
|
|
name: "success",
|
|
req: &plugin.OnCreateStorageBucketRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expRsp: &plugin.OnCreateStorageBucketResponse{
|
|
Persisted: &storagebuckets.StorageBucketPersisted{
|
|
Data: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sp := new(StoragePlugin)
|
|
rsp, err := sp.OnCreateStorageBucket(ctx, tt.req)
|
|
if tt.expErrMsg != "" {
|
|
require.ErrorContains(t, err, tt.expErrMsg)
|
|
require.Nil(t, rsp)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rsp)
|
|
require.Empty(t, cmp.Diff(tt.expRsp, rsp, protocmp.Transform()))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOnUpdateStorageBucket(t *testing.T) {
|
|
ctx := context.Background()
|
|
server := internaltest.NewMinioServer(t)
|
|
|
|
bucketName := "test-bucket"
|
|
require.NoError(t, server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{}))
|
|
|
|
tests := []struct {
|
|
name string
|
|
req *plugin.OnUpdateStorageBucketRequest
|
|
expected *plugin.OnUpdateStorageBucketResponse
|
|
err string
|
|
errCode codes.Code
|
|
}{
|
|
{
|
|
name: "nilNewBucket",
|
|
req: &plugin.OnUpdateStorageBucketRequest{},
|
|
err: "new bucket is required",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "emptyNewBucketName",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{},
|
|
},
|
|
err: "new bucketName is required",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "nilCurrentBucket",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
},
|
|
},
|
|
err: "current bucket is required",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "nilCurrentBucketAttributes",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{},
|
|
},
|
|
err: "empty attributes input",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "nilNewBucketAttributes",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Secrets: new(structpb.Struct),
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "empty attributes input",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "nilNewBucketSecrets",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "empty secrets input",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "errorReadingAttributes",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Secrets: new(structpb.Struct),
|
|
Attributes: new(structpb.Struct),
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "empty attributes input",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "errorReadingSecrets",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: new(structpb.Struct),
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "empty secrets input",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalidBucketNameUpdate",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo2",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "cannot update attribute BucketName",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalidBucketPrefixUpdate",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
BucketPrefix: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "cannot update attribute BucketPrefix",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalidRegionUpdate",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
"region": structpb.NewStringValue("a region"),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "cannot update attribute Region",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "invalidEndpointUrlUpdate",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://localhost:9000"),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: "foo",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "cannot update attribute EndpointUrl",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "usingNonServiceAccount",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.RootUsername),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.RootPassword),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
Persisted: &storagebuckets.StorageBucketPersisted{
|
|
Data: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.RootUsername),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.RootPassword),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "The specified service account is not found (Specified service account does not exist)",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "dryRunFail",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: func() map[string]*structpb.Value {
|
|
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyPutObject,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
|
|
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
|
|
}
|
|
}(),
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
Persisted: &storagebuckets.StorageBucketPersisted{
|
|
Data: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.RootUsername),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.RootPassword),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
err: "failed to verify provided minio environment: failed to put object",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "success",
|
|
req: &plugin.OnUpdateStorageBucketRequest{
|
|
NewBucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
CurrentBucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
},
|
|
Persisted: &storagebuckets.StorageBucketPersisted{
|
|
Data: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.RootUsername),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.RootPassword),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expected: &plugin.OnUpdateStorageBucketResponse{
|
|
Persisted: &storagebuckets.StorageBucketPersisted{
|
|
Data: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
require := require.New(t)
|
|
sp := new(StoragePlugin)
|
|
res, err := sp.OnUpdateStorageBucket(ctx, tt.req)
|
|
if tt.err != "" {
|
|
require.ErrorContains(err, tt.err)
|
|
require.Equal(tt.errCode.String(), status.Code(err).String())
|
|
require.Nil(res)
|
|
return
|
|
}
|
|
|
|
require.NoError(err)
|
|
require.NotNil(res)
|
|
require.EqualValues(tt.expected, res)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestOnDeleteStorageBucket(t *testing.T) {
|
|
sp := new(StoragePlugin)
|
|
rsp, err := sp.OnDeleteStorageBucket(context.Background(), &plugin.OnDeleteStorageBucketRequest{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rsp)
|
|
}
|
|
|
|
func TestValidatePermissions(t *testing.T) {
|
|
ctx := context.Background()
|
|
server := internaltest.NewMinioServer(t)
|
|
|
|
bucketName := "test-bucket"
|
|
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
minioClFn func(t *testing.T) *minio.Client
|
|
req *plugin.ValidatePermissionsRequest
|
|
expErrMsg string
|
|
}{
|
|
{
|
|
name: "nilBucket",
|
|
req: &plugin.ValidatePermissionsRequest{Bucket: nil},
|
|
expErrMsg: "no storage bucket information found",
|
|
},
|
|
{
|
|
name: "noBucketName",
|
|
req: &plugin.ValidatePermissionsRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: "",
|
|
},
|
|
},
|
|
expErrMsg: "storage bucket name is required",
|
|
},
|
|
{
|
|
name: "badStorageAttributes",
|
|
req: &plugin.ValidatePermissionsRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
},
|
|
},
|
|
expErrMsg: "empty attributes input",
|
|
},
|
|
{
|
|
name: "badStorageSecrets",
|
|
req: &plugin.ValidatePermissionsRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://foo"),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expErrMsg: "empty secrets input",
|
|
},
|
|
{
|
|
name: "usingNonServiceAccount",
|
|
req: &plugin.ValidatePermissionsRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.RootUsername),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.RootPassword),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
expErrMsg: "The specified service account is not found (Specified service account does not exist)",
|
|
},
|
|
{
|
|
name: "dryRunFail",
|
|
req: &plugin.ValidatePermissionsRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: func() map[string]*structpb.Value {
|
|
creds, err := server.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyPutObject,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(creds.AccessKey),
|
|
ConstSecretAccessKey: structpb.NewStringValue(creds.SecretKey),
|
|
}
|
|
}(),
|
|
},
|
|
},
|
|
},
|
|
expErrMsg: "failed to verify provided minio environment: failed to put object",
|
|
},
|
|
{
|
|
name: "success",
|
|
req: &plugin.ValidatePermissionsRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sp := new(StoragePlugin)
|
|
rsp, err := sp.ValidatePermissions(ctx, tt.req)
|
|
if tt.expErrMsg != "" {
|
|
require.ErrorContains(t, err, tt.expErrMsg)
|
|
require.Nil(t, rsp)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rsp)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHeadObject(t *testing.T) {
|
|
ctx := context.Background()
|
|
server := internaltest.NewMinioServer(t)
|
|
|
|
bucketName := "test-bucket"
|
|
err := server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
objectKey := fmt.Sprintf("test-head-object-%s", uuid.New().String())
|
|
rd := bytes.NewReader([]byte("test-head-object-contents"))
|
|
_, err = server.Client.PutObject(ctx, bucketName, objectKey, rd, rd.Size(), minio.PutObjectOptions{})
|
|
require.NoError(t, err)
|
|
|
|
bucketPrefix := "prefix"
|
|
rd2 := bytes.NewReader([]byte("test-head-object-contents"))
|
|
_, err = server.Client.PutObject(ctx, bucketName, path.Join(bucketPrefix, objectKey), rd2, rd2.Size(), minio.PutObjectOptions{})
|
|
require.NoError(t, err)
|
|
|
|
tests := []struct {
|
|
name string
|
|
req *plugin.HeadObjectRequest
|
|
expErrMsg string
|
|
}{
|
|
{
|
|
name: "nilBucket",
|
|
req: &plugin.HeadObjectRequest{Bucket: nil},
|
|
expErrMsg: "no storage bucket information found",
|
|
},
|
|
{
|
|
name: "emptyBucket",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{},
|
|
},
|
|
expErrMsg: "storage bucket name is required",
|
|
},
|
|
{
|
|
name: "emptyObjectKey",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
},
|
|
Key: "",
|
|
},
|
|
expErrMsg: "object key is required",
|
|
},
|
|
{
|
|
name: "badStorageAttributes",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"endpoint_url": structpb.NewStringValue("foo"),
|
|
},
|
|
},
|
|
},
|
|
Key: "object-key",
|
|
},
|
|
expErrMsg: "attributes.endpoint_url.format: unknown protocol, should be http:// or https://",
|
|
},
|
|
{
|
|
name: "badStorageSecrets",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"endpoint_url": structpb.NewStringValue("http://foo"),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"access_key_id": structpb.NewStringValue("foo"),
|
|
},
|
|
},
|
|
},
|
|
Key: "object-key",
|
|
},
|
|
expErrMsg: "secrets.secret_access_key: missing required value \"secret_access_key\"",
|
|
},
|
|
{
|
|
name: "statObjectFail",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://localhost:1"),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue("foo"),
|
|
ConstSecretAccessKey: structpb.NewStringValue("bar"),
|
|
},
|
|
},
|
|
},
|
|
Key: "object-key",
|
|
},
|
|
expErrMsg: "failed to stat object",
|
|
},
|
|
{
|
|
name: "nonExistantKey",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: "doesnt-exist",
|
|
},
|
|
expErrMsg: "failed to stat object: The specified key does not exist.",
|
|
},
|
|
{
|
|
name: "success",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: objectKey,
|
|
},
|
|
},
|
|
{
|
|
name: "successWithBucketPrefix",
|
|
req: &plugin.HeadObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
BucketPrefix: bucketPrefix,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: objectKey,
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sp := new(StoragePlugin)
|
|
rsp, err := sp.HeadObject(ctx, tt.req)
|
|
if tt.expErrMsg != "" {
|
|
require.ErrorContains(t, err, tt.expErrMsg)
|
|
require.Nil(t, rsp)
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
require.NotNil(t, rsp)
|
|
require.Equal(t, rd.Size(), rsp.GetContentLength())
|
|
require.NotEmpty(t, rsp.GetLastModified().AsTime())
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGetObject(t *testing.T) {}
|
|
|
|
func TestPutObject(t *testing.T) {
|
|
td := t.TempDir()
|
|
|
|
dirPath := func(p string) string {
|
|
return path.Join(td, p)
|
|
}
|
|
|
|
ctx := context.Background()
|
|
server := internaltest.NewMinioServer(t)
|
|
|
|
bucketName := "test-bucket"
|
|
require.NoError(t, server.Client.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{}))
|
|
|
|
cases := []struct {
|
|
name string
|
|
req *plugin.PutObjectRequest
|
|
setup func(*plugin.PutObjectRequest) (string, error)
|
|
err string
|
|
errCode codes.Code
|
|
}{
|
|
{
|
|
name: "nilRequest",
|
|
err: "no storage bucket information found",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "nilBucket",
|
|
req: &plugin.PutObjectRequest{Bucket: nil},
|
|
err: "no storage bucket information found",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "emptyBucket",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{},
|
|
},
|
|
err: "storage bucket name is required",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "emptyObjectKey",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
},
|
|
Key: "",
|
|
},
|
|
err: "object key is required",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "emptyFilePath",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
},
|
|
Key: "test-key",
|
|
Path: "",
|
|
},
|
|
err: "path is required",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "badStorageAttributes",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"endpoint_url": structpb.NewStringValue("foo"),
|
|
},
|
|
},
|
|
},
|
|
Key: "object-key",
|
|
Path: "file-path",
|
|
},
|
|
err: "attributes.endpoint_url.format: unknown protocol, should be http:// or https://",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "badStorageSecrets",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"endpoint_url": structpb.NewStringValue("http://foo"),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
"access_key_id": structpb.NewStringValue("foo"),
|
|
},
|
|
},
|
|
},
|
|
Key: "object-key",
|
|
Path: "file-path",
|
|
},
|
|
err: "secrets.secret_access_key: missing required value \"secret_access_key\"",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "emptyFile",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: "test-key",
|
|
Path: dirPath("empty-test-file"),
|
|
},
|
|
setup: func(req *plugin.PutObjectRequest) (string, error) {
|
|
file, err := os.Create(req.Path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
file.Close()
|
|
return "", nil
|
|
},
|
|
err: "file is empty",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "pathIsDir",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: "test-key",
|
|
Path: td,
|
|
},
|
|
err: "path is not a file",
|
|
errCode: codes.InvalidArgument,
|
|
},
|
|
{
|
|
name: "success",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: "test-key",
|
|
Path: dirPath("test-file"),
|
|
},
|
|
setup: func(req *plugin.PutObjectRequest) (string, error) {
|
|
file, err := os.Create(req.Path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
data := "test file data"
|
|
|
|
if _, err = file.WriteString(data); err != nil {
|
|
return "", err
|
|
}
|
|
return data, nil
|
|
},
|
|
},
|
|
{
|
|
name: "successWithBucketPrefix",
|
|
req: &plugin.PutObjectRequest{
|
|
Bucket: &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
BucketPrefix: "test-prefix",
|
|
Attributes: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstEndpointUrl: structpb.NewStringValue("http://" + server.ApiAddr),
|
|
},
|
|
},
|
|
Secrets: &structpb.Struct{
|
|
Fields: map[string]*structpb.Value{
|
|
ConstAccessKeyId: structpb.NewStringValue(server.ServiceAccountAccessKeyId),
|
|
ConstSecretAccessKey: structpb.NewStringValue(server.ServiceAccountSecretAccessKey),
|
|
},
|
|
},
|
|
},
|
|
Key: "test-key-with-prefix",
|
|
Path: dirPath("test-file-with-prefix"),
|
|
},
|
|
setup: func(req *plugin.PutObjectRequest) (string, error) {
|
|
file, err := os.Create(req.Path)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
defer file.Close()
|
|
|
|
data := "test file data for bucket with prefix"
|
|
|
|
if _, err = file.WriteString(data); err != nil {
|
|
return "", err
|
|
}
|
|
return data, nil
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range cases {
|
|
tt := tt
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
assert, require := assert.New(t), require.New(t)
|
|
var checksum []byte
|
|
var content string
|
|
if tt.setup != nil {
|
|
var err error
|
|
content, err = tt.setup(tt.req)
|
|
require.NoError(err)
|
|
hash := sha256.New()
|
|
_, err = hash.Write([]byte(content))
|
|
require.NoError(err)
|
|
checksum = hash.Sum(nil)
|
|
require.NoError(err)
|
|
}
|
|
|
|
sp := new(StoragePlugin)
|
|
res, err := sp.PutObject(ctx, tt.req)
|
|
|
|
if tt.err != "" {
|
|
assert.ErrorContains(err, tt.err)
|
|
assert.Equal(tt.errCode.String(), status.Code(err).String())
|
|
return
|
|
}
|
|
|
|
require.NotNil(res)
|
|
assert.NotEmpty(res.GetChecksumSha_256())
|
|
assert.Equal(checksum, res.ChecksumSha_256)
|
|
|
|
// check the actual file content is the same
|
|
cl, err := minio.New(server.ApiAddr, &minio.Options{
|
|
Creds: credentials.NewStaticV4(server.RootUsername, server.RootPassword, ""),
|
|
Secure: false,
|
|
})
|
|
require.NoError(err)
|
|
obj, err := cl.GetObject(ctx, bucketName, path.Join(tt.req.Bucket.GetBucketPrefix(), tt.req.GetKey()), minio.GetObjectOptions{})
|
|
require.NoError(err)
|
|
defer obj.Close()
|
|
|
|
contentLen := len([]byte(content))
|
|
reader := bufio.NewReader(obj)
|
|
buffer := make([]byte, contentLen)
|
|
n, err := reader.Read(buffer)
|
|
require.NoError(err)
|
|
|
|
assert.Equal(contentLen, n)
|
|
assert.Equal(content, string(buffer))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDeleteObjects(t *testing.T) {}
|
|
|
|
func TestDryRun(t *testing.T) {
|
|
ctx := context.Background()
|
|
|
|
tests := []struct {
|
|
name string
|
|
minioClFn func(t *testing.T, s *internaltest.MinioServer) *minio.Client
|
|
makeBucketFn func(t *testing.T, cl *minio.Client) string
|
|
// This test verifies that each message in the err slice is contained in
|
|
// the error string, effectively &&-ing the checks together.
|
|
expErrMsgs []string
|
|
}{
|
|
{
|
|
name: "bucketNotExists",
|
|
makeBucketFn: func(t *testing.T, cl *minio.Client) string { return "unknownbucket" },
|
|
expErrMsgs: []string{"The specified bucket does not exist"},
|
|
},
|
|
{
|
|
name: "putObjectFail",
|
|
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
|
|
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyPutObject,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
cl, err := minio.New(s.ApiAddr, &minio.Options{
|
|
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
|
|
Secure: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return cl
|
|
},
|
|
makeBucketFn: func(t *testing.T, cl *minio.Client) string {
|
|
bucketName := "testdryrun-putobjectfail"
|
|
err := cl.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
return bucketName
|
|
},
|
|
expErrMsgs: []string{
|
|
"failed to put object",
|
|
"Access Denied",
|
|
},
|
|
},
|
|
{
|
|
name: "statObjectFail",
|
|
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
|
|
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyStatObject,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
cl, err := minio.New(s.ApiAddr, &minio.Options{
|
|
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
|
|
Secure: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return cl
|
|
},
|
|
makeBucketFn: func(t *testing.T, cl *minio.Client) string {
|
|
bucketName := "testdryrun-statobjectfail"
|
|
err := cl.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
return bucketName
|
|
},
|
|
expErrMsgs: []string{
|
|
"failed to stat object",
|
|
"Access Denied",
|
|
},
|
|
},
|
|
{
|
|
name: "listObjectsFail",
|
|
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
|
|
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyListBucket,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
cl, err := minio.New(s.ApiAddr, &minio.Options{
|
|
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
|
|
Secure: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return cl
|
|
},
|
|
makeBucketFn: func(t *testing.T, cl *minio.Client) string {
|
|
bucketName := "testdryrun-listobjectsfail"
|
|
err := cl.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
return bucketName
|
|
},
|
|
expErrMsgs: []string{
|
|
"failed to list objects in bucket",
|
|
"Access Denied",
|
|
},
|
|
},
|
|
{
|
|
name: "removeObjectFail",
|
|
minioClFn: func(t *testing.T, s *internaltest.MinioServer) *minio.Client {
|
|
creds, err := s.AdminClient.AddServiceAccount(ctx, madmin.AddServiceAccountReq{
|
|
Policy: policyDenyDeleteObject,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
cl, err := minio.New(s.ApiAddr, &minio.Options{
|
|
Creds: credentials.NewStaticV4(creds.AccessKey, creds.SecretKey, ""),
|
|
Secure: false,
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
return cl
|
|
},
|
|
makeBucketFn: func(t *testing.T, cl *minio.Client) string {
|
|
bucketName := "testdryrun-removeobjectfail"
|
|
err := cl.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
|
|
return bucketName
|
|
},
|
|
expErrMsgs: []string{
|
|
"failed to remove object",
|
|
"Access Denied",
|
|
},
|
|
},
|
|
{
|
|
name: "success",
|
|
makeBucketFn: func(t *testing.T, cl *minio.Client) string {
|
|
bucketName := "testdryrun-success"
|
|
err := cl.MakeBucket(ctx, bucketName, minio.MakeBucketOptions{})
|
|
require.NoError(t, err)
|
|
return bucketName
|
|
},
|
|
},
|
|
}
|
|
|
|
server := internaltest.NewMinioServer(t)
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
minioClient := server.Client
|
|
if tt.minioClFn != nil {
|
|
minioClient = tt.minioClFn(t, server)
|
|
}
|
|
|
|
bucketName := tt.makeBucketFn(t, minioClient)
|
|
err := dryRun(ctx, minioClient, &storagebuckets.StorageBucket{
|
|
BucketName: bucketName,
|
|
})
|
|
if len(tt.expErrMsgs) > 0 {
|
|
for _, errMsg := range tt.expErrMsgs {
|
|
require.ErrorContains(t, err, errMsg)
|
|
}
|
|
return
|
|
}
|
|
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestEnsureServiceAccount(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
saFunc func(s *internaltest.MinioServer) (*StorageAttributes, *StorageSecrets)
|
|
expErrMsg string
|
|
}{
|
|
{
|
|
name: "nonServiceAccount",
|
|
saFunc: func(s *internaltest.MinioServer) (*StorageAttributes, *StorageSecrets) {
|
|
return &StorageAttributes{
|
|
EndpointUrl: s.ApiAddr,
|
|
UseSSL: false,
|
|
}, &StorageSecrets{
|
|
AccessKeyId: s.RootUsername,
|
|
SecretAccessKey: s.RootPassword,
|
|
}
|
|
},
|
|
expErrMsg: "The specified service account is not found (Specified service account does not exist)",
|
|
},
|
|
{
|
|
name: "serviceAccount",
|
|
saFunc: func(s *internaltest.MinioServer) (*StorageAttributes, *StorageSecrets) {
|
|
return &StorageAttributes{
|
|
EndpointUrl: s.ApiAddr,
|
|
UseSSL: false,
|
|
}, &StorageSecrets{
|
|
AccessKeyId: s.ServiceAccountAccessKeyId,
|
|
SecretAccessKey: s.ServiceAccountSecretAccessKey,
|
|
}
|
|
},
|
|
},
|
|
}
|
|
|
|
server := internaltest.NewMinioServer(t)
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
sa, sec := tt.saFunc(server)
|
|
err := ensureServiceAccount(context.Background(), sa, sec)
|
|
if tt.expErrMsg != "" {
|
|
require.ErrorContains(t, err, tt.expErrMsg)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
}
|