Skip to content

Commit 963c1b5

Browse files
minio multitenancy
1 parent c516e14 commit 963c1b5

File tree

6 files changed

+549
-237
lines changed

6 files changed

+549
-237
lines changed

pkg/handlers/create.go

Lines changed: 160 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -110,20 +110,8 @@ func MakeCreateHandler(cfg *types.Config, back types.ServerlessBackend) gin.Hand
110110

111111
if len(service.AllowedUsers) > 0 {
112112
// If AllowedUsers is empty don't add uid
113-
service.Labels["uid"] = full_uid[0:8]
114-
115-
// If the uid of the owner is not on the allowed_users list append it
116-
ownerOnList := false
117-
for _, user := range service.AllowedUsers {
118-
if user == service.Owner {
119-
ownerOnList = true
120-
break
121-
}
122-
}
123-
if !ownerOnList {
124-
service.AllowedUsers = append(service.AllowedUsers, uid)
125-
}
126-
// Check if the uid's from allowed_users have and associated MinIO user
113+
service.Labels["uid"] = full_uid[:10]
114+
// Check if the uid's from allowed_users have and asociated MinIO user
127115
// and create it if not
128116
uids := mc.CheckUsersInCache(service.AllowedUsers)
129117
if len(uids) > 0 {
@@ -133,6 +121,39 @@ func MakeCreateHandler(cfg *types.Config, back types.ServerlessBackend) gin.Hand
133121
mc.CreateSecretForOIDC(uid, sk)
134122
}
135123
}
124+
125+
path := strings.Trim(service.Input[0].Path, "/")
126+
splitPath := strings.SplitN(path, "/", 2)
127+
128+
ownerOnList := false
129+
// Create service bucket list if isolation_level = user
130+
if strings.ToUpper(service.IsolationLevel) == "USER" {
131+
var userBucket string
132+
for _, user := range service.AllowedUsers {
133+
134+
// Check the uid of the owner is on the allowed_users list
135+
if user == service.Owner {
136+
ownerOnList = true
137+
}
138+
// Fill the list of private buckets to create
139+
userBucket = splitPath[0] + "-" + user[:10]
140+
service.BucketList = append(service.BucketList, userBucket)
141+
}
142+
} else {
143+
index := 0
144+
for !ownerOnList {
145+
// Check the uid of the owner is on the allowed_users list
146+
// If isolation level is not user the list may not need to be go through fully
147+
if service.AllowedUsers[index] == service.Owner {
148+
ownerOnList = true
149+
}
150+
index++
151+
}
152+
}
153+
if !ownerOnList {
154+
service.AllowedUsers = append(service.AllowedUsers, uid)
155+
}
156+
136157
}
137158
}
138159

@@ -155,7 +176,7 @@ func MakeCreateHandler(cfg *types.Config, back types.ServerlessBackend) gin.Hand
155176
}
156177

157178
// Create buckets/folders based on the Input and Output and enable notifications
158-
if err := createBuckets(&service, cfg, minIOAdminClient, service.AllowedUsers, false); err != nil {
179+
if err := createBuckets(&service, cfg, minIOAdminClient, false); err != nil {
159180
if err == errInput {
160181
c.String(http.StatusBadRequest, err.Error())
161182
} else {
@@ -232,12 +253,16 @@ func checkValues(service *types.Service, cfg *types.Config) {
232253
service.Token = utils.GenerateToken()
233254
}
234255

235-
func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *utils.MinIOAdminClient, allowed_users []string, isUpdate bool) error {
256+
func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *utils.MinIOAdminClient, isUpdate bool) error {
236257
var s3Client *s3.S3
237258
var cdmiClient *cdmi.Client
238259
var provName, provID string
239260

261+
// Create private buckets
262+
263+
// Create shared buckets (if defined)
240264
// Create input buckets
265+
createLogger.Printf("Creating input buckets ..")
241266
for _, in := range service.Input {
242267
provID, provName = getProviderInfo(in.Provider)
243268

@@ -269,10 +294,11 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *
269294
path := strings.Trim(in.Path, " /")
270295
// Split buckets and folders from path
271296
splitPath := strings.SplitN(path, "/", 2)
272-
// Create bucket
297+
273298
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
274299
Bucket: aws.String(splitPath[0]),
275300
})
301+
276302
if err != nil {
277303
if aerr, ok := err.(awserr.Error); ok {
278304
// Check if the error is caused because the bucket already exists
@@ -286,13 +312,66 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *
286312
}
287313
}
288314

315+
// Create generic folder(s)
316+
var folderKey string
317+
if len(splitPath) == 2 {
318+
// Add "/" to the end of the key in order to create a folder
319+
folderKey = fmt.Sprintf("%s/", splitPath[1])
320+
_, err := s3Client.PutObject(&s3.PutObjectInput{
321+
Bucket: aws.String(splitPath[0]),
322+
Key: aws.String(folderKey),
323+
})
324+
if err != nil {
325+
return fmt.Errorf("error creating folder \"%s\" in bucket \"%s\": %v", folderKey, splitPath[0], err)
326+
}
327+
// Enable MinIO notifications based on the Input []StorageIOConfig
328+
if err := enableInputNotification(s3Client, service.GetMinIOWebhookARN(), splitPath[0], folderKey); err != nil {
329+
return err
330+
}
331+
}
332+
333+
if strings.ToUpper(service.IsolationLevel) == "USER" && len(service.BucketList) > 0 {
334+
for i, b := range service.BucketList {
335+
// Create a bucket for each allowed user if allowed_users is not empty
336+
_, err = s3Client.CreateBucket(&s3.CreateBucketInput{
337+
Bucket: aws.String(b),
338+
})
339+
if err != nil {
340+
if aerr, ok := err.(awserr.Error); ok {
341+
// Check if the error is caused because the bucket already exists
342+
if aerr.Code() == s3.ErrCodeBucketAlreadyExists || aerr.Code() == s3.ErrCodeBucketAlreadyOwnedByYou {
343+
log.Printf("The bucket \"%s\" already exists\n", b)
344+
} else {
345+
return fmt.Errorf("error creating bucket %s: %v", b, err)
346+
}
347+
} else {
348+
return fmt.Errorf("error creating bucket %s: %v", b, err)
349+
}
350+
}
351+
_, err := s3Client.PutObject(&s3.PutObjectInput{
352+
Bucket: aws.String(b),
353+
Key: aws.String(folderKey),
354+
})
355+
if err != nil {
356+
return fmt.Errorf("error creating folder \"%s\" in bucket \"%s\": %v", folderKey, b, err)
357+
}
358+
if err := enableInputNotification(s3Client, service.GetMinIOWebhookARN(), b, folderKey); err != nil {
359+
return err
360+
}
361+
362+
if !isAdminUser {
363+
minIOAdminClient.CreateAddPolicy(b, service.AllowedUsers[i], false)
364+
}
365+
}
366+
}
367+
289368
// Create group for the service and add users
290369
// Check if users in allowed_users have a MinIO associated user
291-
// If new allowed users list is empty the service becomes public
370+
// If new allowed users list is empty the service becames public
292371
if !isUpdate {
293372
if !isAdminUser {
294-
if len(allowed_users) == 0 {
295-
err = minIOAdminClient.AddServiceToAllUsersGroup(splitPath[0])
373+
if len(service.AllowedUsers) == 0 {
374+
err = minIOAdminClient.CreateAddPolicy(splitPath[0], ALL_USERS_GROUP, true)
296375
if err != nil {
297376
return fmt.Errorf("error adding service %s to all users group: %v", splitPath[0], err)
298377
}
@@ -302,38 +381,26 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *
302381
return fmt.Errorf("error creating service group for bucket %s: %v", splitPath[0], err)
303382
}
304383

305-
err = minIOAdminClient.UpdateUsersInGroup(allowed_users, splitPath[0], false)
384+
err = minIOAdminClient.UpdateUsersInGroup(service.AllowedUsers, splitPath[0], false)
385+
if err != nil {
386+
return err
387+
}
388+
err = minIOAdminClient.CreateAddPolicy(splitPath[0], splitPath[0], true)
306389
if err != nil {
307390
return err
308391
}
309392
}
310393
}
311394
}
312-
// Create folder(s)
313-
if len(splitPath) == 2 {
314-
// Add "/" to the end of the key in order to create a folder
315-
folderKey := fmt.Sprintf("%s/", splitPath[1])
316-
_, err := s3Client.PutObject(&s3.PutObjectInput{
317-
Bucket: aws.String(splitPath[0]),
318-
Key: aws.String(folderKey),
319-
})
320-
if err != nil {
321-
return fmt.Errorf("error creating folder \"%s\" in bucket \"%s\": %v", folderKey, splitPath[0], err)
322-
}
323-
}
324-
325-
// Enable MinIO notifications based on the Input []StorageIOConfig
326-
if err := enableInputNotification(s3Client, service.GetMinIOWebhookARN(), in); err != nil {
327-
return err
328-
}
329395
}
330-
396+
createLogger.Printf("Creating output buckets ..")
331397
// Create output buckets
332398
for _, out := range service.Output {
333399
provID, provName = getProviderInfo(out.Provider)
334400
// Check if the provider identifier is defined in StorageProviders
335401
if !isStorageProviderDefined(provName, provID, service.StorageProviders) {
336-
disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
402+
// TODO fix
403+
disableInputNotifications(s3Client, service.GetMinIOWebhookARN(), "")
337404
return fmt.Errorf("the StorageProvider \"%s.%s\" is not defined", provName, provID)
338405
}
339406

@@ -350,44 +417,63 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *
350417
s3Client = service.StorageProviders.S3[provID].GetS3Client()
351418
}
352419
// Create bucket
353-
_, err := s3Client.CreateBucket(&s3.CreateBucketInput{
354-
Bucket: aws.String(splitPath[0]),
355-
})
356-
if err != nil {
357-
if aerr, ok := err.(awserr.Error); ok {
358-
// Check if the error is caused because the bucket already exists
359-
if aerr.Code() == s3.ErrCodeBucketAlreadyExists || aerr.Code() == s3.ErrCodeBucketAlreadyOwnedByYou {
360-
log.Printf("The bucket \"%s\" already exists\n", splitPath[0])
361-
} else {
362-
disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
363-
return fmt.Errorf("error creating bucket %s: %v", splitPath[0], err)
364-
}
365-
} else {
366-
disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
367-
return fmt.Errorf("error creating bucket %s: %v", splitPath[0], err)
368-
}
369-
}
420+
// ==== TODO check if is necessary repeat the create bucket code ====
421+
// _, err := s3Client.CreateBucket(&s3.CreateBucketInput{
422+
// Bucket: aws.String(splitPath[0]),
423+
// })
424+
425+
// // TODO Add disable notifications in case of an error for every bucket on service.BucketList
426+
// if err != nil {
427+
// if aerr, ok := err.(awserr.Error); ok {
428+
// // Check if the error is caused because the bucket already exists
429+
// if aerr.Code() == s3.ErrCodeBucketAlreadyExists || aerr.Code() == s3.ErrCodeBucketAlreadyOwnedByYou {
430+
// log.Printf("The bucket \"%s\" already exists\n", splitPath[0])
431+
// } else {
432+
// disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
433+
// return fmt.Errorf("error creating bucket %s: %v", splitPath[0], err)
434+
// }
435+
// } else {
436+
// disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
437+
// return fmt.Errorf("error creating bucket %s: %v", splitPath[0], err)
438+
// }
439+
// }
370440
// Create folder(s)
441+
var folderKey string
371442
if len(splitPath) == 2 {
372443
// Add "/" to the end of the key in order to create a folder
373-
folderKey := fmt.Sprintf("%s/", splitPath[1])
444+
folderKey = fmt.Sprintf("%s/", splitPath[1])
374445
_, err := s3Client.PutObject(&s3.PutObjectInput{
375446
Bucket: aws.String(splitPath[0]),
376447
Key: aws.String(folderKey),
377448
})
378449
if err != nil {
379-
disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
450+
disableInputNotifications(s3Client, service.GetMinIOWebhookARN(), splitPath[0])
380451
return fmt.Errorf("error creating folder \"%s\" in bucket \"%s\": %v", folderKey, splitPath[0], err)
381452
}
382453
}
454+
455+
if service.IsolationLevel == "USER" && len(service.BucketList) > 0 {
456+
for _, b := range service.BucketList {
457+
_, err := s3Client.PutObject(&s3.PutObjectInput{
458+
Bucket: aws.String(b),
459+
Key: aws.String(folderKey),
460+
})
461+
if err != nil {
462+
return fmt.Errorf("error creating folder \"%s\" in bucket \"%s\": %v", folderKey, b, err)
463+
}
464+
465+
}
466+
}
467+
383468
case types.OnedataName:
384469
cdmiClient = service.StorageProviders.Onedata[provID].GetCDMIClient()
385470
err := cdmiClient.CreateContainer(fmt.Sprintf("%s/%s", service.StorageProviders.Onedata[provID].Space, path), true)
386471
if err != nil {
387472
if err == cdmi.ErrBadRequest {
388473
log.Printf("Error creating \"%s\" folder in Onedata. Error: %v\n", path, err)
389474
} else {
390-
disableInputNotifications(service.GetMinIOWebhookARN(), service.Input, cfg.MinIOProvider)
475+
// TODO fix
476+
disableInputNotifications(s3Client, service.GetMinIOWebhookARN(), "")
391477
return fmt.Errorf("error connecting to Onedata's Oneprovider \"%s\". Error: %v", service.StorageProviders.Onedata[provID].OneproviderHost, err)
392478
}
393479
}
@@ -431,7 +517,7 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *
431517
}
432518
if !isUpdate {
433519
if !isAdminUser {
434-
if len(allowed_users) == 0 {
520+
if len(service.AllowedUsers) == 0 {
435521
err = minIOAdminClient.AddServiceToAllUsersGroup(splitPath[0])
436522
if err != nil {
437523
return fmt.Errorf("error adding service %s to all users group: %v", splitPath[0], err)
@@ -442,7 +528,7 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient *
442528
return fmt.Errorf("error creating service group for bucket %s: %v", splitPath[0], err)
443529
}
444530

445-
err = minIOAdminClient.UpdateUsersInGroup(allowed_users, splitPath[0], false)
531+
err = minIOAdminClient.UpdateUsersInGroup(service.AllowedUsers, splitPath[0], false)
446532
if err != nil {
447533
return err
448534
}
@@ -530,32 +616,32 @@ func registerMinIOWebhook(name string, token string, minIO *types.MinIOProvider,
530616
return minIOAdminClient.RestartServer()
531617
}
532618

533-
func enableInputNotification(minIOClient *s3.S3, arnStr string, input types.StorageIOConfig) error {
534-
path := strings.Trim(input.Path, " /")
535-
// Split buckets and folders from path
536-
splitPath := strings.SplitN(path, "/", 2)
537-
619+
// TODO pass the user UID string
620+
func enableInputNotification(minIOClient *s3.S3, arnStr string, bucket string, path string) error {
621+
// path := strings.Trim(input.Path, " /")
622+
// // Split buckets and folders from path
623+
// splitPath := strings.SplitN(path, "/", 2)
538624
// Get current BucketNotificationConfiguration
539625
gbncRequest := &s3.GetBucketNotificationConfigurationRequest{
540-
Bucket: aws.String(splitPath[0]),
626+
Bucket: aws.String(bucket),
541627
}
542628
nCfg, err := minIOClient.GetBucketNotificationConfiguration(gbncRequest)
543629
if err != nil {
544-
return fmt.Errorf("error getting bucket \"%s\" notifications: %v", splitPath[0], err)
630+
return fmt.Errorf("error getting bucket \"%s\" notifications: %v", bucket, err)
545631
}
546632
queueConfiguration := s3.QueueConfiguration{
547633
QueueArn: aws.String(arnStr),
548634
Events: []*string{aws.String(s3.EventS3ObjectCreated)},
549635
}
550636

551637
// Add folder filter if required
552-
if len(splitPath) == 2 {
638+
if path != "" {
553639
queueConfiguration.Filter = &s3.NotificationConfigurationFilter{
554640
Key: &s3.KeyFilter{
555641
FilterRules: []*s3.FilterRule{
556642
{
557643
Name: aws.String(s3.FilterRuleNamePrefix),
558-
Value: aws.String(fmt.Sprintf("%s/", splitPath[1])),
644+
Value: aws.String(path),
559645
},
560646
},
561647
},
@@ -565,13 +651,14 @@ func enableInputNotification(minIOClient *s3.S3, arnStr string, input types.Stor
565651
// Append the new queueConfiguration
566652
nCfg.QueueConfigurations = append(nCfg.QueueConfigurations, &queueConfiguration)
567653
pbncInput := &s3.PutBucketNotificationConfigurationInput{
568-
Bucket: aws.String(splitPath[0]),
654+
Bucket: aws.String(bucket),
569655
NotificationConfiguration: nCfg,
570656
}
571657

572658
// Enable the notification
573659
_, err = minIOClient.PutBucketNotificationConfiguration(pbncInput)
574-
if err != nil {
660+
661+
if err != nil && !strings.Contains(err.Error(), "An object key name filtering rule defined with overlapping prefixes") {
575662
return fmt.Errorf("error enabling bucket notification: %v", err)
576663
}
577664

0 commit comments

Comments
 (0)