implement authorizer filter
Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
@@ -9,13 +9,15 @@ import (
|
|||||||
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
|
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||||
"k8s.io/apimachinery/pkg/util/sets"
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
|
||||||
"k8s.io/apiserver/pkg/authentication/request/union"
|
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
"kubesphere.io/kubesphere/pkg/api/iam"
|
"kubesphere.io/kubesphere/pkg/api/iam"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
|
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
|
||||||
authenticationrequest "kubesphere.io/kubesphere/pkg/apiserver/authentication/request"
|
authenticationrequest "kubesphere.io/kubesphere/pkg/apiserver/authentication/request"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/path"
|
||||||
|
unionauthorizer "kubesphere.io/kubesphere/pkg/apiserver/authorization/union"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/dispatch"
|
"kubesphere.io/kubesphere/pkg/apiserver/dispatch"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/filters"
|
"kubesphere.io/kubesphere/pkg/apiserver/filters"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
@@ -179,13 +181,17 @@ func (s *APIServer) buildHandlerChain() {
|
|||||||
})
|
})
|
||||||
|
|
||||||
handler := s.Server.Handler
|
handler := s.Server.Handler
|
||||||
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
|
|
||||||
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch)
|
|
||||||
handler = filters.WithAuthorization(handler, authorizerfactory.NewAlwaysAllowAuthorizer())
|
|
||||||
|
|
||||||
authn := union.New(&authenticationrequest.AnonymousAuthenticator{}, bearertoken.New(jwttoken.NewTokenAuthenticator(s.CacheClient, s.AuthenticateOptions.JwtSecret)))
|
|
||||||
handler = filters.WithAuthentication(handler, authn, failed)
|
|
||||||
handler = filters.WithRequestInfo(handler, requestInfoResolver)
|
handler = filters.WithRequestInfo(handler, requestInfoResolver)
|
||||||
|
authn := unionauth.New(&authenticationrequest.AnonymousAuthenticator{}, bearertoken.New(jwttoken.NewTokenAuthenticator(s.CacheClient, s.AuthenticateOptions.JwtSecret)))
|
||||||
|
handler = filters.WithAuthentication(handler, authn, failed)
|
||||||
|
|
||||||
|
excludedPaths := []string{"/oauth/authorize", "/oauth/token"}
|
||||||
|
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
|
||||||
|
authorizer := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer())
|
||||||
|
handler = filters.WithAuthorization(handler, authorizer)
|
||||||
|
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch)
|
||||||
|
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
|
||||||
|
|
||||||
s.Server.Handler = handler
|
s.Server.Handler = handler
|
||||||
}
|
}
|
||||||
@@ -194,7 +200,7 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
|
|||||||
klog.V(0).Info("Start cache objects")
|
klog.V(0).Info("Start cache objects")
|
||||||
|
|
||||||
discoveryClient := s.KubernetesClient.Kubernetes().Discovery()
|
discoveryClient := s.KubernetesClient.Kubernetes().Discovery()
|
||||||
apiResourcesList, err := discoveryClient.ServerResources()
|
_, apiResourcesList, err := discoveryClient.ServerGroupsAndResources()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
190
pkg/apiserver/authorization/authorizer/interfaces.go
Normal file
190
pkg/apiserver/authorization/authorizer/interfaces.go
Normal file
@@ -0,0 +1,190 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 authorizer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Attributes is an interface used by an Authorizer to get information about a request
|
||||||
|
// that is used to make an authorization decision.
|
||||||
|
type Attributes interface {
|
||||||
|
// GetUser returns the user.Info object to authorize
|
||||||
|
GetUser() user.Info
|
||||||
|
|
||||||
|
// GetVerb returns the kube verb associated with API requests (this includes get, list, watch, create, update, patch, delete, deletecollection, and proxy),
|
||||||
|
// or the lowercased HTTP verb associated with non-API requests (this includes get, put, post, patch, and delete)
|
||||||
|
GetVerb() string
|
||||||
|
|
||||||
|
// When IsReadOnly() == true, the request has no side effects, other than
|
||||||
|
// caching, logging, and other incidentals.
|
||||||
|
IsReadOnly() bool
|
||||||
|
|
||||||
|
// Indicates whether or not the request should be handled by kubernetes or kubesphere
|
||||||
|
IsKubernetesRequest() bool
|
||||||
|
|
||||||
|
// The cluster of the object, if a request is for a REST object.
|
||||||
|
GetCluster() string
|
||||||
|
|
||||||
|
// The workspace of the object, if a request is for a REST object.
|
||||||
|
GetWorkspace() string
|
||||||
|
|
||||||
|
// The namespace of the object, if a request is for a REST object.
|
||||||
|
GetNamespace() string
|
||||||
|
|
||||||
|
// The devops project of the object, if a request is for a REST object.
|
||||||
|
GetDevopsProject() string
|
||||||
|
|
||||||
|
// The kind of object, if a request is for a REST object.
|
||||||
|
GetResource() string
|
||||||
|
|
||||||
|
// GetSubresource returns the subresource being requested, if present
|
||||||
|
GetSubresource() string
|
||||||
|
|
||||||
|
// GetName returns the name of the object as parsed off the request. This will not be present for all request types, but
|
||||||
|
// will be present for: get, update, delete
|
||||||
|
GetName() string
|
||||||
|
|
||||||
|
// The group of the resource, if a request is for a REST object.
|
||||||
|
GetAPIGroup() string
|
||||||
|
|
||||||
|
// GetAPIVersion returns the version of the group requested, if a request is for a REST object.
|
||||||
|
GetAPIVersion() string
|
||||||
|
|
||||||
|
// IsResourceRequest returns true for requests to API resources, like /api/v1/nodes,
|
||||||
|
// and false for non-resource endpoints like /api, /healthz
|
||||||
|
IsResourceRequest() bool
|
||||||
|
|
||||||
|
// GetPath returns the path of the request
|
||||||
|
GetPath() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorizer makes an authorization decision based on information gained by making
|
||||||
|
// zero or more calls to methods of the Attributes interface. It returns nil when an action is
|
||||||
|
// authorized, otherwise it returns an error.
|
||||||
|
type Authorizer interface {
|
||||||
|
Authorize(a Attributes) (authorized Decision, reason string, err error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AuthorizerFunc func(a Attributes) (Decision, string, error)
|
||||||
|
|
||||||
|
func (f AuthorizerFunc) Authorize(a Attributes) (Decision, string, error) {
|
||||||
|
return f(a)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuleResolver provides a mechanism for resolving the list of rules that apply to a given user within a namespace.
|
||||||
|
type RuleResolver interface {
|
||||||
|
// RulesFor get the list of cluster wide rules, the list of rules in the specific namespace, incomplete status and errors.
|
||||||
|
RulesFor(user user.Info, namespace string) ([]ResourceRuleInfo, []NonResourceRuleInfo, bool, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RequestAttributesGetter provides a function that extracts Attributes from an http.Request
|
||||||
|
type RequestAttributesGetter interface {
|
||||||
|
GetRequestAttributes(user.Info, *http.Request) Attributes
|
||||||
|
}
|
||||||
|
|
||||||
|
// AttributesRecord implements Attributes interface.
|
||||||
|
type AttributesRecord struct {
|
||||||
|
User user.Info
|
||||||
|
Verb string
|
||||||
|
Cluster string
|
||||||
|
Workspace string
|
||||||
|
Namespace string
|
||||||
|
DevopsProject string
|
||||||
|
APIGroup string
|
||||||
|
APIVersion string
|
||||||
|
Resource string
|
||||||
|
Subresource string
|
||||||
|
Name string
|
||||||
|
KubernetesRequest bool
|
||||||
|
ResourceRequest bool
|
||||||
|
Path string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetUser() user.Info {
|
||||||
|
return a.User
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetVerb() string {
|
||||||
|
return a.Verb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsReadOnly() bool {
|
||||||
|
return a.Verb == "get" || a.Verb == "list" || a.Verb == "watch"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetCluster() string {
|
||||||
|
return a.Cluster
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetWorkspace() string {
|
||||||
|
return a.Workspace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetNamespace() string {
|
||||||
|
return a.Namespace
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetDevopsProject() string {
|
||||||
|
return a.DevopsProject
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetResource() string {
|
||||||
|
return a.Resource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetSubresource() string {
|
||||||
|
return a.Subresource
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetName() string {
|
||||||
|
return a.Name
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIGroup() string {
|
||||||
|
return a.APIGroup
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetAPIVersion() string {
|
||||||
|
return a.APIVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsResourceRequest() bool {
|
||||||
|
return a.ResourceRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) IsKubernetesRequest() bool {
|
||||||
|
return a.KubernetesRequest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a AttributesRecord) GetPath() string {
|
||||||
|
return a.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
type Decision int
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DecisionDeny means that an authorizer decided to deny the action.
|
||||||
|
DecisionDeny Decision = iota
|
||||||
|
// DecisionAllow means that an authorizer decided to allow the action.
|
||||||
|
DecisionAllow
|
||||||
|
// DecisionNoOpionion means that an authorizer has no opinion on whether
|
||||||
|
// to allow or deny an action.
|
||||||
|
DecisionNoOpinion
|
||||||
|
)
|
||||||
73
pkg/apiserver/authorization/authorizer/rule.go
Normal file
73
pkg/apiserver/authorization/authorizer/rule.go
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2017 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 authorizer
|
||||||
|
|
||||||
|
type ResourceRuleInfo interface {
|
||||||
|
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||||
|
GetVerbs() []string
|
||||||
|
// GetAPIGroups return the names of the APIGroup that contains the resources.
|
||||||
|
GetAPIGroups() []string
|
||||||
|
// GetResources return a list of resources the rule applies to.
|
||||||
|
GetResources() []string
|
||||||
|
// GetResourceNames return a white list of names that the rule applies to.
|
||||||
|
GetResourceNames() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultResourceRuleInfo holds information that describes a rule for the resource
|
||||||
|
type DefaultResourceRuleInfo struct {
|
||||||
|
Verbs []string
|
||||||
|
APIGroups []string
|
||||||
|
Resources []string
|
||||||
|
ResourceNames []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetVerbs() []string {
|
||||||
|
return i.Verbs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetAPIGroups() []string {
|
||||||
|
return i.APIGroups
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetResources() []string {
|
||||||
|
return i.Resources
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultResourceRuleInfo) GetResourceNames() []string {
|
||||||
|
return i.ResourceNames
|
||||||
|
}
|
||||||
|
|
||||||
|
type NonResourceRuleInfo interface {
|
||||||
|
// GetVerbs returns a list of kubernetes resource API verbs.
|
||||||
|
GetVerbs() []string
|
||||||
|
// GetNonResourceURLs return a set of partial urls that a user should have access to.
|
||||||
|
GetNonResourceURLs() []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// DefaultNonResourceRuleInfo holds information that describes a rule for the non-resource
|
||||||
|
type DefaultNonResourceRuleInfo struct {
|
||||||
|
Verbs []string
|
||||||
|
NonResourceURLs []string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultNonResourceRuleInfo) GetVerbs() []string {
|
||||||
|
return i.Verbs
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *DefaultNonResourceRuleInfo) GetNonResourceURLs() []string {
|
||||||
|
return i.NonResourceURLs
|
||||||
|
}
|
||||||
32
pkg/apiserver/authorization/authorizerfactory/opa.go
Normal file
32
pkg/apiserver/authorization/authorizerfactory/opa.go
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 The KubeSphere Authors.
|
||||||
|
*
|
||||||
|
* 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 authorizerfactory
|
||||||
|
|
||||||
|
import "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
|
||||||
|
type opaAuthorizer struct{}
|
||||||
|
|
||||||
|
func (opaAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
|
||||||
|
// TODO implement.
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewOPAAuthorizer() *opaAuthorizer {
|
||||||
|
return new(opaAuthorizer)
|
||||||
|
}
|
||||||
18
pkg/apiserver/authorization/path/doc.go
Normal file
18
pkg/apiserver/authorization/path/doc.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 path contains an authorizer that allows certain paths and path prefixes.
|
||||||
|
package path // import "k8s.io/apiserver/pkg/authorization/path"
|
||||||
67
pkg/apiserver/authorization/path/path.go
Normal file
67
pkg/apiserver/authorization/path/path.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 path
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewAuthorizer returns an authorizer which accepts a given set of paths.
|
||||||
|
// Each path is either a fully matching path or it ends in * in case a prefix match is done. A leading / is optional.
|
||||||
|
func NewAuthorizer(alwaysAllowPaths []string) (authorizer.Authorizer, error) {
|
||||||
|
var prefixes []string
|
||||||
|
paths := sets.NewString()
|
||||||
|
for _, p := range alwaysAllowPaths {
|
||||||
|
p = strings.TrimPrefix(p, "/")
|
||||||
|
if len(p) == 0 {
|
||||||
|
// matches "/"
|
||||||
|
paths.Insert(p)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.ContainsRune(p[:len(p)-1], '*') {
|
||||||
|
return nil, fmt.Errorf("only trailing * allowed in %q", p)
|
||||||
|
}
|
||||||
|
if strings.HasSuffix(p, "*") {
|
||||||
|
prefixes = append(prefixes, p[:len(p)-1])
|
||||||
|
} else {
|
||||||
|
paths.Insert(p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.AuthorizerFunc(func(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
if a.IsResourceRequest() {
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
pth := strings.TrimPrefix(a.GetPath(), "/")
|
||||||
|
if paths.Has(pth) {
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, prefix := range prefixes {
|
||||||
|
if strings.HasPrefix(pth, prefix) {
|
||||||
|
return authorizer.DecisionAllow, "", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.DecisionNoOpinion, "", nil
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
77
pkg/apiserver/authorization/path/path_test.go
Normal file
77
pkg/apiserver/authorization/path/path_test.go
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 path
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNewAuthorizer(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
excludedPaths []string
|
||||||
|
allowed, denied, noOpinion []string
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{"inner star", []string{"/foo*bar"}, nil, nil, nil, true},
|
||||||
|
{"double star", []string{"/foo**"}, nil, nil, nil, true},
|
||||||
|
{"empty", nil, nil, nil, []string{"/"}, false},
|
||||||
|
{"slash", []string{"/"}, []string{"/"}, nil, []string{"/foo", "//"}, false},
|
||||||
|
{"foo", []string{"/foo"}, []string{"/foo", "foo"}, nil, []string{"/", "", "/bar", "/foo/", "/fooooo", "//foo"}, false},
|
||||||
|
{"foo slash", []string{"/foo/"}, []string{"/foo/"}, nil, []string{"/", "", "/bar", "/foo", "/fooooo"}, false},
|
||||||
|
{"foo slash star", []string{"/foo/*"}, []string{"/foo/", "/foo/bar/bla"}, nil, []string{"/", "", "/foo", "/bar", "/fooooo"}, false},
|
||||||
|
{"foo bar", []string{"/foo", "/bar"}, []string{"/foo", "/bar"}, nil, []string{"/", "", "/foo/", "/bar/", "/fooooo"}, false},
|
||||||
|
{"foo star", []string{"/foo*"}, []string{"/foo", "/foooo"}, nil, []string{"/", "", "/fo", "/bar"}, false},
|
||||||
|
{"star", []string{"/*"}, []string{"/", "", "/foo", "/foooo"}, nil, nil, false},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
a, err := NewAuthorizer(tt.excludedPaths)
|
||||||
|
if err != nil && !tt.wantErr {
|
||||||
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
}
|
||||||
|
if err == nil && tt.wantErr {
|
||||||
|
t.Fatalf("expected error, didn't get any")
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, cases := range []struct {
|
||||||
|
paths []string
|
||||||
|
want authorizer.Decision
|
||||||
|
}{
|
||||||
|
{tt.allowed, authorizer.DecisionAllow},
|
||||||
|
{tt.denied, authorizer.DecisionDeny},
|
||||||
|
{tt.noOpinion, authorizer.DecisionNoOpinion},
|
||||||
|
} {
|
||||||
|
for _, pth := range cases.paths {
|
||||||
|
info := authorizer.AttributesRecord{
|
||||||
|
Path: pth,
|
||||||
|
}
|
||||||
|
if got, _, err := a.Authorize(info); err != nil {
|
||||||
|
t.Errorf("NewAuthorizer(%v).Authorize(%q) return unexpected error: %v", tt.excludedPaths, pth, err)
|
||||||
|
} else if got != cases.want {
|
||||||
|
t.Errorf("NewAuthorizer(%v).Authorize(%q) = %v, want %v", tt.excludedPaths, pth, got, cases.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
105
pkg/apiserver/authorization/union/union.go
Normal file
105
pkg/apiserver/authorization/union/union.go
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 union implements an authorizer that combines multiple subauthorizer.
|
||||||
|
// The union authorizer iterates over each subauthorizer and returns the first
|
||||||
|
// decision that is either an Allow decision or a Deny decision. If a
|
||||||
|
// subauthorizer returns a NoOpinion, then the union authorizer moves onto the
|
||||||
|
// next authorizer or, if the subauthorizer was the last authorizer, returns
|
||||||
|
// NoOpinion as the aggregate decision. I.e. union authorizer creates an
|
||||||
|
// aggregate decision and supports short-circuit allows and denies from
|
||||||
|
// subauthorizers.
|
||||||
|
package union
|
||||||
|
|
||||||
|
import (
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
)
|
||||||
|
|
||||||
|
// unionAuthzHandler authorizer against a chain of authorizer.Authorizer
|
||||||
|
type unionAuthzHandler []authorizer.Authorizer
|
||||||
|
|
||||||
|
// New returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||||
|
func New(authorizationHandlers ...authorizer.Authorizer) authorizer.Authorizer {
|
||||||
|
return unionAuthzHandler(authorizationHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Authorizes against a chain of authorizer.Authorizer objects and returns nil if successful and returns error if unsuccessful
|
||||||
|
func (authzHandler unionAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
var (
|
||||||
|
errlist []error
|
||||||
|
reasonlist []string
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, currAuthzHandler := range authzHandler {
|
||||||
|
decision, reason, err := currAuthzHandler.Authorize(a)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
errlist = append(errlist, err)
|
||||||
|
}
|
||||||
|
if len(reason) != 0 {
|
||||||
|
reasonlist = append(reasonlist, reason)
|
||||||
|
}
|
||||||
|
switch decision {
|
||||||
|
case authorizer.DecisionAllow, authorizer.DecisionDeny:
|
||||||
|
return decision, reason, err
|
||||||
|
case authorizer.DecisionNoOpinion:
|
||||||
|
// continue to the next authorizer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return authorizer.DecisionNoOpinion, strings.Join(reasonlist, "\n"), utilerrors.NewAggregate(errlist)
|
||||||
|
}
|
||||||
|
|
||||||
|
// unionAuthzRulesHandler authorizer against a chain of authorizer.RuleResolver
|
||||||
|
type unionAuthzRulesHandler []authorizer.RuleResolver
|
||||||
|
|
||||||
|
// NewRuleResolvers returns an authorizer that authorizes against a chain of authorizer.Authorizer objects
|
||||||
|
func NewRuleResolvers(authorizationHandlers ...authorizer.RuleResolver) authorizer.RuleResolver {
|
||||||
|
return unionAuthzRulesHandler(authorizationHandlers)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RulesFor against a chain of authorizer.RuleResolver objects and returns nil if successful and returns error if unsuccessful
|
||||||
|
func (authzHandler unionAuthzRulesHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||||
|
var (
|
||||||
|
errList []error
|
||||||
|
resourceRulesList []authorizer.ResourceRuleInfo
|
||||||
|
nonResourceRulesList []authorizer.NonResourceRuleInfo
|
||||||
|
)
|
||||||
|
incompleteStatus := false
|
||||||
|
|
||||||
|
for _, currAuthzHandler := range authzHandler {
|
||||||
|
resourceRules, nonResourceRules, incomplete, err := currAuthzHandler.RulesFor(user, namespace)
|
||||||
|
|
||||||
|
if incomplete == true {
|
||||||
|
incompleteStatus = true
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
errList = append(errList, err)
|
||||||
|
}
|
||||||
|
if len(resourceRules) > 0 {
|
||||||
|
resourceRulesList = append(resourceRulesList, resourceRules...)
|
||||||
|
}
|
||||||
|
if len(nonResourceRules) > 0 {
|
||||||
|
nonResourceRulesList = append(nonResourceRulesList, nonResourceRules...)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resourceRulesList, nonResourceRulesList, incompleteStatus, utilerrors.NewAggregate(errList)
|
||||||
|
}
|
||||||
265
pkg/apiserver/authorization/union/union_test.go
Normal file
265
pkg/apiserver/authorization/union/union_test.go
Normal file
@@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2014 The Kubernetes Authors.
|
||||||
|
|
||||||
|
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 union
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
|
"reflect"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type mockAuthzHandler struct {
|
||||||
|
decision authorizer.Decision
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *mockAuthzHandler) Authorize(a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||||
|
return mock.decision, "", mock.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationSecondPasses(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
handler2 := &mockAuthzHandler{decision: authorizer.DecisionAllow}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
authorized, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if authorized != authorizer.DecisionAllow {
|
||||||
|
t.Errorf("Unexpected authorization failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationFirstPasses(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{decision: authorizer.DecisionAllow}
|
||||||
|
handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
authorized, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if authorized != authorizer.DecisionAllow {
|
||||||
|
t.Errorf("Unexpected authorization failure")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationNonePasses(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
handler2 := &mockAuthzHandler{decision: authorizer.DecisionNoOpinion}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
authorized, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if authorized == authorizer.DecisionAllow {
|
||||||
|
t.Errorf("Expected failed authorization")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationError(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzHandler{err: fmt.Errorf("foo")}
|
||||||
|
handler2 := &mockAuthzHandler{err: fmt.Errorf("foo")}
|
||||||
|
authzHandler := New(handler1, handler2)
|
||||||
|
|
||||||
|
_, _, err := authzHandler.Authorize(nil)
|
||||||
|
if err == nil {
|
||||||
|
t.Errorf("Expected error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type mockAuthzRuleHandler struct {
|
||||||
|
resourceRules []authorizer.ResourceRuleInfo
|
||||||
|
nonResourceRules []authorizer.NonResourceRuleInfo
|
||||||
|
err error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mock *mockAuthzRuleHandler) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
|
||||||
|
if mock.err != nil {
|
||||||
|
return []authorizer.ResourceRuleInfo{}, []authorizer.NonResourceRuleInfo{}, false, mock.err
|
||||||
|
}
|
||||||
|
return mock.resourceRules, mock.nonResourceRules, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationResourceRules(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzRuleHandler{
|
||||||
|
resourceRules: []authorizer.ResourceRuleInfo{
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"bindings"},
|
||||||
|
},
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"get", "list", "watch"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
handler2 := &mockAuthzRuleHandler{
|
||||||
|
resourceRules: []authorizer.ResourceRuleInfo{
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"events"},
|
||||||
|
},
|
||||||
|
&authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
ResourceNames: []string{"foo"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []authorizer.DefaultResourceRuleInfo{
|
||||||
|
{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"bindings"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"get", "list", "watch"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"*"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"events"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
APIGroups: []string{"*"},
|
||||||
|
Resources: []string{"*"},
|
||||||
|
ResourceNames: []string{"foo"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authzRulesHandler := NewRuleResolvers(handler1, handler2)
|
||||||
|
|
||||||
|
rules, _, _, _ := authzRulesHandler.RulesFor(nil, "")
|
||||||
|
actual := getResourceRules(rules)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationNonResourceRules(t *testing.T) {
|
||||||
|
handler1 := &mockAuthzRuleHandler{
|
||||||
|
nonResourceRules: []authorizer.NonResourceRuleInfo{
|
||||||
|
&authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
handler2 := &mockAuthzRuleHandler{
|
||||||
|
nonResourceRules: []authorizer.NonResourceRuleInfo{
|
||||||
|
&authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api/*"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
expected := []authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Verbs: []string{"get"},
|
||||||
|
NonResourceURLs: []string{"/api/*"},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
authzRulesHandler := NewRuleResolvers(handler1, handler2)
|
||||||
|
|
||||||
|
_, rules, _, _ := authzRulesHandler.RulesFor(nil, "")
|
||||||
|
actual := getNonResourceRules(rules)
|
||||||
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
|
t.Errorf("Expected: \n%#v\n but actual: \n%#v\n", expected, actual)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getResourceRules(infos []authorizer.ResourceRuleInfo) []authorizer.DefaultResourceRuleInfo {
|
||||||
|
rules := make([]authorizer.DefaultResourceRuleInfo, len(infos))
|
||||||
|
for i, info := range infos {
|
||||||
|
rules[i] = authorizer.DefaultResourceRuleInfo{
|
||||||
|
Verbs: info.GetVerbs(),
|
||||||
|
APIGroups: info.GetAPIGroups(),
|
||||||
|
Resources: info.GetResources(),
|
||||||
|
ResourceNames: info.GetResourceNames(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNonResourceRules(infos []authorizer.NonResourceRuleInfo) []authorizer.DefaultNonResourceRuleInfo {
|
||||||
|
rules := make([]authorizer.DefaultNonResourceRuleInfo, len(infos))
|
||||||
|
for i, info := range infos {
|
||||||
|
rules[i] = authorizer.DefaultNonResourceRuleInfo{
|
||||||
|
Verbs: info.GetVerbs(),
|
||||||
|
NonResourceURLs: info.GetNonResourceURLs(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return rules
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAuthorizationUnequivocalDeny(t *testing.T) {
|
||||||
|
cs := []struct {
|
||||||
|
authorizers []authorizer.Authorizer
|
||||||
|
decision authorizer.Decision
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{},
|
||||||
|
decision: authorizer.DecisionNoOpinion,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionAllow},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionDeny},
|
||||||
|
},
|
||||||
|
decision: authorizer.DecisionAllow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionDeny},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionAllow},
|
||||||
|
},
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
authorizers: []authorizer.Authorizer{
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionNoOpinion},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionDeny, err: errors.New("webhook failed closed")},
|
||||||
|
&mockAuthzHandler{decision: authorizer.DecisionAllow},
|
||||||
|
},
|
||||||
|
decision: authorizer.DecisionDeny,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for i, c := range cs {
|
||||||
|
t.Run(fmt.Sprintf("case %v", i), func(t *testing.T) {
|
||||||
|
authzHandler := New(c.authorizers...)
|
||||||
|
|
||||||
|
decision, _, _ := authzHandler.Authorize(nil)
|
||||||
|
if decision != c.decision {
|
||||||
|
t.Errorf("Unexpected authorization failure: %v, expected: %v", decision, c.decision)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,10 +3,10 @@ package filters
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
|
||||||
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
|
||||||
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
|
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
|
||||||
"k8s.io/klog"
|
"k8s.io/klog"
|
||||||
|
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
|
||||||
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
"kubesphere.io/kubesphere/pkg/apiserver/request"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@@ -59,12 +59,16 @@ func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error)
|
|||||||
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
attribs.ResourceRequest = requestInfo.IsResourceRequest
|
||||||
attribs.Path = requestInfo.Path
|
attribs.Path = requestInfo.Path
|
||||||
attribs.Verb = requestInfo.Verb
|
attribs.Verb = requestInfo.Verb
|
||||||
|
attribs.Cluster = requestInfo.Cluster
|
||||||
|
attribs.Workspace = requestInfo.Workspace
|
||||||
|
attribs.KubernetesRequest = requestInfo.IsKubernetesRequest
|
||||||
|
|
||||||
attribs.APIGroup = requestInfo.APIGroup
|
attribs.APIGroup = requestInfo.APIGroup
|
||||||
attribs.APIVersion = requestInfo.APIVersion
|
attribs.APIVersion = requestInfo.APIVersion
|
||||||
attribs.Resource = requestInfo.Resource
|
attribs.Resource = requestInfo.Resource
|
||||||
attribs.Subresource = requestInfo.Subresource
|
attribs.Subresource = requestInfo.Subresource
|
||||||
attribs.Namespace = requestInfo.Namespace
|
attribs.Namespace = requestInfo.Namespace
|
||||||
|
attribs.DevopsProject = requestInfo.DevopsProject
|
||||||
attribs.Name = requestInfo.Name
|
attribs.Name = requestInfo.Name
|
||||||
|
|
||||||
return &attribs, nil
|
return &attribs, nil
|
||||||
|
|||||||
@@ -26,20 +26,22 @@ var kubernetesAPIPrefixes = sets.NewString("api", "apis")
|
|||||||
type RequestInfo struct {
|
type RequestInfo struct {
|
||||||
*k8srequest.RequestInfo
|
*k8srequest.RequestInfo
|
||||||
|
|
||||||
// IsKubeSphereRequest indicates whether or not the request should be handled by kubernetes or kubesphere
|
// IsKubernetesRequest indicates whether or not the request should be handled by kubernetes or kubesphere
|
||||||
IsKubernetesRequest bool
|
IsKubernetesRequest bool
|
||||||
|
|
||||||
// Workspace of requested namespace, for non-workspaced resources, this may be empty
|
// Workspace of requested resource, for non-workspaced resources, this may be empty
|
||||||
Workspace string
|
Workspace string
|
||||||
|
|
||||||
// Cluster of requested resource, this is empty in single-cluster environment
|
// Cluster of requested resource, this is empty in single-cluster environment
|
||||||
Cluster string
|
Cluster string
|
||||||
|
|
||||||
|
// Devops project of requested resource, this may be empty
|
||||||
|
DevopsProject string
|
||||||
}
|
}
|
||||||
|
|
||||||
type RequestInfoFactory struct {
|
type RequestInfoFactory struct {
|
||||||
APIPrefixes sets.String
|
APIPrefixes sets.String
|
||||||
GrouplessAPIPrefixes sets.String
|
GrouplessAPIPrefixes sets.String
|
||||||
k8sRequestInfoFactory *k8srequest.RequestInfoFactory
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
// NewRequestInfo returns the information from the http request. If error is not nil, RequestInfo holds the information as best it is known before the failure
|
||||||
|
|||||||
94
pkg/apiserver/request/requestinfo_test.go
Normal file
94
pkg/apiserver/request/requestinfo_test.go
Normal file
@@ -0,0 +1,94 @@
|
|||||||
|
/*
|
||||||
|
*
|
||||||
|
* Copyright 2020 The KubeSphere Authors.
|
||||||
|
*
|
||||||
|
* 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 request
|
||||||
|
|
||||||
|
import (
|
||||||
|
"k8s.io/apimachinery/pkg/util/sets"
|
||||||
|
"net/http"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestRequestInfoResolver() RequestInfoResolver {
|
||||||
|
requestInfoResolver := &RequestInfoFactory{
|
||||||
|
APIPrefixes: sets.NewString("api", "apis", "kapis", "kapi"),
|
||||||
|
GrouplessAPIPrefixes: sets.NewString("api", "kapi"),
|
||||||
|
}
|
||||||
|
|
||||||
|
return requestInfoResolver
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
url string
|
||||||
|
method string
|
||||||
|
expectedErr error
|
||||||
|
expectedVerb string
|
||||||
|
expectedResource string
|
||||||
|
expectedIsResourceRequest bool
|
||||||
|
expectedCluster string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "login",
|
||||||
|
url: "/oauth/authorize?client_id=ks-console&response_type=token",
|
||||||
|
method: http.MethodPost,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "POST",
|
||||||
|
expectedResource: "",
|
||||||
|
expectedIsResourceRequest: false,
|
||||||
|
expectedCluster: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "list namespaces",
|
||||||
|
url: "/kapis/resources.kubesphere.io/v1alpha2/namespaces",
|
||||||
|
method: http.MethodGet,
|
||||||
|
expectedErr: nil,
|
||||||
|
expectedVerb: "list",
|
||||||
|
expectedResource: "namespaces",
|
||||||
|
expectedIsResourceRequest: true,
|
||||||
|
expectedCluster: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
requestInfoResolver := newTestRequestInfoResolver()
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
req, err := http.NewRequest(test.method, test.url, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
requestInfo, err := requestInfoResolver.NewRequestInfo(req)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
if test.expectedErr != err {
|
||||||
|
t.Errorf("%s: expected error %v, actual %v", test.name, test.expectedErr, err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if test.expectedVerb != requestInfo.Verb {
|
||||||
|
t.Errorf("%s: expected verb %v, actual %+v", test.name, test.expectedVerb, requestInfo.Verb)
|
||||||
|
}
|
||||||
|
if test.expectedResource != requestInfo.Resource {
|
||||||
|
t.Errorf("%s: expected resource %v, actual %+v", test.name, test.expectedResource, requestInfo.Resource)
|
||||||
|
}
|
||||||
|
if test.expectedIsResourceRequest != requestInfo.IsResourceRequest {
|
||||||
|
t.Errorf("%s: expected is resource request %v, actual %+v", test.name, test.expectedIsResourceRequest, requestInfo.IsResourceRequest)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user