@@ -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"])
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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,
|
||||
})
|
||||
}
|
||||
@@ -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{})
|
||||
|
||||
@@ -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}
|
||||
}
|
||||
|
||||
84
pkg/apiserver/authorization/authorizerfactory/opa_test.go
Normal file
84
pkg/apiserver/authorization/authorizerfactory/opa_test.go
Normal 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")
|
||||
}
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
100
pkg/models/iam/am/am.go
Normal 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")
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
* limitations under the License.
|
||||
* /
|
||||
*/
|
||||
package iam
|
||||
package im
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
@@ -16,4 +16,4 @@
|
||||
* /
|
||||
*/
|
||||
|
||||
package iam
|
||||
package im
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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, ¶ms.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, ¶ms.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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user