package api import ( "fmt" "sort" "strings" "unicode" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/validation" "k8s.io/kubernetes/pkg/auth/user" "k8s.io/kubernetes/pkg/serviceaccount" "k8s.io/kubernetes/pkg/util/sets" // uservalidation "github.com/openshift/origin/pkg/user/api/validation" ) // NormalizeResources expands all resource groups and forces all resources to lower case. // If the rawResources are already normalized, it returns the original set to avoid the // allocation and GC cost, since this is hit multiple times for every REST call. // That means you should NEVER MODIFY THE RESULT of this call. func NormalizeResources(rawResources sets.String) sets.String { // we only need to expand groups if the exist and we don't create them with groups // by default. Only accept the cost of expansion if we're doing work. needsNormalization := false for currResource := range rawResources { if needsNormalizing(currResource) { needsNormalization = true break } } if !needsNormalization { return rawResources } ret := sets.String{} toVisit := rawResources.List() visited := sets.String{} for i := 0; i < len(toVisit); i++ { currResource := toVisit[i] if visited.Has(currResource) { continue } visited.Insert(currResource) if !strings.HasPrefix(currResource, resourceGroupPrefix) { ret.Insert(strings.ToLower(currResource)) continue } if resourceTypes, exists := groupsToResources[currResource]; exists { toVisit = append(toVisit, resourceTypes...) } } return ret } func needsNormalizing(in string) bool { if strings.HasPrefix(in, resourceGroupPrefix) { return true } for _, r := range in { if unicode.IsUpper(r) { return true } } return false } func (r PolicyRule) String() string { return "PolicyRule" + r.CompactString() } // CompactString exposes a compact string representation for use in escalation error messages func (r PolicyRule) CompactString() string { formatStringParts := []string{} formatArgs := []interface{}{} if len(r.Verbs) > 0 { formatStringParts = append(formatStringParts, "Verbs:%q") formatArgs = append(formatArgs, r.Verbs.List()) } if len(r.APIGroups) > 0 { formatStringParts = append(formatStringParts, "APIGroups:%q") formatArgs = append(formatArgs, r.APIGroups) } if len(r.Resources) > 0 { formatStringParts = append(formatStringParts, "Resources:%q") formatArgs = append(formatArgs, r.Resources.List()) } if len(r.ResourceNames) > 0 { formatStringParts = append(formatStringParts, "ResourceNames:%q") formatArgs = append(formatArgs, r.ResourceNames.List()) } if r.AttributeRestrictions != nil { formatStringParts = append(formatStringParts, "Restrictions:%q") formatArgs = append(formatArgs, r.AttributeRestrictions) } if len(r.NonResourceURLs) > 0 { formatStringParts = append(formatStringParts, "NonResourceURLs:%q") formatArgs = append(formatArgs, r.NonResourceURLs.List()) } formatString := "{" + strings.Join(formatStringParts, ", ") + "}" return fmt.Sprintf(formatString, formatArgs...) } func getRoleBindingValues(roleBindingMap map[string]*RoleBinding) []*RoleBinding { ret := []*RoleBinding{} for _, currBinding := range roleBindingMap { ret = append(ret, currBinding) } return ret } func SortRoleBindings(roleBindingMap map[string]*RoleBinding, reverse bool) []*RoleBinding { roleBindings := getRoleBindingValues(roleBindingMap) if reverse { sort.Sort(sort.Reverse(RoleBindingSorter(roleBindings))) } else { sort.Sort(RoleBindingSorter(roleBindings)) } return roleBindings } type PolicyBindingSorter []PolicyBinding func (s PolicyBindingSorter) Len() int { return len(s) } func (s PolicyBindingSorter) Less(i, j int) bool { return s[i].Name < s[j].Name } func (s PolicyBindingSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } type RoleBindingSorter []*RoleBinding func (s RoleBindingSorter) Len() int { return len(s) } func (s RoleBindingSorter) Less(i, j int) bool { return s[i].Name < s[j].Name } func (s RoleBindingSorter) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func GetPolicyBindingName(policyRefNamespace string) string { return fmt.Sprintf("%s:%s", policyRefNamespace, PolicyName) } var ClusterPolicyBindingName = GetPolicyBindingName("") func BuildSubjects(users, groups []string, userNameValidator, groupNameValidator validation.ValidateNameFunc) []kapi.ObjectReference { subjects := []kapi.ObjectReference{} for _, user := range users { saNamespace, saName, err := serviceaccount.SplitUsername(user) if err == nil { subjects = append(subjects, kapi.ObjectReference{Kind: ServiceAccountKind, Namespace: saNamespace, Name: saName}) continue } kind := UserKind if len(userNameValidator(user, false)) != 0 { kind = SystemUserKind } subjects = append(subjects, kapi.ObjectReference{Kind: kind, Name: user}) } for _, group := range groups { kind := GroupKind if len(groupNameValidator(group, false)) != 0 { kind = SystemGroupKind } subjects = append(subjects, kapi.ObjectReference{Kind: kind, Name: group}) } return subjects } // StringSubjectsFor returns users and groups for comparison against user.Info. currentNamespace is used to // to create usernames for service accounts where namespace=="". func StringSubjectsFor(currentNamespace string, subjects []kapi.ObjectReference) ([]string, []string) { // these MUST be nil to indicate empty var users, groups []string for _, subject := range subjects { switch subject.Kind { case ServiceAccountKind: namespace := currentNamespace if len(subject.Namespace) > 0 { namespace = subject.Namespace } if len(namespace) > 0 { users = append(users, serviceaccount.MakeUsername(namespace, subject.Name)) } case UserKind, SystemUserKind: users = append(users, subject.Name) case GroupKind, SystemGroupKind: groups = append(groups, subject.Name) } } return users, groups } // SubjectsStrings returns users, groups, serviceaccounts, unknown for display purposes. currentNamespace is used to // hide the subject.Namespace for ServiceAccounts in the currentNamespace func SubjectsStrings(currentNamespace string, subjects []kapi.ObjectReference) ([]string, []string, []string, []string) { users := []string{} groups := []string{} sas := []string{} others := []string{} for _, subject := range subjects { switch subject.Kind { case ServiceAccountKind: if len(subject.Namespace) > 0 && currentNamespace != subject.Namespace { sas = append(sas, subject.Namespace+"/"+subject.Name) } else { sas = append(sas, subject.Name) } case UserKind, SystemUserKind: users = append(users, subject.Name) case GroupKind, SystemGroupKind: groups = append(groups, subject.Name) default: others = append(others, fmt.Sprintf("%s/%s/%s", subject.Kind, subject.Namespace, subject.Name)) } } return users, groups, sas, others } // SubjectsContainUser returns true if the provided subjects contain the named user. currentNamespace // is used to identify service accounts that are defined in a relative fashion. func SubjectsContainUser(subjects []kapi.ObjectReference, currentNamespace string, user string) bool { if !strings.HasPrefix(user, serviceaccount.ServiceAccountUsernamePrefix) { for _, subject := range subjects { switch subject.Kind { case UserKind, SystemUserKind: if user == subject.Name { return true } } } return false } for _, subject := range subjects { switch subject.Kind { case ServiceAccountKind: namespace := currentNamespace if len(subject.Namespace) > 0 { namespace = subject.Namespace } if len(namespace) == 0 { continue } if user == serviceaccount.MakeUsername(namespace, subject.Name) { return true } case UserKind, SystemUserKind: if user == subject.Name { return true } } } return false } // SubjectsContainAnyGroup returns true if the provided subjects any of the named groups. func SubjectsContainAnyGroup(subjects []kapi.ObjectReference, groups []string) bool { for _, subject := range subjects { switch subject.Kind { case GroupKind, SystemGroupKind: for _, group := range groups { if group == subject.Name { return true } } } } return false } func AddUserToSAR(user user.Info, sar *SubjectAccessReview) *SubjectAccessReview { origScopes := user.GetExtra()[ScopesKey] scopes := make([]string, len(origScopes), len(origScopes)) copy(scopes, origScopes) sar.User = user.GetName() sar.Groups = sets.NewString(user.GetGroups()...) sar.Scopes = scopes return sar } func AddUserToLSAR(user user.Info, lsar *LocalSubjectAccessReview) *LocalSubjectAccessReview { origScopes := user.GetExtra()[ScopesKey] scopes := make([]string, len(origScopes), len(origScopes)) copy(scopes, origScopes) lsar.User = user.GetName() lsar.Groups = sets.NewString(user.GetGroups()...) lsar.Scopes = scopes return lsar } // +gencopy=false // PolicyRuleBuilder let's us attach methods. A no-no for API types type PolicyRuleBuilder struct { PolicyRule PolicyRule } func NewRule(verbs ...string) *PolicyRuleBuilder { return &PolicyRuleBuilder{ PolicyRule: PolicyRule{ Verbs: sets.NewString(verbs...), Resources: sets.String{}, ResourceNames: sets.String{}, }, } } func (r *PolicyRuleBuilder) Groups(groups ...string) *PolicyRuleBuilder { r.PolicyRule.APIGroups = append(r.PolicyRule.APIGroups, groups...) return r } func (r *PolicyRuleBuilder) Resources(resources ...string) *PolicyRuleBuilder { r.PolicyRule.Resources.Insert(resources...) return r } func (r *PolicyRuleBuilder) Names(names ...string) *PolicyRuleBuilder { r.PolicyRule.ResourceNames.Insert(names...) return r } func (r *PolicyRuleBuilder) RuleOrDie() PolicyRule { ret, err := r.Rule() if err != nil { panic(err) } return ret } func (r *PolicyRuleBuilder) Rule() (PolicyRule, error) { if len(r.PolicyRule.Verbs) == 0 { return PolicyRule{}, fmt.Errorf("verbs are required: %#v", r.PolicyRule) } switch { case len(r.PolicyRule.NonResourceURLs) > 0: if len(r.PolicyRule.APIGroups) != 0 || len(r.PolicyRule.Resources) != 0 || len(r.PolicyRule.ResourceNames) != 0 { return PolicyRule{}, fmt.Errorf("non-resource rule may not have apiGroups, resources, or resourceNames: %#v", r.PolicyRule) } case len(r.PolicyRule.Resources) > 0: if len(r.PolicyRule.NonResourceURLs) != 0 { return PolicyRule{}, fmt.Errorf("resource rule may not have nonResourceURLs: %#v", r.PolicyRule) } if len(r.PolicyRule.APIGroups) == 0 { return PolicyRule{}, fmt.Errorf("resource rule must have apiGroups: %#v", r.PolicyRule) } default: return PolicyRule{}, fmt.Errorf("a rule must have either nonResourceURLs or resources: %#v", r.PolicyRule) } return r.PolicyRule, nil } type SortableRuleSlice []PolicyRule func (s SortableRuleSlice) Len() int { return len(s) } func (s SortableRuleSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func (s SortableRuleSlice) Less(i, j int) bool { return strings.Compare(s[i].String(), s[j].String()) < 0 }