Skip to content

Commit 831e1d3

Browse files
authored
Add authorization mechanisms in new Katib UI backend (#1983)
* UI(back): Add authorization mechanisms in new Katib UI backend * Introduce helper ENV vars and functions for authentication and authorization checks. The authz checks are using SubjectAcessReviews objects. * BACKEND_MODE={dev,prod}: skip authz when in dev mode * APP_DISABLE_AUTH={bool}: skip authz if explicity requested * Introduce a client-go client to construct SubjectAccessReview objects. * Before any request proceed to K8s api-server: * check if authorization must be skipped (BACKEND_MODE, APP_DISBLE_AUTH) * check if a username is proviced in request Header * query the K8s api-server with SAR to ensure that the user has appropriate access privilleges * Replace the /katib/fetch_experiment/ route with /katib/fetch_namespaces_experiments. This route expects a namespace as a query parameter from which all experiments will be fetched. Signed-off-by: Apostolos Gerakaris <[email protected]> * UI(front): Provide a namespace as a query parameter This is needed for the new /katib/fetch_namespaced_experiments route. Signed-off-by: Apostolos Gerakaris <[email protected]> * Update README for running locally without auth Update the README of the web app to expose that devs should set APP_DISABLE_AUTH=true when running locally, since there's no authnz when running locally. Signed-off-by: Apostolos Gerakaris <[email protected]> * remove duplicated variable types Signed-off-by: Apostolos Gerakaris <[email protected]> * Review fixes * proper error handling. * switch to Go's build-in errors package. * set appropriate verbs when constructing SAR objects. Signed-off-by: Apostolos Gerakaris <[email protected]> * review: Use controller-runtime client to create SAR objects Signed-off-by: Apostolos Gerakaris <[email protected]> * Review fixes * fix backend routes. * '/katib/fetch_namespaces' to fetch experiments in a namespace * 'FetchExperiments' handler * hit the appropriate route from frontend and provide namespace as a query parameter to fetch experiments * remove remove BACKEND_MODE env var in favour of the more specific APP_DISABLE_AUTH Signed-off-by: Apostolos Gerakaris <[email protected]> * Review fixes * Add constants for CRUD actions * Add plural for experiments and suggestions as constants * Add GetUsername logic under IsAuthorized and handle errors properly * Have APP_DISABLE_AUTH by default as true, since currently Katib doesn't support this feature in standalone mode. Signed-off-by: Apostolos Gerakaris <[email protected]> Signed-off-by: Apostolos Gerakaris <[email protected]>
1 parent 0a1cb31 commit 831e1d3

File tree

9 files changed

+403
-42
lines changed

9 files changed

+403
-42
lines changed

cmd/new-ui/v1beta1/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func main() {
4848
http.HandleFunc("/katib/", kuh.ServeIndex(*buildDir))
4949
http.Handle("/katib/static/", http.StripPrefix("/katib/", frontend))
5050

51-
http.HandleFunc("/katib/fetch_experiments/", kuh.FetchAllExperiments)
51+
http.HandleFunc("/katib/fetch_experiments/", kuh.FetchExperiments)
5252

5353
http.HandleFunc("/katib/create_experiment/", kuh.CreateExperiment)
5454

pkg/controller.v1beta1/consts/const.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,8 +26,23 @@ import (
2626

2727
const (
2828

29+
// ActionTypeCreate is the create CRUD action
30+
ActionTypeCreate = "create"
31+
// ActionTypeList is the list CRUD action
32+
ActionTypeList = "list"
33+
// ActionTypeGet is the get CRUD action
34+
ActionTypeGet = "get"
35+
// ActionTypeUpdate is the update CRUD action
36+
ActionTypeUpdate = "update"
37+
// ActionTypeDelete is the delete CRUD action
38+
ActionTypeDelete = "delete"
39+
2940
// PluralTrial is the plural for Trial object
3041
PluralTrial = "trials"
42+
// PluralExperiment is the plural for Experiment object
43+
PluralExperiment = "experiments"
44+
// PluralSuggestion is the plural for Suggestion object
45+
PluralSuggestion = "suggestions"
3146

3247
// ConfigExperimentSuggestionName is the config name of the
3348
// suggestion client implementation in experiment controller.

pkg/new-ui/v1beta1/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ This is the recommended way to test the web app e2e. In order to build the UI an
9292
For example, if you use port-forwarding to expose `katib-db-manager`, run this command:
9393

9494
```
95+
export APP_DISABLE_AUTH=true
9596
go run main.go --build-dir=../../../pkg/new-ui/v1beta1/frontend/dist --port=8080 --db-manager-address=localhost:6789
9697
```
9798

pkg/new-ui/v1beta1/authzn.go

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package v1beta1
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"log"
8+
"net/http"
9+
"strings"
10+
11+
"github.com/kubeflow/katib/pkg/util/v1beta1/env"
12+
v1 "k8s.io/api/authorization/v1"
13+
"k8s.io/apimachinery/pkg/runtime/schema"
14+
"sigs.k8s.io/controller-runtime/pkg/client"
15+
)
16+
17+
// ENV variables
18+
var (
19+
USER_HEADER = env.GetEnvOrDefault("USERID_HEADER", "kubeflow-userid")
20+
USER_PREFIX = env.GetEnvOrDefault("USERID_PREFIX", ":")
21+
DISABLE_AUTH = env.GetEnvOrDefault("APP_DISABLE_AUTH", "true")
22+
)
23+
24+
// Function for constructing SubjectAccessReviews (SAR) objects
25+
func CreateSAR(user, verb, namespace, resource, subresource, name string, schema schema.GroupVersion) *v1.SubjectAccessReview {
26+
27+
sar := &v1.SubjectAccessReview{
28+
Spec: v1.SubjectAccessReviewSpec{
29+
User: user,
30+
ResourceAttributes: &v1.ResourceAttributes{
31+
Namespace: namespace,
32+
Verb: verb,
33+
Group: schema.Group,
34+
Version: schema.Version,
35+
Resource: resource,
36+
Subresource: subresource,
37+
Name: name,
38+
},
39+
},
40+
}
41+
return sar
42+
}
43+
44+
func IsAuthorized(verb, namespace, resource, subresource, name string, schema schema.GroupVersion, client client.Client, r *http.Request) (string, error) {
45+
46+
// We disable authn/authz checks when in standalone mode.
47+
if DISABLE_AUTH == "true" {
48+
log.Printf("APP_DISABLE_AUTH set to True. Skipping authentication/authorization checks")
49+
return "", nil
50+
}
51+
// Check if an incoming request is from an authenticated user (kubeflow mode: kubeflow-userid header)
52+
if r.Header.Get(USER_HEADER) == "" {
53+
return "", errors.New("user header not present")
54+
}
55+
user := r.Header.Get(USER_HEADER)
56+
user = strings.Replace(user, USER_PREFIX, "", 1)
57+
58+
// Check if the user is authorized to perform a given action on katib/k8s resources.
59+
sar := CreateSAR(user, verb, namespace, resource, subresource, name, schema)
60+
err := client.Create(context.TODO(), sar)
61+
if err != nil {
62+
log.Printf("Error submitting SubjectAccessReview: %v, %s", sar, err.Error())
63+
return user, err
64+
}
65+
66+
if sar.Status.Allowed {
67+
return user, nil
68+
}
69+
70+
msg := generateUnauthorizedMessage(user, verb, namespace, resource, subresource, schema, sar)
71+
return user, errors.New(msg)
72+
}
73+
74+
func generateUnauthorizedMessage(user, verb, namespace, resource, subresource string, schema schema.GroupVersion, sar *v1.SubjectAccessReview) string {
75+
76+
msg := fmt.Sprintf("User: %s is not authorized to %s", user, verb)
77+
78+
if schema.Group == "" {
79+
msg += fmt.Sprintf(" %s/%s", schema.Version, resource)
80+
} else {
81+
msg += fmt.Sprintf(" %s/%s/%s", schema.Group, schema.Version, resource)
82+
}
83+
84+
if subresource != "" {
85+
msg += fmt.Sprintf("/%s", subresource)
86+
}
87+
88+
if namespace != "" {
89+
msg += fmt.Sprintf(" in namespace: %s", namespace)
90+
}
91+
if sar.Status.Reason != "" {
92+
msg += fmt.Sprintf(" ,reason: %s", sar.Status.Reason)
93+
}
94+
return msg
95+
}

0 commit comments

Comments
 (0)