From 62de9664e90da00f6e960b6146d0b95d87716bcf Mon Sep 17 00:00:00 2001 From: catttam Date: Wed, 31 Jan 2024 10:23:38 +0100 Subject: [PATCH] Changes for OSCAR MinIO user --- pkg/handlers/create.go | 139 +++++++++++++++++++-------------- pkg/handlers/list.go | 41 ++++++---- pkg/handlers/read.go | 44 ++++++----- pkg/handlers/update.go | 50 ++++++------ pkg/utils/auth/auth.go | 6 +- pkg/utils/auth/multitenancy.go | 19 +++++ pkg/utils/minio.go | 8 ++ 7 files changed, 189 insertions(+), 118 deletions(-) diff --git a/pkg/handlers/create.go b/pkg/handlers/create.go index e541114a..8d4bbce0 100644 --- a/pkg/handlers/create.go +++ b/pkg/handlers/create.go @@ -46,70 +46,90 @@ var errInput = errors.New("unrecognized input (valid inputs are MinIO and dCache // Custom logger var createLogger = log.New(os.Stdout, "[CREATE] ", log.Flags()) +var isAdminUser = false // MakeCreateHandler makes a handler for creating services func MakeCreateHandler(cfg *types.Config, back types.ServerlessBackend) gin.HandlerFunc { return func(c *gin.Context) { var service types.Service + authHeader := c.GetHeader("Authorization") + if len(strings.Split(authHeader, "Bearer")) > 0 { + isAdminUser = true + } - uidOrigin, uidExists := c.Get("uidOrigin") - mcUntyped, mcExists := c.Get("multitenancyConfig") + // Check service values and set defaults + checkValues(&service, cfg) - if !mcExists { - c.String(http.StatusInternalServerError, fmt.Sprintln("Missing multitenancy config")) - } - if !uidExists { - c.String(http.StatusInternalServerError, fmt.Sprintln("Missing EGI user uid")) - } + /////////////////////////////////// + ////////////// here /////////////// + /////////////////////////////////// + // Check if users in allowed_users have a MinIO associated user + minIOAdminClient, _ := utils.MakeMinIOAdminClient(cfg) - mc, mcParsed := mcUntyped.(*auth.MultitenancyConfig) - uid, uidParsed := uidOrigin.(string) + // Service is created by an EGI user + if !isAdminUser { - if !mcParsed { - c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing multitenancy config: %v", mcParsed)) - return - } + uidOrigin, uidExists := c.Get("uidOrigin") + mcUntyped, mcExists := c.Get("multitenancyConfig") - createLogger.Println("Multitenancy config: ", mc) + if !mcExists { + c.String(http.StatusInternalServerError, "Missing multitenancy config") + } - if !uidParsed { - c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing uid origin: %v", uidParsed)) - return - } + if !uidExists { + c.String(http.StatusInternalServerError, "Missing EGI user uid") + } - if err := c.ShouldBindJSON(&service); err != nil { - c.String(http.StatusBadRequest, fmt.Sprintf("The service specification is not valid: %v", err)) - return - } + mc, mcParsed := mcUntyped.(*auth.MultitenancyConfig) + uid, uidParsed := uidOrigin.(string) - // Check service values and set defaults - checkValues(&service, cfg) - full_uid := auth.FormatUID(uid) - // Check if the service VO is present on the cluster VO's and if the user creating the service is enrrolled in such - if service.VO != "" { - for _, vo := range cfg.OIDCGroups { - if vo == service.VO { - authHeader := c.GetHeader("Authorization") - err := checkIdentity(&service, cfg, authHeader) - if err != nil { - c.String(http.StatusBadRequest, fmt.Sprintln(err)) + if !mcParsed { + c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing multitenancy config: %v", mcParsed)) + return + } + + createLogger.Println("Multitenancy config: ", mc) + + if !uidParsed { + c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing uid origin: %v", uidParsed)) + return + } + + if err := c.ShouldBindJSON(&service); err != nil { + c.String(http.StatusBadRequest, fmt.Sprintf("The service specification is not valid: %v", err)) + return + } + + full_uid := auth.FormatUID(uid) + // Check if the service VO is present on the cluster VO's and if the user creating the service is enrrolled in such + if service.VO != "" { + for _, vo := range cfg.OIDCGroups { + if vo == service.VO { + authHeader := c.GetHeader("Authorization") + err := checkIdentity(&service, cfg, authHeader) + if err != nil { + c.String(http.StatusBadRequest, fmt.Sprintln(err)) + } + + // If AllowedUsers is empty don't add uid + if len(service.AllowedUsers) == 0 { + service.Labels["uid"] = full_uid[0:8] + service.AllowedUsers = append(service.AllowedUsers, uid) + createLogger.Println("Creating service for user: ", uid) + } + break } - service.Labels["uid"] = full_uid[0:8] - service.AllowedUsers = append(service.AllowedUsers, uid) - createLogger.Println("Creating service for user: ", uid) - break } } - } - - // Check if users in allowed_users have a MinIO associated user - minIOAdminClient, _ := utils.MakeMinIOAdminClient(cfg) - uids := mc.CheckUsersInCache(service.AllowedUsers) - if len(uids) > 0 { - for _, uid := range uids { - sk, _ := auth.GenerateRandomKey(8) - minIOAdminClient.CreateMinIOUser(uid, sk) - mc.CreateSecretForOIDC(uid, sk) + if len(service.AllowedUsers) == 0 { + uids := mc.CheckUsersInCache(service.AllowedUsers) + if len(uids) > 0 { + for _, uid := range uids { + sk, _ := auth.GenerateRandomKey(8) + minIOAdminClient.CreateMinIOUser(uid, sk) + mc.CreateSecretForOIDC(uid, sk) + } + } } } @@ -271,16 +291,20 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient * } // Create group for the service and add users - // TODO error control - err = minIOAdminClient.CreateServiceGroup(splitPath[0]) - if err != nil { - return fmt.Errorf("error creating service group for bucket %s: %v", splitPath[0], err) - } - err = minIOAdminClient.AddUserToGroup(allowed_users, splitPath[0]) - if err != nil { - return err + if !isAdminUser { + if len(allowed_users) < 1 { + err = minIOAdminClient.AddServiceToAllUsersGroup(splitPath[0]) + } else { + err = minIOAdminClient.CreateServiceGroup(splitPath[0]) + if err != nil { + return fmt.Errorf("error creating service group for bucket %s: %v", splitPath[0], err) + } + err = minIOAdminClient.AddUserToGroup(allowed_users, splitPath[0]) + if err != nil { + return err + } + } } - // Create folder(s) if len(splitPath) == 2 { // Add "/" to the end of the key in order to create a folder @@ -298,7 +322,6 @@ func createBuckets(service *types.Service, cfg *types.Config, minIOAdminClient * if err := enableInputNotification(s3Client, service.GetMinIOWebhookARN(), in); err != nil { return err } - } // Create output buckets diff --git a/pkg/handlers/list.go b/pkg/handlers/list.go index 37b634c9..390caada 100644 --- a/pkg/handlers/list.go +++ b/pkg/handlers/list.go @@ -19,6 +19,7 @@ package handlers import ( "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/grycap/oscar/v2/pkg/types" @@ -27,34 +28,42 @@ import ( // MakeListHandler makes a handler for listing services func MakeListHandler(back types.ServerlessBackend) gin.HandlerFunc { return func(c *gin.Context) { + + authHeader := c.GetHeader("Authorization") + services, err := back.ListServices() if err != nil { c.String(http.StatusInternalServerError, err.Error()) return } - uidOrigin, uidExists := c.Get("uidOrigin") - if !uidExists { - c.String(http.StatusInternalServerError, fmt.Sprintln("Missing EGI user uid")) - } + if len(strings.Split(authHeader, "Bearer")) > 0 { + uidOrigin, uidExists := c.Get("uidOrigin") + if !uidExists { + c.String(http.StatusInternalServerError, fmt.Sprintln("Missing EGI user uid")) + } - uid, uidParsed := uidOrigin.(string) + uid, uidParsed := uidOrigin.(string) - if !uidParsed { - c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing uid origin: %v", uidParsed)) - return - } + if !uidParsed { + c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing uid origin: %v", uidParsed)) + return + } - var allowedServicesForUser []*types.Service - for _, service := range services { - for _, id := range service.AllowedUsers { - if uid == id { - allowedServicesForUser = append(allowedServicesForUser, service) - break + var allowedServicesForUser []*types.Service + for _, service := range services { + for _, id := range service.AllowedUsers { + if uid == id { + allowedServicesForUser = append(allowedServicesForUser, service) + break + } } } + + c.JSON(http.StatusOK, allowedServicesForUser) + } else { + c.JSON(http.StatusOK, services) } - c.JSON(http.StatusOK, allowedServicesForUser) } } diff --git a/pkg/handlers/read.go b/pkg/handlers/read.go index 51b6c932..c309a201 100644 --- a/pkg/handlers/read.go +++ b/pkg/handlers/read.go @@ -19,6 +19,7 @@ package handlers import ( "fmt" "net/http" + "strings" "github.com/gin-gonic/gin" "github.com/grycap/oscar/v2/pkg/types" @@ -28,8 +29,10 @@ import ( // MakeReadHandler makes a handler for reading a service func MakeReadHandler(back types.ServerlessBackend) gin.HandlerFunc { return func(c *gin.Context) { - service, err := back.ReadService(c.Param("serviceName")) + authHeader := c.GetHeader("Authorization") + + service, err := back.ReadService(c.Param("serviceName")) if err != nil { // Check if error is caused because the service is not found if errors.IsNotFound(err) || errors.IsGone(err) { @@ -39,30 +42,31 @@ func MakeReadHandler(back types.ServerlessBackend) gin.HandlerFunc { } return } + if len(strings.Split(authHeader, "Bearer")) > 0 { + uidOrigin, uidExists := c.Get("uidOrigin") + if !uidExists { + c.String(http.StatusInternalServerError, fmt.Sprintln("Missing EGI user uid")) + } - uidOrigin, uidExists := c.Get("uidOrigin") - if !uidExists { - c.String(http.StatusInternalServerError, fmt.Sprintln("Missing EGI user uid")) - } - - uid, uidParsed := uidOrigin.(string) + uid, uidParsed := uidOrigin.(string) - if !uidParsed { - c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing uid origin: %v", uidParsed)) - return - } + if !uidParsed { + c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing uid origin: %v", uidParsed)) + return + } - var isAllowed bool - for _, id := range service.AllowedUsers { - if uid == id { - isAllowed = true - break + var isAllowed bool + for _, id := range service.AllowedUsers { + if uid == id { + isAllowed = true + break + } } - } - if !isAllowed { - c.String(http.StatusForbidden, "User %s doesn't have permision to get this service", uid) - return + if !isAllowed { + c.String(http.StatusForbidden, "User %s doesn't have permision to get this service", uid) + return + } } c.JSON(http.StatusOK, service) diff --git a/pkg/handlers/update.go b/pkg/handlers/update.go index 8c984fe4..d965b83d 100644 --- a/pkg/handlers/update.go +++ b/pkg/handlers/update.go @@ -40,29 +40,6 @@ func MakeUpdateHandler(cfg *types.Config, back types.ServerlessBackend) gin.Hand return } - mcUntyped, mcExists := c.Get("multitenancyConfig") - - if !mcExists { - c.String(http.StatusInternalServerError, fmt.Sprintln("Missing multitenancy config")) - } - - mc, mcParsed := mcUntyped.(auth.MultitenancyConfig) - - if !mcParsed { - c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing multitenancy config: %v", mcParsed)) - } - - // Check if users in allowed_users have a MinIO associated user - minIOAdminClient, _ := utils.MakeMinIOAdminClient(cfg) - uids := mc.CheckUsersInCache(newService.AllowedUsers) - if len(uids) == 0 { - for _, uid := range uids { - sk, _ := auth.GenerateRandomKey(8) - minIOAdminClient.CreateMinIOUser(uid, sk) - mc.CreateSecretForOIDC(uid, sk) - } - } - // Check service values and set defaults checkValues(&newService, cfg) @@ -91,6 +68,33 @@ func MakeUpdateHandler(cfg *types.Config, back types.ServerlessBackend) gin.Hand } } + minIOAdminClient, _ := utils.MakeMinIOAdminClient(cfg) + if !isAdminUser { + mcUntyped, mcExists := c.Get("multitenancyConfig") + + if !mcExists { + c.String(http.StatusInternalServerError, fmt.Sprintln("Missing multitenancy config")) + } + + mc, mcParsed := mcUntyped.(auth.MultitenancyConfig) + + if !mcParsed { + c.String(http.StatusInternalServerError, fmt.Sprintf("Error parsing multitenancy config: %v", mcParsed)) + } + + // Check if users in allowed_users have a MinIO associated user + if len(newService.AllowedUsers) == 0 { + uids := mc.CheckUsersInCache(newService.AllowedUsers) + if len(uids) == 0 { + for _, uid := range uids { + sk, _ := auth.GenerateRandomKey(8) + minIOAdminClient.CreateMinIOUser(uid, sk) + mc.CreateSecretForOIDC(uid, sk) + } + } + } + } + // Update the service if err := back.UpdateService(newService); err != nil { c.String(http.StatusInternalServerError, fmt.Sprintf("Error updating the service: %v", err)) diff --git a/pkg/utils/auth/auth.go b/pkg/utils/auth/auth.go index b0c22545..efac6186 100644 --- a/pkg/utils/auth/auth.go +++ b/pkg/utils/auth/auth.go @@ -43,13 +43,17 @@ func CustomAuth(cfg *types.Config, kubeClientset *kubernetes.Clientset) gin.Hand cfg.Username: cfg.Password, }) - //TODO Initialize MinIO client and create all_users_group minIOAdminClient, err := utils.MakeMinIOAdminClient(cfg) if err != nil { // TODO manage error } + // Slice to add default user to all users group on MinIO + var oscarUser []string + oscarUser[0] = "console" + minIOAdminClient.CreateAllUsersGroup() + minIOAdminClient.AddUserToGroup(oscarUser, "all_users_group") oidcHandler := getOIDCMiddleware(kubeClientset, minIOAdminClient, cfg.OIDCIssuer, cfg.OIDCSubject, cfg.OIDCGroups) return func(c *gin.Context) { diff --git a/pkg/utils/auth/multitenancy.go b/pkg/utils/auth/multitenancy.go index 4d69f7bc..95e0df05 100644 --- a/pkg/utils/auth/multitenancy.go +++ b/pkg/utils/auth/multitenancy.go @@ -20,6 +20,7 @@ import ( "context" "crypto/rand" "encoding/base64" + "fmt" "regexp" v1 "k8s.io/api/core/v1" @@ -57,6 +58,7 @@ func (mc *MultitenancyConfig) UpdateCache(uid string) { } func (mc *MultitenancyConfig) ClearCache() { + // TODO delete associated secrets mc.usersCache = nil } @@ -122,6 +124,23 @@ func (mc *MultitenancyConfig) CreateSecretForOIDC(uid string, sk string) error { return nil } +func (mc *MultitenancyConfig) GetUserCredentials(uid string) (string, string, error) { + secretName := FormatUID((uid)) + secret, err := mc.kubeClientset.CoreV1().Secrets(ServicesNamespace).Get(context.TODO(), secretName, metav1.GetOptions{}) + if err != nil { + return "", "", err + } + + encodedData := secret.Data + access_key := string(encodedData["accessKey"]) + secret_key := string(encodedData["secretKey"]) + + if access_key != "" && secret_key != "" { + return access_key, secret_key, nil + } + return "", "", fmt.Errorf("Error decoding secret data") +} + func GenerateRandomKey(length int) (string, error) { key := make([]byte, length) _, err := rand.Read(key) diff --git a/pkg/utils/minio.go b/pkg/utils/minio.go index 76e69070..03d58258 100644 --- a/pkg/utils/minio.go +++ b/pkg/utils/minio.go @@ -116,6 +116,14 @@ func (minIOAdminClient *MinIOAdminClient) CreateServiceGroup(bucketName string) return nil } +func (minIOAdminClient *MinIOAdminClient) AddServiceToAllUsersGroup(bucketName string) error { + err := createPolicy(minIOAdminClient.adminClient, bucketName) + if err != nil { + return err + } + + return nil +} func (minIOAdminClient *MinIOAdminClient) AddUserToGroup(users []string, groupName string) error { group := madmin.GroupAddRemove{