diff --git a/docs/fdl.md b/docs/fdl.md index d56b5114..a6811a8e 100644 --- a/docs/fdl.md +++ b/docs/fdl.md @@ -80,6 +80,7 @@ storage_providers: | `memory`
*string* | Memory limit for the service following the [kubernetes format](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-memory). Optional (default: 256Mi) | | `cpu`
*string* | CPU limit for the service following the [kubernetes format](https://kubernetes.io/docs/concepts/configuration/manage-compute-resources-container/#meaning-of-cpu). Optional (default: 0.2) | | `enable_gpu`
*bool* | Parameter to enable the use of GPU for the service. Requires a device plugin deployed on the cluster (More info: [Kubernetes device plugins](https://kubernetes.io/docs/tasks/manage-gpus/scheduling-gpus/#using-device-plugins)). Optional (default: false) | +| `enable_sgx`
*bool* | Parameter to enable the use of SGX plugin on the cluster containers. (More info: [SGX plugin documentation](https://sconedocs.github.io/helm_sgxdevplugin/)). Optional (default: false) | | `image_prefetch`
*bool* | Parameter to enable the use of image caching. Optional (default: false) | | `total_memory`
*string* | Limit for the memory used by all the service's jobs running simultaneously. Apache YuniKorn scheduler is required to work. Same format as Memory, but internally translated to MB (integer). Optional (default: "") | | `total_cpu`
*string* | Limit for the virtual CPUs used by all the service's jobs running simultaneously. Apache YuniKorn scheduler is required to work. Same format as CPU, but internally translated to millicores (integer). Optional (default: "") | diff --git a/pkg/backends/k8s.go b/pkg/backends/k8s.go index d583e0da..bd10c4d6 100644 --- a/pkg/backends/k8s.go +++ b/pkg/backends/k8s.go @@ -85,6 +85,14 @@ func (k *KubeBackend) CreateService(service types.Service) error { return err } + if service.VO != "" { + for _, vo := range k.config.OIDCGroups { + if vo == service.VO { + service.Labels["vo"] = service.VO + } + } + } + // Create podSpec from the service podSpec, err := service.ToPodSpec(k.config) if err != nil { diff --git a/pkg/backends/knative.go b/pkg/backends/knative.go index 3a5800fe..3629ce57 100644 --- a/pkg/backends/knative.go +++ b/pkg/backends/knative.go @@ -278,6 +278,15 @@ func (kn *KnativeBackend) createKNServiceDefinition(service *types.Service) (*kn // https://knative.dev/docs/serving/services/private-services/ service.Labels[types.KnativeVisibilityLabel] = types.KnativeClusterLocalValue + // Add to the service labels the user VO for accounting on k8s pods + if service.VO != "" { + for _, vo := range kn.config.OIDCGroups { + if vo == service.VO { + service.Labels["vo"] = service.VO + } + } + } + podSpec, err := service.ToPodSpec(kn.config) if err != nil { return nil, err @@ -302,6 +311,8 @@ func (kn *KnativeBackend) createKNServiceDefinition(service *types.Service) (*kn types.KnativeMinScaleAnnotation: strconv.Itoa(service.Synchronous.MinScale), types.KnativeMaxScaleAnnotation: strconv.Itoa(service.Synchronous.MaxScale), }, + //Empty labels map to avoid nil pointer errors + Labels: map[string]string{}, }, Spec: knv1.RevisionSpec{ ContainerConcurrency: &containerConcurrency, @@ -312,6 +323,11 @@ func (kn *KnativeBackend) createKNServiceDefinition(service *types.Service) (*kn }, } + // Add to the service labels the user VO for accounting on knative pods + if service.Labels["vo"] != "" { + knSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Labels["vo"] = service.Labels["vo"] + } + if service.EnableSGX { knSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations["kubernetes.podspec-securitycontext"] = "enabled" knSvc.Spec.ConfigurationSpec.Template.ObjectMeta.Annotations["kubernetes.containerspec-addcapabilities"] = "enabled" diff --git a/pkg/handlers/create.go b/pkg/handlers/create.go index 780ec664..bf6e15bd 100644 --- a/pkg/handlers/create.go +++ b/pkg/handlers/create.go @@ -31,6 +31,7 @@ import ( "github.com/grycap/cdmi-client-go" "github.com/grycap/oscar/v2/pkg/types" "github.com/grycap/oscar/v2/pkg/utils" + "github.com/grycap/oscar/v2/pkg/utils/auth" k8sErrors "k8s.io/apimachinery/pkg/api/errors" ) @@ -46,6 +47,7 @@ var errInput = errors.New("unrecognized input (valid inputs are MinIO and dCache func MakeCreateHandler(cfg *types.Config, back types.ServerlessBackend) gin.HandlerFunc { return func(c *gin.Context) { var service types.Service + if err := c.ShouldBindJSON(&service); err != nil { c.String(http.StatusBadRequest, fmt.Sprintf("The service specification is not valid: %v", err)) return @@ -54,6 +56,24 @@ func MakeCreateHandler(cfg *types.Config, back types.ServerlessBackend) gin.Hand // Check service values and set defaults checkValues(&service, cfg) + if service.VO != "" { + oidcManager, _ := auth.NewOIDCManager(cfg.OIDCIssuer, cfg.OIDCSubject, cfg.OIDCGroups) + + authHeader := c.GetHeader("Authorization") + rawToken := strings.TrimPrefix(authHeader, "Bearer ") + hasVO, err2 := oidcManager.UserHasVO(rawToken, service.VO) + + if err2 != nil { + c.String(http.StatusInternalServerError, err2.Error()) + return + } + + if !hasVO { + c.String(http.StatusBadRequest, fmt.Sprintf("This user isn't enrrolled on the vo: %v", service.VO)) + return + } + } + // Create the service if err := back.CreateService(service); err != nil { // Check if error is caused because the service name provided already exists @@ -120,6 +140,10 @@ func checkValues(service *types.Service, cfg *types.Config) { service.Labels[types.YunikornApplicationIDLabel] = service.Name service.Labels[types.YunikornQueueLabel] = fmt.Sprintf("%s.%s.%s", types.YunikornRootQueue, types.YunikornOscarQueue, service.Name) + if service.VO != "" { + service.Labels["vo"] = service.VO + } + // Create default annotations map if service.Annotations == nil { service.Annotations = make(map[string]string) diff --git a/pkg/types/service.go b/pkg/types/service.go index 13f83180..45182cb1 100644 --- a/pkg/types/service.go +++ b/pkg/types/service.go @@ -224,6 +224,10 @@ type Service struct { // Optional Annotations map[string]string `json:"annotations"` + // Parameter to specify the VO from the user creating the service + // Optional + VO string `json:"vo"` + // Labels user-defined Kubernetes labels to be set in job's definition // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ // Optional diff --git a/pkg/types/service_test.go b/pkg/types/service_test.go index 20b5242a..a8a08562 100644 --- a/pkg/types/service_test.go +++ b/pkg/types/service_test.go @@ -251,6 +251,7 @@ environment: TEST_VAR: testvalue annotations: testannotation: testannotationvalue +vo: "" labels: testlabel: testlabelvalue storage_providers: diff --git a/pkg/utils/auth/oidc.go b/pkg/utils/auth/oidc.go index a1c7bb46..b1baa65c 100644 --- a/pkg/utils/auth/oidc.go +++ b/pkg/utils/auth/oidc.go @@ -45,7 +45,7 @@ type userInfo struct { } // newOIDCManager returns a new oidcManager or error if the oidc.Provider can't be created -func newOIDCManager(issuer string, subject string, groups []string) (*oidcManager, error) { +func NewOIDCManager(issuer string, subject string, groups []string) (*oidcManager, error) { provider, err := oidc.NewProvider(context.TODO(), issuer) if err != nil { return nil, err @@ -66,7 +66,7 @@ func newOIDCManager(issuer string, subject string, groups []string) (*oidcManage // getIODCMiddleware returns the Gin's handler middleware to validate OIDC-based auth func getOIDCMiddleware(issuer string, subject string, groups []string) gin.HandlerFunc { - oidcManager, err := newOIDCManager(issuer, subject, groups) + oidcManager, err := NewOIDCManager(issuer, subject, groups) if err != nil { return func(c *gin.Context) { c.AbortWithStatus(http.StatusUnauthorized) @@ -140,6 +140,19 @@ func getGroups(urns []string) []string { return groups } +func (om *oidcManager) UserHasVO(rawToken string, vo string) (bool, error) { + ui, err := om.getUserInfo(rawToken) + if err != nil { + return false, err + } + for _, gr := range ui.groups { + if vo == gr { + return true, nil + } + } + return false, nil +} + // isAuthorised checks if a token is authorised to access the API func (om *oidcManager) isAuthorised(rawToken string) bool { // Check if the token is valid