// Authorize makes a REST request to the remote service describing the attempted action as a JSON // serialized api.authorization.v1beta1.SubjectAccessReview object. An example request body is // provided bellow. // // { // "apiVersion": "authorization.k8s.io/v1beta1", // "kind": "SubjectAccessReview", // "spec": { // "resourceAttributes": { // "namespace": "kittensandponies", // "verb": "GET", // "group": "group3", // "resource": "pods" // }, // "user": "******", // "group": [ // "group1", // "group2" // ] // } // } // // The remote service is expected to fill the SubjectAccessReviewStatus field to either allow or // disallow access. A permissive response would return: // // { // "apiVersion": "authorization.k8s.io/v1beta1", // "kind": "SubjectAccessReview", // "status": { // "allowed": true // } // } // // To disallow access, the remote service would return: // // { // "apiVersion": "authorization.k8s.io/v1beta1", // "kind": "SubjectAccessReview", // "status": { // "allowed": false, // "reason": "user does not have read access to the namespace" // } // } // func (w *WebhookAuthorizer) Authorize(attr authorizer.Attributes) (authorized bool, reason string, err error) { r := &authorization.SubjectAccessReview{} if user := attr.GetUser(); user != nil { r.Spec = authorization.SubjectAccessReviewSpec{ User: user.GetName(), Groups: user.GetGroups(), Extra: convertToSARExtra(user.GetExtra()), } } if attr.IsResourceRequest() { r.Spec.ResourceAttributes = &authorization.ResourceAttributes{ Namespace: attr.GetNamespace(), Verb: attr.GetVerb(), Group: attr.GetAPIGroup(), Version: attr.GetAPIVersion(), Resource: attr.GetResource(), Subresource: attr.GetSubresource(), Name: attr.GetName(), } } else { r.Spec.NonResourceAttributes = &authorization.NonResourceAttributes{ Path: attr.GetPath(), Verb: attr.GetVerb(), } } key, err := json.Marshal(r.Spec) if err != nil { return false, "", err } if entry, ok := w.responseCache.Get(string(key)); ok { r.Status = entry.(authorization.SubjectAccessReviewStatus) } else { var ( result *authorization.SubjectAccessReview err error ) webhook.WithExponentialBackoff(w.initialBackoff, func() error { result, err = w.subjectAccessReview.Create(r) return err }) if err != nil { // An error here indicates bad configuration or an outage. Log for debugging. glog.Errorf("Failed to make webhook authorizer request: %v", err) return false, "", err } r.Status = result.Status if r.Status.Allowed { w.responseCache.Add(string(key), r.Status, w.authorizedTTL) } else { w.responseCache.Add(string(key), r.Status, w.unauthorizedTTL) } } return r.Status.Allowed, r.Status.Reason, nil }
func resourceMatches(p api.Policy, a authorizer.Attributes) bool { // A resource policy cannot match a non-resource request if a.IsResourceRequest() { if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() { if p.Spec.Resource == "*" || p.Spec.Resource == a.GetResource() { if p.Spec.APIGroup == "*" || p.Spec.APIGroup == a.GetAPIGroup() { return true } } } } return false }
func forbiddenMessage(attributes authorizer.Attributes) string { username := "" if user := attributes.GetUser(); user != nil { username = user.GetName() } resource := attributes.GetResource() if group := attributes.GetAPIGroup(); len(group) > 0 { resource = resource + "." + group } if subresource := attributes.GetSubresource(); len(subresource) > 0 { resource = resource + "/" + subresource } if ns := attributes.GetNamespace(); len(ns) > 0 { return fmt.Sprintf("User %q cannot %s %s in the namespace %q.", username, attributes.GetVerb(), resource, ns) } return fmt.Sprintf("User %q cannot %s %s at the cluster scope.", username, attributes.GetVerb(), resource) }
func (r *RBACAuthorizer) Authorize(requestAttributes authorizer.Attributes) (bool, string, error) { rules, ruleResolutionError := r.authorizationRuleResolver.RulesFor(requestAttributes.GetUser(), requestAttributes.GetNamespace()) if RulesAllow(requestAttributes, rules...) { return true, "", nil } // Build a detailed log of the denial. // Make the whole block conditional so we don't do a lot of string-building we won't use. if glog.V(2) { var operation string if requestAttributes.IsResourceRequest() { operation = fmt.Sprintf( "%q on \"%v.%v/%v\"", requestAttributes.GetVerb(), requestAttributes.GetResource(), requestAttributes.GetAPIGroup(), requestAttributes.GetSubresource(), ) } else { operation = fmt.Sprintf("%q nonResourceURL %q", requestAttributes.GetVerb(), requestAttributes.GetPath()) } var scope string if ns := requestAttributes.GetNamespace(); len(ns) > 0 { scope = fmt.Sprintf("in namespace %q", ns) } else { scope = "cluster-wide" } glog.Infof("RBAC DENY: user %q groups %v cannot %s %s", requestAttributes.GetUser().GetName(), requestAttributes.GetUser().GetGroups(), operation, scope) } reason := "" if ruleResolutionError != nil { reason = fmt.Sprintf("%v", ruleResolutionError) } return false, reason, nil }
// AllowedSubjects returns the subjects that can perform an action and any errors encountered while computing the list. // It is possible to have both subjects and errors returned if some rolebindings couldn't be resolved, but others could be. func (r *SubjectAccessEvaluator) AllowedSubjects(requestAttributes authorizer.Attributes) ([]rbac.Subject, error) { subjects := []rbac.Subject{{Kind: rbac.GroupKind, Name: user.SystemPrivilegedGroup}} if len(r.superUser) > 0 { subjects = append(subjects, rbac.Subject{Kind: rbac.UserKind, APIVersion: "v1alpha1", Name: r.superUser}) } errorlist := []error{} if clusterRoleBindings, err := r.clusterRoleBindingLister.ListClusterRoleBindings(); err != nil { errorlist = append(errorlist, err) } else { for _, clusterRoleBinding := range clusterRoleBindings { rules, err := r.roleToRuleMapper.GetRoleReferenceRules(clusterRoleBinding.RoleRef, "") if err != nil { // if we have an error, just keep track of it and keep processing. Since rules are additive, // missing a reference is bad, but we can continue with other rolebindings and still have a list // that does not contain any invalid values errorlist = append(errorlist, err) } if RulesAllow(requestAttributes, rules...) { subjects = append(subjects, clusterRoleBinding.Subjects...) } } } if namespace := requestAttributes.GetNamespace(); len(namespace) > 0 { if roleBindings, err := r.roleBindingLister.ListRoleBindings(namespace); err != nil { errorlist = append(errorlist, err) } else { for _, roleBinding := range roleBindings { rules, err := r.roleToRuleMapper.GetRoleReferenceRules(roleBinding.RoleRef, namespace) if err != nil { // if we have an error, just keep track of it and keep processing. Since rules are additive, // missing a reference is bad, but we can continue with other rolebindings and still have a list // that does not contain any invalid values errorlist = append(errorlist, err) } if RulesAllow(requestAttributes, rules...) { subjects = append(subjects, roleBinding.Subjects...) } } } } dedupedSubjects := []rbac.Subject{} for _, subject := range subjects { found := false for _, curr := range dedupedSubjects { if curr == subject { found = true break } } if !found { dedupedSubjects = append(dedupedSubjects, subject) } } return subjects, utilerrors.NewAggregate(errorlist) }