Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Authorize create run requests #2663

Merged
merged 71 commits into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
75c309d
add namespace to some run APIs
gaoning777 Nov 26, 2019
566cc10
update only the create run api
gaoning777 Nov 26, 2019
1060cbb
add resourcereference for namespace runs
gaoning777 Nov 26, 2019
05151b6
pass user identity header from the gRPC server to KFP service
gaoning777 Nov 26, 2019
e6c1443
add variables in const
gaoning777 Nov 26, 2019
fdb260b
Merge branch 'add-ns-in-API' into get-user-identity-from-header
gaoning777 Nov 26, 2019
be5a167
declare a flag and fill in the authorizations
gaoning777 Nov 26, 2019
659165e
add types to toModel func
gaoning777 Nov 26, 2019
f92c90f
Merge branch 'add-ns-in-API' into get-user-identity-from-header
gaoning777 Nov 26, 2019
5016f3a
Merge branch 'get-user-identity-from-header' into authorize-requests
gaoning777 Nov 26, 2019
755f542
bug fix
gaoning777 Nov 27, 2019
ee45be5
strip the namespace resource reference when mapping to the db model
gaoning777 Nov 27, 2019
2ace3fc
Merge branch 'add-ns-in-API' into get-user-identity-from-header
gaoning777 Nov 27, 2019
aea1924
Merge branch 'get-user-identity-from-header' into authorize-requests
gaoning777 Nov 27, 2019
bdd7dac
Merge branch 'master' into add-ns-in-API
gaoning777 Nov 27, 2019
204824b
add unit tests
gaoning777 Nov 27, 2019
b0aaf18
Merge branch 'add-ns-in-API' into get-user-identity-from-header
gaoning777 Nov 27, 2019
50eb63c
Merge branch 'get-user-identity-from-header' into authorize-requests
gaoning777 Nov 27, 2019
a21b60a
add authorization
gaoning777 Nov 28, 2019
a7d3db3
interpret json response
gaoning777 Nov 28, 2019
8085cbf
use gofmt
gaoning777 Dec 2, 2019
c5db317
Merge branch 'add-ns-in-API' into get-user-identity-from-header
gaoning777 Dec 2, 2019
91963c0
Merge branch 'get-user-identity-from-header' into authorize-requests
gaoning777 Dec 2, 2019
cefc903
add more meaningful error message; format
gaoning777 Dec 2, 2019
a48a554
refactoring codes
gaoning777 Dec 2, 2019
836573a
replace belonging relationshipreference to owner
gaoning777 Dec 3, 2019
80ab78d
put a todo for further investigation of using namespace or uuid
gaoning777 Dec 3, 2019
7cc2397
apply gofmt
gaoning777 Dec 3, 2019
882872d
revert minor change
gaoning777 Dec 3, 2019
4c21962
Merge branch 'add-ns-in-API' into authorize-requests
gaoning777 Dec 3, 2019
42fa5c5
refactor codes
gaoning777 Dec 3, 2019
cd9967e
minor change
gaoning777 Dec 3, 2019
7d273e5
Merge branch 'master' into authorize-requests
gaoning777 Dec 4, 2019
7c5cdc1
use internal server error in kfam client
gaoning777 Dec 4, 2019
bf30a21
minor change
gaoning777 Dec 4, 2019
a497f79
use timeout in kfam client
gaoning777 Dec 4, 2019
d24542a
make kfam service host/port configurable
gaoning777 Dec 4, 2019
f3a303d
minor changes
gaoning777 Dec 4, 2019
d4fb0f5
update name
gaoning777 Dec 4, 2019
67f414a
rename
gaoning777 Dec 4, 2019
24e5994
update the util function to accept a list of resourcereferences
gaoning777 Dec 4, 2019
bbe6f56
better error message
gaoning777 Dec 4, 2019
96aa2e3
reformat
gaoning777 Dec 4, 2019
1815bf9
remove IsRequestAuthorized func
gaoning777 Dec 4, 2019
4aa1064
add kfam host and port in config.json
gaoning777 Dec 5, 2019
aa08a9a
generalize the auth code
gaoning777 Dec 5, 2019
66a1150
rename KFAMInterface to KFAMClientInterface
gaoning777 Dec 5, 2019
008e7a6
add kfam fake for tests
gaoning777 Dec 5, 2019
39df35b
add build bazel
gaoning777 Dec 5, 2019
752c8de
add unit tests for util func
gaoning777 Dec 5, 2019
d68ad13
remove the config
gaoning777 Dec 5, 2019
50b1d19
add unit test for authorization with httptest
gaoning777 Dec 6, 2019
60ded01
only intialize the kfam client when kubeflow deployment
gaoning777 Dec 6, 2019
20d70c7
minor change
gaoning777 Dec 9, 2019
01a0cd4
fix typo
gaoning777 Dec 9, 2019
15cfb0e
wrap the whole auth func
gaoning777 Dec 9, 2019
1aea670
update authz logic to be enabled when it is kubeflow deployment
gaoning777 Dec 9, 2019
7158548
change flag from kubeflow deployment to multiuser mode
gaoning777 Dec 10, 2019
20de11a
gofmt
gaoning777 Dec 10, 2019
c4fcb9c
move fake kfam to the original kfam; create multiple fake kfam clients
gaoning777 Dec 10, 2019
cca49c4
combine authorize func, add unit tests for util_test
gaoning777 Dec 10, 2019
f2e75eb
Merge branch 'master' into authorize-requests
gaoning777 Dec 10, 2019
f649b6a
wrap errors
gaoning777 Dec 10, 2019
18864db
fix unit test
gaoning777 Dec 10, 2019
673db26
service unauthorized info to user
gaoning777 Dec 10, 2019
8744848
better user errors
gaoning777 Dec 10, 2019
52eab3e
revert some accidental change
gaoning777 Dec 10, 2019
df09ce1
Update util.go
IronPan Dec 10, 2019
be2806b
make functions local
gaoning777 Dec 10, 2019
3ebff5a
Merge branch 'authorize-requests' of https://github.com/gaoning777/pi…
gaoning777 Dec 10, 2019
c371e20
deduplicate return values from isauthorized
gaoning777 Dec 10, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 17 additions & 1 deletion backend/src/apiserver/client/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")

go_library(
name = "go_default_library",
Expand All @@ -8,10 +8,13 @@ go_library(
"scheduled_workflow.go",
"sql.go",
"workflow.go",
"kfam.go",
"kfam_fake.go",
],
importpath = "github.com/kubeflow/pipelines/backend/src/apiserver/client",
visibility = ["//visibility:public"],
deps = [
"//backend/src/common/util:go_default_library",
"//backend/src/crd/pkg/client/clientset/versioned:go_default_library",
"//backend/src/crd/pkg/client/clientset/versioned/typed/scheduledworkflow/v1beta1:go_default_library",
"@com_github_argoproj_argo//pkg/client/clientset/versioned:go_default_library",
Expand All @@ -26,3 +29,16 @@ go_library(
"@io_k8s_client_go//rest:go_default_library",
],
)

go_test(
name = "go_default_test",
srcs = [
"kfam_test.go",
],
data = glob(["test/**/*"]), # keep
embed = [":go_default_library"],
deps = [
"//backend/src/common/util:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
],
)
100 changes: 100 additions & 0 deletions backend/src/apiserver/client/kfam.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
"encoding/json"
"fmt"
"net/http"
"time"

"github.com/kubeflow/pipelines/backend/src/common/util"
"github.com/pkg/errors"
)

type KFAMClientInterface interface {
IsAuthorized(userIdentity string, namespace string) (bool, error)
}

type KFAMClient struct {
kfamServiceUrl string
}

type User struct {
Kind string
Name string
}

type RoleRef struct {
ApiGroup string
Kind string
Name string
}

type Binding struct {
User User
ReferredNamespace string
RoleRef RoleRef
}

type Bindings struct {
Bindings []Binding
}

const (
HTTP_TIMEOUT_SECONDS = 10
)

func (c *KFAMClient) IsAuthorized(userIdentity string, namespace string) (bool, error) {
gaoning777 marked this conversation as resolved.
Show resolved Hide resolved
req, err := http.NewRequest("GET", c.kfamServiceUrl, nil)
if err != nil {
return false, util.NewInternalServerError(err, "Failed to create a KFAM http request.")
}
q := req.URL.Query()
q.Add("user", userIdentity)
req.URL.RawQuery = q.Encode()

var httpClient = &http.Client{Timeout: HTTP_TIMEOUT_SECONDS * time.Second}

resp, err := httpClient.Get(req.URL.String())
if err != nil {
return false, util.NewInternalServerError(err, "Failed to connect to the KFAM service.")
}
if resp.StatusCode != http.StatusOK {
return false, util.NewInternalServerError(errors.New("Requests to the KFAM service failed."), resp.Status)
}
defer resp.Body.Close()

jsonBindings := new(Bindings)
err = json.NewDecoder(resp.Body).Decode(jsonBindings)

if err != nil {
return false, util.NewInternalServerError(err, "Failed to parse KFAM response.")
}

nsFound := false
for _, jsonBinding := range jsonBindings.Bindings {
if jsonBinding.ReferredNamespace == namespace {
nsFound = true
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

stop early by return true, null

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code will break the for loop and return immediately, correct?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah correct.

break
}
}
return nsFound, nil
}

func NewKFAMClient(kfamServiceHost string, kfamServicePort string) *KFAMClient {
kfamServiceUrl := fmt.Sprintf("http://%s:%s/kfam/v1/bindings", kfamServiceHost, kfamServicePort)
return &KFAMClient{kfamServiceUrl}
}
37 changes: 37 additions & 0 deletions backend/src/apiserver/client/kfam_fake.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

type FakeKFAMClientAuthorized struct {
}

func NewFakeKFAMClientAuthorized() *FakeKFAMClientAuthorized {
return &FakeKFAMClientAuthorized{}
}

func (c *FakeKFAMClientAuthorized) IsAuthorized(userIdentity string, namespace string) (bool, error) {
return true, nil
}

type FakeKFAMClientUnauthorized struct {
}

func NewFakeKFAMClientUnauthorized() *FakeKFAMClientUnauthorized {
return &FakeKFAMClientUnauthorized{}
}

func (c *FakeKFAMClientUnauthorized) IsAuthorized(userIdentity string, namespace string) (bool, error) {
return false, nil
}
46 changes: 46 additions & 0 deletions backend/src/apiserver/client/kfam_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright 2019 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client
import(
"fmt"
"net/http"
"net/http/httptest"
"testing"

"github.com/stretchr/testify/assert"
)

func TestIsAuthorize(t *testing.T) {
expect_response := []byte (`{"bindings":[{"user": {"kind": "User","name": "userA@google.com"},"referredNamespace": "nsA","RoleRef": {"apiGroup": "","kind": "ClusterRole", "name":"edit"}},{"user": {"kind": "User","name": "userA@google.com"},"referredNamespace": "nsB","RoleRef": {"apiGroup": "","kind": "ClusterRole", "name":"admin"}}]}`)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(200)
w.Write(expect_response)
}))
defer srv.Close()
fmt.Println(srv.URL)
kfam_client := NewKFAMClient("","")
kfam_client.kfamServiceUrl = srv.URL
authorized, err := kfam_client.IsAuthorized("user", "nsA")
assert.Nil(t, err)
assert.True(t, authorized)

authorized, err = kfam_client.IsAuthorized("user", "nsB")
assert.Nil(t, err)
assert.True(t, authorized)

authorized, err = kfam_client.IsAuthorized("user", "nsC")
assert.Nil(t, err)
assert.False(t, authorized)
}
10 changes: 10 additions & 0 deletions backend/src/apiserver/client_manager.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ const (
mysqlPassword = "DBConfig.Password"
mysqlDBName = "DBConfig.DBName"
mysqlGroupConcatMaxLen = "DBConfig.GroupConcatMaxLen"
kfamServiceHost = "KFAM_SERVICE_HOST"
kfamServicePort = "KFAM_SERVICE_PORT"

visualizationServiceHost = "ML_PIPELINE_VISUALIZATIONSERVER_SERVICE_HOST"
visualizationServicePort = "ML_PIPELINE_VISUALIZATIONSERVER_SERVICE_PORT"
Expand All @@ -67,6 +69,7 @@ type ClientManager struct {
wfClient workflowclient.WorkflowInterface
swfClient scheduledworkflowclient.ScheduledWorkflowInterface
podClient v1.PodInterface
kfamClient client.KFAMClientInterface
time util.TimeInterface
uuid util.UUIDGeneratorInterface
}
Expand Down Expand Up @@ -115,6 +118,10 @@ func (c *ClientManager) PodClient() v1.PodInterface {
return c.podClient
}

func (c *ClientManager) KFAMClient() client.KFAMClientInterface {
return c.kfamClient
}

func (c *ClientManager) Time() util.TimeInterface {
return c.time
}
Expand Down Expand Up @@ -154,6 +161,9 @@ func (c *ClientManager) init() {
runStore := storage.NewRunStore(db, c.time)
c.runStore = runStore

if common.IsMultiUserMode() {
c.kfamClient = client.NewKFAMClient(common.GetStringConfig(kfamServiceHost), common.GetStringConfig(kfamServicePort))
}
glog.Infof("Client manager initialized successfully")
}

Expand Down
8 changes: 8 additions & 0 deletions backend/src/apiserver/common/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,10 @@ import (
"github.com/spf13/viper"
)

const (
MultiUserMode string = "MULTIUSER"
)

func GetStringConfig(configName string) string {
if !viper.IsSet(configName) {
glog.Fatalf("Please specify flag %s", configName)
Expand Down Expand Up @@ -53,3 +57,7 @@ func GetDurationConfig(configName string) time.Duration {
}
return viper.GetDuration(configName)
}

func IsMultiUserMode() bool {
return GetBoolConfigWithDefault(MultiUserMode, false)
}
4 changes: 4 additions & 0 deletions backend/src/apiserver/common/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,10 @@ const (
Creator Relationship = "Creator"
)

const (
GoogleIAPUserIdentityHeader string = "x-goog-authenticated-user-email"
)

func ToModelResourceType(apiType api.ResourceType) (ResourceType, error) {
switch apiType {
case api.ResourceType_EXPERIMENT:
Expand Down
13 changes: 12 additions & 1 deletion backend/src/apiserver/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,17 @@ func main() {
clientManager.Close()
}

// A custom http request header matcher to pass on the user identity
// Reference: https://github.com/grpc-ecosystem/grpc-gateway/blob/master/docs/_docs/customizingyourgateway.md#mapping-from-http-request-headers-to-grpc-client-metadata
func grpcCustomMatcher(key string) (string, bool) {
switch strings.ToLower(key) {
case common.GoogleIAPUserIdentityHeader:
return strings.ToLower(key), true
default:
return strings.ToLower(key), false
}
}

func startRpcServer(resourceManager *resource.ResourceManager) {
glog.Info("Starting RPC server")
listener, err := net.Listen("tcp", *rpcPortFlag)
Expand Down Expand Up @@ -107,7 +118,7 @@ func startHttpProxy(resourceManager *resource.ResourceManager) {
defer cancel()

// Create gRPC HTTP MUX and register services.
mux := runtime.NewServeMux()
mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(grpcCustomMatcher))
registerHttpHandlerFromEndpoint(api.RegisterPipelineServiceHandlerFromEndpoint, "PipelineService", ctx, mux)
registerHttpHandlerFromEndpoint(api.RegisterExperimentServiceHandlerFromEndpoint, "ExperimentService", ctx, mux)
registerHttpHandlerFromEndpoint(api.RegisterJobServiceHandlerFromEndpoint, "JobService", ctx, mux)
Expand Down
2 changes: 2 additions & 0 deletions backend/src/apiserver/resource/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ go_library(
"//backend/src/apiserver/list:go_default_library",
"//backend/src/apiserver/model:go_default_library",
"//backend/src/apiserver/storage:go_default_library",
"//backend/src/apiserver/client:go_default_library",
"//backend/src/common/util:go_default_library",
"//backend/src/crd/pkg/apis/scheduledworkflow/v1beta1:go_default_library",
"//backend/src/crd/pkg/client/clientset/versioned/typed/scheduledworkflow/v1beta1:go_default_library",
Expand Down Expand Up @@ -52,6 +53,7 @@ go_test(
"//backend/src/apiserver/common:go_default_library",
"//backend/src/apiserver/model:go_default_library",
"//backend/src/apiserver/storage:go_default_library",
"//backend/src/apiserver/client:go_default_library",
"//backend/src/common/util:go_default_library",
"//backend/src/crd/pkg/apis/scheduledworkflow/v1beta1:go_default_library",
"@com_github_argoproj_argo//pkg/apis/workflow/v1alpha1:go_default_library",
Expand Down
7 changes: 7 additions & 0 deletions backend/src/apiserver/resource/client_manager_fake.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package resource
import (
workflowclient "github.com/argoproj/argo/pkg/client/clientset/versioned/typed/workflow/v1alpha1"
"github.com/golang/glog"
"github.com/kubeflow/pipelines/backend/src/apiserver/client"
"github.com/kubeflow/pipelines/backend/src/apiserver/storage"
"github.com/kubeflow/pipelines/backend/src/common/util"
scheduledworkflowclient "github.com/kubeflow/pipelines/backend/src/crd/pkg/client/clientset/versioned/typed/scheduledworkflow/v1beta1"
Expand All @@ -41,6 +42,7 @@ type FakeClientManager struct {
workflowClientFake *FakeWorkflowClient
scheduledWorkflowClientFake *FakeScheduledWorkflowClient
podClientFake v1.PodInterface
KfamClientFake client.KFAMClientInterface
time util.TimeInterface
uuid util.UUIDGeneratorInterface
}
Expand Down Expand Up @@ -76,6 +78,7 @@ func NewFakeClientManager(time util.TimeInterface, uuid util.UUIDGeneratorInterf
objectStore: storage.NewFakeObjectStore(),
scheduledWorkflowClientFake: NewScheduledWorkflowClientFake(),
podClientFake: FakePodClient{},
KfamClientFake: client.NewFakeKFAMClientAuthorized(),
time: time,
uuid: uuid,
}, nil
Expand Down Expand Up @@ -146,6 +149,10 @@ func (f *FakeClientManager) PodClient() v1.PodInterface {
return f.podClientFake
}

func (f *FakeClientManager) KFAMClient() client.KFAMClientInterface {
return f.KfamClientFake
}

func (f *FakeClientManager) Close() error {
return f.db.Close()
}
1 change: 1 addition & 0 deletions backend/src/apiserver/resource/model_converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,7 @@ func (r *ResourceManager) toModelResourceReferences(
if err != nil {
return nil, util.Wrap(err, "Failed to find the referred resource")
}

//TODO(gaoning777) further investigation: Is the plain namespace a good option? maybe uuid for distinctness even with namespace deletion/recreation.
modelRef := &model.ResourceReference{
ResourceUUID: resourceId,
Expand Down
Loading