Signed-off-by: hongming <talonwan@yunify.com>
This commit is contained in:
hongming
2020-03-19 22:44:05 +08:00
parent 23f6be88c6
commit 9769357005
332 changed files with 69808 additions and 4129 deletions

View File

@@ -1,248 +0,0 @@
/*
Copyright 2019 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 authenticate
import (
"errors"
"fmt"
"k8s.io/apimachinery/pkg/util/sets"
"k8s.io/apiserver/pkg/authentication/user"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"log"
"net/http"
"strconv"
"strings"
"time"
"github.com/dgrijalva/jwt-go"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
type Auth struct {
Rule *Rule
Next httpserver.Handler
}
type Rule struct {
Secret []byte
Path string
RedisOptions *cache.Options
TokenIdleTimeout time.Duration
RedisClient cache.Interface
ExclusionRules []internal.ExclusionRule
}
type User struct {
Username string `json:"username"`
UID string `json:"uid"`
Groups *[]string `json:"groups,omitempty"`
Extra *map[string]interface{} `json:"extra,omitempty"`
}
var requestInfoFactory = request.RequestInfoFactory{
APIPrefixes: sets.NewString("api", "apis", "kapis", "kapi"),
GrouplessAPIPrefixes: sets.NewString("api")}
func (h Auth) ServeHTTP(resp http.ResponseWriter, req *http.Request) (int, error) {
for _, rule := range h.Rule.ExclusionRules {
if httpserver.Path(req.URL.Path).Matches(rule.Path) && (rule.Method == internal.AllMethod || req.Method == rule.Method) {
return h.Next.ServeHTTP(resp, req)
}
}
if httpserver.Path(req.URL.Path).Matches(h.Rule.Path) {
uToken, err := h.ExtractToken(req)
if err != nil {
return h.HandleUnauthorized(resp, err), nil
}
token, err := h.Validate(uToken)
if err != nil {
return h.HandleUnauthorized(resp, err), nil
}
req, err = h.InjectContext(req, token)
if err != nil {
return h.HandleUnauthorized(resp, err), nil
}
}
return h.Next.ServeHTTP(resp, req)
}
func (h Auth) InjectContext(req *http.Request, token *jwt.Token) (*http.Request, error) {
payload, ok := token.Claims.(jwt.MapClaims)
if !ok {
return nil, errors.New("invalid payload")
}
for header := range req.Header {
if strings.HasPrefix(header, "X-Token-") {
req.Header.Del(header)
}
}
usr := &user.DefaultInfo{}
username, ok := payload["username"].(string)
if ok && username != "" {
req.Header.Set("X-Token-Username", username)
usr.Name = username
}
uid := payload["uid"]
if uid != nil {
switch uid.(type) {
case int:
req.Header.Set("X-Token-UID", strconv.Itoa(uid.(int)))
usr.UID = strconv.Itoa(uid.(int))
break
case string:
req.Header.Set("X-Token-UID", uid.(string))
usr.UID = uid.(string)
break
}
}
groups, ok := payload["groups"].([]string)
if ok && len(groups) > 0 {
req.Header.Set("X-Token-Groups", strings.Join(groups, ","))
usr.Groups = groups
}
// hard code, support jenkins auth plugin
if httpserver.Path(req.URL.Path).Matches("/kapis/jenkins.kubesphere.io") ||
httpserver.Path(req.URL.Path).Matches("job") ||
httpserver.Path(req.URL.Path).Matches("/kapis/devops.kubesphere.io/v1alpha2") {
req.SetBasicAuth(username, token.Raw)
}
context := request.WithUser(req.Context(), usr)
requestInfo, err := requestInfoFactory.NewRequestInfo(req)
if err == nil {
context = request.WithRequestInfo(context, requestInfo)
} else {
return nil, err
}
req = req.WithContext(context)
return req, nil
}
func (h Auth) Validate(uToken string) (*jwt.Token, error) {
if len(uToken) == 0 {
return nil, fmt.Errorf("token length is zero")
}
token, err := jwt.Parse(uToken, h.ProvideKey)
if err != nil {
klog.Errorln(err)
return nil, err
}
payload, ok := token.Claims.(jwt.MapClaims)
if !ok {
err := fmt.Errorf("invalid payload")
klog.Errorln(err)
return nil, err
}
username, ok := payload["username"].(string)
if !ok {
err := fmt.Errorf("invalid payload")
klog.Errorln(err)
return nil, err
}
if _, ok = payload["exp"]; ok {
// allow static token has expiration time
return token, nil
}
tokenKey := fmt.Sprintf("kubesphere:users:%s:token:%s", username, uToken)
exist, err := h.Rule.RedisClient.Exists(tokenKey)
if err != nil {
klog.Error(err)
return nil, err
}
if exist {
// reset expiration time if token exist
h.Rule.RedisClient.Expire(tokenKey, h.Rule.TokenIdleTimeout)
return token, nil
} else {
return nil, errors.New("illegal token")
}
}
func (h Auth) HandleUnauthorized(w http.ResponseWriter, err error) int {
message := fmt.Sprintf("Unauthorized,%v", err)
w.Header().Add("WWW-Authenticate", message)
log.Println(message)
return http.StatusUnauthorized
}
func (h Auth) ExtractToken(r *http.Request) (string, error) {
jwtHeader := strings.Split(r.Header.Get("Authorization"), " ")
if jwtHeader[0] == "Bearer" && len(jwtHeader) == 2 {
return jwtHeader[1], nil
}
jwtCookie, err := r.Cookie("token")
if err == nil {
return jwtCookie.Value, nil
}
jwtQuery := r.URL.Query().Get("token")
if jwtQuery != "" {
return jwtQuery, nil
}
return "", fmt.Errorf("no token found")
}
func (h Auth) ProvideKey(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); ok {
return h.Rule.Secret, nil
} else {
return nil, fmt.Errorf("expect token signed with HMAC but got %v", token.Header["alg"])
}
}

View File

@@ -1,155 +0,0 @@
/*
Copyright 2019 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 authenticate
import (
"fmt"
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"time"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func Setup(c *caddy.Controller) error {
rule, err := parse(c)
if err != nil {
return err
}
stopCh := make(chan struct{})
c.OnStartup(func() error {
rule.RedisClient, err = cache.NewRedisClient(rule.RedisOptions, stopCh)
// ensure redis is connected when startup
if err != nil {
return err
}
fmt.Println("Authenticate middleware is initiated")
return nil
})
c.OnShutdown(func() error {
close(stopCh)
return nil
})
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return &Auth{Next: next, Rule: rule}
})
return nil
}
func parse(c *caddy.Controller) (*Rule, error) {
rule := &Rule{}
rule.ExclusionRules = make([]internal.ExclusionRule, 0)
if c.Next() {
args := c.RemainingArgs()
switch len(args) {
case 0:
for c.NextBlock() {
switch c.Val() {
case "path":
if !c.NextArg() {
return nil, c.ArgErr()
}
rule.Path = c.Val()
if c.NextArg() {
return nil, c.ArgErr()
}
case "token-idle-timeout":
if !c.NextArg() {
return nil, c.ArgErr()
}
if timeout, err := time.ParseDuration(c.Val()); err != nil {
return nil, c.ArgErr()
} else {
rule.TokenIdleTimeout = timeout
}
if c.NextArg() {
return nil, c.ArgErr()
}
case "redis-url":
if !c.NextArg() {
return nil, c.ArgErr()
}
options := &cache.Options{Host: c.Val()}
if err := options.Validate(); len(err) > 0 {
return nil, c.ArgErr()
} else {
rule.RedisOptions = options
}
if c.NextArg() {
return nil, c.ArgErr()
}
case "secret":
if !c.NextArg() {
return nil, c.ArgErr()
}
rule.Secret = []byte(c.Val())
if c.NextArg() {
return nil, c.ArgErr()
}
case "except":
if !c.NextArg() {
return nil, c.ArgErr()
}
method := c.Val()
if !sliceutil.HasString(internal.HttpMethods, method) {
return nil, c.ArgErr()
}
for c.NextArg() {
path := c.Val()
rule.ExclusionRules = append(rule.ExclusionRules, internal.ExclusionRule{Method: method, Path: path})
}
}
}
default:
return nil, c.ArgErr()
}
}
if c.Next() {
return nil, c.ArgErr()
}
if rule.RedisOptions == nil {
return nil, c.Err("redis-url must be specified")
}
return rule, nil
}

View File

@@ -1,309 +0,0 @@
/*
Copyright 2019 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 authentication
import (
"context"
"errors"
"fmt"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/client-go/informers"
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"log"
"net/http"
"strings"
"github.com/mholt/caddy/caddyhttp/httpserver"
"k8s.io/api/rbac/v1"
k8serr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
)
type Authentication struct {
Rule *Rule
Next httpserver.Handler
informerFactory informers.SharedInformerFactory
}
type Rule struct {
Path string
KubernetesOptions *k8s.KubernetesOptions
ExclusionRules []internal.ExclusionRule
}
func (c *Authentication) ServeHTTP(w http.ResponseWriter, r *http.Request) (int, error) {
if httpserver.Path(r.URL.Path).Matches(c.Rule.Path) {
for _, rule := range c.Rule.ExclusionRules {
if httpserver.Path(r.URL.Path).Matches(rule.Path) && (rule.Method == internal.AllMethod || r.Method == rule.Method) {
return c.Next.ServeHTTP(w, r)
}
}
attrs, err := getAuthorizerAttributes(r.Context())
// without authenticate, no requestInfo found in the context
if err != nil {
return c.Next.ServeHTTP(w, r)
}
permitted, err := c.permissionValidate(attrs)
if err != nil {
return http.StatusInternalServerError, err
}
if !permitted {
err = k8serr.NewForbidden(schema.GroupResource{Group: attrs.GetAPIGroup(), Resource: attrs.GetResource()}, attrs.GetName(), fmt.Errorf("permission undefined"))
return handleForbidden(w, err), nil
}
}
return c.Next.ServeHTTP(w, r)
}
func handleForbidden(w http.ResponseWriter, err error) int {
message := fmt.Sprintf("Forbidden,%s", err.Error())
w.Header().Add("WWW-Authenticate", message)
log.Println(message)
return http.StatusForbidden
}
func (c *Authentication) permissionValidate(attrs authorizer.Attributes) (bool, error) {
if attrs.GetResource() == "users" && attrs.GetUser().GetName() == attrs.GetName() {
return true, nil
}
permitted, err := c.clusterRoleValidate(attrs)
if err != nil {
log.Println("lister error", err)
return false, err
}
if permitted {
return true, nil
}
if attrs.GetNamespace() != "" {
permitted, err = c.roleValidate(attrs)
if err != nil {
log.Println("lister error", err)
return false, err
}
if permitted {
return true, nil
}
}
return false, nil
}
func (c *Authentication) roleValidate(attrs authorizer.Attributes) (bool, error) {
roleBindingLister := c.informerFactory.Rbac().V1().RoleBindings().Lister()
roleLister := c.informerFactory.Rbac().V1().Roles().Lister()
roleBindings, err := roleBindingLister.RoleBindings(attrs.GetNamespace()).List(labels.Everything())
if err != nil {
return false, err
}
fullSource := attrs.GetResource()
if attrs.GetSubresource() != "" {
fullSource = fullSource + "/" + attrs.GetSubresource()
}
for _, roleBinding := range roleBindings {
if iam.ContainsUser(roleBinding.Subjects, attrs.GetUser().GetName()) {
role, err := roleLister.Roles(attrs.GetNamespace()).Get(roleBinding.RoleRef.Name)
if err != nil {
if k8serr.IsNotFound(err) {
continue
}
return false, err
}
for _, rule := range role.Rules {
if ruleMatchesRequest(rule, attrs.GetAPIGroup(), "", attrs.GetResource(), attrs.GetSubresource(), attrs.GetName(), attrs.GetVerb()) {
return true, nil
}
}
}
}
return false, nil
}
func (c *Authentication) clusterRoleValidate(attrs authorizer.Attributes) (bool, error) {
clusterRoleBindingLister := c.informerFactory.Rbac().V1().ClusterRoleBindings().Lister()
clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything())
clusterRoleLister := c.informerFactory.Rbac().V1().ClusterRoles().Lister()
if err != nil {
return false, err
}
for _, clusterRoleBinding := range clusterRoleBindings {
if iam.ContainsUser(clusterRoleBinding.Subjects, attrs.GetUser().GetName()) {
clusterRole, err := clusterRoleLister.Get(clusterRoleBinding.RoleRef.Name)
if err != nil {
if k8serr.IsNotFound(err) {
continue
}
return false, err
}
for _, rule := range clusterRole.Rules {
if attrs.IsResourceRequest() {
if ruleMatchesRequest(rule, attrs.GetAPIGroup(), "", attrs.GetResource(), attrs.GetSubresource(), attrs.GetName(), attrs.GetVerb()) {
return true, nil
}
} else {
if ruleMatchesRequest(rule, "", attrs.GetPath(), "", "", "", attrs.GetVerb()) {
return true, nil
}
}
}
}
}
return false, nil
}
func ruleMatchesResources(rule v1.PolicyRule, apiGroup string, resource string, subresource string, resourceName string) bool {
if resource == "" {
return false
}
if !sliceutil.HasString(rule.APIGroups, apiGroup) && !sliceutil.HasString(rule.APIGroups, v1.ResourceAll) {
return false
}
if len(rule.ResourceNames) > 0 && !sliceutil.HasString(rule.ResourceNames, resourceName) {
return false
}
combinedResource := resource
if subresource != "" {
combinedResource = combinedResource + "/" + subresource
}
for _, res := range rule.Resources {
// match "*"
if res == v1.ResourceAll || res == combinedResource {
return true
}
// match "*/subresource"
if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") {
return true
}
// match "resource/*"
if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") {
return true
}
}
return false
}
func ruleMatchesRequest(rule v1.PolicyRule, apiGroup string, nonResourceURL string, resource string, subresource string, resourceName string, verb string) bool {
if !sliceutil.HasString(rule.Verbs, verb) && !sliceutil.HasString(rule.Verbs, v1.VerbAll) {
return false
}
if nonResourceURL == "" {
return ruleMatchesResources(rule, apiGroup, resource, subresource, resourceName)
} else {
return ruleMatchesNonResource(rule, nonResourceURL)
}
}
func ruleMatchesNonResource(rule v1.PolicyRule, nonResourceURL string) bool {
if nonResourceURL == "" {
return false
}
for _, spec := range rule.NonResourceURLs {
if pathMatches(nonResourceURL, spec) {
return true
}
}
return false
}
func pathMatches(path, spec string) bool {
if spec == "*" {
return true
}
if spec == path {
return true
}
if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
return true
}
return false
}
func getAuthorizerAttributes(ctx context.Context) (authorizer.Attributes, error) {
attribs := authorizer.AttributesRecord{}
user, ok := request.UserFrom(ctx)
if ok {
attribs.User = user
}
requestInfo, found := request.RequestInfoFrom(ctx)
if !found {
return nil, errors.New("no RequestInfo found in the context")
}
// Start with common attributes that apply to resource and non-resource requests
attribs.ResourceRequest = requestInfo.IsResourceRequest
attribs.Path = requestInfo.Path
attribs.Verb = requestInfo.Verb
attribs.APIGroup = requestInfo.APIGroup
attribs.APIVersion = requestInfo.APIVersion
attribs.Resource = requestInfo.Resource
attribs.Subresource = requestInfo.Subresource
attribs.Namespace = requestInfo.Namespace
attribs.Name = requestInfo.Name
return &attribs, nil
}

View File

@@ -1,128 +0,0 @@
/*
Copyright 2019 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 authentication
import (
"fmt"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/internal"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"kubesphere.io/kubesphere/pkg/informers"
)
// Setup is called by Caddy to parse the config block
func Setup(c *caddy.Controller) error {
rule, err := parse(c)
if err != nil {
return err
}
if rule.KubernetesOptions == nil && rule.KubernetesOptions.KubeConfig == "" {
klog.Warning("no kubeconfig provided, will use in cluster config, this may not work")
}
kubeClient, err := k8s.NewKubernetesClient(rule.KubernetesOptions)
if err != nil {
return err
}
informerFactory := informers.NewInformerFactories(kubeClient.Kubernetes(), nil, nil, nil)
stopChan := make(chan struct{}, 0)
c.OnStartup(func() error {
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().Roles().Lister()
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().RoleBindings().Lister()
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().ClusterRoles().Lister()
informerFactory.KubernetesSharedInformerFactory().Rbac().V1().ClusterRoleBindings().Lister()
informerFactory.KubernetesSharedInformerFactory().Start(stopChan)
informerFactory.KubernetesSharedInformerFactory().WaitForCacheSync(stopChan)
fmt.Println("Authentication middleware is initiated")
return nil
})
c.OnShutdown(func() error {
close(stopChan)
return nil
})
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return &Authentication{Next: next, Rule: rule, informerFactory: informerFactory.KubernetesSharedInformerFactory()}
})
return nil
}
func parse(c *caddy.Controller) (*Rule, error) {
rule := &Rule{}
rule.ExclusionRules = make([]internal.ExclusionRule, 0)
if c.Next() {
args := c.RemainingArgs()
switch len(args) {
case 0:
for c.NextBlock() {
switch c.Val() {
case "path":
if !c.NextArg() {
return rule, c.ArgErr()
}
rule.Path = c.Val()
if c.NextArg() {
return rule, c.ArgErr()
}
break
case "except":
if !c.NextArg() {
return nil, c.ArgErr()
}
method := c.Val()
if !sliceutil.HasString(internal.HttpMethods, method) {
return nil, c.ArgErr()
}
for c.NextArg() {
path := c.Val()
rule.ExclusionRules = append(rule.ExclusionRules, internal.ExclusionRule{Method: method, Path: path})
}
break
}
}
case 1:
rule.Path = args[0]
if c.NextBlock() {
return rule, c.ArgErr()
}
default:
return rule, c.ArgErr()
}
}
if c.Next() {
return rule, c.ArgErr()
}
return rule, nil
}

View File

@@ -1,32 +0,0 @@
/*
*
* Copyright 2019 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 internal
import "net/http"
const AllMethod = "*"
var HttpMethods = []string{AllMethod, http.MethodPost, http.MethodDelete,
http.MethodPatch, http.MethodPut, http.MethodGet, http.MethodOptions, http.MethodConnect}
// Path exclusion rule
type ExclusionRule struct {
Method string
Path string
}

View File

@@ -1,93 +0,0 @@
/*
Copyright 2019 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 authenticate
import (
"fmt"
"net/http"
"github.com/mholt/caddy"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
func Setup(c *caddy.Controller) error {
handler, err := parse(c)
if err != nil {
return err
}
c.OnStartup(func() error {
fmt.Println("Swagger middleware is initiated")
return nil
})
httpserver.GetConfig(c).AddMiddleware(func(next httpserver.Handler) httpserver.Handler {
return &Swagger{Next: next, Handler: handler}
})
return nil
}
func parse(c *caddy.Controller) (Handler, error) {
handler := Handler{URL: "/swagger-ui", FilePath: "/var/static/swagger-ui"}
if c.Next() {
args := c.RemainingArgs()
switch len(args) {
case 0:
for c.NextBlock() {
switch c.Val() {
case "url":
if !c.NextArg() {
return handler, c.ArgErr()
}
handler.URL = c.Val()
if c.NextArg() {
return handler, c.ArgErr()
}
case "filePath":
if !c.NextArg() {
return handler, c.ArgErr()
}
handler.FilePath = c.Val()
if c.NextArg() {
return handler, c.ArgErr()
}
default:
return handler, c.ArgErr()
}
}
default:
return handler, c.ArgErr()
}
}
if c.Next() {
return handler, c.ArgErr()
}
handler.Handler = http.StripPrefix(handler.URL, http.FileServer(http.Dir(handler.FilePath)))
return handler, nil
}

View File

@@ -1,45 +0,0 @@
/*
Copyright 2019 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 authenticate
import (
"net/http"
"github.com/mholt/caddy/caddyhttp/httpserver"
)
type Swagger struct {
Handler Handler
Next httpserver.Handler
}
type Handler struct {
URL string
FilePath string
Handler http.Handler
}
func (h Swagger) ServeHTTP(resp http.ResponseWriter, req *http.Request) (int, error) {
if httpserver.Path(req.URL.Path).Matches(h.Handler.URL) {
h.Handler.Handler.ServeHTTP(resp, req)
return http.StatusOK, nil
}
return h.Next.ServeHTTP(resp, req)
}

View File

@@ -1,26 +0,0 @@
package apigateway
import (
"github.com/mholt/caddy"
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/authenticate"
"kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/authentication"
swagger "kubesphere.io/kubesphere/pkg/apigateway/caddy-plugin/swagger"
)
func RegisterPlugins() {
caddy.RegisterPlugin("swagger", caddy.Plugin{
ServerType: "http",
Action: swagger.Setup,
})
caddy.RegisterPlugin("authenticate", caddy.Plugin{
ServerType: "http",
Action: authenticate.Setup,
})
caddy.RegisterPlugin("authentication", caddy.Plugin{
ServerType: "http",
Action: authentication.Setup,
})
}

View File

@@ -33,6 +33,7 @@ import (
servicemeshv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/servicemesh/metrics/v1alpha2"
tenantv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/tenant/v1alpha2"
terminalv1alpha2 "kubesphere.io/kubesphere/pkg/kapis/terminal/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
@@ -103,8 +104,6 @@ type APIServer struct {
//
LdapClient ldap.Interface
//
}
func (s *APIServer) PrepareRun() error {
@@ -188,7 +187,7 @@ func (s *APIServer) buildHandlerChain() {
excludedPaths := []string{"/oauth/authorize", "/oauth/token"}
pathAuthorizer, _ := path.NewAuthorizer(excludedPaths)
authorizer := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer())
authorizer := unionauthorizer.New(pathAuthorizer, authorizerfactory.NewOPAAuthorizer(am.NewAMOperator(s.KubernetesClient.Kubernetes(), s.InformerFactory.KubernetesSharedInformerFactory())))
handler = filters.WithAuthorization(handler, authorizer)
handler = filters.WithMultipleClusterDispatcher(handler, dispatch.DefaultClusterDispatch)
handler = filters.WithKubeAPIServer(handler, s.KubernetesClient.Config(), &errorResponder{})

View File

@@ -18,15 +18,111 @@
package authorizerfactory
import "kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
import (
"context"
"github.com/open-policy-agent/opa/rego"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
am2 "kubesphere.io/kubesphere/pkg/models/iam/am"
)
type opaAuthorizer struct{}
func (opaAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
// TODO implement.
return authorizer.DecisionAllow, "", nil
type opaAuthorizer struct {
am am2.AccessManagementInterface
}
func NewOPAAuthorizer() *opaAuthorizer {
return new(opaAuthorizer)
func (o *opaAuthorizer) Authorize(a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
platformRole, err := o.am.GetPlatformRole(a.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check platform role policy rules
if a, r, e := o.roleAuthorize(platformRole, a); a == authorizer.DecisionAllow {
return a, r, e
}
// it's not in cluster resource, permission denied
// TODO declare implicit cluster info in request Info
if a.GetCluster() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
}
clusterRole, err := o.am.GetClusterRole(a.GetCluster(), a.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check cluster role policy rules
if a, r, e := o.roleAuthorize(clusterRole, a); a == authorizer.DecisionAllow {
return a, r, e
}
// it's not in cluster resource, permission denied
if a.GetWorkspace() == "" && a.GetNamespace() == "" && a.GetDevopsProject() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
}
workspaceRole, err := o.am.GetWorkspaceRole(a.GetWorkspace(), a.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check workspace role policy rules
if a, r, e := o.roleAuthorize(workspaceRole, a); a == authorizer.DecisionAllow {
return a, r, e
}
// it's not in workspace resource, permission denied
if a.GetNamespace() == "" && a.GetDevopsProject() == "" {
return authorizer.DecisionDeny, "permission undefined", nil
}
if a.GetNamespace() != "" {
namespaceRole, err := o.am.GetNamespaceRole(a.GetNamespace(), a.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check namespace role policy rules
if a, r, e := o.roleAuthorize(namespaceRole, a); a == authorizer.DecisionAllow {
return a, r, e
}
}
if a.GetDevopsProject() != "" {
devOpsRole, err := o.am.GetDevOpsRole(a.GetNamespace(), a.GetUser().GetName())
if err != nil {
return authorizer.DecisionDeny, "", err
}
// check devops role policy rules
if a, r, e := o.roleAuthorize(devOpsRole, a); a == authorizer.DecisionAllow {
return a, r, e
}
}
return authorizer.DecisionDeny, "", nil
}
func (o *opaAuthorizer) roleAuthorize(role am2.Role, a authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", role.GetRego())).PrepareForEval(context.Background())
if err != nil {
return authorizer.DecisionDeny, "", err
}
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
}
func NewOPAAuthorizer(am am2.AccessManagementInterface) *opaAuthorizer {
return &opaAuthorizer{am: am}
}

View File

@@ -0,0 +1,84 @@
/*
*
* 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 (
"context"
"github.com/open-policy-agent/opa/rego"
"k8s.io/apiserver/pkg/authentication/user"
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"testing"
)
func TestPlatformRole(t *testing.T) {
module := `package platform.authz
default allow = false
allow {
input.User.name == "admin"
}
allow {
is_admin
}
is_admin {
input.User.Groups[_] == "admin"
}
`
query, err := rego.New(rego.Query("data.authz.allow"), rego.Module("authz.rego", module)).PrepareForEval(context.Background())
if err != nil {
t.Fatal(err)
}
input := authorizer.AttributesRecord{
User: &user.DefaultInfo{
Name: "admin",
UID: "0",
Groups: []string{"admin"},
Extra: nil,
},
Verb: "list",
Cluster: "",
Workspace: "",
Namespace: "",
DevopsProject: "",
APIGroup: "",
APIVersion: "v1",
Resource: "nodes",
Subresource: "",
Name: "",
KubernetesRequest: true,
ResourceRequest: true,
Path: "/api/v1/nodes",
}
results, err := query.Eval(context.Background(), rego.EvalInput(input))
if err != nil {
t.Log(err)
}
if len(results) > 0 && results[0].Expressions[0].Value == true {
t.Log("allowed")
} else {
t.Log("deny")
}
}

View File

@@ -17,9 +17,8 @@ limitations under the License.
package path
import (
"kubesphere.io/kubesphere/pkg/apiserver/authorization/authorizer"
"testing"
"k8s.io/apiserver/pkg/authorization/authorizer"
)
func TestNewAuthorizer(t *testing.T) {

View File

@@ -32,11 +32,9 @@ import (
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/simple/client/openpitrix"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"openpitrix.io/openpitrix/pkg/pb"
"reflect"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
@@ -183,14 +181,6 @@ func (r *ReconcileNamespace) Reconcile(request reconcile.Request) (reconcile.Res
return reconcile.Result{}, err
}
if err = r.checkAndCreateRoles(instance); err != nil {
return reconcile.Result{}, err
}
if err = r.checkAndCreateRoleBindings(instance); err != nil {
return reconcile.Result{}, err
}
if err := r.checkAndCreateRuntime(instance); err != nil {
return reconcile.Result{}, err
}
@@ -210,152 +200,6 @@ func (r *ReconcileNamespace) isControlledByWorkspace(namespace *corev1.Namespace
return true, nil
}
// Create default roles
func (r *ReconcileNamespace) checkAndCreateRoles(namespace *corev1.Namespace) error {
for _, role := range defaultRoles {
found := &rbac.Role{}
err := r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: role.Name}, found)
if err != nil {
if errors.IsNotFound(err) {
role := role.DeepCopy()
role.Namespace = namespace.Name
err = r.Create(context.TODO(), role)
if err != nil {
klog.Error(err)
return err
}
} else {
klog.Error(err)
return err
}
}
if !reflect.DeepEqual(found.Rules, role.Rules) {
found.Rules = role.Rules
if err := r.Update(context.TODO(), found); err != nil {
klog.Error(err)
return err
}
}
}
return nil
}
func (r *ReconcileNamespace) checkAndCreateRoleBindings(namespace *corev1.Namespace) error {
workspaceName := namespace.Labels[constants.WorkspaceLabelKey]
creatorName := namespace.Annotations[constants.CreatorAnnotationKey]
creator := rbac.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: creatorName}
workspaceAdminBinding := &rbac.ClusterRoleBinding{}
err := r.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("workspace:%s:admin", workspaceName)}, workspaceAdminBinding)
if err != nil {
return err
}
adminBinding := &rbac.RoleBinding{}
adminBinding.Name = admin.Name
adminBinding.Namespace = namespace.Name
adminBinding.RoleRef = rbac.RoleRef{Name: admin.Name, APIGroup: "rbac.authorization.k8s.io", Kind: "Role"}
adminBinding.Subjects = workspaceAdminBinding.Subjects
if creator.Name != "" {
if adminBinding.Subjects == nil {
adminBinding.Subjects = make([]rbac.Subject, 0)
}
if !iam.ContainsUser(adminBinding.Subjects, creatorName) {
adminBinding.Subjects = append(adminBinding.Subjects, creator)
}
}
found := &rbac.RoleBinding{}
err = r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: adminBinding.Name}, found)
if errors.IsNotFound(err) {
err = r.Create(context.TODO(), adminBinding)
if err != nil {
klog.Errorf("creating role binding namespace: %s,role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
return err
}
found = adminBinding
} else if err != nil {
klog.Errorf("get role binding namespace: %s,role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
return err
}
if !reflect.DeepEqual(found.RoleRef, adminBinding.RoleRef) {
err = r.Delete(context.TODO(), found)
if err != nil {
klog.Errorf("deleting role binding namespace: %s, role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
return err
}
err = fmt.Errorf("conflict role binding %s.%s, waiting for recreate", namespace.Name, adminBinding.Name)
klog.Errorf("conflict role binding namespace: %s, role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
return err
}
if !reflect.DeepEqual(found.Subjects, adminBinding.Subjects) {
found.Subjects = adminBinding.Subjects
err = r.Update(context.TODO(), found)
if err != nil {
klog.Errorf("updating role binding namespace: %s, role binding: %s, error: %s", namespace.Name, adminBinding.Name, err)
return err
}
}
workspaceViewerBinding := &rbac.ClusterRoleBinding{}
err = r.Get(context.TODO(), types.NamespacedName{Name: fmt.Sprintf("workspace:%s:viewer", workspaceName)}, workspaceViewerBinding)
if err != nil {
return err
}
viewerBinding := &rbac.RoleBinding{}
viewerBinding.Name = viewer.Name
viewerBinding.Namespace = namespace.Name
viewerBinding.RoleRef = rbac.RoleRef{Name: viewer.Name, APIGroup: "rbac.authorization.k8s.io", Kind: "Role"}
viewerBinding.Subjects = workspaceViewerBinding.Subjects
err = r.Get(context.TODO(), types.NamespacedName{Namespace: namespace.Name, Name: viewerBinding.Name}, found)
if errors.IsNotFound(err) {
err = r.Create(context.TODO(), viewerBinding)
if err != nil {
klog.Errorf("creating role binding namespace: %s, role binding: %s, error: %s", namespace.Name, viewerBinding.Name, err)
return err
}
found = viewerBinding
} else if err != nil {
return err
}
if !reflect.DeepEqual(found.RoleRef, viewerBinding.RoleRef) {
err = r.Delete(context.TODO(), found)
if err != nil {
klog.Errorf("deleting conflict role binding namespace: %s, role binding: %s, %s", namespace.Name, viewerBinding.Name, err)
return err
}
err = fmt.Errorf("conflict role binding %s.%s, waiting for recreate", namespace.Name, viewerBinding.Name)
klog.Errorf("conflict role binding namespace: %s, role binding: %s, error: %s", namespace.Name, viewerBinding.Name, err)
return err
}
if !reflect.DeepEqual(found.Subjects, viewerBinding.Subjects) {
found.Subjects = viewerBinding.Subjects
err = r.Update(context.TODO(), found)
if err != nil {
klog.Errorf("updating role binding namespace: %s, role binding: %s, error: %s", namespace.Name, viewerBinding.Name, err)
return err
}
}
return nil
}
// Create openpitrix runtime
func (r *ReconcileNamespace) checkAndCreateRuntime(namespace *corev1.Namespace) error {

View File

@@ -2,19 +2,14 @@ package v1alpha2
import (
"errors"
"fmt"
"github.com/emicklei/go-restful"
"github.com/go-ldap/ldap"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
iamv1alpha2 "kubesphere.io/kubesphere/pkg/api/iam/v1alpha2"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
"kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/iam/im"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
apierr "kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
@@ -30,14 +25,14 @@ const (
)
type iamHandler struct {
amOperator iam.AccessManagementInterface
imOperator iam.IdentityManagementInterface
amOperator am.AccessManagementInterface
imOperator im.IdentityManagementInterface
}
func newIAMHandler(k8sClient k8s.Client, factory informers.InformerFactory, ldapClient ldappool.Interface, cacheClient cache.Interface, options *iamapi.AuthenticationOptions) *iamHandler {
return &iamHandler{
amOperator: iam.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
imOperator: iam.NewIMOperator(ldapClient, cacheClient, options),
amOperator: am.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
imOperator: im.NewIMOperator(ldapClient, cacheClient, options),
}
}
@@ -96,7 +91,7 @@ func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) {
token, err := h.imOperator.Login(loginRequest.Username, loginRequest.Password, ip)
if err != nil {
if err == iam.AuthRateLimitExceeded {
if err == im.AuthRateLimitExceeded {
klog.V(4).Infoln(err)
resp.WriteHeaderAndEntity(http.StatusTooManyRequests, err)
return
@@ -110,151 +105,19 @@ func (h *iamHandler) Login(req *restful.Request, resp *restful.Response) {
}
func (h *iamHandler) CreateUser(req *restful.Request, resp *restful.Response) {
var createRequest iamv1alpha2.CreateUserRequest
err := req.ReadEntity(&createRequest)
if err != nil {
klog.V(4).Infoln(err)
api.HandleBadRequest(resp, nil, err)
return
}
if err := createRequest.Validate(); err != nil {
klog.V(4).Infoln(err)
api.HandleBadRequest(resp, nil, err)
return
}
created, err := h.imOperator.CreateUser(createRequest.User)
if err != nil {
if err == iam.UserAlreadyExists {
klog.V(4).Infoln(err)
resp.WriteHeaderAndEntity(http.StatusConflict, err)
return
}
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
err = h.amOperator.CreateClusterRoleBinding(created.Username, createRequest.ClusterRole)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(created)
panic("implement me")
}
func (h *iamHandler) DeleteUser(req *restful.Request, resp *restful.Response) {
username := req.PathParameter("user")
operator := req.HeaderParameter(constants.UserNameHeader)
if operator == username {
err := errors.New("cannot delete yourself")
klog.V(4).Infoln(err)
api.HandleForbidden(resp, nil, err)
return
}
err := h.amOperator.UnBindAllRoles(username)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
err = h.imOperator.DeleteUser(username)
// TODO release user resources
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(apierr.None)
panic("implement me")
}
func (h *iamHandler) ModifyUser(request *restful.Request, response *restful.Response) {
username := request.PathParameter("user")
operator := request.HeaderParameter(constants.UserNameHeader)
var modifyUserRequest iamv1alpha2.ModifyUserRequest
err := request.ReadEntity(&modifyUserRequest)
if err != nil {
klog.V(4).Infoln(err)
api.HandleBadRequest(response, nil, err)
return
}
if username != modifyUserRequest.Username {
err = fmt.Errorf("the name of user (%s) does not match the name on the URL (%s)", modifyUserRequest.Username, username)
klog.V(4).Infoln(err)
api.HandleBadRequest(response, nil, err)
return
}
if err = modifyUserRequest.Validate(); err != nil {
klog.V(4).Infoln(err)
api.HandleBadRequest(response, nil, err)
return
}
// change password by self
if operator == modifyUserRequest.Username && modifyUserRequest.Password != "" {
}
result, err := h.imOperator.ModifyUser(modifyUserRequest.User)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(response, nil, err)
return
}
// TODO modify cluster role
response.WriteEntity(result)
panic("implement me")
}
func (h *iamHandler) DescribeUser(req *restful.Request, resp *restful.Response) {
username := req.PathParameter("user")
user, err := h.imOperator.DescribeUser(username)
if err != nil {
if err == iam.UserNotExists {
klog.V(4).Infoln(err)
api.HandleNotFound(resp, nil, err)
return
}
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
// TODO append more user info
clusterRole, err := h.amOperator.GetClusterRole(username)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result := iamv1alpha2.UserDetail{
User: user,
ClusterRole: clusterRole.Name,
}
resp.WriteEntity(result)
panic("implement me")
}
func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) {
@@ -282,202 +145,35 @@ func (h *iamHandler) ListUsers(req *restful.Request, resp *restful.Response) {
}
func (h *iamHandler) ListUserRoles(req *restful.Request, resp *restful.Response) {
username := req.PathParameter("user")
roles, err := h.imOperator.GetUserRoles(username)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(roles)
panic("implement me")
}
func (h *iamHandler) ListRoles(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
limit, offset := params.ParsePaging(req)
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
conditions, err := params.ParseConditions(req)
if err != nil {
klog.V(4).Infoln(err)
api.HandleBadRequest(resp, nil, err)
return
}
result, err := h.amOperator.ListRoles(namespace, conditions, orderBy, reverse, limit, offset)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(result)
panic("implement me")
}
func (h *iamHandler) ListClusterRoles(req *restful.Request, resp *restful.Response) {
limit, offset := params.ParsePaging(req)
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
reverse := params.GetBoolValueWithDefault(req, params.ReverseParam, true)
conditions, err := params.ParseConditions(req)
if err != nil {
klog.V(4).Infoln(err)
api.HandleBadRequest(resp, nil, err)
return
}
result, err := h.amOperator.ListClusterRoles(conditions, orderBy, reverse, limit, offset)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(result)
panic("implement me")
}
func (h *iamHandler) ListRoleUsers(req *restful.Request, resp *restful.Response) {
role := req.PathParameter("role")
namespace := req.PathParameter("namespace")
roleBindings, err := h.amOperator.ListRoleBindings(namespace, role)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result := make([]*iamapi.User, 0)
for _, roleBinding := range roleBindings {
for _, subject := range roleBinding.Subjects {
if subject.Kind == rbacv1.UserKind {
user, err := h.imOperator.DescribeUser(subject.Name)
// skip if user not exist
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
continue
}
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result = append(result, user)
}
}
}
resp.WriteEntity(result)
panic("implement me")
}
// List users by namespace
func (h *iamHandler) ListNamespaceUsers(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
roleBindings, err := h.amOperator.ListRoleBindings(namespace, "")
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result := make([]*iamapi.User, 0)
for _, roleBinding := range roleBindings {
for _, subject := range roleBinding.Subjects {
if subject.Kind == rbacv1.UserKind {
user, err := h.imOperator.DescribeUser(subject.Name)
// skip if user not exist
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
continue
}
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result = append(result, user)
}
}
}
resp.WriteEntity(result)
panic("implement me")
}
func (h *iamHandler) ListClusterRoleUsers(req *restful.Request, resp *restful.Response) {
clusterRole := req.PathParameter("clusterrole")
clusterRoleBindings, err := h.amOperator.ListClusterRoleBindings(clusterRole)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result := make([]*iamapi.User, 0)
for _, roleBinding := range clusterRoleBindings {
for _, subject := range roleBinding.Subjects {
if subject.Kind == rbacv1.UserKind {
user, err := h.imOperator.DescribeUser(subject.Name)
// skip if user not exist
if ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) {
continue
}
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
result = append(result, user)
}
}
}
resp.WriteEntity(result)
}
func (h *iamHandler) RulesMapping(req *restful.Request, resp *restful.Response) {
rules := policy.RoleRuleMapping
resp.WriteEntity(rules)
}
func (h *iamHandler) ClusterRulesMapping(req *restful.Request, resp *restful.Response) {
rules := policy.ClusterRoleRuleMapping
resp.WriteEntity(rules)
panic("implement me")
}
func (h *iamHandler) ListClusterRoleRules(req *restful.Request, resp *restful.Response) {
clusterRole := req.PathParameter("clusterrole")
rules, err := h.amOperator.GetClusterRoleSimpleRules(clusterRole)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(rules)
panic("implement me")
}
func (h *iamHandler) ListRoleRules(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
role := req.PathParameter("role")
rules, err := h.amOperator.GetRoleSimpleRules(namespace, role)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(rules)
panic("implement me")
}
func (h *iamHandler) ListWorkspaceRoles(request *restful.Request, response *restful.Response) {

View File

@@ -29,7 +29,6 @@ import (
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/simple/client/cache"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
@@ -124,29 +123,6 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
Param(ws.PathParameter("clusterrole", "cluster role name")).
Returns(http.StatusOK, api.StatusOK, []iamv1alpha2.ListUserResponse{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/clusterroles/{clusterrole}/rules").
To(handler.ListClusterRoleRules).
Doc("List all policy rules of the specified cluster role.").
Param(ws.PathParameter("clusterrole", "cluster role name")).
Returns(http.StatusOK, api.StatusOK, []policy.SimpleRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/namespaces/{namespace}/roles/{role}/rules").
To(handler.ListRoleRules).
Doc("List all policy rules of the specified role in the given namespace.").
Param(ws.PathParameter("namespace", "kubernetes namespace")).
Param(ws.PathParameter("role", "role name")).
Returns(http.StatusOK, api.StatusOK, []policy.SimpleRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/rulesmapping/clusterroles").
To(handler.ClusterRulesMapping).
Doc("Get the mapping relationships between cluster roles and policy rules.").
Returns(http.StatusOK, api.StatusOK, policy.ClusterRoleRuleMapping).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/rulesmapping/roles").
To(handler.RulesMapping).
Doc("Get the mapping relationships between namespaced roles and policy rules.").
Returns(http.StatusOK, api.StatusOK, policy.RoleRuleMapping).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.AccessManagementTag}))
ws.Route(ws.GET("/workspaces/{workspace}/roles").
To(handler.ListWorkspaceRoles).

View File

@@ -3,14 +3,12 @@ package v1alpha2
import (
"github.com/emicklei/go-restful"
v1 "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
k8serr "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/util/net"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/api"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models/iam"
am2 "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/monitoring"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/tenant"
@@ -18,39 +16,22 @@ import (
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"net/http"
"strings"
)
type tenantHandler struct {
tenant tenant.Interface
am iam.AccessManagementInterface
am am2.AccessManagementInterface
}
func newTenantHandler(k8sClient k8s.Client, factory informers.InformerFactory, db *mysql.Database) *tenantHandler {
return &tenantHandler{
tenant: tenant.New(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory(), factory.KubeSphereSharedInformerFactory(), db),
am: iam.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
am: am2.NewAMOperator(k8sClient.Kubernetes(), factory.KubernetesSharedInformerFactory()),
}
}
func (h *tenantHandler) ListWorkspaceRules(req *restful.Request, resp *restful.Response) {
workspace := req.PathParameter("workspace")
username := req.HeaderParameter(constants.UserNameHeader)
rules, err := h.tenant.GetWorkspaceSimpleRules(workspace, username)
if err != nil {
klog.Errorln(err)
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteEntity(rules)
}
func (h *tenantHandler) ListWorkspaces(req *restful.Request, resp *restful.Response) {
username := req.HeaderParameter(constants.UserNameHeader)
orderBy := params.GetStringValueWithDefault(req, params.OrderByParam, v1alpha2.CreateTime)
@@ -256,136 +237,3 @@ func (h *tenantHandler) DeleteDevopsProject(req *restful.Request, resp *restful.
func (h *tenantHandler) CreateDevopsProject(req *restful.Request, resp *restful.Response) {
}
func (h *tenantHandler) ListNamespaceRules(req *restful.Request, resp *restful.Response) {
namespace := req.PathParameter("namespace")
username := req.HeaderParameter(constants.UserNameHeader)
rules, err := h.tenant.GetNamespaceSimpleRules(namespace, username)
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(rules)
}
func (h *tenantHandler) ListDevopsRules(req *restful.Request, resp *restful.Response) {
devops := req.PathParameter("devops")
username := req.HeaderParameter(constants.UserNameHeader)
rules, err := h.tenant.GetUserDevopsSimpleRules(username, devops)
if err != nil {
api.HandleInternalError(resp, nil, err)
return
}
resp.WriteAsJson(rules)
}
//TODO(wansir): We need move this part to logging module
//func (h *tenantHandler) LogQuery(req *restful.Request, resp *restful.Response) {
// operation := req.QueryParameter("operation")
// req, err := h.regenerateLoggingRequest(req)
// switch {
// case err != nil:
// api.HandleInternalError(resp, err)
// case req != nil:
// loggingv1alpha2.Get(req, loggingv1alpha2.LevelCluster, h.k8s, h.lo, resp)
// default:
// if operation == "export" {
// resp.Header().Set(restful.HEADER_ContentType, "text/plain")
// resp.Header().Set("Content-Disposition", "attachment")
// resp.Write(nil)
// } else {
// resp.WriteAsJson(v1alpha2.APIResponse{Logs: new(loggingclient.Logs)})
// }
// }
//}
// override namespace query conditions
//TODO(wansir): We need move this part to logging module
func (h *tenantHandler) regenerateLoggingRequest(req *restful.Request) (*restful.Request, error) {
username := req.HeaderParameter(constants.UserNameHeader)
// regenerate the request for log query
newUrl := net.FormatURL("http", "127.0.0.1", 80, "/kapis/logging.kubesphere.io/v1alpha2/cluster")
values := req.Request.URL.Query()
clusterRoleRules, err := h.am.GetClusterPolicyRules(username)
if err != nil {
klog.Errorln(err)
return nil, err
}
hasClusterLogAccess := iam.RulesMatchesRequired(clusterRoleRules, rbacv1.PolicyRule{Verbs: []string{"get"}, Resources: []string{"*"}, APIGroups: []string{"logging.kubesphere.io"}})
// if the user is not a cluster admin
if !hasClusterLogAccess {
queryNamespaces := strings.Split(req.QueryParameter("namespaces"), ",")
// then the user can only view logs of namespaces he belongs to
namespaces := make([]string, 0)
roles, err := h.am.GetRoles("", username)
if err != nil {
klog.Errorln(err)
return nil, err
}
for _, role := range roles {
if !sliceutil.HasString(namespaces, role.Namespace) && iam.RulesMatchesRequired(role.Rules, rbacv1.PolicyRule{Verbs: []string{"get"}, Resources: []string{"*"}, APIGroups: []string{"logging.kubesphere.io"}}) {
namespaces = append(namespaces, role.Namespace)
}
}
// if the user belongs to no namespace
// then no log visible
if len(namespaces) == 0 {
return nil, nil
} else if len(queryNamespaces) == 1 && queryNamespaces[0] == "" {
values.Set("namespaces", strings.Join(namespaces, ","))
} else {
inter := intersection(queryNamespaces, namespaces)
if len(inter) == 0 {
return nil, nil
}
values.Set("namespaces", strings.Join(inter, ","))
}
}
newUrl.RawQuery = values.Encode()
// forward the request to logging model
newHttpRequest, _ := http.NewRequest(http.MethodGet, newUrl.String(), nil)
return restful.NewRequest(newHttpRequest), nil
}
func intersection(s1, s2 []string) (inter []string) {
hash := make(map[string]bool)
for _, e := range s1 {
hash[e] = true
}
for _, e := range s2 {
// If elements present in the hashmap then append intersection list.
if hash[e] {
inter = append(inter, e)
}
}
//Remove dups from slice.
inter = removeDups(inter)
return
}
//Remove dups from slice.
func removeDups(elements []string) (nodups []string) {
encountered := make(map[string]bool)
for _, element := range elements {
if !encountered[element] {
nodups = append(nodups, element)
encountered[element] = true
}
}
return
}

View File

@@ -29,7 +29,6 @@ import (
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/informers"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
"kubesphere.io/kubesphere/pkg/server/errors"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/k8s"
@@ -59,24 +58,6 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
Param(ws.PathParameter("workspace", "workspace name")).
Returns(http.StatusOK, api.StatusOK, v1alpha1.Workspace{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/rules").
To(handler.ListWorkspaceRules).
Param(ws.PathParameter("workspace", "workspace name")).
Doc("List the rules of the specified workspace for the current user").
Returns(http.StatusOK, api.StatusOK, policy.SimpleRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/namespaces/{namespace}/rules").
To(handler.ListNamespaceRules).
Param(ws.PathParameter("namespace", "the name of the namespace")).
Doc("List the rules of the specified namespace for the current user").
Returns(http.StatusOK, api.StatusOK, policy.SimpleRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/devops/{devops}/rules").
To(handler.ListDevopsRules).
Param(ws.PathParameter("devops", "devops project ID")).
Doc("List the rules of the specified DevOps project for the current user").
Returns(http.StatusOK, api.StatusOK, policy.SimpleRule{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
ws.Route(ws.GET("/workspaces/{workspace}/namespaces").
To(handler.ListNamespaces).
Param(ws.PathParameter("workspace", "workspace name")).
@@ -151,32 +132,6 @@ func AddToContainer(c *restful.Container, k8sClient k8s.Client, factory informer
Doc("Delete the specified devops project from the workspace").
Returns(http.StatusOK, api.StatusOK, devopsv1alpha2.DevOpsProject{}).
Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}))
//ws.Route(ws.GET("/logs").
// To(handler.LogQuery).
// Doc("Query cluster-level logs in a multi-tenants environment").
// Param(ws.QueryParameter("operation", "Operation type. This can be one of four types: query (for querying logs), statistics (for retrieving statistical data), histogram (for displaying log count by time interval) and export (for exporting logs). Defaults to query.").DefaultValue("query").DataType("string").Required(false)).
// Param(ws.QueryParameter("workspaces", "A comma-separated list of workspaces. This field restricts the query to specified workspaces. For example, the following filter matches the workspace my-ws and demo-ws: `my-ws,demo-ws`").DataType("string").Required(false)).
// Param(ws.QueryParameter("workspace_query", "A comma-separated list of keywords. Differing from **workspaces**, this field performs fuzzy matching on workspaces. For example, the following value limits the query to workspaces whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
// Param(ws.QueryParameter("namespaces", "A comma-separated list of namespaces. This field restricts the query to specified namespaces. For example, the following filter matches the namespace my-ns and demo-ns: `my-ns,demo-ns`").DataType("string").Required(false)).
// Param(ws.QueryParameter("namespace_query", "A comma-separated list of keywords. Differing from **namespaces**, this field performs fuzzy matching on namespaces. For example, the following value limits the query to namespaces whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
// Param(ws.QueryParameter("workloads", "A comma-separated list of workloads. This field restricts the query to specified workloads. For example, the following filter matches the workload my-wl and demo-wl: `my-wl,demo-wl`").DataType("string").Required(false)).
// Param(ws.QueryParameter("workload_query", "A comma-separated list of keywords. Differing from **workloads**, this field performs fuzzy matching on workloads. For example, the following value limits the query to workloads whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
// Param(ws.QueryParameter("pods", "A comma-separated list of pods. This field restricts the query to specified pods. For example, the following filter matches the pod my-po and demo-po: `my-po,demo-po`").DataType("string").Required(false)).
// Param(ws.QueryParameter("pod_query", "A comma-separated list of keywords. Differing from **pods**, this field performs fuzzy matching on pods. For example, the following value limits the query to pods whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
// Param(ws.QueryParameter("containers", "A comma-separated list of containers. This field restricts the query to specified containers. For example, the following filter matches the container my-cont and demo-cont: `my-cont,demo-cont`").DataType("string").Required(false)).
// Param(ws.QueryParameter("container_query", "A comma-separated list of keywords. Differing from **containers**, this field performs fuzzy matching on containers. For example, the following value limits the query to containers whose name contains the word my(My,MY,...) *OR* demo(Demo,DemO,...): `my,demo`.").DataType("string").Required(false)).
// Param(ws.QueryParameter("log_query", "A comma-separated list of keywords. The query returns logs which contain at least one keyword. Case-insensitive matching. For example, if the field is set to `err,INFO`, the query returns any log containing err(ERR,Err,...) *OR* INFO(info,InFo,...).").DataType("string").Required(false)).
// Param(ws.QueryParameter("interval", "Time interval. It requires **operation** is set to histogram. The format is [0-9]+[smhdwMqy]. Defaults to 15m (i.e. 15 min).").DefaultValue("15m").DataType("string").Required(false)).
// Param(ws.QueryParameter("start_time", "Start time of query. Default to 0. The format is a string representing milliseconds since the epoch, eg. 1559664000000.").DataType("string").Required(false)).
// Param(ws.QueryParameter("end_time", "End time of query. Default to now. The format is a string representing milliseconds since the epoch, eg. 1559664000000.").DataType("string").Required(false)).
// Param(ws.QueryParameter("sort", "Sort order. One of acs, desc. This field sorts logs by timestamp.").DataType("string").DefaultValue("desc").Required(false)).
// Param(ws.QueryParameter("from", "The offset from the result set. This field returns query results from the specified offset. It requires **operation** is set to query. Defaults to 0 (i.e. from the beginning of the result set).").DataType("integer").DefaultValue("0").Required(false)).
// Param(ws.QueryParameter("size", "Size of result to return. It requires **operation** is set to query. Defaults to 10 (i.e. 10 log records).").DataType("integer").DefaultValue("10").Required(false)).
// Metadata(restfulspec.KeyOpenAPITags, []string{constants.TenantResourcesTag}).
// Writes(v1alpha2.Response{}).
// Returns(http.StatusOK, api.StatusOK, v1alpha2.Response{})).
// Consumes(restful.MIME_JSON, restful.MIME_XML).
// Produces(restful.MIME_JSON, "text/plain")
c.Add(ws)
return nil

View File

@@ -1,606 +0,0 @@
/*
Copyright 2019 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 iam
import (
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/clusterrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/role"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/utils/k8sutil"
)
const (
ClusterRoleKind = "ClusterRole"
NamespaceAdminRoleBindName = "admin"
NamespaceViewerRoleBindName = "viewer"
)
type AccessManagementInterface interface {
GetClusterRole(username string) (*rbacv1.ClusterRole, error)
UnBindAllRoles(username string) error
ListRoleBindings(namespace string, role string) ([]*rbacv1.RoleBinding, error)
CreateClusterRoleBinding(username string, clusterRole string) error
ListRoles(namespace string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
ListClusterRoles(conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error)
GetClusterRoleSimpleRules(clusterRole string) ([]policy.SimpleRule, error)
GetRoleSimpleRules(namespace string, role string) ([]policy.SimpleRule, error)
GetRoles(namespace, username string) ([]*rbacv1.Role, error)
GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error)
GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error)
GetWorkspaceRoleSimpleRules(workspace, roleName string) []policy.SimpleRule
GetWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error)
GetWorkspaceRoleMap(username string) (map[string]string, error)
}
type amOperator struct {
informers informers.SharedInformerFactory
resources resource.ResourceGetter
kubeClient kubernetes.Interface
}
func (am *amOperator) ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error) {
panic("implement me")
}
func (am *amOperator) GetRoles(namespace, username string) ([]*rbacv1.Role, error) {
panic("implement me")
}
func (am *amOperator) GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error) {
panic("implement me")
}
func (am *amOperator) GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error) {
panic("implement me")
}
func (am *amOperator) GetWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) {
panic("implement me")
}
func (am *amOperator) UnBindAllRoles(username string) error {
panic("implement me")
}
func NewAMOperator(kubeClient kubernetes.Interface, informers informers.SharedInformerFactory) *amOperator {
resourceGetter := resource.ResourceGetter{}
resourceGetter.Add(v1alpha2.Role, role.NewRoleSearcher(informers))
resourceGetter.Add(v1alpha2.ClusterRoles, clusterrole.NewClusterRoleSearcher(informers))
return &amOperator{
informers: informers,
resources: resourceGetter,
kubeClient: kubeClient,
}
}
func (am *amOperator) GetDevopsRoleSimpleRules(role string) []policy.SimpleRule {
var rules []policy.SimpleRule
switch role {
case "developer":
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"view", "trigger"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "devops", Actions: []string{"view"}},
}
break
case "owner":
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"create", "edit", "view", "delete", "trigger"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"create", "edit", "view", "delete"}},
{Name: "credentials", Actions: []string{"create", "edit", "view", "delete"}},
{Name: "devops", Actions: []string{"edit", "view", "delete"}},
}
break
case "maintainer":
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"create", "edit", "view", "delete", "trigger"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "credentials", Actions: []string{"create", "edit", "view", "delete"}},
{Name: "devops", Actions: []string{"view"}},
}
break
case "reporter":
fallthrough
default:
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"view"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "devops", Actions: []string{"view"}},
}
break
}
return rules
}
// Get user roles in namespace
func (am *amOperator) GetUserRoles(namespace, username string) ([]*rbacv1.Role, error) {
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
roleBindingLister := am.informers.Rbac().V1().RoleBindings().Lister()
roleLister := am.informers.Rbac().V1().Roles().Lister()
roleBindings, err := roleBindingLister.RoleBindings(namespace).List(labels.Everything())
if err != nil {
klog.Errorln(err)
return nil, err
}
roles := make([]*rbacv1.Role, 0)
for _, roleBinding := range roleBindings {
if ContainsUser(roleBinding.Subjects, username) {
if roleBinding.RoleRef.Kind == ClusterRoleKind {
clusterRole, err := clusterRoleLister.Get(roleBinding.RoleRef.Name)
if err != nil {
if apierrors.IsNotFound(err) {
klog.Warningf("cluster role %s not found but bind user %s in namespace %s", roleBinding.RoleRef.Name, username, namespace)
continue
} else {
klog.Errorln(err)
return nil, err
}
}
role := rbacv1.Role{}
role.TypeMeta = clusterRole.TypeMeta
role.ObjectMeta = clusterRole.ObjectMeta
role.Rules = clusterRole.Rules
role.Namespace = roleBinding.Namespace
roles = append(roles, &role)
} else {
role, err := roleLister.Roles(roleBinding.Namespace).Get(roleBinding.RoleRef.Name)
if err != nil {
if apierrors.IsNotFound(err) {
klog.Warningf("namespace %s role %s not found, but bind user %s", namespace, roleBinding.RoleRef.Name, username)
continue
} else {
klog.Errorln(err)
return nil, err
}
}
roles = append(roles, role)
}
}
}
return roles, nil
}
func (am *amOperator) GetUserClusterRoles(username string) (*rbacv1.ClusterRole, []*rbacv1.ClusterRole, error) {
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
clusterRoleBindingLister := am.informers.Rbac().V1().ClusterRoleBindings().Lister()
clusterRoleBindings, err := clusterRoleBindingLister.List(labels.Everything())
if err != nil {
klog.Errorln(err)
return nil, nil, err
}
clusterRoles := make([]*rbacv1.ClusterRole, 0)
userFacingClusterRole := &rbacv1.ClusterRole{}
for _, clusterRoleBinding := range clusterRoleBindings {
if ContainsUser(clusterRoleBinding.Subjects, username) {
clusterRole, err := clusterRoleLister.Get(clusterRoleBinding.RoleRef.Name)
if err != nil {
if apierrors.IsNotFound(err) {
klog.Warningf("cluster role %s not found but bind user %s", clusterRoleBinding.RoleRef.Name, username)
continue
} else {
klog.Errorln(err)
return nil, nil, err
}
}
if clusterRoleBinding.Name == username {
userFacingClusterRole = clusterRole
}
clusterRoles = append(clusterRoles, clusterRole)
}
}
return userFacingClusterRole, clusterRoles, nil
}
func (am *amOperator) GetClusterRole(username string) (*rbacv1.ClusterRole, error) {
userFacingClusterRole, _, err := am.GetUserClusterRoles(username)
if err != nil {
return nil, err
}
return userFacingClusterRole, nil
}
func (am *amOperator) GetUserClusterRules(username string) ([]rbacv1.PolicyRule, error) {
_, clusterRoles, err := am.GetUserClusterRoles(username)
if err != nil {
return nil, err
}
rules := make([]rbacv1.PolicyRule, 0)
for _, clusterRole := range clusterRoles {
rules = append(rules, clusterRole.Rules...)
}
return rules, nil
}
func (am *amOperator) GetUserRules(namespace, username string) ([]rbacv1.PolicyRule, error) {
roles, err := am.GetUserRoles(namespace, username)
if err != nil {
return nil, err
}
rules := make([]rbacv1.PolicyRule, 0)
for _, role := range roles {
rules = append(rules, role.Rules...)
}
return rules, nil
}
func (am *amOperator) GetWorkspaceRoleBindings(workspace string) ([]*rbacv1.ClusterRoleBinding, error) {
clusterRoleBindings, err := am.informers.Rbac().V1().ClusterRoleBindings().Lister().List(labels.Everything())
if err != nil {
klog.Errorln("get cluster role bindings", err)
return nil, err
}
result := make([]*rbacv1.ClusterRoleBinding, 0)
for _, roleBinding := range clusterRoleBindings {
if k8sutil.IsControlledBy(roleBinding.OwnerReferences, "Workspace", workspace) {
result = append(result, roleBinding)
}
}
return result, nil
}
//func (am *amOperator) GetWorkspaceRole(workspace, role string) (*rbacv1.ClusterRole, error) {
// if !sliceutil.HasString(constants.WorkSpaceRoles, role) {
// return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role)
// }
// role = fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-"))
// return am.informers.Rbac().V1().ClusterRoles().Lister().Get(role)
//}
func (am *amOperator) GetWorkspaceRoleMap(username string) (map[string]string, error) {
clusterRoleBindings, err := am.informers.Rbac().V1().ClusterRoleBindings().Lister().List(labels.Everything())
if err != nil {
klog.Errorln("get cluster role bindings", err)
return nil, err
}
result := make(map[string]string, 0)
for _, roleBinding := range clusterRoleBindings {
if workspace := k8sutil.GetControlledWorkspace(roleBinding.OwnerReferences); workspace != "" &&
ContainsUser(roleBinding.Subjects, username) {
result[workspace] = roleBinding.RoleRef.Name
}
}
return result, nil
}
func (am *amOperator) GetUserWorkspaceRole(workspace, username string) (*rbacv1.ClusterRole, error) {
workspaceRoleMap, err := am.GetWorkspaceRoleMap(username)
if err != nil {
return nil, err
}
if workspaceRole := workspaceRoleMap[workspace]; workspaceRole != "" {
return am.informers.Rbac().V1().ClusterRoles().Lister().Get(workspaceRole)
}
return nil, apierrors.NewNotFound(schema.GroupResource{Resource: "workspace user"}, username)
}
func (am *amOperator) GetRoleBindings(namespace string, roleName string) ([]*rbacv1.RoleBinding, error) {
roleBindingLister := am.informers.Rbac().V1().RoleBindings().Lister()
roleBindings, err := roleBindingLister.RoleBindings(namespace).List(labels.Everything())
if err != nil {
klog.Errorln(err)
return nil, err
}
items := make([]*rbacv1.RoleBinding, 0)
for _, roleBinding := range roleBindings {
if roleName == "" {
items = append(items, roleBinding)
} else if roleBinding.RoleRef.Name == roleName {
items = append(items, roleBinding)
}
}
return items, nil
}
func (am *amOperator) GetClusterRoleBindings(clusterRoleName string) ([]*rbacv1.ClusterRoleBinding, error) {
clusterRoleBindingLister := am.informers.Rbac().V1().ClusterRoleBindings().Lister()
roleBindings, err := clusterRoleBindingLister.List(labels.Everything())
if err != nil {
klog.Errorln(err)
return nil, err
}
items := make([]*rbacv1.ClusterRoleBinding, 0)
for _, roleBinding := range roleBindings {
if roleBinding.RoleRef.Name == clusterRoleName {
items = append(items, roleBinding)
}
}
return items, nil
}
func (am *amOperator) ListRoles(namespace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
return am.resources.ListResources(namespace, v1alpha2.Roles, conditions, orderBy, reverse, limit, offset)
}
func (am *amOperator) ListRoleBindings(namespace string, role string) ([]*rbacv1.RoleBinding, error) {
rbs, err := am.informers.Rbac().V1().RoleBindings().Lister().RoleBindings(namespace).List(labels.Everything())
if err != nil {
return nil, err
}
result := make([]*rbacv1.RoleBinding, 0)
for _, rb := range rbs {
if rb.RoleRef.Name == role {
result = append(result, rb.DeepCopy())
}
}
return result, nil
}
func (am *amOperator) ListWorkspaceRoles(workspace string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
conditions.Match[v1alpha2.OwnerName] = workspace
conditions.Match[v1alpha2.OwnerKind] = "Workspace"
result, err := am.resources.ListResources("", v1alpha2.ClusterRoles, conditions, orderBy, reverse, limit, offset)
if err != nil {
return nil, err
}
for i, item := range result.Items {
if role, ok := item.(*rbacv1.ClusterRole); ok {
role = role.DeepCopy()
role.Name = role.Annotations[constants.DisplayNameAnnotationKey]
result.Items[i] = role
}
}
return result, nil
}
func (am *amOperator) ListClusterRoles(conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
return am.resources.ListResources("", v1alpha2.ClusterRoles, conditions, orderBy, reverse, limit, offset)
}
func (am *amOperator) GetWorkspaceRoleSimpleRules(workspace, roleName string) []policy.SimpleRule {
workspaceRules := make([]policy.SimpleRule, 0)
switch roleName {
case constants.WorkspaceAdmin:
workspaceRules = []policy.SimpleRule{
{Name: "workspaces", Actions: []string{"edit", "delete", "view"}},
{Name: "members", Actions: []string{"edit", "delete", "create", "view"}},
{Name: "devops", Actions: []string{"edit", "delete", "create", "view"}},
{Name: "projects", Actions: []string{"edit", "delete", "create", "view"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "apps", Actions: []string{"view", "create", "manage"}},
{Name: "repos", Actions: []string{"view", "manage"}},
}
case constants.WorkspaceRegular:
workspaceRules = []policy.SimpleRule{
{Name: "members", Actions: []string{"view"}},
{Name: "devops", Actions: []string{"view", "create"}},
{Name: "projects", Actions: []string{"view", "create"}},
{Name: "apps", Actions: []string{"view", "create"}},
{Name: "repos", Actions: []string{"view"}},
}
case constants.WorkspaceViewer:
workspaceRules = []policy.SimpleRule{
{Name: "workspaces", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "devops", Actions: []string{"view"}},
{Name: "projects", Actions: []string{"view"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "apps", Actions: []string{"view"}},
{Name: "repos", Actions: []string{"view"}},
}
case constants.WorkspacesManager:
workspaceRules = []policy.SimpleRule{
{Name: "workspaces", Actions: []string{"edit", "delete", "view"}},
{Name: "members", Actions: []string{"edit", "delete", "create", "view"}},
{Name: "roles", Actions: []string{"view"}},
}
}
return workspaceRules
}
// Convert cluster role to rules
func (am *amOperator) GetClusterRoleSimpleRules(clusterRoleName string) ([]policy.SimpleRule, error) {
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
clusterRole, err := clusterRoleLister.Get(clusterRoleName)
if err != nil {
klog.Errorln(err)
return nil, err
}
return getClusterSimpleRule(clusterRole.Rules), nil
}
func (am *amOperator) GetUserClusterSimpleRules(username string) ([]policy.SimpleRule, error) {
clusterRules, err := am.GetUserClusterRules(username)
if err != nil {
return nil, err
}
return getClusterSimpleRule(clusterRules), nil
}
// Convert roles to rules
func (am *amOperator) GetRoleSimpleRules(namespace string, roleName string) ([]policy.SimpleRule, error) {
roleLister := am.informers.Rbac().V1().Roles().Lister()
role, err := roleLister.Roles(namespace).Get(roleName)
if err != nil {
klog.Errorln(err)
return nil, err
}
return ConvertToSimpleRule(role.Rules), nil
}
func getClusterSimpleRule(policyRules []rbacv1.PolicyRule) []policy.SimpleRule {
rules := make([]policy.SimpleRule, 0)
for i := 0; i < len(policy.ClusterRoleRuleMapping); i++ {
validActions := make([]string, 0)
for j := 0; j < (len(policy.ClusterRoleRuleMapping[i].Actions)); j++ {
if rulesMatchesAction(policyRules, policy.ClusterRoleRuleMapping[i].Actions[j]) {
validActions = append(validActions, policy.ClusterRoleRuleMapping[i].Actions[j].Name)
}
}
if len(validActions) > 0 {
rules = append(rules, policy.SimpleRule{Name: policy.ClusterRoleRuleMapping[i].Name, Actions: validActions})
}
}
return rules
}
func ConvertToSimpleRule(policyRules []rbacv1.PolicyRule) []policy.SimpleRule {
simpleRules := make([]policy.SimpleRule, 0)
for i := 0; i < len(policy.RoleRuleMapping); i++ {
rule := policy.SimpleRule{Name: policy.RoleRuleMapping[i].Name}
rule.Actions = make([]string, 0)
for j := 0; j < len(policy.RoleRuleMapping[i].Actions); j++ {
if rulesMatchesAction(policyRules, policy.RoleRuleMapping[i].Actions[j]) {
rule.Actions = append(rule.Actions, policy.RoleRuleMapping[i].Actions[j].Name)
}
}
if len(rule.Actions) > 0 {
simpleRules = append(simpleRules, rule)
}
}
return simpleRules
}
func (am *amOperator) CreateClusterRoleBinding(username string, clusterRoleName string) error {
clusterRoleLister := am.informers.Rbac().V1().ClusterRoles().Lister()
_, err := clusterRoleLister.Get(clusterRoleName)
if err != nil {
klog.Errorln(err)
return err
}
// TODO move to user controller
if clusterRoleName == constants.ClusterAdmin {
// create kubectl pod if cluster role is cluster-admin
//if err := kubectl.CreateKubectlDeploy(username); err != nil {
// klog.Error("create user terminal pod failed", username, err)
//}
} else {
// delete kubectl pod if cluster role is not cluster-admin, whether it exists or not
//if err := kubectl.DelKubectlDeploy(username); err != nil {
// klog.Error("delete user terminal pod failed", username, err)
//}
}
clusterRoleBinding := &rbacv1.ClusterRoleBinding{}
clusterRoleBinding.Name = username
clusterRoleBinding.RoleRef = rbacv1.RoleRef{Name: clusterRoleName, Kind: ClusterRoleKind}
clusterRoleBinding.Subjects = []rbacv1.Subject{{Kind: rbacv1.UserKind, Name: username}}
clusterRoleBindingLister := am.informers.Rbac().V1().ClusterRoleBindings().Lister()
found, err := clusterRoleBindingLister.Get(username)
if apierrors.IsNotFound(err) {
_, err = am.kubeClient.RbacV1().ClusterRoleBindings().Create(clusterRoleBinding)
if err != nil {
klog.Errorln("create cluster role binding", err)
return err
}
return nil
} else if err != nil {
return err
}
// cluster role changed
if found.RoleRef.Name != clusterRoleName {
deletePolicy := metav1.DeletePropagationBackground
gracePeriodSeconds := int64(0)
deleteOption := &metav1.DeleteOptions{PropagationPolicy: &deletePolicy, GracePeriodSeconds: &gracePeriodSeconds}
err = am.kubeClient.RbacV1().ClusterRoleBindings().Delete(found.Name, deleteOption)
if err != nil {
klog.Errorln(err)
return err
}
_, err = am.kubeClient.RbacV1().ClusterRoleBindings().Create(clusterRoleBinding)
if err != nil {
klog.Errorln(err)
return err
}
return nil
}
if !ContainsUser(found.Subjects, username) {
found.Subjects = clusterRoleBinding.Subjects
_, err = am.kubeClient.RbacV1().ClusterRoleBindings().Update(found)
if err != nil {
klog.Errorln("update cluster role binding", err)
return err
}
}
return nil
}

100
pkg/models/iam/am/am.go Normal file
View File

@@ -0,0 +1,100 @@
/*
*
* 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 am
import (
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/clusterrole"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/resource"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2/role"
)
const (
ClusterRoleKind = "ClusterRole"
NamespaceAdminRoleBindName = "admin"
NamespaceViewerRoleBindName = "viewer"
)
type AccessManagementInterface interface {
GetPlatformRole(username string) (Role, error)
GetClusterRole(cluster, username string) (Role, error)
GetWorkspaceRole(workspace, username string) (Role, error)
GetNamespaceRole(namespace, username string) (Role, error)
GetDevOpsRole(project, username string) (Role, error)
}
type Role interface {
GetName() string
GetRego() string
}
type amOperator struct {
informers informers.SharedInformerFactory
resources resource.ResourceGetter
kubeClient kubernetes.Interface
}
func (am *amOperator) ListClusterRoleBindings(clusterRole string) ([]*rbacv1.ClusterRoleBinding, error) {
panic("implement me")
}
func (am *amOperator) GetRoles(namespace, username string) ([]*rbacv1.Role, error) {
panic("implement me")
}
func (am *amOperator) GetClusterPolicyRules(username string) ([]rbacv1.PolicyRule, error) {
panic("implement me")
}
func (am *amOperator) GetPolicyRules(namespace, username string) ([]rbacv1.PolicyRule, error) {
panic("implement me")
}
func (am *amOperator) GetWorkspaceRole(workspace, username string) (Role, error) {
panic("implement me")
}
func NewAMOperator(kubeClient kubernetes.Interface, informers informers.SharedInformerFactory) AccessManagementInterface {
resourceGetter := resource.ResourceGetter{}
resourceGetter.Add(v1alpha2.Role, role.NewRoleSearcher(informers))
resourceGetter.Add(v1alpha2.ClusterRoles, clusterrole.NewClusterRoleSearcher(informers))
return &amOperator{
informers: informers,
resources: resourceGetter,
kubeClient: kubeClient,
}
}
func (am *amOperator) GetPlatformRole(username string) (Role, error) {
panic("implement me")
}
func (am *amOperator) GetClusterRole(cluster, username string) (Role, error) {
panic("implement me")
}
func (am *amOperator) GetNamespaceRole(namespace, username string) (Role, error) {
panic("implement me")
}
func (am *amOperator) GetDevOpsRole(namespace, username string) (Role, error) {
panic("implement me")
}

View File

@@ -15,7 +15,7 @@
* limitations under the License.
* /
*/
package iam
package im
import (
"fmt"

View File

@@ -16,4 +16,4 @@
* /
*/
package iam
package im

File diff suppressed because it is too large Load Diff

View File

@@ -1,213 +0,0 @@
/*
*
* 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 iam
import (
rbacv1 "k8s.io/api/rbac/v1"
"kubesphere.io/kubesphere/pkg/api/iam"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
"strings"
)
func RulesMatchesRequired(rules []rbacv1.PolicyRule, required rbacv1.PolicyRule) bool {
for _, rule := range rules {
if ruleMatchesRequired(rule, required) {
return true
}
}
return false
}
func rulesMatchesAction(rules []rbacv1.PolicyRule, action policy.Action) bool {
for _, required := range action.Rules {
if !RulesMatchesRequired(rules, required) {
return false
}
}
return true
}
func ruleMatchesRequired(rule rbacv1.PolicyRule, required rbacv1.PolicyRule) bool {
if len(required.NonResourceURLs) == 0 {
for _, apiGroup := range required.APIGroups {
for _, resource := range required.Resources {
resources := strings.Split(resource, "/")
resource = resources[0]
var subsource string
if len(resources) > 1 {
subsource = resources[1]
}
if len(required.ResourceNames) == 0 {
for _, verb := range required.Verbs {
if !ruleMatchesRequest(rule, apiGroup, "", resource, subsource, "", verb) {
return false
}
}
} else {
for _, resourceName := range required.ResourceNames {
for _, verb := range required.Verbs {
if !ruleMatchesRequest(rule, apiGroup, "", resource, subsource, resourceName, verb) {
return false
}
}
}
}
}
}
} else {
for _, apiGroup := range required.APIGroups {
for _, nonResourceURL := range required.NonResourceURLs {
for _, verb := range required.Verbs {
if !ruleMatchesRequest(rule, apiGroup, nonResourceURL, "", "", "", verb) {
return false
}
}
}
}
}
return true
}
func ruleMatchesResources(rule rbacv1.PolicyRule, apiGroup string, resource string, subresource string, resourceName string) bool {
if resource == "" {
return false
}
if !hasString(rule.APIGroups, apiGroup) && !hasString(rule.APIGroups, rbacv1.ResourceAll) {
return false
}
if len(rule.ResourceNames) > 0 && !hasString(rule.ResourceNames, resourceName) {
return false
}
combinedResource := resource
if subresource != "" {
combinedResource = combinedResource + "/" + subresource
}
for _, res := range rule.Resources {
// match "*"
if res == rbacv1.ResourceAll || res == combinedResource {
return true
}
// match "*/subresource"
if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") {
return true
}
// match "resource/*"
if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") {
return true
}
}
return false
}
func ruleMatchesRequest(rule rbacv1.PolicyRule, apiGroup string, nonResourceURL string, resource string, subresource string, resourceName string, verb string) bool {
if !hasString(rule.Verbs, verb) && !hasString(rule.Verbs, rbacv1.VerbAll) {
return false
}
if nonResourceURL == "" {
return ruleMatchesResources(rule, apiGroup, resource, subresource, resourceName)
} else {
return ruleMatchesNonResource(rule, nonResourceURL)
}
}
func ruleMatchesNonResource(rule rbacv1.PolicyRule, nonResourceURL string) bool {
if nonResourceURL == "" {
return false
}
for _, spec := range rule.NonResourceURLs {
if pathMatches(nonResourceURL, spec) {
return true
}
}
return false
}
func pathMatches(path, spec string) bool {
// Allow wildcard match
if spec == "*" {
return true
}
// Allow exact match
if spec == path {
return true
}
// Allow a trailing * subpath match
if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
return true
}
return false
}
func hasString(slice []string, value string) bool {
for _, s := range slice {
if s == value {
return true
}
}
return false
}
func ContainsUser(subjects interface{}, username string) bool {
switch subjects.(type) {
case []*rbacv1.Subject:
for _, subject := range subjects.([]*rbacv1.Subject) {
if subject.Kind == rbacv1.UserKind && subject.Name == username {
return true
}
}
case []rbacv1.Subject:
for _, subject := range subjects.([]rbacv1.Subject) {
if subject.Kind == rbacv1.UserKind && subject.Name == username {
return true
}
}
case []iam.User:
for _, u := range subjects.([]iam.User) {
if u.Username == username {
return true
}
}
case []*iam.User:
for _, u := range subjects.([]*iam.User) {
if u.Username == username {
return true
}
}
}
return false
}

View File

@@ -26,7 +26,6 @@ import (
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
"kubesphere.io/kubesphere/pkg/server/params"
dsClient "kubesphere.io/kubesphere/pkg/simple/client/devops"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
@@ -38,7 +37,6 @@ type DevOpsProjectOperator interface {
CreateDevOpsProject(username string, workspace string, req *v1alpha2.DevOpsProject) (*v1alpha2.DevOpsProject, error)
GetDevOpsProjectsCount(username string) (uint32, error)
DeleteDevOpsProject(projectId, username string) error
GetUserDevOpsSimpleRules(username, projectId string) ([]policy.SimpleRule, error)
}
type devopsProjectOperator struct {
@@ -208,16 +206,6 @@ func (o *devopsProjectOperator) CreateDevOpsProject(username string, workspace s
return project, nil
}
func (o *devopsProjectOperator) GetUserDevOpsSimpleRules(username, projectId string) ([]policy.SimpleRule, error) {
role, err := o.getProjectUserRole(username, projectId)
if err != nil {
klog.Errorf("%+v", err)
return nil, restful.NewError(http.StatusForbidden, err.Error())
}
return GetDevopsRoleSimpleRules(role), nil
}
func (o *devopsProjectOperator) getProjectUserRole(username, projectId string) (string, error) {
if username == devops.KS_ADMIN {
return dsClient.ProjectOwner, nil
@@ -235,47 +223,3 @@ func (o *devopsProjectOperator) getProjectUserRole(username, projectId string) (
return membership.Role, nil
}
func GetDevopsRoleSimpleRules(role string) []policy.SimpleRule {
var rules []policy.SimpleRule
switch role {
case "developer":
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"view", "trigger"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "devops", Actions: []string{"view"}},
}
break
case "owner":
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"create", "edit", "view", "delete", "trigger"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"create", "edit", "view", "delete"}},
{Name: "credentials", Actions: []string{"create", "edit", "view", "delete"}},
{Name: "devops", Actions: []string{"edit", "view", "delete"}},
}
break
case "maintainer":
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"create", "edit", "view", "delete", "trigger"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "credentials", Actions: []string{"create", "edit", "view", "delete"}},
{Name: "devops", Actions: []string{"view"}},
}
break
case "reporter":
fallthrough
default:
rules = []policy.SimpleRule{
{Name: "pipelines", Actions: []string{"view"}},
{Name: "roles", Actions: []string{"view"}},
{Name: "members", Actions: []string{"view"}},
{Name: "devops", Actions: []string{"view"}},
}
break
}
return rules
}

View File

@@ -19,17 +19,13 @@ package tenant
import (
"k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/labels"
k8sinformers "k8s.io/client-go/informers"
kubernetes "k8s.io/client-go/kubernetes"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models/iam"
am2 "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"sort"
"strings"
)
@@ -41,7 +37,7 @@ type NamespaceInterface interface {
type namespaceSearcher struct {
k8s kubernetes.Interface
informers k8sinformers.SharedInformerFactory
am iam.AccessManagementInterface
am am2.AccessManagementInterface
}
func (s *namespaceSearcher) CreateNamespace(workspace string, namespace *v1.Namespace, username string) (*v1.Namespace, error) {
@@ -57,7 +53,7 @@ func (s *namespaceSearcher) CreateNamespace(workspace string, namespace *v1.Name
return s.k8s.CoreV1().Namespaces().Create(namespace)
}
func newNamespaceOperator(k8s kubernetes.Interface, informers k8sinformers.SharedInformerFactory, am iam.AccessManagementInterface) NamespaceInterface {
func newNamespaceOperator(k8s kubernetes.Interface, informers k8sinformers.SharedInformerFactory, am am2.AccessManagementInterface) NamespaceInterface {
return &namespaceSearcher{k8s: k8s, informers: informers, am: am}
}
@@ -111,76 +107,9 @@ func (s *namespaceSearcher) compare(a, b *v1.Namespace, orderBy string) bool {
}
func (s *namespaceSearcher) GetNamespaces(username string) ([]*v1.Namespace, error) {
roles, err := s.am.GetRoles("", username)
if err != nil {
return nil, err
}
namespaces := make([]*v1.Namespace, 0)
namespaceLister := s.informers.Core().V1().Namespaces().Lister()
for _, role := range roles {
namespace, err := namespaceLister.Get(role.Namespace)
if err != nil {
klog.Errorf("get namespace failed: %+v", err)
return nil, err
}
if !containsNamespace(namespaces, namespace) {
namespaces = append(namespaces, namespace)
}
}
return namespaces, nil
}
func containsNamespace(namespaces []*v1.Namespace, namespace *v1.Namespace) bool {
for _, item := range namespaces {
if item.Name == namespace.Name {
return true
}
}
return false
panic("implement me")
}
func (s *namespaceSearcher) Search(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1.Namespace, error) {
rules, err := s.am.GetClusterPolicyRules(username)
if err != nil {
return nil, err
}
namespaces := make([]*v1.Namespace, 0)
if iam.RulesMatchesRequired(rules, rbacv1.PolicyRule{Verbs: []string{"list"}, APIGroups: []string{"tenant.kubesphere.io"}, Resources: []string{"namespaces"}}) {
namespaces, err = s.informers.Core().V1().Namespaces().Lister().List(labels.Everything())
} else {
namespaces, err = s.GetNamespaces(username)
}
if err != nil {
return nil, err
}
result := make([]*v1.Namespace, 0)
for _, namespace := range namespaces {
if s.match(conditions.Match, namespace) && s.fuzzy(conditions.Fuzzy, namespace) {
result = append(result, namespace)
}
}
// order & reverse
sort.Slice(result, func(i, j int) bool {
if reverse {
i, j = j, i
}
return s.compare(result[i], result[j], orderBy)
})
return result, nil
}
func CreateNamespace() {
panic("implement me")
}

View File

@@ -19,20 +19,14 @@ package tenant
import (
"k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
k8sinformers "k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
ksinformers "kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/models"
"kubesphere.io/kubesphere/pkg/models/iam"
"kubesphere.io/kubesphere/pkg/models/iam/policy"
am2 "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"strconv"
)
type Interface interface {
@@ -42,17 +36,14 @@ type Interface interface {
ListWorkspaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
ListNamespaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error)
ListDevopsProjects(username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error)
GetWorkspaceSimpleRules(workspace, username string) ([]policy.SimpleRule, error)
GetNamespaceSimpleRules(namespace, username string) ([]policy.SimpleRule, error)
CountDevOpsProjects(username string) (uint32, error)
DeleteDevOpsProject(username, projectId string) error
GetUserDevopsSimpleRules(username string, devops string) (interface{}, error)
}
type tenantOperator struct {
workspaces WorkspaceInterface
namespaces NamespaceInterface
am iam.AccessManagementInterface
am am2.AccessManagementInterface
devops DevOpsProjectOperator
}
@@ -65,7 +56,7 @@ func (t *tenantOperator) DeleteDevOpsProject(username, projectId string) error {
}
func (t *tenantOperator) GetUserDevopsSimpleRules(username string, projectId string) (interface{}, error) {
return t.devops.GetUserDevOpsSimpleRules(username, projectId)
panic("implement me")
}
func (t *tenantOperator) ListDevopsProjects(username string, conditions *params.Conditions, orderBy string, reverse bool, limit int, offset int) (*models.PageableResponse, error) {
@@ -77,7 +68,7 @@ func (t *tenantOperator) DeleteNamespace(workspace, namespace string) error {
}
func New(client kubernetes.Interface, informers k8sinformers.SharedInformerFactory, ksinformers ksinformers.SharedInformerFactory, db *mysql.Database) Interface {
amOperator := iam.NewAMOperator(client, informers)
amOperator := am2.NewAMOperator(client, informers)
return &tenantOperator{
workspaces: newWorkspaceOperator(client, informers, ksinformers, amOperator, db),
namespaces: newNamespaceOperator(client, informers, amOperator),
@@ -96,105 +87,11 @@ func (t *tenantOperator) DescribeWorkspace(username, workspaceName string) (*v1a
return nil, err
}
if username != "" {
workspace = t.appendAnnotations(username, workspace)
}
return workspace, nil
}
func (t *tenantOperator) ListWorkspaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {
workspaces, err := t.workspaces.SearchWorkspace(username, conditions, orderBy, reverse)
if err != nil {
return nil, err
}
// limit offset
result := make([]interface{}, 0)
for i, workspace := range workspaces {
if len(result) < limit && i >= offset {
workspace := t.appendAnnotations(username, workspace)
result = append(result, workspace)
}
}
return &models.PageableResponse{Items: result, TotalCount: len(workspaces)}, nil
}
func (t *tenantOperator) GetWorkspaceSimpleRules(workspace, username string) ([]policy.SimpleRule, error) {
clusterRules, err := t.am.GetClusterPolicyRules(username)
if err != nil {
return nil, err
}
// cluster-admin
if iam.RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"*"},
}) {
return t.am.GetWorkspaceRoleSimpleRules(workspace, constants.WorkspaceAdmin), nil
}
workspaceRole, err := t.am.GetWorkspaceRole(workspace, username)
// workspaces-manager
if iam.RulesMatchesRequired(clusterRules, rbacv1.PolicyRule{
Verbs: []string{"*"},
APIGroups: []string{"*"},
Resources: []string{"workspaces", "workspaces/*"},
}) {
return t.am.GetWorkspaceRoleSimpleRules(workspace, constants.WorkspacesManager), nil
}
if err != nil {
if apierrors.IsNotFound(err) {
return []policy.SimpleRule{}, nil
}
klog.Error(err)
return nil, err
}
return t.am.GetWorkspaceRoleSimpleRules(workspace, workspaceRole.Annotations[constants.DisplayNameAnnotationKey]), nil
}
func (t *tenantOperator) GetNamespaceSimpleRules(namespace, username string) ([]policy.SimpleRule, error) {
clusterRules, err := t.am.GetClusterPolicyRules(username)
if err != nil {
return nil, err
}
rules, err := t.am.GetPolicyRules(namespace, username)
if err != nil {
return nil, err
}
rules = append(rules, clusterRules...)
return iam.ConvertToSimpleRule(rules), nil
}
func (t *tenantOperator) appendAnnotations(username string, workspace *v1alpha1.Workspace) *v1alpha1.Workspace {
workspace = workspace.DeepCopy()
if workspace.Annotations == nil {
workspace.Annotations = make(map[string]string)
}
if ns, err := t.ListNamespaces(username, &params.Conditions{Match: map[string]string{constants.WorkspaceLabelKey: workspace.Name}}, "", false, 1, 0); err == nil {
workspace.Annotations["kubesphere.io/namespace-count"] = strconv.Itoa(ns.TotalCount)
}
if devops, err := t.ListDevopsProjects(username, &params.Conditions{Match: map[string]string{"workspace": workspace.Name}}, "", false, 1, 0); err == nil {
workspace.Annotations["kubesphere.io/devops-count"] = strconv.Itoa(devops.TotalCount)
}
userCount, err := t.workspaces.CountUsersInWorkspace(workspace.Name)
if err == nil {
workspace.Annotations["kubesphere.io/member-count"] = strconv.Itoa(userCount)
}
return workspace
panic("implement me")
}
func (t *tenantOperator) ListNamespaces(username string, conditions *params.Conditions, orderBy string, reverse bool, limit, offset int) (*models.PageableResponse, error) {

View File

@@ -18,27 +18,22 @@
package tenant
import (
"fmt"
core "k8s.io/api/core/v1"
rbacv1 "k8s.io/api/rbac/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/informers"
"k8s.io/client-go/kubernetes"
"k8s.io/klog"
"kubesphere.io/kubesphere/pkg/apis/tenant/v1alpha1"
"kubesphere.io/kubesphere/pkg/client/informers/externalversions"
"kubesphere.io/kubesphere/pkg/constants"
"kubesphere.io/kubesphere/pkg/db"
"kubesphere.io/kubesphere/pkg/models/devops"
"kubesphere.io/kubesphere/pkg/models/iam"
am2 "kubesphere.io/kubesphere/pkg/models/iam/am"
"kubesphere.io/kubesphere/pkg/models/resources/v1alpha2"
"kubesphere.io/kubesphere/pkg/server/params"
"kubesphere.io/kubesphere/pkg/simple/client/mysql"
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
"sort"
"strings"
"k8s.io/api/rbac/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels"
@@ -69,14 +64,14 @@ type workspaceOperator struct {
client kubernetes.Interface
informers informers.SharedInformerFactory
ksInformers externalversions.SharedInformerFactory
am iam.AccessManagementInterface
am am2.AccessManagementInterface
// TODO: use db interface instead of mysql client
// we can refactor this after rewrite devops using crd
db *mysql.Database
}
func newWorkspaceOperator(client kubernetes.Interface, informers informers.SharedInformerFactory, ksinformers externalversions.SharedInformerFactory, am iam.AccessManagementInterface, db *mysql.Database) WorkspaceInterface {
func newWorkspaceOperator(client kubernetes.Interface, informers informers.SharedInformerFactory, ksinformers externalversions.SharedInformerFactory, am am2.AccessManagementInterface, db *mysql.Database) WorkspaceInterface {
return &workspaceOperator{
client: client,
informers: informers,
@@ -111,96 +106,12 @@ func (w *workspaceOperator) DeleteNamespace(workspace string, namespace string)
}
func (w *workspaceOperator) RemoveUser(workspace string, username string) error {
workspaceRole, err := w.am.GetWorkspaceRole(workspace, username)
if err != nil {
return err
}
err = w.deleteWorkspaceRoleBinding(workspace, username, workspaceRole.Annotations[constants.DisplayNameAnnotationKey])
if err != nil {
return err
}
return nil
panic("implement me")
}
func (w *workspaceOperator) AddUser(workspaceName string, user *InWorkspaceUser) error {
workspaceRole, err := w.am.GetWorkspaceRole(workspaceName, user.Username)
if err != nil && !apierrors.IsNotFound(err) {
klog.Errorf("get workspace role failed: %+v", err)
return err
}
workspaceRoleName := fmt.Sprintf("workspace:%s:%s", workspaceName, strings.TrimPrefix(user.WorkspaceRole, "workspace-"))
var currentWorkspaceRoleName string
if workspaceRole != nil {
currentWorkspaceRoleName = workspaceRole.Name
}
if currentWorkspaceRoleName != workspaceRoleName && currentWorkspaceRoleName != "" {
err := w.deleteWorkspaceRoleBinding(workspaceName, user.Username, workspaceRole.Annotations[constants.DisplayNameAnnotationKey])
if err != nil {
klog.Errorf("delete workspace role binding failed: %+v", err)
return err
}
} else if currentWorkspaceRoleName != "" {
return nil
}
return w.createWorkspaceRoleBinding(workspaceName, user.Username, user.WorkspaceRole)
}
func (w *workspaceOperator) createWorkspaceRoleBinding(workspace, username string, role string) error {
if !sliceutil.HasString(constants.WorkSpaceRoles, role) {
return apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role)
}
roleBindingName := fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-"))
workspaceRoleBinding, err := w.informers.Rbac().V1().ClusterRoleBindings().Lister().Get(roleBindingName)
if err != nil {
return err
}
if !iam.ContainsUser(workspaceRoleBinding.Subjects, username) {
workspaceRoleBinding = workspaceRoleBinding.DeepCopy()
workspaceRoleBinding.Subjects = append(workspaceRoleBinding.Subjects, v1.Subject{APIGroup: "rbac.authorization.k8s.io", Kind: "User", Name: username})
_, err = w.client.RbacV1().ClusterRoleBindings().Update(workspaceRoleBinding)
if err != nil {
klog.Errorf("update workspace role binding failed: %+v", err)
return err
}
}
return nil
}
func (w *workspaceOperator) deleteWorkspaceRoleBinding(workspace, username string, role string) error {
if !sliceutil.HasString(constants.WorkSpaceRoles, role) {
return apierrors.NewNotFound(schema.GroupResource{Resource: "workspace role"}, role)
}
roleBindingName := fmt.Sprintf("workspace:%s:%s", workspace, strings.TrimPrefix(role, "workspace-"))
workspaceRoleBinding, err := w.informers.Rbac().V1().ClusterRoleBindings().Lister().Get(roleBindingName)
if err != nil {
return err
}
workspaceRoleBinding = workspaceRoleBinding.DeepCopy()
for i, v := range workspaceRoleBinding.Subjects {
if v.Kind == v1.UserKind && v.Name == username {
workspaceRoleBinding.Subjects = append(workspaceRoleBinding.Subjects[:i], workspaceRoleBinding.Subjects[i+1:]...)
i--
}
}
workspaceRoleBinding, err = w.client.RbacV1().ClusterRoleBindings().Update(workspaceRoleBinding)
return err
panic("implement me")
}
func (w *workspaceOperator) CountDevopsProjectsInWorkspace(workspaceName string) (int, error) {
@@ -288,50 +199,7 @@ func (*workspaceOperator) compare(a, b *v1alpha1.Workspace, orderBy string) bool
}
func (w *workspaceOperator) SearchWorkspace(username string, conditions *params.Conditions, orderBy string, reverse bool) ([]*v1alpha1.Workspace, error) {
rules, err := w.am.GetClusterPolicyRules(username)
if err != nil {
return nil, err
}
workspaces := make([]*v1alpha1.Workspace, 0)
if iam.RulesMatchesRequired(rules, rbacv1.PolicyRule{Verbs: []string{"list"}, APIGroups: []string{"tenant.kubesphere.io"}, Resources: []string{"workspaces"}}) {
workspaces, err = w.ksInformers.Tenant().V1alpha1().Workspaces().Lister().List(labels.Everything())
if err != nil {
return nil, err
}
} else {
workspaceRoles, err := w.am.GetWorkspaceRoleMap(username)
if err != nil {
return nil, err
}
for k := range workspaceRoles {
workspace, err := w.ksInformers.Tenant().V1alpha1().Workspaces().Lister().Get(k)
if err != nil {
return nil, err
}
workspaces = append(workspaces, workspace)
}
}
result := make([]*v1alpha1.Workspace, 0)
for _, workspace := range workspaces {
if w.match(conditions.Match, workspace) && w.fuzzy(conditions.Fuzzy, workspace) {
result = append(result, workspace)
}
}
// order & reverse
sort.Slice(result, func(i, j int) bool {
if reverse {
i, j = j, i
}
return w.compare(result[i], result[j], orderBy)
})
return result, nil
panic("implement me")
}
func (w *workspaceOperator) GetWorkspace(workspaceName string) (*v1alpha1.Workspace, error) {