clean old devops code

todo impl use informer

Signed-off-by: runzexia <runzexia@yunify.com>
This commit is contained in:
runzexia
2020-04-01 15:54:57 +08:00
parent 56482f1feb
commit 9a6ba04a37
1000 changed files with 31387 additions and 139332 deletions

View File

@@ -8,7 +8,6 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema"
urlruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/request/bearertoken"
unionauth "k8s.io/apiserver/pkg/authentication/request/union"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/klog"
@@ -16,6 +15,7 @@ import (
"kubesphere.io/kubesphere/pkg/apiserver/authentication/authenticators/jwttoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/anonymous"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/basictoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/request/bearertoken"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/token"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizerfactory"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/path"
@@ -30,7 +30,7 @@ import (
devopsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/devops/v1alpha2"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/iam/v1alpha2"
loggingv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/logging/v1alpha2"
monitoringv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha2"
monitoringv1alpha3 "kubesphere.io/kubesphere/pkg/kapis/monitoring/v1alpha3"
"kubesphere.io/kubesphere/pkg/kapis/oauth"
openpitrixv1 "kubesphere.io/kubesphere/pkg/kapis/openpitrix/v1"
operationsv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/operations/v1alpha2"
@@ -137,12 +137,15 @@ func (s *APIServer) installKubeSphereAPIs() {
urlruntime.Must(configv1alpha2.AddToContainer(s.container, s.Config))
urlruntime.Must(resourcev1alpha3.AddToContainer(s.container, s.InformerFactory))
urlruntime.Must(loggingv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.LoggingClient))
urlruntime.Must(monitoringv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.MonitoringClient))
urlruntime.Must(monitoringv1alpha3.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.MonitoringClient))
urlruntime.Must(openpitrixv1.AddToContainer(s.container, s.InformerFactory, s.OpenpitrixClient))
urlruntime.Must(operationsv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes()))
urlruntime.Must(resourcesv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.InformerFactory))
urlruntime.Must(terminalv1alpha2.AddToContainer(s.container, s.KubernetesClient.Kubernetes(), s.KubernetesClient.Config()))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, s.KubernetesClient, s.InformerFactory, s.LdapClient, s.CacheClient, s.Config.AuthenticationOptions))
urlruntime.Must(iamv1alpha2.AddToContainer(s.container, im.NewOperator(s.KubernetesClient.KubeSphere(),
s.InformerFactory.KubeSphereSharedInformerFactory()),
am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()),
s.Config.AuthenticationOptions))
urlruntime.Must(oauth.AddToContainer(s.container, token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient), s.Config.AuthenticationOptions))
urlruntime.Must(servicemeshv1alpha2.AddToContainer(s.container))
devopsv1alpha2Service := ksruntime.NewWebService(devopsv1alpha2.GroupVersion)
@@ -185,18 +188,20 @@ func (s *APIServer) buildHandlerChain() {
handler := s.Server.Handler
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Tower().V1alpha1().Agents().Lister()))
clusterDispatcher := dispatch.NewClusterDispatch(s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Agents().Lister(), s.InformerFactory.KubeSphereSharedInformerFactory().Cluster().V1alpha1().Clusters().Lister())
handler = filters.WithMultipleClusterDispatcher(handler, clusterDispatcher)
excludedPaths := []string{"/oauth/*", "/kapis/config.kubesphere.io/*"}
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
// union authorizers are ordered, don't change the order here
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewFakeAMOperator()))
authorizers := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewAMOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory())))
handler = filters.WithAuthorization(handler, authorizers)
// authenticators are unordered
authn := unionauth.New(anonymous.NewAuthenticator(),
basictoken.New(basic.NewBasicAuthenticator(im.NewFakeOperator())),
basictoken.New(basic.NewBasicAuthenticator(im.NewOperator(s.KubernetesClient.KubeSphere(), s.InformerFactory.KubeSphereSharedInformerFactory()))),
bearertoken.New(jwttoken.NewTokenAuthenticator(token.NewJwtTokenIssuer(token.DefaultIssuerName, s.Config.AuthenticationOptions, s.CacheClient))))
handler = filters.WithAuthentication(handler, authn)
handler = filters.WithRequestInfo(handler, requestInfoResolver)
@@ -276,6 +281,10 @@ func (s *APIServer) waitForResourceSync(stopCh <-chan struct{}) error {
ksGVRs := []schema.GroupVersionResource{
{Group: "tenant.kubesphere.io", Version: "v1alpha1", Resource: "workspaces"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "users"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "roles"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "rolebindings"},
{Group: "iam.kubesphere.io", Version: "v1alpha2", Resource: "policyrules"},
{Group: "tower.kubesphere.io", Version: "v1alpha1", Resource: "agents"},
}

View File

@@ -51,7 +51,7 @@ func (t *basicAuthenticator) AuthenticatePassword(ctx context.Context, username,
return &authenticator.Response{
User: &user.DefaultInfo{
Name: providedUser.GetName(),
UID: providedUser.GetUID(),
UID: string(providedUser.GetUID()),
Groups: []string{user.AllAuthenticated},
},
}, true, nil

View File

@@ -69,4 +69,5 @@ func (options *AuthenticationOptions) AddFlags(fs *pflag.FlagSet, s *Authenticat
fs.IntVar(&options.MaxAuthenticateRetries, "authenticate-max-retries", s.MaxAuthenticateRetries, "")
fs.BoolVar(&options.MultipleLogin, "multiple-login", s.MultipleLogin, "Allow multiple login with the same account, disable means only one user can login at the same time.")
fs.StringVar(&options.JwtSecret, "jwt-secret", s.JwtSecret, "Secret to sign jwt token, must not be empty.")
fs.DurationVar(&options.OAuthOptions.AccessTokenMaxAge, "access-token-max-age", s.OAuthOptions.AccessTokenMaxAge, "AccessTokenMaxAgeSeconds control the lifetime of access tokens, 0 means no expiration.")
}

View File

@@ -0,0 +1,62 @@
/*
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 bearertoken
import (
"errors"
"net/http"
"strings"
"k8s.io/apiserver/pkg/authentication/authenticator"
)
type Authenticator struct {
auth authenticator.Token
}
func New(auth authenticator.Token) *Authenticator {
return &Authenticator{auth}
}
var invalidToken = errors.New("invalid bearer token")
func (a *Authenticator) AuthenticateRequest(req *http.Request) (*authenticator.Response, bool, error) {
auth := strings.TrimSpace(req.Header.Get("Authorization"))
if auth == "" {
return nil, false, nil
}
parts := strings.Split(auth, " ")
if len(parts) < 2 || strings.ToLower(parts[0]) != "bearer" {
return nil, false, nil
}
token := parts[1]
// Empty bearer tokens aren't valid
if len(token) == 0 {
return nil, false, nil
}
resp, ok, err := a.auth.AuthenticateToken(req.Context(), token)
// If the token authenticator didn't error, provide a default error
if !ok && err == nil {
err = invalidToken
}
return resp, ok, err
}

View File

@@ -0,0 +1,192 @@
/*
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 bearertoken
import (
"context"
"errors"
"net/http"
"reflect"
"testing"
"k8s.io/apiserver/pkg/authentication/authenticator"
"k8s.io/apiserver/pkg/authentication/user"
)
func TestAuthenticateRequest(t *testing.T) {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
if token != "token" {
t.Errorf("unexpected token: %s", token)
}
return &authenticator.Response{User: &user.DefaultInfo{Name: "user"}}, true, nil
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if !ok || resp == nil || err != nil {
t.Errorf("expected valid user")
}
}
func TestAuthenticateRequestTokenInvalid(t *testing.T) {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
return nil, false, nil
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if ok || resp != nil {
t.Errorf("expected not authenticated user")
}
if err != invalidToken {
t.Errorf("expected invalidToken error, got %v", err)
}
}
func TestAuthenticateRequestTokenInvalidCustomError(t *testing.T) {
customError := errors.New("custom")
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
return nil, false, customError
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if ok || resp != nil {
t.Errorf("expected not authenticated user")
}
if err != customError {
t.Errorf("expected custom error, got %v", err)
}
}
func TestAuthenticateRequestTokenError(t *testing.T) {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
return nil, false, errors.New("error")
}))
resp, ok, err := auth.AuthenticateRequest(&http.Request{
Header: http.Header{"Authorization": []string{"Bearer token"}},
})
if ok || resp != nil || err == nil {
t.Errorf("expected error")
}
}
func TestAuthenticateRequestBadValue(t *testing.T) {
testCases := []struct {
Req *http.Request
}{
{Req: &http.Request{}},
{Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer"}}}},
{Req: &http.Request{Header: http.Header{"Authorization": []string{"bear token"}}}},
{Req: &http.Request{Header: http.Header{"Authorization": []string{"Bearer: token"}}}},
}
for i, testCase := range testCases {
auth := New(authenticator.TokenFunc(func(ctx context.Context, token string) (*authenticator.Response, bool, error) {
t.Errorf("authentication should not have been called")
return nil, false, nil
}))
user, ok, err := auth.AuthenticateRequest(testCase.Req)
if ok || user != nil || err != nil {
t.Errorf("%d: expected not authenticated (no token)", i)
}
}
}
func TestBearerToken(t *testing.T) {
tests := map[string]struct {
AuthorizationHeaders []string
TokenAuth authenticator.Token
ExpectedUserName string
ExpectedOK bool
ExpectedErr bool
ExpectedAuthorizationHeaders []string
}{
"no header": {
AuthorizationHeaders: nil,
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: nil,
},
"empty header": {
AuthorizationHeaders: []string{""},
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: []string{""},
},
"non-bearer header": {
AuthorizationHeaders: []string{"Basic 123"},
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: []string{"Basic 123"},
},
"empty bearer token": {
AuthorizationHeaders: []string{"Bearer "},
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: false,
ExpectedAuthorizationHeaders: []string{"Bearer "},
},
"invalid bearer token": {
AuthorizationHeaders: []string{"Bearer 123"},
TokenAuth: authenticator.TokenFunc(func(ctx context.Context, t string) (*authenticator.Response, bool, error) { return nil, false, nil }),
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: true,
ExpectedAuthorizationHeaders: []string{"Bearer 123"},
},
"error bearer token": {
AuthorizationHeaders: []string{"Bearer 123"},
TokenAuth: authenticator.TokenFunc(func(ctx context.Context, t string) (*authenticator.Response, bool, error) {
return nil, false, errors.New("error")
}),
ExpectedUserName: "",
ExpectedOK: false,
ExpectedErr: true,
ExpectedAuthorizationHeaders: []string{"Bearer 123"},
},
}
for k, tc := range tests {
req, _ := http.NewRequest("GET", "/", nil)
for _, h := range tc.AuthorizationHeaders {
req.Header.Add("Authorization", h)
}
bearerAuth := New(tc.TokenAuth)
resp, ok, err := bearerAuth.AuthenticateRequest(req)
if tc.ExpectedErr != (err != nil) {
t.Errorf("%s: Expected err=%v, got %v", k, tc.ExpectedErr, err)
continue
}
if ok != tc.ExpectedOK {
t.Errorf("%s: Expected ok=%v, got %v", k, tc.ExpectedOK, ok)
continue
}
if ok && resp.User.GetName() != tc.ExpectedUserName {
t.Errorf("%s: Expected username=%v, got %v", k, tc.ExpectedUserName, resp.User.GetName())
continue
}
if !reflect.DeepEqual(req.Header["Authorization"], tc.ExpectedAuthorizationHeaders) {
t.Errorf("%s: Expected headers=%#v, got %#v", k, tc.ExpectedAuthorizationHeaders, req.Header["Authorization"])
continue
}
}
}

View File

@@ -21,7 +21,8 @@ package token
import (
"fmt"
"github.com/dgrijalva/jwt-go"
"kubesphere.io/kubesphere/pkg/api/iam"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/klog"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
@@ -53,23 +54,29 @@ func (s *jwtTokenIssuer) Verify(tokenString string) (User, error) {
if len(tokenString) == 0 {
return nil, errInvalidToken
}
_, err := s.cache.Get(tokenCacheKey(tokenString))
if err != nil {
if err == cache.ErrNoSuchKey {
return nil, errTokenExpired
}
return nil, err
}
clm := &Claims{}
_, err = jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
_, err := jwt.ParseWithClaims(tokenString, clm, s.keyFunc)
if err != nil {
return nil, err
}
return &iam.User{Name: clm.Username, UID: clm.UID}, nil
// 0 means no expiration.
// validate token cache
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
_, err = s.cache.Get(tokenCacheKey(tokenString))
if err != nil {
if err == cache.ErrNoSuchKey {
return nil, errTokenExpired
}
return nil, err
}
}
return &user.DefaultInfo{Name: clm.Username, UID: clm.UID}, nil
}
func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, error) {
@@ -92,16 +99,28 @@ func (s *jwtTokenIssuer) IssueTo(user User, expiresIn time.Duration) (string, er
tokenString, err := token.SignedString([]byte(s.options.JwtSecret))
if err != nil {
klog.Error(err)
return "", err
}
s.cache.Set(tokenCacheKey(tokenString), tokenString, expiresIn)
// 0 means no expiration.
// validate token cache
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
err = s.cache.Set(tokenCacheKey(tokenString), tokenString, s.options.OAuthOptions.AccessTokenMaxAge)
if err != nil {
klog.Error(err)
return "", err
}
}
return tokenString, nil
}
func (s *jwtTokenIssuer) Revoke(token string) error {
return s.cache.Del(tokenCacheKey(token))
if s.options.OAuthOptions.AccessTokenMaxAge > 0 {
return s.cache.Del(tokenCacheKey(token))
}
return nil
}
func NewJwtTokenIssuer(issuerName string, options *authoptions.AuthenticationOptions, cache cache.Interface) Issuer {

View File

@@ -20,7 +20,8 @@ package token
import (
"github.com/google/go-cmp/cmp"
"kubesphere.io/kubesphere/pkg/api/iam"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/apiserver/authentication/oauth"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"testing"
@@ -48,7 +49,7 @@ func TestJwtTokenIssuer(t *testing.T) {
}
for _, testCase := range testCases {
user := &iam.User{
user := &user.DefaultInfo{
Name: testCase.name,
UID: testCase.uid,
}
@@ -70,3 +71,39 @@ func TestJwtTokenIssuer(t *testing.T) {
})
}
}
func TestTokenVerifyWithoutCacheValidate(t *testing.T) {
options := authoptions.NewAuthenticateOptions()
// do not set token cache and disable token cache validate,
options.OAuthOptions = &oauth.Options{AccessTokenMaxAge: 0}
options.JwtSecret = "kubesphere"
issuer := NewJwtTokenIssuer(DefaultIssuerName, options, nil)
client, err := options.OAuthOptions.OAuthClient("default")
if err != nil {
t.Fatal(err)
}
user := &user.DefaultInfo{
Name: "admin",
UID: "admin",
}
tokenString, err := issuer.IssueTo(user, *client.AccessTokenMaxAge)
if err != nil {
t.Fatal(err)
}
got, err := issuer.Verify(tokenString)
if err != nil {
t.Fatal(err)
}
if diff := cmp.Diff(got, user); diff != "" {
t.Error("token validate failed")
}
}

View File

@@ -21,6 +21,9 @@ package authorizerfactory
import (
"context"
"github.com/open-policy-agent/opa/rego"
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/klog"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/models/iam/am"
)
@@ -29,115 +32,126 @@ type opaAuthorizer struct {
am am.AccessManagementInterface
}
const (
permissionUndefined = "permission undefined"
defaultRegoQuery = "data.authz.allow"
)
// Make decision by request attributes
func (o *opaAuthorizer) Authorize(attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Make decisions based on the authorization policy of different levels of roles
platformRole, err := o.am.GetPlatformRole(attr.GetUser().GetName())
// Error returned when an internal error occurs
// Reason must be returned when access is denied
globalRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.GlobalScope, "", attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check platform role policy rules
if authorized, reason, err = makeDecision(platformRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
if authorized, reason, err = o.makeDecision(globalRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, nil
}
// it's not in cluster resource, permission denied
if attr.GetCluster() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
clusterRole, err := o.am.GetClusterRole(attr.GetCluster(), attr.GetUser().GetName())
clusterRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.ClusterScope, attr.GetCluster(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check cluster role policy rules
if a, r, e := makeDecision(clusterRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err := o.makeDecision(clusterRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, nil
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
// it's not in cluster resource, permission denied
if attr.GetWorkspace() == "" && attr.GetNamespace() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
workspaceRole, err := o.am.GetWorkspaceRole(attr.GetWorkspace(), attr.GetUser().GetName())
workspaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.WorkspaceScope, attr.GetWorkspace(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check workspace role policy rules
if a, r, e := makeDecision(workspaceRole, attr); a == authorizer.DecisionAllow {
return a, r, e
if authorized, reason, err := o.makeDecision(workspaceRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
// it's not in workspace resource, permission denied
if attr.GetNamespace() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
if attr.GetNamespace() != "" {
namespaceRole, err := o.am.GetNamespaceRole(attr.GetCluster(), attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if a, r, e := makeDecision(namespaceRole, attr); a == authorizer.DecisionAllow {
return a, r, e
namespaceRole, err := o.am.GetRoleOfUserInTargetScope(iamv1alpha2.NamespaceScope, attr.GetNamespace(), attr.GetUser().GetName())
if err != nil {
if errors.IsNotFound(err) {
return authorizer.DecisionDeny, err.Error(), nil
}
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if authorized, reason, err := o.makeDecision(namespaceRole, attr); authorized == authorizer.DecisionAllow {
return authorized, reason, err
} else if err != nil {
return authorizer.DecisionDeny, "", err
}
return authorizer.DecisionDeny, "", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
// Make decision base on role
func makeDecision(role am.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
func (o *opaAuthorizer) makeDecision(role *iamv1alpha2.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// Call the rego.New function to create an object that can be prepared or evaluated
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", role.GetRego())).PrepareForEval(context.Background())
for _, ruleRef := range role.Rules {
rule, err := o.am.GetPolicyRule(ruleRef.Name)
if err != nil {
if errors.IsNotFound(err) {
continue
}
return authorizer.DecisionDeny, "", err
}
// Call the rego.New function to create an object that can be prepared or evaluated
// After constructing a new rego.Rego object you can call PrepareForEval() to obtain an executable query
query, err := rego.New(rego.Query(defaultRegoQuery), rego.Module("authz.rego", rule.Rego)).PrepareForEval(context.Background())
if err != nil {
return authorizer.DecisionDeny, "", err
if err != nil {
klog.Errorf("rule syntax error:%s", err)
continue
}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
if err != nil {
klog.Errorf("rule syntax error:%s", err)
continue
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
return authorizer.DecisionAllow, "", nil
}
}
// data example
//{
// "User": {
// "Name": "admin",
// "UID": "0",
// "Groups": [
// "admin"
// ],
// "Extra": null
// },
// "Verb": "list",
// "Cluster": "cluster1",
// "Workspace": "",
// "Namespace": "",
// "APIGroup": "",
// "APIVersion": "v1",
// "Resource": "nodes",
// "Subresource": "",
// "Name": "",
// "KubernetesRequest": true,
// "ResourceRequest": true,
// "Path": "/api/v1/nodes"
//}
// The policy decision is contained in the results returned by the Eval() call. You can inspect the decision and handle it accordingly.
results, err := query.Eval(context.Background(), rego.EvalInput(a))
if err != nil {
return authorizer.DecisionDeny, "", err
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "permission undefined", nil
return authorizer.DecisionDeny, permissionUndefined, nil
}
func NewOPAAuthorizer(am am.AccessManagementInterface) *opaAuthorizer {

View File

@@ -19,22 +19,44 @@
package authorizerfactory
import (
"fmt"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apiserver/pkg/authentication/user"
iamvealpha2 "kubesphere.io/kubesphere/pkg/apis/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"kubesphere.io/kubesphere/pkg/client/clientset/versioned/fake"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"testing"
)
func TestPlatformRole(t *testing.T) {
platformRoles := map[string]am.FakeRole{"admin": {
Name: "admin",
Rego: "package authz\ndefault allow = true",
}, "anonymous": {
Name: "anonymous",
Rego: "package authz\ndefault allow = false",
}, "tom": {
Name: "tom",
Rego: `package authz
func prepare() (am.AccessManagementInterface, error) {
rules := []*iamvealpha2.PolicyRule{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "always-allow",
},
Rego: "package authz\ndefault allow = true",
}, {
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String(),
},
ObjectMeta: metav1.ObjectMeta{
Name: "always-deny",
},
Rego: "package authz\ndefault allow = false",
}, {
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.PolicyRuleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "manage-cluster1-resources",
},
Rego: `package authz
default allow = false
allow {
resources_in_cluster1
@@ -42,11 +64,168 @@ allow {
resources_in_cluster1 {
input.Cluster == "cluster1"
}`,
},
},
}
operator := am.NewFakeAMOperator()
operator.Prepare(platformRoles, nil, nil, nil)
roles := []*iamvealpha2.Role{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "global-admin",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "always-allow",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "anonymous",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "always-deny",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-admin",
},
Target: iamvealpha2.Target{
Scope: iamvealpha2.GlobalScope,
Name: "",
},
Rules: []iamvealpha2.RuleRef{
{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.PolicyRuleKind,
Name: "manage-cluster1-resources",
},
},
},
}
roleBindings := []*iamvealpha2.RoleBinding{
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "global-admin",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "global-admin",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: "admin",
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "anonymous",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "anonymous",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: user.Anonymous,
},
},
},
{
TypeMeta: metav1.TypeMeta{
Kind: iamvealpha2.RoleBindingKind,
APIVersion: iamvealpha2.SchemeGroupVersion.String()},
ObjectMeta: metav1.ObjectMeta{
Name: "cluster1-admin",
},
Scope: iamvealpha2.GlobalScope,
RoleRef: iamvealpha2.RoleRef{
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Kind: iamvealpha2.RoleKind,
Name: "cluster1-admin",
},
Subjects: []iamvealpha2.Subject{
{
Kind: iamvealpha2.UserKind,
APIGroup: iamvealpha2.SchemeGroupVersion.String(),
Name: "tom",
},
},
},
}
ksClient := fake.NewSimpleClientset()
informerFactory := externalversions.NewSharedInformerFactory(ksClient, 0)
for _, rule := range rules {
err := informerFactory.Iam().V1alpha2().PolicyRules().Informer().GetIndexer().Add(rule)
if err != nil {
return nil, fmt.Errorf("add rule:%s", err)
}
}
for _, role := range roles {
err := informerFactory.Iam().V1alpha2().Roles().Informer().GetIndexer().Add(role)
if err != nil {
return nil, fmt.Errorf("add role:%s", err)
}
}
for _, roleBinding := range roleBindings {
err := informerFactory.Iam().V1alpha2().RoleBindings().Informer().GetIndexer().Add(roleBinding)
if err != nil {
return nil, fmt.Errorf("add role binding:%s", err)
}
}
operator := am.NewAMOperator(ksClient, informerFactory)
return operator, nil
}
func TestGlobalRole(t *testing.T) {
operator, err := prepare()
if err != nil {
t.Fatal(err)
}
opa := NewOPAAuthorizer(operator)

View File

@@ -2,11 +2,8 @@ package config
import (
"fmt"
"github.com/emicklei/go-restful"
"github.com/spf13/viper"
"k8s.io/apimachinery/pkg/runtime/schema"
authoptions "kubesphere.io/kubesphere/pkg/apiserver/authentication/options"
"kubesphere.io/kubesphere/pkg/apiserver/runtime"
"kubesphere.io/kubesphere/pkg/simple/client/alerting"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops/jenkins"
@@ -19,7 +16,6 @@ import (
"kubesphere.io/kubesphere/pkg/simple/client/s3"
"kubesphere.io/kubesphere/pkg/simple/client/servicemesh"
"kubesphere.io/kubesphere/pkg/simple/client/sonarqube"
"net/http"
"reflect"
"strings"
)
@@ -123,26 +119,6 @@ func TryLoadFromDisk() (*Config, error) {
return conf, nil
}
// InstallAPI installs api for config
func (conf *Config) InstallAPI(c *restful.Container) {
ws := runtime.NewWebService(schema.GroupVersion{
Group: "",
Version: "v1alpha1",
})
ws.Route(ws.GET("/configz").
To(func(request *restful.Request, response *restful.Response) {
conf.stripEmptyOptions()
response.WriteAsJson(conf.ToMap())
}).
Doc("Get system components configuration").
Produces(restful.MIME_JSON).
Writes(Config{}).
Returns(http.StatusOK, "ok", Config{}))
c.Add(ws)
}
// convertToMap simply converts config to map[string]bool
// to hide sensitive information
func (conf *Config) ToMap() map[string]bool {

View File

@@ -6,39 +6,59 @@ import (
"k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
towerv1alpha1 "kubesphere.io/kubesphere/pkg/apis/tower/v1alpha1"
"k8s.io/klog"
clusterv1alpha1 "kubesphere.io/kubesphere/pkg/apis/cluster/v1alpha1"
"kubesphere.io/kubesphere/pkg/apiserver/request"
"kubesphere.io/kubesphere/pkg/client/listers/tower/v1alpha1"
"kubesphere.io/kubesphere/pkg/client/listers/cluster/v1alpha1"
"net/http"
"strings"
)
const defaultMultipleClusterAgentNamespace = "kubesphere-system"
// Dispatcher defines how to forward request to designated cluster based on cluster name
type Dispatcher interface {
Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler)
}
type clusterDispatch struct {
agentLister v1alpha1.AgentLister
agentLister v1alpha1.AgentLister
clusterLister v1alpha1.ClusterLister
}
func NewClusterDispatch(agentLister v1alpha1.AgentLister) Dispatcher {
func NewClusterDispatch(agentLister v1alpha1.AgentLister, clusterLister v1alpha1.ClusterLister) Dispatcher {
return &clusterDispatch{
agentLister: agentLister,
agentLister: agentLister,
clusterLister: clusterLister,
}
}
func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, handler http.Handler) {
info, _ := request.RequestInfoFrom(req.Context())
if info.Cluster == "" { // fallback to host cluster if cluster name if empty
if len(info.Cluster) == 0 {
klog.Warningf("Request with empty cluster, %v", req.URL)
http.Error(w, fmt.Sprintf("Bad request, empty cluster"), http.StatusBadRequest)
return
}
cluster, err := c.clusterLister.Get(info.Cluster)
if err != nil {
if errors.IsNotFound(err) {
http.Error(w, fmt.Sprintf("cluster %s not found", info.Cluster), http.StatusNotFound)
} else {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
return
}
// request cluster is host cluster, no need go through agent
if isClusterHostCluster(cluster) {
req.URL.Path = strings.Replace(req.URL.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
handler.ServeHTTP(w, req)
return
}
agent, err := c.agentLister.Agents(defaultMultipleClusterAgentNamespace).Get(info.Cluster)
agent, err := c.agentLister.Get(info.Cluster)
if err != nil {
if errors.IsNotFound(err) {
http.Error(w, fmt.Sprintf("cluster %s not found", info.Cluster), http.StatusNotFound)
@@ -54,7 +74,7 @@ func (c *clusterDispatch) Dispatch(w http.ResponseWriter, req *http.Request, han
}
u := *req.URL
u.Host = fmt.Sprintf("%s:%d", agent.Spec.Proxy, agent.Spec.KubeSphereAPIServerPort)
u.Host = agent.Spec.Proxy
u.Path = strings.Replace(u.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
httpProxy := proxy.NewUpgradeAwareHandler(&u, http.DefaultTransport, true, false, c)
@@ -65,9 +85,20 @@ func (c *clusterDispatch) Error(w http.ResponseWriter, req *http.Request, err er
responsewriters.InternalError(w, req, err)
}
func isAgentReady(agent *towerv1alpha1.Agent) bool {
func isAgentReady(agent *clusterv1alpha1.Agent) bool {
for _, condition := range agent.Status.Conditions {
if condition.Type == towerv1alpha1.AgentConnected && condition.Status == corev1.ConditionTrue {
if condition.Type == clusterv1alpha1.AgentConnected && condition.Status == corev1.ConditionTrue {
return true
}
}
return false
}
//
func isClusterHostCluster(cluster *clusterv1alpha1.Cluster) bool {
for key, value := range cluster.Annotations {
if key == clusterv1alpha1.IsHostCluster && value == "true" {
return true
}
}

View File

@@ -42,9 +42,6 @@ func WithAuthentication(handler http.Handler, auth authenticator.Request) http.H
return
}
// authorization header is not required anymore in case of a successful authentication.
req.Header.Del("Authorization")
req = req.WithContext(request.WithUser(req.Context(), resp.User))
handler.ServeHTTP(w, req)
})

View File

@@ -1,7 +1,7 @@
package filters
import (
"fmt"
"k8s.io/apimachinery/pkg/util/proxy"
"k8s.io/apiserver/pkg/endpoints/handlers/responsewriters"
"k8s.io/client-go/rest"
"k8s.io/klog"
@@ -9,9 +9,6 @@ import (
"kubesphere.io/kubesphere/pkg/server/errors"
"net/http"
"net/url"
"strings"
"k8s.io/apimachinery/pkg/util/proxy"
)
// WithKubeAPIServer proxy request to kubernetes service if requests path starts with /api
@@ -35,8 +32,9 @@ func WithKubeAPIServer(handler http.Handler, config *rest.Config, failed proxy.E
s := *req.URL
s.Host = kubernetes.Host
s.Scheme = kubernetes.Scheme
// remove cluster path
s.Path = strings.Replace(s.Path, fmt.Sprintf("/clusters/%s", info.Cluster), "", 1)
// Do not cover k8s client authorization header
req.Header.Del("Authorization")
httpProxy := proxy.NewUpgradeAwareHandler(&s, defaultTransport, true, false, failed)
httpProxy.ServeHTTP(w, req)

View File

@@ -1,16 +1,18 @@
package query
type Field string
type Value string
const (
FieldName = "name"
FieldUID = "uid"
FieldCreationTimeStamp = "creationTimestamp"
FieldLastUpdateTimestamp = "lastUpdateTimestamp"
FieldLabel = "label"
FieldAnnotation = "annotation"
FieldNamespace = "namespace"
FieldStatus = "status"
FieldApplication = "application"
FieldOwner = "owner"
FieldOwnerReference = "ownerReference"
FieldOwnerKind = "ownerKind"
)
@@ -23,9 +25,11 @@ var SortableFields = []Field{
// Field contains all the query field that can be compared
var ComparableFields = []Field{
FieldName,
FieldUID,
FieldLabel,
FieldAnnotation,
FieldNamespace,
FieldStatus,
FieldApplication,
FieldOwner,
FieldOwnerReference,
FieldOwnerKind,
}

View File

@@ -3,7 +3,6 @@ package query
import (
"github.com/emicklei/go-restful"
"strconv"
"strings"
)
const (
@@ -16,24 +15,6 @@ const (
ParameterAscending = "ascending"
)
type Comparable interface {
Compare(Comparable) int
Contains(Comparable) bool
}
type ComparableString string
func (c ComparableString) Compare(comparable Comparable) int {
other := comparable.(ComparableString)
return strings.Compare(string(c), string(other))
}
func (c ComparableString) Contains(comparable Comparable) bool {
other := comparable.(ComparableString)
return strings.Contains(string(c), string(other))
}
// Query represents api search terms
type Query struct {
Pagination *Pagination
@@ -52,29 +33,30 @@ type Pagination struct {
// items per page
Limit int
// page number
Page int
// offset
Offset int
}
var NoPagination = newPagination(-1, -1)
var NoPagination = newPagination(-1, 0)
func newPagination(limit int, page int) *Pagination {
// make sure that pagination is valid
func newPagination(limit int, offset int) *Pagination {
return &Pagination{
Limit: limit,
Page: page,
Limit: limit,
Offset: offset,
}
}
func (p *Pagination) IsValidPagintaion() bool {
return p.Limit >= 0 && p.Page >= 0
}
func (p *Pagination) GetValidPagination(total int) (startIndex, endIndex int) {
if p.Limit == NoPagination.Limit {
return 0, total
}
func (p *Pagination) IsPageAvailable(total, startIndex int) bool {
return total > startIndex && p.Limit > 0
}
if p.Limit < 0 || p.Offset < 0 {
return 0, 0
}
func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int) {
startIndex = p.Limit * p.Page
startIndex = p.Limit * p.Offset
endIndex = startIndex + p.Limit
if endIndex > total {
@@ -86,33 +68,33 @@ func (p *Pagination) GetPaginationSettings(total int) (startIndex, endIndex int)
func New() *Query {
return &Query{
Pagination: &Pagination{
Limit: -1,
Page: -1,
},
SortBy: "",
Ascending: false,
Filters: []Filter{},
Pagination: NoPagination,
SortBy: "",
Ascending: false,
Filters: []Filter{},
}
}
type Filter struct {
Field Field
Value Comparable
Value Value
}
func ParseQueryParameter(request *restful.Request) *Query {
query := New()
limit, err := strconv.ParseInt(request.QueryParameter("limit"), 10, 0)
limit, err := strconv.Atoi(request.QueryParameter("limit"))
// equivalent to undefined, use the default value
if err != nil {
query.Pagination = NoPagination
limit = -1
}
page, err := strconv.Atoi(request.QueryParameter("page"))
// equivalent to undefined, use the default value
if err != nil {
page = 1
}
page, err := strconv.ParseInt(request.QueryParameter("page"), 10, 0)
if err == nil {
query.Pagination = newPagination(int(limit), int(page-1))
}
query.Pagination = newPagination(limit, page-1)
query.SortBy = Field(defaultString(request.QueryParameter("sortBy"), FieldCreationTimeStamp))
@@ -128,13 +110,12 @@ func ParseQueryParameter(request *restful.Request) *Query {
if len(f) != 0 {
query.Filters = append(query.Filters, Filter{
Field: field,
Value: ComparableString(f),
Value: Value(f),
})
}
}
return query
}
func defaultString(value, defaultValue string) string {

View File

@@ -16,7 +16,7 @@ func TestParseQueryParameter(t *testing.T) {
}{
{
"test normal case",
"name=foo&status=Running&application=book&page=1&limit=10&ascending=true",
"label=app.kubernetes.io/name:book&name=foo&status=Running&page=1&limit=10&ascending=true",
&Query{
Pagination: newPagination(10, 0),
SortBy: FieldCreationTimeStamp,
@@ -24,15 +24,15 @@ func TestParseQueryParameter(t *testing.T) {
Filters: []Filter{
{
FieldName,
ComparableString("foo"),
Value("foo"),
},
{
FieldLabel,
Value("app.kubernetes.io/name:book"),
},
{
FieldStatus,
ComparableString("Running"),
},
{
FieldApplication,
ComparableString("book"),
Value("Running"),
},
},
},
@@ -41,13 +41,10 @@ func TestParseQueryParameter(t *testing.T) {
"test bad case",
"xxxx=xxxx&dsfsw=xxxx&page=abc&limit=add&ascending=ssss",
&Query{
Pagination: &Pagination{
Limit: -1,
Page: -1,
},
SortBy: FieldCreationTimeStamp,
Ascending: false,
Filters: []Filter{},
Pagination: NoPagination,
SortBy: FieldCreationTimeStamp,
Ascending: false,
Filters: []Filter{},
},
},
}

View File

@@ -78,8 +78,8 @@ type RequestInfoFactory struct {
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}
// /kapis/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
// With workspaces:
// /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource}
// /kapis/{api-group}/{version}/clusters/{cluster}/namespaces/{namespace}/{resource}/{resourceName}
// /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}
// /kapis/clusters/{cluster}/{api-group}/{version}/namespaces/{namespace}/{resource}/{resourceName}
//
func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, error) {
@@ -111,6 +111,16 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
requestInfo.APIPrefix = currentParts[0]
currentParts = currentParts[1:]
// URL forms: /clusters/{cluster}/*
if currentParts[0] == "clusters" {
if len(currentParts) > 1 {
requestInfo.Cluster = currentParts[1]
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
if !r.GrouplessAPIPrefixes.Has(requestInfo.APIPrefix) {
// one part (APIPrefix) has already been consumed, so this is actually "do we have four parts?"
if len(currentParts) < 3 {
@@ -150,16 +160,6 @@ func (r *RequestInfoFactory) NewRequestInfo(req *http.Request) (*RequestInfo, er
}
}
// URL forms: /clusters/{cluster}/*
if currentParts[0] == "clusters" {
if len(currentParts) > 1 {
requestInfo.Cluster = currentParts[1]
}
if len(currentParts) > 2 {
currentParts = currentParts[2:]
}
}
// URL forms: /workspaces/{workspace}/*
if currentParts[0] == "workspaces" {
if len(currentParts) > 1 {

View File

@@ -59,40 +59,18 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
expectedKubernetesRequest: false,
},
{
name: "list cluster roles",
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/clusterroles",
name: "list clusterRoles of cluster gondor",
url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/clusterroles",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "clusterroles",
expectedIsResourceRequest: true,
expectedCluster: "cluster1",
expectedCluster: "gondor",
expectedKubernetesRequest: true,
},
{
name: "list cluster nodes",
url: "/api/v1/clusters/cluster1/nodes",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "nodes",
expectedIsResourceRequest: true,
expectedCluster: "cluster1",
expectedKubernetesRequest: true,
},
{
name: "list cluster nodes",
url: "/api/v1/clusters/cluster1/nodes",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "nodes",
expectedIsResourceRequest: true,
expectedCluster: "cluster1",
expectedKubernetesRequest: true,
},
{
name: "list cluster nodes",
name: "list nodes",
url: "/api/v1/nodes",
method: http.MethodGet,
expectedErr: nil,
@@ -103,15 +81,26 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
expectedKubernetesRequest: true,
},
{
name: "list roles",
url: "/apis/rbac.authorization.k8s.io/v1/clusters/cluster1/namespaces/namespace1/roles",
name: "list nodes of cluster gondor",
url: "/api/clusters/gondor/v1/nodes",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "nodes",
expectedIsResourceRequest: true,
expectedCluster: "gondor",
expectedKubernetesRequest: true,
},
{
name: "list roles of cluster gondor",
url: "/apis/clusters/gondor/rbac.authorization.k8s.io/v1/namespaces/namespace1/roles",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "roles",
expectedIsResourceRequest: true,
expectedNamespace: "namespace1",
expectedCluster: "cluster1",
expectedCluster: "gondor",
expectedKubernetesRequest: true,
},
{
@@ -139,17 +128,41 @@ func TestRequestInfoFactory_NewRequestInfo(t *testing.T) {
expectedKubernetesRequest: false,
},
{
name: "list namespaces",
url: "/kapis/resources.kubesphere.io/v1alpha3/clusters/cluster1/workspaces/workspace1/namespaces",
name: "list namespaces of cluster gondor",
url: "/kapis/clusters/gondor/resources.kubesphere.io/v1alpha3/workspaces/workspace1/namespaces",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "namespaces",
expectedIsResourceRequest: true,
expectedWorkspace: "workspace1",
expectedCluster: "cluster1",
expectedCluster: "gondor",
expectedKubernetesRequest: false,
},
{
name: "list clusters",
url: "/apis/cluster.kubesphere.io/v1alpha1/clusters",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "list",
expectedResource: "clusters",
expectedIsResourceRequest: true,
expectedWorkspace: "",
expectedCluster: "",
expectedKubernetesRequest: true,
},
{
name: "get cluster gondor",
url: "/apis/cluster.kubesphere.io/v1alpha1/clusters/gondor",
method: http.MethodGet,
expectedErr: nil,
expectedVerb: "get",
expectedResource: "clusters",
expectedIsResourceRequest: true,
expectedWorkspace: "",
expectedCluster: "",
expectedKubernetesRequest: true,
},
{
name: "random query",
url: "/foo/bar",