feat: kubesphere 4.0 (#6115)
* feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> * feat: kubesphere 4.0 Signed-off-by: ci-bot <ci-bot@kubesphere.io> --------- Signed-off-by: ci-bot <ci-bot@kubesphere.io> Co-authored-by: ks-ci-bot <ks-ci-bot@example.com> Co-authored-by: joyceliu <joyceliu@yunify.com>
This commit is contained in:
committed by
GitHub
parent
b5015ec7b9
commit
447a51f08b
84
pkg/componenthelper/auth/rbac/clusterrole_interface.go
Normal file
84
pkg/componenthelper/auth/rbac/clusterrole_interface.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
)
|
||||
|
||||
type ClusterRoleRuleOwner struct {
|
||||
ClusterRole *iamv1beta1.ClusterRole
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetRuleOwnerScope() string {
|
||||
return iamv1beta1.ScopeCluster
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetObject() runtime.Object {
|
||||
return c.ClusterRole
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetNamespace() string {
|
||||
return c.ClusterRole.Namespace
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetName() string {
|
||||
return c.ClusterRole.Name
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetLabels() map[string]string {
|
||||
return c.ClusterRole.Labels
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) SetLabels(label map[string]string) {
|
||||
c.ClusterRole.Labels = label
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetAnnotations() map[string]string {
|
||||
return c.ClusterRole.Annotations
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) SetAnnotations(annotation map[string]string) {
|
||||
c.ClusterRole.Annotations = annotation
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetRules() []rbacv1.PolicyRule {
|
||||
return c.ClusterRole.Rules
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) SetRules(rules []rbacv1.PolicyRule) {
|
||||
c.ClusterRole.Rules = rules
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetAggregationRule() *iamv1beta1.AggregationRoleTemplates {
|
||||
return c.ClusterRole.AggregationRoleTemplates
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) SetAggregationRule(aggregationRoleTemplates *iamv1beta1.AggregationRoleTemplates) {
|
||||
c.ClusterRole.AggregationRoleTemplates = aggregationRoleTemplates
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) GetRegoPolicy() string {
|
||||
if c.ClusterRole.ObjectMeta.Annotations != nil {
|
||||
return c.ClusterRole.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) SetRegoPolicy(rego string) {
|
||||
if c.ClusterRole.ObjectMeta.Annotations == nil {
|
||||
c.ClusterRole.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
c.ClusterRole.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation] = rego
|
||||
}
|
||||
|
||||
func (c ClusterRoleRuleOwner) DeepCopyRuleOwner() RuleOwner {
|
||||
return ClusterRoleRuleOwner{ClusterRole: c.ClusterRole.DeepCopy()}
|
||||
}
|
||||
|
||||
var _ RuleOwner = ClusterRoleRuleOwner{}
|
||||
84
pkg/componenthelper/auth/rbac/globalrole_interfaces.go
Normal file
84
pkg/componenthelper/auth/rbac/globalrole_interfaces.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
)
|
||||
|
||||
type GlobalRoleRuleOwner struct {
|
||||
GlobalRole *iamv1beta1.GlobalRole
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetRuleOwnerScope() string {
|
||||
return iamv1beta1.ScopeGlobal
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetObject() runtime.Object {
|
||||
return g.GlobalRole
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetNamespace() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetName() string {
|
||||
return g.GlobalRole.Name
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetLabels() map[string]string {
|
||||
return g.GlobalRole.Labels
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) SetLabels(m map[string]string) {
|
||||
g.GlobalRole.Labels = m
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetAnnotations() map[string]string {
|
||||
return g.GlobalRole.Annotations
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) SetAnnotations(m map[string]string) {
|
||||
g.GlobalRole.Annotations = m
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetRules() []rbacv1.PolicyRule {
|
||||
return g.GlobalRole.Rules
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) SetRules(rules []rbacv1.PolicyRule) {
|
||||
g.GlobalRole.Rules = rules
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetAggregationRule() *iamv1beta1.AggregationRoleTemplates {
|
||||
return g.GlobalRole.AggregationRoleTemplates
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) SetAggregationRule(aggregationRoleTemplates *iamv1beta1.AggregationRoleTemplates) {
|
||||
g.GlobalRole.AggregationRoleTemplates = aggregationRoleTemplates
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) DeepCopyRuleOwner() RuleOwner {
|
||||
return GlobalRoleRuleOwner{GlobalRole: g.GlobalRole.DeepCopy()}
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) GetRegoPolicy() string {
|
||||
if g.GlobalRole.ObjectMeta.Annotations != nil {
|
||||
return g.GlobalRole.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (g GlobalRoleRuleOwner) SetRegoPolicy(rego string) {
|
||||
if g.GlobalRole.ObjectMeta.Annotations == nil {
|
||||
g.GlobalRole.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
g.GlobalRole.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation] = rego
|
||||
}
|
||||
|
||||
var _ RuleOwner = GlobalRoleRuleOwner{}
|
||||
305
pkg/componenthelper/auth/rbac/helper.go
Normal file
305
pkg/componenthelper/auth/rbac/helper.go
Normal file
@@ -0,0 +1,305 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/go-logr/logr"
|
||||
"github.com/open-policy-agent/opa/ast"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
"kubesphere.io/kubesphere/pkg/utils/sliceutil"
|
||||
)
|
||||
|
||||
const defaultRegoFileName = "authz.rego"
|
||||
|
||||
const (
|
||||
AggregateRoleTemplateFailed = "AggregateRoleTemplateFailed"
|
||||
MessageResourceSynced = "Aggregating roleTemplates successfully"
|
||||
)
|
||||
|
||||
type Helper struct {
|
||||
client.Client
|
||||
}
|
||||
|
||||
func NewHelper(c client.Client) *Helper {
|
||||
return &Helper{c}
|
||||
}
|
||||
|
||||
func (h *Helper) aggregateRoleTemplateRule(roleTemplates []iamv1beta1.RoleTemplate) ([]rbacv1.PolicyRule, []string, error) {
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
newTemplateNames := []string{}
|
||||
for _, rt := range roleTemplates {
|
||||
newTemplateNames = append(newTemplateNames, rt.Name)
|
||||
for _, rule := range rt.Spec.Rules {
|
||||
if !ruleExists(rules, rule) {
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return rules, newTemplateNames, nil
|
||||
}
|
||||
|
||||
func (h *Helper) aggregateRoleTemplateRegoPolicy(roleTemplates []iamv1beta1.RoleTemplate) (string, error) {
|
||||
mergedPolicy := &ast.Module{
|
||||
Rules: make([]*ast.Rule, 0),
|
||||
}
|
||||
for _, rt := range roleTemplates {
|
||||
rawPolicy := rt.Annotations[iamv1beta1.RegoOverrideAnnotation]
|
||||
if rawPolicy == "" {
|
||||
continue
|
||||
}
|
||||
module, err := ast.ParseModule(defaultRegoFileName, rawPolicy)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if mergedPolicy.Package == nil {
|
||||
mergedPolicy.Package = module.Package
|
||||
}
|
||||
if module != nil {
|
||||
mergedPolicy.Rules = append(mergedPolicy.Rules, module.Rules...)
|
||||
}
|
||||
}
|
||||
|
||||
if len(mergedPolicy.Rules) == 0 {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
seenRules := make(map[string]struct{})
|
||||
|
||||
uniqueMergedPolicy := &ast.Module{
|
||||
Package: mergedPolicy.Package,
|
||||
Rules: make([]*ast.Rule, 0),
|
||||
}
|
||||
|
||||
for _, rule := range mergedPolicy.Rules {
|
||||
ruleString := rule.String()
|
||||
if _, seen := seenRules[ruleString]; !seen {
|
||||
uniqueMergedPolicy.Rules = append(uniqueMergedPolicy.Rules, rule)
|
||||
seenRules[ruleString] = struct{}{}
|
||||
}
|
||||
}
|
||||
return uniqueMergedPolicy.String(), nil
|
||||
}
|
||||
|
||||
func (h *Helper) getRoleTemplates(ctx context.Context, owner RuleOwner) ([]iamv1beta1.RoleTemplate, error) {
|
||||
aggregationRule := owner.GetAggregationRule()
|
||||
logger := logr.FromContextOrDiscard(ctx)
|
||||
|
||||
if aggregationRule.RoleSelector == nil {
|
||||
roletemplates := []iamv1beta1.RoleTemplate{}
|
||||
for _, templateName := range aggregationRule.TemplateNames {
|
||||
roleTemplate := &iamv1beta1.RoleTemplate{}
|
||||
if err := h.Get(ctx, types.NamespacedName{Name: templateName}, roleTemplate); err != nil {
|
||||
if errors.IsNotFound(err) {
|
||||
logger.V(4).Info("aggregation role template not found", "name", templateName, "role", owner.GetObject())
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
roletemplates = append(roletemplates, *roleTemplate)
|
||||
}
|
||||
return roletemplates, nil
|
||||
}
|
||||
|
||||
selector := aggregationRule.RoleSelector.DeepCopy()
|
||||
roleTemplateList := &iamv1beta1.RoleTemplateList{}
|
||||
// Ensure the roleTemplate can be aggregated at the specific role scope
|
||||
selector.MatchLabels = labels.Merge(selector.MatchLabels, map[string]string{iamv1beta1.ScopeLabel: owner.GetRuleOwnerScope()})
|
||||
asSelector, err := metav1.LabelSelectorAsSelector(selector)
|
||||
if err != nil {
|
||||
logger.Error(err, "failed to parse role selector", "scope", owner.GetRuleOwnerScope(), "name", owner.GetName())
|
||||
return nil, err
|
||||
}
|
||||
if err = h.List(ctx, roleTemplateList, &client.ListOptions{LabelSelector: asSelector}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return roleTemplateList.Items, nil
|
||||
}
|
||||
|
||||
func (h *Helper) AggregationRole(ctx context.Context, ruleOwner RuleOwner, recorder record.EventRecorder) error {
|
||||
var needUpdate bool
|
||||
if ruleOwner.GetAggregationRule() == nil {
|
||||
return nil
|
||||
}
|
||||
templates, err := h.getRoleTemplates(ctx, ruleOwner)
|
||||
if err != nil {
|
||||
recorder.Event(ruleOwner.GetObject(), corev1.EventTypeWarning, AggregateRoleTemplateFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
newPolicyRules, newTemplateNames, err := h.aggregateRoleTemplateRule(templates)
|
||||
if err != nil {
|
||||
recorder.Event(ruleOwner.GetObject(), corev1.EventTypeWarning, AggregateRoleTemplateFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
cover, uncovered := Covers(ruleOwner.GetRules(), newPolicyRules)
|
||||
|
||||
aggregationRule := ruleOwner.GetAggregationRule()
|
||||
templateNamesEqual := false
|
||||
if aggregationRule != nil {
|
||||
templateNamesEqual = sliceutil.Equal(aggregationRule.TemplateNames, newTemplateNames)
|
||||
}
|
||||
|
||||
if !cover {
|
||||
needUpdate = true
|
||||
newRule := append(ruleOwner.GetRules(), uncovered...)
|
||||
squashedRules := SquashRules(len(newRule), newRule)
|
||||
ruleOwner.SetRules(squashedRules)
|
||||
}
|
||||
|
||||
if !templateNamesEqual {
|
||||
needUpdate = true
|
||||
aggregationRule.TemplateNames = newTemplateNames
|
||||
ruleOwner.SetAggregationRule(aggregationRule)
|
||||
}
|
||||
|
||||
newRegoPolicy, err := h.aggregateRoleTemplateRegoPolicy(templates)
|
||||
if err != nil {
|
||||
recorder.Event(ruleOwner.GetObject(), corev1.EventTypeWarning, AggregateRoleTemplateFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
|
||||
policyCover, err := regoPolicyCover(ruleOwner.GetRegoPolicy(), newRegoPolicy)
|
||||
if err != nil {
|
||||
recorder.Event(ruleOwner.GetObject(), corev1.EventTypeWarning, AggregateRoleTemplateFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
if !policyCover {
|
||||
needUpdate = true
|
||||
ruleOwner.SetRegoPolicy(newRegoPolicy)
|
||||
}
|
||||
if needUpdate {
|
||||
if err = h.Update(ctx, ruleOwner.GetObject().(client.Object)); err != nil {
|
||||
recorder.Event(ruleOwner.GetObject(), corev1.EventTypeWarning, AggregateRoleTemplateFailed, err.Error())
|
||||
return err
|
||||
}
|
||||
recorder.Event(ruleOwner.GetObject(), corev1.EventTypeNormal, "Synced", MessageResourceSynced)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func ruleExists(haystack []rbacv1.PolicyRule, needle rbacv1.PolicyRule) bool {
|
||||
covers, _ := Covers(haystack, []rbacv1.PolicyRule{needle})
|
||||
return covers
|
||||
}
|
||||
|
||||
func regoPolicyCover(owner, servant string) (bool, error) {
|
||||
if servant == "" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
if owner == "" && servant != "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
ownerModule, err := ast.ParseModule(defaultRegoFileName, owner)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
servantModule, err := ast.ParseModule(defaultRegoFileName, servant)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cover := ownerModule.Compare(servantModule) >= 0
|
||||
|
||||
return cover, nil
|
||||
}
|
||||
|
||||
func SquashRules(deep int, rules []rbacv1.PolicyRule) []rbacv1.PolicyRule {
|
||||
var resultRules []rbacv1.PolicyRule
|
||||
for _, rule := range rules {
|
||||
merged := false
|
||||
if cover, _ := Covers(resultRules, []rbacv1.PolicyRule{rule}); cover {
|
||||
continue
|
||||
}
|
||||
for i, rRule := range resultRules {
|
||||
if (containRules(rRule.APIGroups, rule.APIGroups) && equalRules(rRule.Resources, rule.Resources)) ||
|
||||
(containRules(rRule.APIGroups, rule.APIGroups) && equalRules(rRule.Verbs, rule.Verbs)) {
|
||||
merged = true
|
||||
resultRules[i] = mergeRules(rRule, rule)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !merged {
|
||||
resultRules = append(resultRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
if len(resultRules) == deep {
|
||||
return resultRules
|
||||
}
|
||||
return SquashRules(len(resultRules), resultRules)
|
||||
}
|
||||
|
||||
func mergeRules(base, rule rbacv1.PolicyRule) rbacv1.PolicyRule {
|
||||
if !sliceutil.HasString(base.APIGroups, "*") {
|
||||
base.APIGroups = merge(base.APIGroups, rule.APIGroups)
|
||||
}
|
||||
if !sliceutil.HasString(base.Resources, "*") {
|
||||
base.Resources = merge(base.Resources, rule.Resources)
|
||||
}
|
||||
if !sliceutil.HasString(base.Verbs, "*") {
|
||||
base.Verbs = merge(base.Verbs, rule.Verbs)
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func merge(base, rule []string) []string {
|
||||
for _, r := range rule {
|
||||
if !sliceutil.HasString(base, r) {
|
||||
base = append(base, r)
|
||||
}
|
||||
}
|
||||
return base
|
||||
}
|
||||
|
||||
func containRules(base, rule []string) bool {
|
||||
if sliceutil.HasString(base, "*") {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, b := range base {
|
||||
if !sliceutil.HasString(rule, b) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func equalRules(base, rule []string) bool {
|
||||
if len(base) != len(rule) {
|
||||
return false
|
||||
}
|
||||
|
||||
baseMap := make(map[string]int)
|
||||
for _, item := range base {
|
||||
baseMap[item]++
|
||||
}
|
||||
|
||||
for _, item := range rule {
|
||||
count, exists := baseMap[item]
|
||||
if !exists || count == 0 {
|
||||
return false
|
||||
}
|
||||
baseMap[item]--
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
240
pkg/componenthelper/auth/rbac/helper_test.go
Normal file
240
pkg/componenthelper/auth/rbac/helper_test.go
Normal file
@@ -0,0 +1,240 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
|
||||
func TestSquashRules(t *testing.T) {
|
||||
|
||||
var raw = `
|
||||
- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- apps
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- apps/versions
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- applications
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- attachments
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- repos
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- repos/events
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspaces
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspaces
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspaces
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspaces
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspaces
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspaces
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspacemembers
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspacemembers
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- workspacemembers
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- quotas
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- quotas
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- quotas
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- abnormalworkloads
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- abnormalworkloads
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- abnormalworkloads
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- list
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- pods
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- namespaces
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- namespaces
|
||||
verbs:
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- federatednamespaces
|
||||
verbs:
|
||||
- create
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- federatednamespaces
|
||||
verbs:
|
||||
- watch`
|
||||
|
||||
var expectedRaw = `- apiGroups:
|
||||
- application.kubesphere.io
|
||||
resources:
|
||||
- apps
|
||||
- apps/versions
|
||||
- applications
|
||||
- attachments
|
||||
- repos
|
||||
- repos/events
|
||||
verbs:
|
||||
- '*'
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- quotas
|
||||
- workspacemembers
|
||||
- workspaces
|
||||
- abnormalworkloads
|
||||
- pods
|
||||
verbs:
|
||||
- get
|
||||
- list
|
||||
- watch
|
||||
- apiGroups:
|
||||
- '*'
|
||||
resources:
|
||||
- namespaces
|
||||
- federatednamespaces
|
||||
verbs:
|
||||
- create
|
||||
- watch`
|
||||
|
||||
rules := []rbacv1.PolicyRule{}
|
||||
err := yaml.Unmarshal([]byte(raw), &rules)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
squashRules := SquashRules(len(rules), rules)
|
||||
|
||||
expectedRules := []rbacv1.PolicyRule{}
|
||||
err = yaml.Unmarshal([]byte(expectedRaw), &expectedRules)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
lefCovers, _ := Covers(expectedRules, squashRules)
|
||||
rightCover, _ := Covers(squashRules, expectedRules)
|
||||
|
||||
if !lefCovers || !rightCover {
|
||||
t.Errorf("failed")
|
||||
}
|
||||
|
||||
}
|
||||
30
pkg/componenthelper/auth/rbac/interface.go
Normal file
30
pkg/componenthelper/auth/rbac/interface.go
Normal file
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
)
|
||||
|
||||
type RuleOwner interface {
|
||||
GetObject() runtime.Object
|
||||
GetNamespace() string
|
||||
GetName() string
|
||||
GetLabels() map[string]string
|
||||
SetLabels(map[string]string)
|
||||
GetAnnotations() map[string]string
|
||||
SetAnnotations(map[string]string)
|
||||
GetRules() []rbacv1.PolicyRule
|
||||
SetRules([]rbacv1.PolicyRule)
|
||||
GetRegoPolicy() string
|
||||
SetRegoPolicy(string)
|
||||
GetAggregationRule() *iamv1beta1.AggregationRoleTemplates
|
||||
SetAggregationRule(*iamv1beta1.AggregationRoleTemplates)
|
||||
DeepCopyRuleOwner() RuleOwner
|
||||
GetRuleOwnerScope() string
|
||||
}
|
||||
163
pkg/componenthelper/auth/rbac/policy_comparator.go
Normal file
163
pkg/componenthelper/auth/rbac/policy_comparator.go
Normal file
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
// This file is copied from K8s library .
|
||||
// https://k8s.io/component-helpers/auth/rbac/validation/policy_comparator.go
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
)
|
||||
|
||||
func Covers(ownerRules, servantRules []rbacv1.PolicyRule) (bool, []rbacv1.PolicyRule) {
|
||||
// 1. Break every servantRule into individual rule tuples: group, verb, resource, resourceName
|
||||
// 2. Compare the mini-rules against each owner rule. Because the breakdown is down to the most atomic level, we're guaranteed that each mini-servant rule will be either fully covered or not covered by a single owner rule
|
||||
// 3. Any left over mini-rules means that we are not covered and we have a nice list of them.
|
||||
// TODO: it might be nice to collapse the list down into something more human readable
|
||||
|
||||
var rules []rbacv1.PolicyRule
|
||||
for _, servantRule := range servantRules {
|
||||
rules = append(rules, BreakdownRule(servantRule)...)
|
||||
}
|
||||
|
||||
var uncoveredRules []rbacv1.PolicyRule
|
||||
for _, rule := range rules {
|
||||
covered := false
|
||||
for _, ownerRule := range ownerRules {
|
||||
if ruleCovers(ownerRule, rule) {
|
||||
covered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if !covered {
|
||||
uncoveredRules = append(uncoveredRules, rule)
|
||||
}
|
||||
}
|
||||
|
||||
return len(uncoveredRules) == 0, uncoveredRules
|
||||
}
|
||||
|
||||
// BreakdownRule takes a rule and builds an equivalent list of rules that each have at most one verb, one
|
||||
// resource, and one resource name
|
||||
func BreakdownRule(rule rbacv1.PolicyRule) []rbacv1.PolicyRule {
|
||||
var rules []rbacv1.PolicyRule
|
||||
for _, group := range rule.APIGroups {
|
||||
for _, resource := range rule.Resources {
|
||||
for _, verb := range rule.Verbs {
|
||||
if len(rule.ResourceNames) > 0 {
|
||||
for _, resourceName := range rule.ResourceNames {
|
||||
rules = append(rules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}, ResourceNames: []string{resourceName}})
|
||||
}
|
||||
|
||||
} else {
|
||||
rules = append(rules, rbacv1.PolicyRule{APIGroups: []string{group}, Resources: []string{resource}, Verbs: []string{verb}})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Non-resource URLs are unique because they only combine with verbs.
|
||||
for _, nonResourceURL := range rule.NonResourceURLs {
|
||||
for _, verb := range rule.Verbs {
|
||||
rules = append(rules, rbacv1.PolicyRule{NonResourceURLs: []string{nonResourceURL}, Verbs: []string{verb}})
|
||||
}
|
||||
}
|
||||
|
||||
return rules
|
||||
}
|
||||
|
||||
func has(set []string, ele string) bool {
|
||||
for _, s := range set {
|
||||
if s == ele {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func hasAll(set, contains []string) bool {
|
||||
owning := make(map[string]struct{}, len(set))
|
||||
for _, ele := range set {
|
||||
owning[ele] = struct{}{}
|
||||
}
|
||||
for _, ele := range contains {
|
||||
if _, ok := owning[ele]; !ok {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func resourceCoversAll(setResources, coversResources []string) bool {
|
||||
// if we have a star or an exact match on all resources, then we match
|
||||
if has(setResources, rbacv1.ResourceAll) || hasAll(setResources, coversResources) {
|
||||
return true
|
||||
}
|
||||
|
||||
for _, path := range coversResources {
|
||||
// if we have an exact match, then we match.
|
||||
if has(setResources, path) {
|
||||
continue
|
||||
}
|
||||
// if we're not a subresource, then we definitely don't match. fail.
|
||||
if !strings.Contains(path, "/") {
|
||||
return false
|
||||
}
|
||||
tokens := strings.SplitN(path, "/", 2)
|
||||
resourceToCheck := "*/" + tokens[1]
|
||||
if !has(setResources, resourceToCheck) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func nonResourceURLsCoversAll(set, covers []string) bool {
|
||||
for _, path := range covers {
|
||||
covered := false
|
||||
for _, owner := range set {
|
||||
if nonResourceURLCovers(owner, path) {
|
||||
covered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !covered {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func nonResourceURLCovers(ownerPath, subPath string) bool {
|
||||
if ownerPath == subPath {
|
||||
return true
|
||||
}
|
||||
return strings.HasSuffix(ownerPath, "*") && strings.HasPrefix(subPath, strings.TrimRight(ownerPath, "*"))
|
||||
}
|
||||
|
||||
// ruleCovers determines whether the ownerRule (which may have multiple verbs, resources, and resourceNames) covers
|
||||
// the subrule (which may only contain at most one verb, resource, and resourceName)
|
||||
func ruleCovers(ownerRule, subRule rbacv1.PolicyRule) bool {
|
||||
verbMatches := has(ownerRule.Verbs, rbacv1.VerbAll) || hasAll(ownerRule.Verbs, subRule.Verbs)
|
||||
groupMatches := has(ownerRule.APIGroups, rbacv1.APIGroupAll) || hasAll(ownerRule.APIGroups, subRule.APIGroups)
|
||||
resourceMatches := resourceCoversAll(ownerRule.Resources, subRule.Resources)
|
||||
nonResourceURLMatches := nonResourceURLsCoversAll(ownerRule.NonResourceURLs, subRule.NonResourceURLs)
|
||||
|
||||
resourceNameMatches := false
|
||||
|
||||
if len(subRule.ResourceNames) == 0 {
|
||||
resourceNameMatches = len(ownerRule.ResourceNames) == 0
|
||||
} else {
|
||||
resourceNameMatches = len(ownerRule.ResourceNames) == 0 || hasAll(ownerRule.ResourceNames, subRule.ResourceNames)
|
||||
}
|
||||
|
||||
return verbMatches && groupMatches && resourceMatches && resourceNameMatches && nonResourceURLMatches
|
||||
}
|
||||
84
pkg/componenthelper/auth/rbac/role_interfaces.go
Normal file
84
pkg/componenthelper/auth/rbac/role_interfaces.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
)
|
||||
|
||||
type RoleRuleOwner struct {
|
||||
Role *iamv1beta1.Role
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetRuleOwnerScope() string {
|
||||
return iamv1beta1.ScopeNamespace
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetObject() runtime.Object {
|
||||
return r.Role
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetNamespace() string {
|
||||
return r.Role.Namespace
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetName() string {
|
||||
return r.Role.Name
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetLabels() map[string]string {
|
||||
return r.Role.Labels
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) SetLabels(m map[string]string) {
|
||||
r.Role.Labels = m
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetAnnotations() map[string]string {
|
||||
return r.Role.Annotations
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) SetAnnotations(m map[string]string) {
|
||||
r.Role.Annotations = m
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetRules() []rbacv1.PolicyRule {
|
||||
return r.Role.Rules
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) SetRules(rules []rbacv1.PolicyRule) {
|
||||
r.Role.Rules = rules
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetAggregationRule() *iamv1beta1.AggregationRoleTemplates {
|
||||
return r.Role.AggregationRoleTemplates
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) SetAggregationRule(i *iamv1beta1.AggregationRoleTemplates) {
|
||||
r.Role.AggregationRoleTemplates = i
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) GetRegoPolicy() string {
|
||||
if r.Role.ObjectMeta.Annotations != nil {
|
||||
return r.Role.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) SetRegoPolicy(rego string) {
|
||||
if r.Role.ObjectMeta.Annotations == nil {
|
||||
r.Role.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
r.Role.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation] = rego
|
||||
}
|
||||
|
||||
func (r RoleRuleOwner) DeepCopyRuleOwner() RuleOwner {
|
||||
return RoleRuleOwner{Role: r.Role.DeepCopy()}
|
||||
}
|
||||
|
||||
var _ RuleOwner = RoleRuleOwner{}
|
||||
84
pkg/componenthelper/auth/rbac/workspacerole_interfaces.go
Normal file
84
pkg/componenthelper/auth/rbac/workspacerole_interfaces.go
Normal file
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Please refer to the LICENSE file in the root directory of the project.
|
||||
* https://github.com/kubesphere/kubesphere/blob/master/LICENSE
|
||||
*/
|
||||
|
||||
package rbac
|
||||
|
||||
import (
|
||||
rbacv1 "k8s.io/api/rbac/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
iamv1beta1 "kubesphere.io/api/iam/v1beta1"
|
||||
)
|
||||
|
||||
type WorkspaceRoleRuleOwner struct {
|
||||
WorkspaceRole *iamv1beta1.WorkspaceRole
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetRuleOwnerScope() string {
|
||||
return iamv1beta1.ScopeWorkspace
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetObject() runtime.Object {
|
||||
return w.WorkspaceRole
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetNamespace() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetName() string {
|
||||
return w.WorkspaceRole.Name
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetLabels() map[string]string {
|
||||
return w.WorkspaceRole.Labels
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) SetLabels(m map[string]string) {
|
||||
w.WorkspaceRole.Labels = m
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetAnnotations() map[string]string {
|
||||
return w.WorkspaceRole.Annotations
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) SetAnnotations(m map[string]string) {
|
||||
w.WorkspaceRole.Annotations = m
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetRules() []rbacv1.PolicyRule {
|
||||
return w.WorkspaceRole.Rules
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) SetRules(rules []rbacv1.PolicyRule) {
|
||||
w.WorkspaceRole.Rules = rules
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetAggregationRule() *iamv1beta1.AggregationRoleTemplates {
|
||||
return w.WorkspaceRole.AggregationRoleTemplates
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) SetAggregationRule(i *iamv1beta1.AggregationRoleTemplates) {
|
||||
w.WorkspaceRole.AggregationRoleTemplates = i
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) GetRegoPolicy() string {
|
||||
if w.WorkspaceRole.ObjectMeta.Annotations != nil {
|
||||
return w.WorkspaceRole.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) SetRegoPolicy(rego string) {
|
||||
if w.WorkspaceRole.ObjectMeta.Annotations == nil {
|
||||
w.WorkspaceRole.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
w.WorkspaceRole.ObjectMeta.Annotations[iamv1beta1.RegoOverrideAnnotation] = rego
|
||||
}
|
||||
|
||||
func (w WorkspaceRoleRuleOwner) DeepCopyRuleOwner() RuleOwner {
|
||||
return WorkspaceRoleRuleOwner{WorkspaceRole: w.WorkspaceRole.DeepCopy()}
|
||||
}
|
||||
|
||||
var _ RuleOwner = WorkspaceRoleRuleOwner{}
|
||||
Reference in New Issue
Block a user