feat(ws): add auth to backend (#202)

* feat(ws): add auth to backend

Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com>

* add `DISABLE_AUTH` for interim testing (enabled by default)

Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com>

---------

Signed-off-by: Mathew Wicks <5735406+thesuperzapper@users.noreply.github.com>
This commit is contained in:
Mathew Wicks 2025-02-11 12:21:28 -08:00 committed by GitHub
parent 4cbc26eaf4
commit bc6f311ac6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 725 additions and 134 deletions

View File

@ -22,6 +22,8 @@ import (
"github.com/julienschmidt/httprouter"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authorization/authorizer"
"sigs.k8s.io/controller-runtime/pkg/client"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
@ -52,20 +54,26 @@ const (
)
type App struct {
Config config.EnvConfig
Config *config.EnvConfig
logger *slog.Logger
repositories *repositories.Repositories
Scheme *runtime.Scheme
RequestAuthN authenticator.Request
RequestAuthZ authorizer.Authorizer
}
// NewApp creates a new instance of the app
func NewApp(cfg config.EnvConfig, logger *slog.Logger, cl client.Client, scheme *runtime.Scheme) (*App, error) {
func NewApp(cfg *config.EnvConfig, logger *slog.Logger, cl client.Client, scheme *runtime.Scheme, reqAuthN authenticator.Request, reqAuthZ authorizer.Authorizer) (*App, error) {
// TODO: log the configuration on startup
app := &App{
Config: cfg,
logger: logger,
repositories: repositories.NewRepositories(cl),
Scheme: scheme,
RequestAuthN: reqAuthN,
RequestAuthZ: reqAuthZ,
}
return app, nil
}

View File

@ -0,0 +1,73 @@
/*
Copyright 2024.
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
http://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 api
import (
"fmt"
"net/http"
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
)
// requireAuth verifies that the request is authenticated and authorized to take the actions specified by the given policies.
// If this method returns false, the request has been handled and the caller should return immediately.
// If this method returns true, the request is authenticated and authorized to proceed.
// This method should only be called once per request.
func (a *App) requireAuth(w http.ResponseWriter, r *http.Request, policies []*auth.ResourcePolicy) bool {
ctx := r.Context()
// if auth is disabled, allow the request to proceed
if a.Config.DisableAuth {
return true
}
// authenticate the request (extract user and groups from the request headers)
res, ok, err := a.RequestAuthN.AuthenticateRequest(r)
if err != nil {
err = fmt.Errorf("failed to authenticate request: %w", err)
a.serverErrorResponse(w, r, err)
return false
}
if !ok {
a.unauthorizedResponse(w, r)
return false
}
// for each policy, check if the user is authorized to take the requested action
for _, policy := range policies {
attributes := policy.AttributesFor(res.User)
authorized, reason, err := a.RequestAuthZ.Authorize(ctx, attributes)
if err != nil {
err = fmt.Errorf("failed to authorize request for user %q: %w", res.User.GetName(), err)
a.serverErrorResponse(w, r, err)
return false
}
if authorized != authorizer.DecisionAllow {
msg := fmt.Sprintf("authorization was denied for user %q", res.User.GetName())
if reason != "" {
msg = fmt.Sprintf("%s: %s", msg, reason)
}
a.forbiddenResponse(w, r, msg)
return false
}
}
return true
}

View File

@ -46,6 +46,15 @@ func (a *App) LogError(r *http.Request, err error) {
a.logger.Error(err.Error(), "method", method, "uri", uri)
}
func (a *App) LogWarn(r *http.Request, message string) {
var (
method = r.Method
uri = r.URL.RequestURI()
)
a.logger.Warn(message, "method", method, "uri", uri)
}
//nolint:unused
func (a *App) badRequestResponse(w http.ResponseWriter, r *http.Request, err error) {
httpError := &HTTPError{
@ -103,6 +112,30 @@ func (a *App) methodNotAllowedResponse(w http.ResponseWriter, r *http.Request) {
a.errorResponse(w, r, httpError)
}
func (a *App) unauthorizedResponse(w http.ResponseWriter, r *http.Request) {
httpError := &HTTPError{
StatusCode: http.StatusUnauthorized,
ErrorResponse: ErrorResponse{
Code: strconv.Itoa(http.StatusUnauthorized),
Message: "authentication is required to access this resource",
},
}
a.errorResponse(w, r, httpError)
}
func (a *App) forbiddenResponse(w http.ResponseWriter, r *http.Request, msg string) {
a.LogWarn(r, msg)
httpError := &HTTPError{
StatusCode: http.StatusForbidden,
ErrorResponse: ErrorResponse{
Code: strconv.Itoa(http.StatusForbidden),
Message: "you are not authorized to access this resource",
},
}
a.errorResponse(w, r, httpError)
}
//nolint:unused
func (a *App) failedValidationResponse(w http.ResponseWriter, r *http.Request, errors map[string]string) {
message, err := json.Marshal(errors)

View File

@ -26,28 +26,13 @@ import (
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/health_check"
"github.com/kubeflow/notebooks/workspaces/backend/internal/repositories"
)
var _ = Describe("HealthCheck Handler", func() {
var (
a App
)
Context("when backend is healthy", func() {
BeforeEach(func() {
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
})
It("should return a health check response", func() {
By("creating the HTTP request")
req, err := http.NewRequest(http.MethodGet, HealthCheckPath, http.NoBody)

View File

@ -20,7 +20,9 @@ import (
"net/http"
"github.com/julienschmidt/httprouter"
corev1 "k8s.io/api/core/v1"
"github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/namespaces"
)
@ -28,6 +30,18 @@ type NamespacesEnvelope Envelope[[]models.Namespace]
func (a *App) GetNamespacesHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbList,
&corev1.Namespace{},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
namespaces, err := a.repositories.Namespace.GetNamespaces(r.Context())
if err != nil {
a.serverErrorResponse(w, r, err)

View File

@ -29,15 +29,10 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/namespaces"
"github.com/kubeflow/notebooks/workspaces/backend/internal/repositories"
)
var _ = Describe("Namespaces Handler", func() {
var (
a App
)
// NOTE: these tests assume a specific state of the cluster, so cannot be run in parallel with other tests.
// therefore, we run them using the `Serial` Ginkgo decorators.
@ -47,14 +42,6 @@ var _ = Describe("Namespaces Handler", func() {
const namespaceName2 = "get-ns-test-ns2"
BeforeEach(func() {
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
By("creating Namespace 1")
namespace1 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
@ -95,6 +82,9 @@ var _ = Describe("Namespaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, AllNamespacesPath, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetNamespacesHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()

View File

@ -19,39 +19,52 @@ package api
import (
"context"
"fmt"
"log/slog"
"path/filepath"
"runtime"
"testing"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/api/resource"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
"k8s.io/utils/ptr"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/envtest"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
"github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
)
// These tests use Ginkgo (BDD-style Go testing framework). Refer to
// http://onsi.github.io/ginkgo/ to learn more about Ginkgo.
const (
userIdHeader = "userid-header"
userIdPrefix = ""
groupsHeader = "groups-header"
adminUser = "notebooks-admin"
)
var (
testEnv *envtest.Environment
cfg *rest.Config
k8sClient client.Client
a *App
ctx context.Context
cancel context.CancelFunc
)
@ -95,6 +108,30 @@ var _ = BeforeSuite(func() {
Expect(err).NotTo(HaveOccurred())
Expect(k8sClient).NotTo(BeNil())
By("creating the notebooks-admin ClusterRoleBinding")
Expect(k8sClient.Create(ctx, &rbacv1.ClusterRoleBinding{
ObjectMeta: metav1.ObjectMeta{
Name: "notebooks-admin",
},
Subjects: []rbacv1.Subject{
{
Kind: "User",
Name: adminUser,
},
},
RoleRef: rbacv1.RoleRef{
Kind: "ClusterRole",
Name: "cluster-admin",
},
})).To(Succeed())
By("listing the clusterRoles")
clusterRoles := &rbacv1.ClusterRoleList{}
Expect(k8sClient.List(ctx, clusterRoles)).To(Succeed())
for _, clusterRole := range clusterRoles.Items {
fmt.Printf("ClusterRole: %s\n", clusterRole.Name)
}
By("setting up the controller manager")
k8sManager, err := ctrl.NewManager(cfg, ctrl.Options{
Scheme: scheme.Scheme,
@ -104,6 +141,21 @@ var _ = BeforeSuite(func() {
})
Expect(err).NotTo(HaveOccurred())
By("initializing the application logger")
appLogger := slog.New(slog.NewTextHandler(GinkgoWriter, nil))
By("creating the request authenticator")
reqAuthN, err := auth.NewRequestAuthenticator(userIdHeader, userIdPrefix, groupsHeader)
Expect(err).NotTo(HaveOccurred())
By("creating the request authorizer")
reqAuthZ, err := auth.NewRequestAuthorizer(k8sManager.GetConfig(), k8sManager.GetHTTPClient())
Expect(err).NotTo(HaveOccurred())
By("creating the application")
// NOTE: we use the `k8sClient` rather than `k8sManager.GetClient()` to avoid race conditions with the cached client
a, err = NewApp(&config.EnvConfig{}, appLogger, k8sClient, k8sManager.GetScheme(), reqAuthN, reqAuthZ)
go func() {
defer GinkgoRecover()
err = k8sManager.Start(ctx)

View File

@ -22,7 +22,10 @@ import (
"net/http"
"github.com/julienschmidt/httprouter"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspacekinds"
repository "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories/workspacekinds"
)
@ -33,12 +36,25 @@ type WorkspaceKindEnvelope Envelope[models.WorkspaceKind]
func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
name := ps.ByName("name")
if name == "" {
a.serverErrorResponse(w, r, fmt.Errorf("workspace kind name is missing"))
return
}
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbGet,
&kubefloworgv1beta1.WorkspaceKind{
ObjectMeta: metav1.ObjectMeta{Name: name},
},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
workspaceKind, err := a.repositories.WorkspaceKind.GetWorkspaceKind(r.Context(), name)
if err != nil {
if errors.Is(err, repository.ErrWorkspaceKindNotFound) {
@ -60,6 +76,18 @@ func (a *App) GetWorkspaceKindHandler(w http.ResponseWriter, r *http.Request, ps
}
func (a *App) GetWorkspaceKindsHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbList,
&kubefloworgv1beta1.WorkspaceKind{},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
workspaceKinds, err := a.repositories.WorkspaceKind.GetWorkspaceKinds(r.Context())
if err != nil {
a.serverErrorResponse(w, r, err)

View File

@ -32,9 +32,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspacekinds"
"github.com/kubeflow/notebooks/workspaces/backend/internal/repositories"
)
var _ = Describe("WorkspaceKinds Handler", func() {
@ -47,8 +45,6 @@ var _ = Describe("WorkspaceKinds Handler", func() {
const namespaceName1 = "wsk-exist-test-ns1"
var (
a App
workspaceKind1Name string
workspaceKind1Key types.NamespacedName
workspaceKind2Name string
@ -62,14 +58,6 @@ var _ = Describe("WorkspaceKinds Handler", func() {
workspaceKind2Name = fmt.Sprintf("workspacekind-2-%s", uniqueName)
workspaceKind2Key = types.NamespacedName{Name: workspaceKind2Name}
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
By("creating Namespace 1")
namespace1 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
@ -111,7 +99,6 @@ var _ = Describe("WorkspaceKinds Handler", func() {
},
}
Expect(k8sClient.Delete(ctx, namespace1)).To(Succeed())
})
It("should retrieve the all WorkspaceKinds successfully", func() {
@ -119,6 +106,9 @@ var _ = Describe("WorkspaceKinds Handler", func() {
req, err := http.NewRequest(http.MethodGet, AllWorkspaceKindsPath, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspaceKindsHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
@ -164,6 +154,9 @@ var _ = Describe("WorkspaceKinds Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspaceKindHandler")
ps := httprouter.Params{
httprouter.Param{Key: WorkspaceKindNamePathParam, Value: workspaceKind1Name},
@ -206,23 +199,14 @@ var _ = Describe("WorkspaceKinds Handler", func() {
// therefore, we run them using the `Serial` Ginkgo decorators.
Context("with no existing WorkspaceKinds", Serial, func() {
var a App
BeforeEach(func() {
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
})
It("should return an empty list of WorkspaceKinds", func() {
By("creating the HTTP request")
req, err := http.NewRequest(http.MethodGet, AllWorkspaceKindsPath, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
@ -254,6 +238,9 @@ var _ = Describe("WorkspaceKinds Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspaceKindHandler")
ps := httprouter.Params{
httprouter.Param{Key: WorkspaceNamePathParam, Value: missingWorkspaceKindName},

View File

@ -23,7 +23,10 @@ import (
"net/http"
"github.com/julienschmidt/httprouter"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces"
repository "github.com/kubeflow/notebooks/workspaces/backend/internal/repositories/workspaces"
)
@ -34,20 +37,35 @@ type WorkspaceEnvelope Envelope[models.Workspace]
func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
namespace := ps.ByName(NamespacePathParam)
workspaceName := ps.ByName(WorkspaceNamePathParam)
var workspace models.Workspace
var err error
if namespace == "" {
a.serverErrorResponse(w, r, fmt.Errorf("namespace is nil"))
return
}
workspaceName := ps.ByName(WorkspaceNamePathParam)
if workspaceName == "" {
a.serverErrorResponse(w, r, fmt.Errorf("workspaceName is nil"))
return
}
workspace, err = a.repositories.Workspace.GetWorkspace(r.Context(), namespace, workspaceName)
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbGet,
&kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: workspaceName,
},
},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
workspace, err := a.repositories.Workspace.GetWorkspace(r.Context(), namespace, workspaceName)
if err != nil {
if errors.Is(err, repository.ErrWorkspaceNotFound) {
a.notFoundResponse(w, r)
@ -71,6 +89,22 @@ func (a *App) GetWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps htt
func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
namespace := ps.ByName(NamespacePathParam)
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbList,
&kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
},
},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
var workspaces []models.Workspace
var err error
if namespace == "" {
@ -95,7 +129,6 @@ func (a *App) GetWorkspacesHandler(w http.ResponseWriter, r *http.Request, ps ht
func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
namespace := ps.ByName("namespace")
if namespace == "" {
a.serverErrorResponse(w, r, fmt.Errorf("namespace is missing"))
return
@ -109,6 +142,23 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps
workspaceModel.Namespace = namespace
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbCreate,
&kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: workspaceModel.Name,
},
},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
createdWorkspace, err := a.repositories.Workspace.CreateWorkspace(r.Context(), workspaceModel)
if err != nil {
a.serverErrorResponse(w, r, fmt.Errorf("error creating workspace: %w", err))
@ -129,18 +179,34 @@ func (a *App) CreateWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps
func (a *App) DeleteWorkspaceHandler(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
namespace := ps.ByName("namespace")
workspaceName := ps.ByName("name")
if namespace == "" {
a.serverErrorResponse(w, r, fmt.Errorf("namespace is missing"))
return
}
workspaceName := ps.ByName("name")
if workspaceName == "" {
a.serverErrorResponse(w, r, fmt.Errorf("workspace name is missing"))
return
}
// =========================== AUTH ===========================
authPolicies := []*auth.ResourcePolicy{
auth.NewResourcePolicy(
auth.ResourceVerbDelete,
&kubefloworgv1beta1.Workspace{
ObjectMeta: metav1.ObjectMeta{
Namespace: namespace,
Name: workspaceName,
},
},
),
}
if success := a.requireAuth(w, r, authPolicies); !success {
return
}
// ============================================================
err := a.repositories.Workspace.DeleteWorkspace(r.Context(), namespace, workspaceName)
if err != nil {
if errors.Is(err, repository.ErrWorkspaceNotFound) {

View File

@ -34,9 +34,7 @@ import (
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/ptr"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
models "github.com/kubeflow/notebooks/workspaces/backend/internal/models/workspaces"
"github.com/kubeflow/notebooks/workspaces/backend/internal/repositories"
)
var _ = Describe("Workspaces Handler", func() {
@ -50,8 +48,6 @@ var _ = Describe("Workspaces Handler", func() {
const namespaceName2 = "ws-exist-ns2"
var (
a App
workspaceName1 string
workspaceKey1 types.NamespacedName
workspaceName2 string
@ -74,14 +70,6 @@ var _ = Describe("Workspaces Handler", func() {
workspaceKindName = fmt.Sprintf("workspacekind-%s", uniqueName)
workspaceKindKey = types.NamespacedName{Name: workspaceKindName}
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
By("creating Namespace 1")
namespace1 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
@ -173,6 +161,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, AllWorkspacesPath, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
@ -225,6 +216,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
@ -277,6 +271,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspaceHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
@ -327,8 +324,6 @@ var _ = Describe("Workspaces Handler", func() {
const namespaceName1 = "ws-invalid-ns1"
var (
a App
workspaceMissingWskName string
workspaceMissingWskKey types.NamespacedName
@ -353,14 +348,6 @@ var _ = Describe("Workspaces Handler", func() {
workspaceKindName = fmt.Sprintf("workspacekind-%s", uniqueName)
workspaceKindKey = types.NamespacedName{Name: workspaceKindName}
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
By("creating Namespace 1")
namespace1 := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
@ -439,6 +426,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
@ -506,6 +496,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspaceHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: namespaceName1},
@ -542,23 +535,14 @@ var _ = Describe("Workspaces Handler", func() {
// therefore, we run them using the `Serial` Ginkgo decorators.
Context("with no existing Workspaces", Serial, func() {
var a App
BeforeEach(func() {
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
})
It("should return an empty list of Workspaces for all namespaces", func() {
By("creating the HTTP request")
req, err := http.NewRequest(http.MethodGet, AllWorkspacesPath, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
ps := httprouter.Params{}
rr := httptest.NewRecorder()
@ -590,6 +574,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspacesHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: missingNamespace},
@ -625,6 +612,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err := http.NewRequest(http.MethodGet, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing GetWorkspaceHandler")
ps := httprouter.Params{
httprouter.Param{Key: NamespacePathParam, Value: missingNamespace},
@ -647,7 +637,6 @@ var _ = Describe("Workspaces Handler", func() {
const namespaceNameCrud = "ws-crud-ns"
var (
a App
workspaceName string
workspaceKey types.NamespacedName
workspaceKindName string
@ -661,14 +650,6 @@ var _ = Describe("Workspaces Handler", func() {
workspaceKindName = fmt.Sprintf("workspacekind-%s", uniqueName)
workspaceKindKey = types.NamespacedName{Name: workspaceKindName}
repos := repositories.NewRepositories(k8sClient)
a = App{
Config: config.EnvConfig{
Port: 4000,
},
repositories: repos,
}
By("creating the Namespace")
namespaceA := &corev1.Namespace{
ObjectMeta: metav1.ObjectMeta{
@ -755,6 +736,9 @@ var _ = Describe("Workspaces Handler", func() {
Expect(err).NotTo(HaveOccurred())
req.Header.Set("Content-Type", "application/json")
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing CreateWorkspaceHandler")
rr := httptest.NewRecorder()
ps := httprouter.Params{
@ -783,6 +767,9 @@ var _ = Describe("Workspaces Handler", func() {
req, err = http.NewRequest(http.MethodDelete, path, http.NoBody)
Expect(err).NotTo(HaveOccurred())
By("setting the auth headers")
req.Header.Set(userIdHeader, adminUser)
By("executing DeleteWorkspaceHandler")
rr = httptest.NewRecorder()
ps = httprouter.Params{

View File

@ -26,27 +26,78 @@ import (
metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server"
application "github.com/kubeflow/notebooks/workspaces/backend/api"
"github.com/kubeflow/notebooks/workspaces/backend/internal/auth"
"github.com/kubeflow/notebooks/workspaces/backend/internal/config"
"github.com/kubeflow/notebooks/workspaces/backend/internal/helper"
"github.com/kubeflow/notebooks/workspaces/backend/internal/server"
)
func main() {
var cfg config.EnvConfig
flag.IntVar(&cfg.Port, "port", getEnvAsInt("PORT", 4000), "API server port")
// Define command line flags
cfg := &config.EnvConfig{}
flag.IntVar(&cfg.Port,
"port",
getEnvAsInt("PORT", 4000),
"API server port",
)
flag.Float64Var(
&cfg.ClientQPS,
"client-qps",
getEnvAsFloat64("CLIENT_QPS", 50),
"QPS configuration passed to rest.Client",
)
flag.IntVar(
&cfg.ClientBurst,
"client-burst",
getEnvAsInt("CLIENT_BURST", 100),
"Maximum Burst configuration passed to rest.Client",
)
flag.BoolVar(
// TODO: remove before GA
&cfg.DisableAuth,
"disable-auth",
getEnvAsBool("DISABLE_AUTH", true),
"Disable authentication and authorization",
)
flag.StringVar(
&cfg.UserIdHeader,
"userid-header",
getEnvAsStr("USERID_HEADER", "kubeflow-userid"),
"Key of request header containing user id",
)
flag.StringVar(
&cfg.UserIdPrefix,
"userid-prefix",
getEnvAsStr("USERID_PREFIX", ":"),
"Request header user id common prefix",
)
flag.StringVar(
&cfg.GroupsHeader,
"groups-header",
getEnvAsStr("GROUPS_HEADER", "kubeflow-groups"),
"Key of request header containing user groups",
)
// Initialize the logger
logger := slog.New(slog.NewTextHandler(os.Stdout, nil))
kubeconfig, err := helper.GetKubeconfig()
// Build the Kubernetes client configuration
kubeconfig, err := ctrl.GetConfig()
if err != nil {
logger.Error("failed to get kubeconfig", "error", err)
logger.Error("failed to get Kubernetes config", "error", err)
os.Exit(1)
}
kubeconfig.QPS = float32(cfg.ClientQPS)
kubeconfig.Burst = cfg.ClientBurst
// Build the Kubernetes scheme
scheme, err := helper.BuildScheme()
if err != nil {
logger.Error("failed to build Kubernetes scheme", "error", err)
os.Exit(1)
}
// Create the controller manager
mgr, err := ctrl.NewManager(kubeconfig, ctrl.Options{
Scheme: scheme,
Metrics: metricsserver.Options{
@ -60,7 +111,21 @@ func main() {
os.Exit(1)
}
app, err := application.NewApp(cfg, logger, mgr.GetClient(), mgr.GetScheme())
// Create the request authenticator
reqAuthN, err := auth.NewRequestAuthenticator(cfg.UserIdHeader, cfg.UserIdPrefix, cfg.GroupsHeader)
if err != nil {
logger.Error("failed to create request authenticator", "error", err)
os.Exit(1)
}
// Create the request authorizer
reqAuthZ, err := auth.NewRequestAuthorizer(mgr.GetConfig(), mgr.GetHTTPClient())
if err != nil {
logger.Error("failed to create request authorizer", "error", err)
}
// Create the application and server
app, err := application.NewApp(cfg, logger, mgr.GetClient(), mgr.GetScheme(), reqAuthN, reqAuthZ)
if err != nil {
logger.Error("failed to create app", "error", err)
os.Exit(1)
@ -75,6 +140,7 @@ func main() {
os.Exit(1)
}
// Start the controller manager
logger.Info("starting manager")
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
logger.Error("problem running manager", "error", err)
@ -90,3 +156,28 @@ func getEnvAsInt(name string, defaultVal int) int {
}
return defaultVal
}
func getEnvAsFloat64(name string, defaultVal float64) float64 {
if value, exists := os.LookupEnv(name); exists {
if floatValue, err := strconv.ParseFloat(value, 64); err == nil {
return floatValue
}
}
return defaultVal
}
func getEnvAsStr(name string, defaultVal string) string {
if value, exists := os.LookupEnv(name); exists {
return value
}
return defaultVal
}
func getEnvAsBool(name string, defaultVal bool) bool {
if value, exists := os.LookupEnv(name); exists {
if boolValue, err := strconv.ParseBool(value); err == nil {
return boolValue
}
}
return defaultVal
}

View File

@ -9,23 +9,29 @@ require (
github.com/kubeflow/notebooks/workspaces/controller v0.0.0
github.com/onsi/ginkgo/v2 v2.19.0
github.com/onsi/gomega v1.33.1
github.com/stretchr/testify v1.9.0
k8s.io/api v0.31.0
k8s.io/apimachinery v0.31.0
k8s.io/apiserver v0.31.0
k8s.io/client-go v0.31.0
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8
sigs.k8s.io/controller-runtime v0.19.1
)
require (
github.com/antlr4-go/antlr/v4 v4.13.0 // indirect
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/blang/semver/v4 v4.0.0 // indirect
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/emicklei/go-restful/v3 v3.11.0 // indirect
github.com/evanphx/json-patch/v5 v5.9.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/fsnotify/fsnotify v1.7.0 // indirect
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
github.com/go-logr/logr v1.4.2 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-logr/zapr v1.3.0 // indirect
github.com/go-openapi/jsonpointer v0.19.6 // indirect
github.com/go-openapi/jsonreference v0.20.2 // indirect
@ -34,12 +40,15 @@ require (
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/cel-go v0.20.1 // indirect
github.com/google/gnostic-models v0.6.8 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/gofuzz v1.2.0 // indirect
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect
github.com/imdario/mergo v0.3.6 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
@ -47,31 +56,46 @@ require (
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/prometheus/client_golang v1.19.1 // indirect
github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect
github.com/spf13/cobra v1.8.1 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stoewer/go-strcase v1.2.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 // indirect
go.opentelemetry.io/otel/metric v1.28.0 // indirect
go.opentelemetry.io/otel/sdk v1.28.0 // indirect
go.opentelemetry.io/otel/trace v1.28.0 // indirect
go.opentelemetry.io/proto/otlp v1.3.1 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.26.0 // indirect
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc // indirect
golang.org/x/net v0.26.0 // indirect
golang.org/x/oauth2 v0.21.0 // indirect
golang.org/x/sync v0.7.0 // indirect
golang.org/x/sys v0.21.0 // indirect
golang.org/x/term v0.21.0 // indirect
golang.org/x/text v0.16.0 // indirect
golang.org/x/time v0.3.0 // indirect
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect
gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 // indirect
google.golang.org/grpc v1.65.0 // indirect
google.golang.org/protobuf v1.34.2 // indirect
gopkg.in/inf.v0 v0.9.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
k8s.io/apiextensions-apiserver v0.31.0 // indirect
k8s.io/component-base v0.31.0 // indirect
k8s.io/klog/v2 v2.130.1 // indirect
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 // indirect
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 // indirect
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd // indirect
sigs.k8s.io/structured-merge-diff/v4 v4.4.1 // indirect
sigs.k8s.io/yaml v1.4.0 // indirect

View File

@ -1,7 +1,16 @@
github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI=
github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a h1:idn718Q4B6AGu/h5Sxe66HYVdqdGu2l9Iebqhi/AEoA=
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM=
github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -13,12 +22,17 @@ github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8
github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ=
github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg=
github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E=
github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ=
github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg=
github.com/go-openapi/jsonpointer v0.19.6 h1:eCs3fxoIi3Wh6vtgmLTOjdhSpiqphQ+DaPn38N2ZdrE=
@ -36,6 +50,8 @@ github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/cel-go v0.20.1 h1:nDx9r8S3L4pE61eDdt8igGj8rf5kjYR3ILxWIpWNi84=
github.com/google/cel-go v0.20.1/go.mod h1:kWcIzTsPX0zmQ+H3TirHstLLf9ep5QTsZBN9u4dOYLg=
github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I=
github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYuF5OpfBSENuI8U=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
@ -48,8 +64,12 @@ github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af h1:kmjWCqn2qkEml422C2
github.com/google/pprof v0.0.0-20240525223248-4bfdf5a9a2af/go.mod h1:K1liHPHnj73Fdn/EKuT8nrFqBihUSKXoLYU0BuatOYo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0=
github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k=
github.com/imdario/mergo v0.3.6 h1:xTNEAn+kxVO7dTZGu0CegyqKZmoWFI0rF8UxjlB2d28=
github.com/imdario/mergo v0.3.6/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
@ -93,12 +113,18 @@ github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0leargg
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/stoewer/go-strcase v1.2.0 h1:Z2iHWqGXH00XYgqDmNgQbIBxf3wrNq0F3feEy0ainaU=
github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
@ -108,6 +134,22 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg=
go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo=
go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0 h1:3Q/xZUyC1BBkualc9ROb4G8qkH90LXEIICcs5zv1OYY=
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.28.0/go.mod h1:s75jGIWA9OfCMzF0xr+ZgfrB5FEbbV7UuYo32ahUiFI=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0 h1:qFffATk0X+HD+f1Z8lswGiOQYKHRlzfmdJm0wEaVrFA=
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.27.0/go.mod h1:MOiCmryaYtc+V0Ei+Tx9o5S1ZjA7kzLucuVuyzBZloQ=
go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q=
go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s=
go.opentelemetry.io/otel/sdk v1.28.0 h1:b9d7hIry8yZsgtbmM0DKyPWMMUMlK9NEKuIG4aBqWyE=
go.opentelemetry.io/otel/sdk v1.28.0/go.mod h1:oYj7ClPUA7Iw3m+r7GeEjz0qckQRJK2B8zjcZEfu7Pg=
go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g=
go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI=
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@ -132,6 +174,8 @@ golang.org/x/oauth2 v0.21.0/go.mod h1:XYTD2NtWslqkgxebSiOHnXEap4TF09sJSc7H1sXbht
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -157,6 +201,12 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw=
gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157 h1:7whR9kGa5LUwFtpLm2ArCEejtnxlGeLbAyjFY8sGNFw=
google.golang.org/genproto/googleapis/api v0.0.0-20240528184218-531527333157/go.mod h1:99sLkeliLXfdj2J75X3Ho+rrVCaJze0uwN7zDDkjPVU=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094 h1:BwIjyKYGsK9dMCBOorzRri8MQwmi7mT9rGHsCEinZkA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20240701130421-f6361c86f094/go.mod h1:Ue6ibwXGpU+dqIcODieyLOcgj7z8+IcskoNIgZxtrFY=
google.golang.org/grpc v1.65.0 h1:bs/cUb4lp1G5iImFFd3u5ixQzweKizoZJAwBNLR42lc=
google.golang.org/grpc v1.65.0/go.mod h1:WgYC2ypjlB0EiQi6wdKixMqukr6lBc0Vo+oOgjrM5ZQ=
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -166,6 +216,7 @@ gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSP
gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M=
gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
@ -178,14 +229,20 @@ k8s.io/apiextensions-apiserver v0.31.0 h1:fZgCVhGwsclj3qCw1buVXCV6khjRzKC5eCFt24
k8s.io/apiextensions-apiserver v0.31.0/go.mod h1:b9aMDEYaEe5sdK+1T0KU78ApR/5ZVp4i56VacZYEHxk=
k8s.io/apimachinery v0.31.0 h1:m9jOiSr3FoSSL5WO9bjm1n6B9KROYYgNZOb4tyZ1lBc=
k8s.io/apimachinery v0.31.0/go.mod h1:rsPdaZJfTfLsNJSQzNHQvYoTmxhoOEofxtOsF3rtsMo=
k8s.io/apiserver v0.31.0 h1:p+2dgJjy+bk+B1Csz+mc2wl5gHwvNkC9QJV+w55LVrY=
k8s.io/apiserver v0.31.0/go.mod h1:KI9ox5Yu902iBnnyMmy7ajonhKnkeZYJhTZ/YI+WEMk=
k8s.io/client-go v0.31.0 h1:QqEJzNjbN2Yv1H79SsS+SWnXkBgVu4Pj3CJQgbx0gI8=
k8s.io/client-go v0.31.0/go.mod h1:Y9wvC76g4fLjmU0BA+rV+h2cncoadjvjjkkIGoTLcGU=
k8s.io/component-base v0.31.0 h1:/KIzGM5EvPNQcYgwq5NwoQBaOlVFrghoVGr8lG6vNRs=
k8s.io/component-base v0.31.0/go.mod h1:TYVuzI1QmN4L5ItVdMSXKvH7/DtvIuas5/mm8YT3rTo=
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340 h1:BZqlfIlq5YbRMFko6/PM7FjZpUb45WallggurYhKGag=
k8s.io/kube-openapi v0.0.0-20240228011516-70dd3763d340/go.mod h1:yD4MZYeKMBwQKVht279WycxKyM84kkAx2DPrTXaeb98=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8 h1:pUdcCO1Lk/tbT5ztQWOBi5HBgbBP1J8+AsQnQCKsi8A=
k8s.io/utils v0.0.0-20240711033017-18e509b52bc8/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3 h1:2770sDpzrjjsAtVhSeUFseziht227YAWYHLGNM8QPwY=
sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.30.3/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw=
sigs.k8s.io/controller-runtime v0.19.1 h1:Son+Q40+Be3QWb+niBXAg2vFiYWolDjjRfO8hn/cxOk=
sigs.k8s.io/controller-runtime v0.19.1/go.mod h1:iRmWllt8IlaLjvTTDLhRBXIEtkCK6hwVBJJsYS9Ajf4=
sigs.k8s.io/json v0.0.0-20221116044647-bc3834ca7abd h1:EDPBXCAspyGV4jQlpZSudPeMmr1bNJefnuqLsRAsHZo=

View File

@ -0,0 +1,70 @@
/*
Copyright 2024.
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
http://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 auth
import (
"fmt"
"net/http"
"strings"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
"k8s.io/apiserver/pkg/authentication/user"
)
// NewRequestAuthenticator returns a new request authenticator based on the provided configuration.
func NewRequestAuthenticator(useridHeader string, useridPrefix string, groupsHeader string) (authenticator.Request, error) {
// create an upstream `requestHeaderAuthRequestHandler` to extract user and groups from the request headers
requestHeaderAuthenticator, err := headerrequest.New(
[]string{useridHeader},
[]string{groupsHeader},
nil,
)
if err != nil {
return nil, fmt.Errorf("failed to create request header authenticator: %w", err)
}
// if the user id prefix is empty, return the upstream authenticator as is
if useridPrefix == "" {
return requestHeaderAuthenticator, nil
}
// wrap the upstream authenticator to trim the user prefix from the user id
requestAuthenticator := authenticator.RequestFunc(func(req *http.Request) (*authenticator.Response, bool, error) {
response, ok, err := requestHeaderAuthenticator.AuthenticateRequest(req)
if err != nil {
return nil, false, err
}
// if the request was not authenticated, return the response as is
if !ok {
return response, ok, nil
}
// trim the user id prefix from the username
return &authenticator.Response{
User: &user.DefaultInfo{
Name: strings.TrimPrefix(response.User.GetName(), useridPrefix),
Groups: response.User.GetGroups(),
Extra: response.User.GetExtra(),
},
}, true, nil
})
return requestAuthenticator, nil
}

View File

@ -0,0 +1,128 @@
/*
Copyright 2024.
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
http://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 auth
import (
"fmt"
"net/http"
"time"
"k8s.io/apimachinery/pkg/util/wait"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
)
const (
allowCacheTTL = 10 * time.Second
denyCacheTTL = 10 * time.Second
)
// NewRequestAuthorizer returns a new request authorizer based on the provided configuration.
// loosely based on `WithAuthenticationAndAuthorization` from: https://github.com/kubernetes-sigs/controller-runtime/blob/v0.20.1/pkg/metrics/filters/filters.go#L36-L122
func NewRequestAuthorizer(restConfig *rest.Config, httpClient *http.Client) (authorizer.Authorizer, error) {
authorizationV1Client, err := authorizationv1.NewForConfigAndClient(restConfig, httpClient)
if err != nil {
return nil, err
}
authorizerConfig := authorizerfactory.DelegatingAuthorizerConfig{
SubjectAccessReviewClient: authorizationV1Client,
// AllowCacheTTL is the length of time that a successful authorization response will be cached
AllowCacheTTL: allowCacheTTL,
// DenyCacheTTL is the length of time that a denied authorization response will be cached
DenyCacheTTL: denyCacheTTL,
// wait.Backoff is copied from: https://github.com/kubernetes/apiserver/blob/v0.29.0/pkg/server/options/authentication.go#L43-L50
// options.DefaultAuthWebhookRetryBackoff is not used to avoid a dependency on "k8s.io/apiserver/pkg/server/options".
WebhookRetryBackoff: &wait.Backoff{
Duration: 500 * time.Millisecond,
Factor: 1.5,
Jitter: 0.2,
Steps: 5,
},
}
delegatingAuthorizer, err := authorizerConfig.New()
if err != nil {
return nil, fmt.Errorf("failed to create authorizer: %w", err)
}
return delegatingAuthorizer, nil
}
type ResourcePolicy struct {
Verb ResourceVerb
Group string
Version string
Kind string
Namespace string
Name string
}
// NewResourcePolicy returns a new resource policy based on the provided verb and resource object.
func NewResourcePolicy(verb ResourceVerb, object client.Object) *ResourcePolicy {
policy := &ResourcePolicy{
Verb: verb,
Group: object.GetObjectKind().GroupVersionKind().Group,
Version: object.GetObjectKind().GroupVersionKind().Version,
Kind: object.GetObjectKind().GroupVersionKind().Kind,
}
if object.GetNamespace() != "" {
policy.Namespace = object.GetNamespace()
}
if object.GetName() != "" {
policy.Name = object.GetName()
}
return policy
}
// AttributesFor returns an authorizer.Attributes which could be used with an authorizer.Authorizer to authorize the user for the resource policy.
func (p *ResourcePolicy) AttributesFor(u user.Info) authorizer.Attributes {
return authorizer.AttributesRecord{
User: u,
Verb: string(p.Verb),
Namespace: p.Namespace,
APIGroup: p.Group,
APIVersion: p.Version,
Resource: p.Kind,
Name: p.Name,
ResourceRequest: true,
}
}
// ResourceVerb represents a verb for an action on a resource.
type ResourceVerb string
const (
ResourceVerbCreate ResourceVerb = "create"
ResourceVerbGet ResourceVerb = "get"
ResourceVerbList ResourceVerb = "list"
ResourceVerbUpdate ResourceVerb = "update"
ResourceVerbPatch ResourceVerb = "patch"
ResourceVerbDelete ResourceVerb = "delete"
)

View File

@ -18,4 +18,13 @@ package config
type EnvConfig struct {
Port int
ClientQPS float64
ClientBurst int
DisableAuth bool
UserIdHeader string
UserIdPrefix string
GroupsHeader string
}

View File

@ -19,22 +19,11 @@ package helper
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
clientRest "k8s.io/client-go/rest"
"k8s.io/client-go/tools/clientcmd"
kubefloworgv1beta1 "github.com/kubeflow/notebooks/workspaces/controller/api/v1beta1"
"k8s.io/apimachinery/pkg/runtime"
clientgoscheme "k8s.io/client-go/kubernetes/scheme"
)
// GetKubeconfig returns the current KUBECONFIG configuration based on the default loading rules.
func GetKubeconfig() (*clientRest.Config, error) {
loadingRules := clientcmd.NewDefaultClientConfigLoadingRules()
configOverrides := &clientcmd.ConfigOverrides{}
kubeConfig := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, configOverrides)
return kubeConfig.ClientConfig()
}
// BuildScheme returns builds a new runtime scheme with all the necessary types registered.
func BuildScheme() (*runtime.Scheme, error) {
scheme := runtime.NewScheme()