未验证 提交 23f6be88 编写于 作者: H hongming

implement authorizer filter

Signed-off-by: Nhongming <talonwan@yunify.com>
上级 0a07e5f6
......@@ -9,13 +9,15 @@ import (
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
"k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/authorization/authorizerfactory"
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
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/filters"
"kubesphere.io/kubesphere/pkg/apiserver/request"
......@@ -179,13 +181,17 @@ func (s *APIServer) buildHandlerChain() {
})
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)
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
}
......@@ -194,7 +200,7 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
klog.V(0).Info("Start cache objects")
discoveryClient := s.KubernetesClient.Kubernetes().Discovery()
apiResourcesList, err := discoveryClient.ServerResources()
_, apiResourcesList, err := discoveryClient.ServerGroupsAndResources()
if err != nil {
return err
}
......
/*
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
)
/*
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
}
/*
*
* 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)
}
/*
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"
/*
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
}
/*
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)
}
}
}
})
}
}
/*
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)
}
/*
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 (
"context"
"errors"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
k8srequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"net/http"
)
......@@ -59,12 +59,16 @@ func GetAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error)
attribs.ResourceRequest = requestInfo.IsResourceRequest
attribs.Path = requestInfo.Path
attribs.Verb = requestInfo.Verb
attribs.Cluster = requestInfo.Cluster
attribs.Workspace = requestInfo.Workspace
attribs.KubernetesRequest = requestInfo.IsKubernetesRequest
attribs.APIGroup = requestInfo.APIGroup
attribs.APIVersion = requestInfo.APIVersion
attribs.Resource = requestInfo.Resource
attribs.Subresource = requestInfo.Subresource
attribs.Namespace = requestInfo.Namespace
attribs.DevopsProject = requestInfo.DevopsProject
attribs.Name = requestInfo.Name
return &attribs, nil
......
......@@ -26,20 +26,22 @@ var kubernetesAPIPrefixes = sets.NewString("api", "apis")
type RequestInfo struct {
*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
// 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
// Cluster of requested resource, this is empty in single-cluster environment
Cluster string
// Devops project of requested resource, this may be empty
DevopsProject string
}
type RequestInfoFactory struct {
APIPrefixes sets.String
GrouplessAPIPrefixes sets.String
k8sRequestInfoFactory *k8srequest.RequestInfoFactory
APIPrefixes sets.String
GrouplessAPIPrefixes sets.String
}
// 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
......
/*
*
* 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)
}
}
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册