|
| 1 | +package users |
| 2 | + |
| 3 | +import ( |
| 4 | + "fmt" |
| 5 | + |
| 6 | + v3 "github.com/rancher/rancher/pkg/apis/management.cattle.io/v3" |
| 7 | + "github.com/rancher/webhook/pkg/admission" |
| 8 | + "github.com/rancher/webhook/pkg/auth" |
| 9 | + controllerv3 "github.com/rancher/webhook/pkg/generated/controllers/management.cattle.io/v3" |
| 10 | + objectsv3 "github.com/rancher/webhook/pkg/generated/objects/management.cattle.io/v3" |
| 11 | + admissionv1 "k8s.io/api/admission/v1" |
| 12 | + admissionregistrationv1 "k8s.io/api/admissionregistration/v1" |
| 13 | + apierrors "k8s.io/apimachinery/pkg/api/errors" |
| 14 | + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 15 | + "k8s.io/apimachinery/pkg/runtime/schema" |
| 16 | + "k8s.io/apiserver/pkg/authentication/user" |
| 17 | + authorizationv1 "k8s.io/client-go/kubernetes/typed/authorization/v1" |
| 18 | + "k8s.io/kubernetes/pkg/registry/rbac/validation" |
| 19 | +) |
| 20 | + |
| 21 | +var ( |
| 22 | + gvr = schema.GroupVersionResource{ |
| 23 | + Group: "management.cattle.io", |
| 24 | + Version: "v3", |
| 25 | + Resource: "users", |
| 26 | + } |
| 27 | + manageUsersVerb = "manage-users" |
| 28 | +) |
| 29 | + |
| 30 | +type admitter struct { |
| 31 | + resolver validation.AuthorizationRuleResolver |
| 32 | + sar authorizationv1.SubjectAccessReviewInterface |
| 33 | + userAttributeCache controllerv3.UserAttributeCache |
| 34 | +} |
| 35 | + |
| 36 | +// Validator validates tokens. |
| 37 | +type Validator struct { |
| 38 | + admitter admitter |
| 39 | +} |
| 40 | + |
| 41 | +// NewValidator returns a new Validator instance. |
| 42 | +func NewValidator(userAttributeCache controllerv3.UserAttributeCache, sar authorizationv1.SubjectAccessReviewInterface, defaultResolver validation.AuthorizationRuleResolver) *Validator { |
| 43 | + return &Validator{ |
| 44 | + admitter: admitter{ |
| 45 | + resolver: defaultResolver, |
| 46 | + userAttributeCache: userAttributeCache, |
| 47 | + sar: sar, |
| 48 | + }, |
| 49 | + } |
| 50 | +} |
| 51 | + |
| 52 | +// GVR returns the GroupVersionResource. |
| 53 | +func (v *Validator) GVR() schema.GroupVersionResource { |
| 54 | + return gvr |
| 55 | +} |
| 56 | + |
| 57 | +// Operations returns list of operations handled by the validator. |
| 58 | +func (v *Validator) Operations() []admissionregistrationv1.OperationType { |
| 59 | + return []admissionregistrationv1.OperationType{admissionregistrationv1.Update, admissionregistrationv1.Delete} |
| 60 | +} |
| 61 | + |
| 62 | +func (v *Validator) ValidatingWebhook(clientConfig admissionregistrationv1.WebhookClientConfig) []admissionregistrationv1.ValidatingWebhook { |
| 63 | + return []admissionregistrationv1.ValidatingWebhook{ |
| 64 | + *admission.NewDefaultValidatingWebhook(v, clientConfig, admissionregistrationv1.ClusterScope, v.Operations()), |
| 65 | + } |
| 66 | +} |
| 67 | + |
| 68 | +// Admitters returns the admitter objects. |
| 69 | +func (v *Validator) Admitters() []admission.Admitter { |
| 70 | + return []admission.Admitter{&v.admitter} |
| 71 | +} |
| 72 | + |
| 73 | +func (a *admitter) Admit(request *admission.Request) (*admissionv1.AdmissionResponse, error) { |
| 74 | + // Check if requester has manage-user verb |
| 75 | + hasManageUsers, err := auth.RequestUserHasVerb(request, gvr, a.sar, manageUsersVerb, "", "") |
| 76 | + if err != nil { |
| 77 | + return nil, fmt.Errorf("failed to check if requester has manage-users verb: %w", err) |
| 78 | + } |
| 79 | + |
| 80 | + if hasManageUsers { |
| 81 | + return &admissionv1.AdmissionResponse{Allowed: true}, nil |
| 82 | + } |
| 83 | + userObj, err := objectsv3.UserFromRequest(&request.AdmissionRequest) |
| 84 | + if err != nil { |
| 85 | + return nil, fmt.Errorf("failed to get current User from request: %w", err) |
| 86 | + } |
| 87 | + |
| 88 | + // Need the UserAttribute to find the groups |
| 89 | + userAttribute, err := a.userAttributeCache.Get(userObj.Name) |
| 90 | + if err != nil && !apierrors.IsNotFound(err) { |
| 91 | + return nil, fmt.Errorf("failed to get UserAttribute for %s: %w", userObj.Name, err) |
| 92 | + } |
| 93 | + |
| 94 | + userInfo := &user.DefaultInfo{ |
| 95 | + Name: userObj.Name, |
| 96 | + Groups: getGroupsFromUserAttribute(userAttribute), |
| 97 | + } |
| 98 | + |
| 99 | + // Get all rules for the user being modified |
| 100 | + rules, err := a.resolver.RulesFor(userInfo, "") |
| 101 | + if err != nil { |
| 102 | + return nil, fmt.Errorf("failed to get rules for user %v: %w", userObj, err) |
| 103 | + } |
| 104 | + |
| 105 | + // Ensure that rules of the user being modified aren't greater than the rules of the user making the request |
| 106 | + err = auth.ConfirmNoEscalation(request, rules, "", a.resolver) |
| 107 | + if err != nil { |
| 108 | + return &admissionv1.AdmissionResponse{ |
| 109 | + Allowed: false, |
| 110 | + Result: &metav1.Status{ |
| 111 | + Status: metav1.StatusFailure, |
| 112 | + Reason: "ConfirmNoEscalationError", |
| 113 | + Message: fmt.Sprintf("request is attempting to modify user with more permissions than requester %v", err), |
| 114 | + }, |
| 115 | + }, nil |
| 116 | + } |
| 117 | + return &admissionv1.AdmissionResponse{Allowed: true}, nil |
| 118 | +} |
| 119 | + |
| 120 | +// getGroupsFromUserAttributes gets the list of group principals from a UserAttribute. |
| 121 | +// |
| 122 | +// Warning: UserAttributes are only updated when a user logs in, so this may not have the up to date Group Principals. |
| 123 | +func getGroupsFromUserAttribute(userAttribute *v3.UserAttribute) []string { |
| 124 | + result := []string{} |
| 125 | + if userAttribute == nil { |
| 126 | + return result |
| 127 | + } |
| 128 | + for _, principals := range userAttribute.GroupPrincipals { |
| 129 | + for _, principal := range principals.Items { |
| 130 | + result = append(result, principal.Name) |
| 131 | + } |
| 132 | + } |
| 133 | + return result |
| 134 | +} |
0 commit comments