mirror of
https://github.com/rancher/steve.git
synced 2025-07-02 01:32:10 +00:00
212 lines
6.7 KiB
Go
212 lines
6.7 KiB
Go
package accesscontrol
|
|
|
|
import (
|
|
"sort"
|
|
|
|
rbacv1controllers "github.com/rancher/wrangler/v3/pkg/generated/controllers/rbac/v1"
|
|
rbacv1 "k8s.io/api/rbac/v1"
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
"k8s.io/apiserver/pkg/authentication/serviceaccount"
|
|
)
|
|
|
|
const (
|
|
rbacGroup = rbacv1.GroupName
|
|
All = "*"
|
|
|
|
groupKind = rbacv1.GroupKind
|
|
userKind = rbacv1.UserKind
|
|
svcAccountKind = rbacv1.ServiceAccountKind
|
|
|
|
clusterRoleKind = "ClusterRole"
|
|
roleKind = "Role"
|
|
)
|
|
|
|
type policyRuleIndex struct {
|
|
crCache rbacv1controllers.ClusterRoleCache
|
|
rCache rbacv1controllers.RoleCache
|
|
crbCache rbacv1controllers.ClusterRoleBindingCache
|
|
rbCache rbacv1controllers.RoleBindingCache
|
|
roleIndexKey string
|
|
clusterRoleIndexKey string
|
|
}
|
|
|
|
func newPolicyRuleIndex(user bool, rbac rbacv1controllers.Interface) *policyRuleIndex {
|
|
key := groupKind
|
|
if user {
|
|
key = userKind
|
|
}
|
|
pi := &policyRuleIndex{
|
|
crCache: rbac.ClusterRole().Cache(),
|
|
rCache: rbac.Role().Cache(),
|
|
crbCache: rbac.ClusterRoleBinding().Cache(),
|
|
rbCache: rbac.RoleBinding().Cache(),
|
|
clusterRoleIndexKey: "crb" + key,
|
|
roleIndexKey: "rb" + key,
|
|
}
|
|
|
|
pi.crbCache.AddIndexer(pi.clusterRoleIndexKey, clusterRoleBindingBySubjectIndexer(key))
|
|
pi.rbCache.AddIndexer(pi.roleIndexKey, roleBindingBySubjectIndexer(key))
|
|
|
|
return pi
|
|
}
|
|
|
|
func clusterRoleBindingBySubjectIndexer(kind string) func(crb *rbacv1.ClusterRoleBinding) ([]string, error) {
|
|
return func(crb *rbacv1.ClusterRoleBinding) ([]string, error) {
|
|
if crb.RoleRef.Kind != "ClusterRole" {
|
|
return nil, nil
|
|
}
|
|
return indexSubjects(kind, crb.Subjects), nil
|
|
}
|
|
}
|
|
|
|
func roleBindingBySubjectIndexer(key string) func(rb *rbacv1.RoleBinding) ([]string, error) {
|
|
return func(rb *rbacv1.RoleBinding) ([]string, error) {
|
|
return indexSubjects(key, rb.Subjects), nil
|
|
}
|
|
}
|
|
|
|
func indexSubjects(kind string, subjects []rbacv1.Subject) []string {
|
|
var result []string
|
|
for _, subject := range subjects {
|
|
if subjectIs(kind, subject) {
|
|
result = append(result, subject.Name)
|
|
} else if kind == userKind && subjectIsServiceAccount(subject) {
|
|
// Index is for Users and this references a service account
|
|
result = append(result, serviceaccount.MakeUsername(subject.Namespace, subject.Name))
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// addAccess appends a set of PolicyRules to a given AccessSet
|
|
func addAccess(accessSet *AccessSet, namespace string, roleRef roleRef) {
|
|
for _, rule := range roleRef.rules {
|
|
if len(rule.Resources) > 0 {
|
|
addResourceAccess(accessSet, namespace, rule)
|
|
} else if roleRef.kind == clusterRoleKind {
|
|
accessSet.AddNonResourceURLs(rule.Verbs, rule.NonResourceURLs)
|
|
}
|
|
}
|
|
}
|
|
|
|
func addResourceAccess(accessSet *AccessSet, namespace string, rule rbacv1.PolicyRule) {
|
|
for _, group := range rule.APIGroups {
|
|
for _, resource := range rule.Resources {
|
|
names := rule.ResourceNames
|
|
if len(names) == 0 {
|
|
names = []string{All}
|
|
}
|
|
for _, resourceName := range names {
|
|
for _, verb := range rule.Verbs {
|
|
access := Access{
|
|
Namespace: namespace,
|
|
ResourceName: resourceName,
|
|
}
|
|
|
|
// The first condition namespace != All is to determine if it is a RoleBinding.
|
|
// The second and third conditions are to check if the resource is for "namespaces" in core group.
|
|
// In kubernetes, rule are valid if they satisfy the following
|
|
// - Should be `namespaces` GR
|
|
// - From RoleBindings in `namespace`
|
|
// - From Rule with ResourceName `*`` or the `namespace` itself.
|
|
// Ref: https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/apiserver/pkg/endpoints/request/requestinfo.go#L194
|
|
// If the ResourceName is `All` || namespace itself then only the current namespace is considered as Resourcename
|
|
// In the case of Rolebinding for the resource "namespaces" in core group, access.Namespace
|
|
// is set to All since namespace on the resource "namespaces" is not valid.
|
|
if namespace != All && resource == "namespaces" && group == "" && (resourceName == All || resourceName == namespace) {
|
|
access.Namespace = All
|
|
access.ResourceName = namespace
|
|
}
|
|
|
|
accessSet.Add(verb,
|
|
schema.GroupResource{
|
|
Group: group,
|
|
Resource: resource,
|
|
}, access)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func subjectIs(kind string, subject rbacv1.Subject) bool {
|
|
return subject.APIGroup == rbacGroup && subject.Kind == kind
|
|
}
|
|
|
|
func subjectIsServiceAccount(subject rbacv1.Subject) bool {
|
|
return subject.APIGroup == "" && subject.Kind == svcAccountKind && subject.Namespace != ""
|
|
}
|
|
|
|
// getRules obtain the actual Role or ClusterRole pointed at by a RoleRef, and returns PolicyRules and the resource version
|
|
func (p *policyRuleIndex) getRules(namespace string, roleRef rbacv1.RoleRef) ([]rbacv1.PolicyRule, string) {
|
|
switch roleRef.Kind {
|
|
case "ClusterRole":
|
|
role, err := p.crCache.Get(roleRef.Name)
|
|
if err != nil {
|
|
return nil, ""
|
|
}
|
|
return role.Rules, role.ResourceVersion
|
|
case "Role":
|
|
role, err := p.rCache.Get(namespace, roleRef.Name)
|
|
if err != nil {
|
|
return nil, ""
|
|
}
|
|
return role.Rules, role.ResourceVersion
|
|
}
|
|
|
|
return nil, ""
|
|
}
|
|
|
|
func (p *policyRuleIndex) getClusterRoleBindings(subjectName string) []*rbacv1.ClusterRoleBinding {
|
|
result, err := p.crbCache.GetByIndex(p.clusterRoleIndexKey, subjectName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return result[i].UID < result[j].UID
|
|
})
|
|
return result
|
|
}
|
|
|
|
func (p *policyRuleIndex) getRoleBindings(subjectName string) []*rbacv1.RoleBinding {
|
|
result, err := p.rbCache.GetByIndex(p.roleIndexKey, subjectName)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
sort.Slice(result, func(i, j int) bool {
|
|
return string(result[i].UID) < string(result[j].UID)
|
|
})
|
|
return result
|
|
}
|
|
|
|
// getRoleRefs gathers rules from roles granted to a given subject through RoleBindings and ClusterRoleBindings
|
|
func (p *policyRuleIndex) getRoleRefs(subjectName string) subjectGrants {
|
|
var clusterRoleBindings []roleRef
|
|
for _, crb := range p.getClusterRoleBindings(subjectName) {
|
|
rules, resourceVersion := p.getRules(All, crb.RoleRef)
|
|
clusterRoleBindings = append(clusterRoleBindings, roleRef{
|
|
roleName: crb.RoleRef.Name,
|
|
resourceVersion: resourceVersion,
|
|
rules: rules,
|
|
kind: clusterRoleKind,
|
|
})
|
|
}
|
|
|
|
var roleBindings []roleRef
|
|
for _, rb := range p.getRoleBindings(subjectName) {
|
|
rules, resourceVersion := p.getRules(rb.Namespace, rb.RoleRef)
|
|
roleBindings = append(roleBindings, roleRef{
|
|
roleName: rb.RoleRef.Name,
|
|
namespace: rb.Namespace,
|
|
resourceVersion: resourceVersion,
|
|
rules: rules,
|
|
kind: roleKind,
|
|
})
|
|
}
|
|
|
|
return subjectGrants{
|
|
roleBindings: roleBindings,
|
|
clusterRoleBindings: clusterRoleBindings,
|
|
}
|
|
}
|