func (a *gcPermissionsEnforcement) Admit(attributes admission.Attributes) (err error) { // if we aren't changing owner references, then the edit is always allowed if !isChangingOwnerReference(attributes.GetObject(), attributes.GetOldObject()) { return nil } deleteAttributes := authorizer.AttributesRecord{ User: attributes.GetUserInfo(), Verb: "delete", Namespace: attributes.GetNamespace(), APIGroup: attributes.GetResource().Group, APIVersion: attributes.GetResource().Version, Resource: attributes.GetResource().Resource, Subresource: attributes.GetSubresource(), Name: attributes.GetName(), ResourceRequest: true, Path: "", } allowed, reason, err := a.authorizer.Authorize(deleteAttributes) if allowed { return nil } return admission.NewForbidden(attributes, fmt.Errorf("cannot set an ownerRef on a resource you can't delete: %v, %v", reason, err)) }
// Admit determines if the endpoints object should be admitted func (r *restrictedEndpointsAdmission) Admit(a kadmission.Attributes) error { if a.GetResource().GroupResource() != kapi.Resource("endpoints") { return nil } ep, ok := a.GetObject().(*kapi.Endpoints) if !ok { return nil } old, ok := a.GetOldObject().(*kapi.Endpoints) if ok && reflect.DeepEqual(ep.Subsets, old.Subsets) { return nil } restrictedIP := r.findRestrictedIP(ep) if restrictedIP == "" { return nil } allow, err := r.checkAccess(a) if err != nil { return err } if !allow { return kadmission.NewForbidden(a, fmt.Errorf("endpoint address %s is not allowed", restrictedIP)) } return nil }
// Admit determines if the service should be admitted based on the configured network CIDR. func (r *externalIPRanger) Admit(a kadmission.Attributes) error { if a.GetResource().GroupResource() != kapi.Resource("services") { return nil } svc, ok := a.GetObject().(*kapi.Service) // if we can't convert then we don't handle this object so just return if !ok { return nil } // Determine if an ingress ip address should be allowed as an // external ip by checking the loadbalancer status of the previous // object state. Only updates need to be validated against the // ingress ip since the loadbalancer status cannot be set on // create. ingressIP := "" retrieveIngressIP := a.GetOperation() == kadmission.Update && r.allowIngressIP && svc.Spec.Type == kapi.ServiceTypeLoadBalancer if retrieveIngressIP { old, ok := a.GetOldObject().(*kapi.Service) ipPresent := ok && old != nil && len(old.Status.LoadBalancer.Ingress) > 0 if ipPresent { ingressIP = old.Status.LoadBalancer.Ingress[0].IP } } var errs field.ErrorList switch { // administrator disabled externalIPs case len(svc.Spec.ExternalIPs) > 0 && len(r.admit) == 0: onlyIngressIP := len(svc.Spec.ExternalIPs) == 1 && svc.Spec.ExternalIPs[0] == ingressIP if !onlyIngressIP { errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs"), "externalIPs have been disabled")) } // administrator has limited the range case len(svc.Spec.ExternalIPs) > 0 && len(r.admit) > 0: for i, s := range svc.Spec.ExternalIPs { ip := net.ParseIP(s) if ip == nil { errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs").Index(i), "externalIPs must be a valid address")) continue } notIngressIP := s != ingressIP if (NetworkSlice(r.reject).Contains(ip) || !NetworkSlice(r.admit).Contains(ip)) && notIngressIP { errs = append(errs, field.Forbidden(field.NewPath("spec", "externalIPs").Index(i), "externalIP is not allowed")) continue } } } if len(errs) > 0 { return apierrs.NewInvalid(a.GetKind().GroupKind(), a.GetName(), errs) } return nil }
// checkRequest verifies that the request does not exceed any quota constraint. it returns back a copy of quotas not yet persisted // that capture what the usage would be if the request succeeded. It return an error if the is insufficient quota to satisfy the request func (e *quotaEvaluator) checkRequest(quotas []api.ResourceQuota, a admission.Attributes) ([]api.ResourceQuota, error) { namespace := a.GetNamespace() evaluators := e.registry.Evaluators() evaluator, found := evaluators[a.GetKind().GroupKind()] if !found { return quotas, nil } op := a.GetOperation() operationResources := evaluator.OperationResources(op) if len(operationResources) == 0 { return quotas, nil } // find the set of quotas that are pertinent to this request // reject if we match the quota, but usage is not calculated yet // reject if the input object does not satisfy quota constraints // if there are no pertinent quotas, we can just return inputObject := a.GetObject() interestingQuotaIndexes := []int{} for i := range quotas { resourceQuota := quotas[i] match := evaluator.Matches(&resourceQuota, inputObject) if !match { continue } hardResources := quota.ResourceNames(resourceQuota.Status.Hard) evaluatorResources := evaluator.MatchesResources() requiredResources := quota.Intersection(hardResources, evaluatorResources) err := evaluator.Constraints(requiredResources, inputObject) if err != nil { return nil, admission.NewForbidden(a, fmt.Errorf("Failed quota: %s: %v", resourceQuota.Name, err)) } if !hasUsageStats(&resourceQuota) { return nil, admission.NewForbidden(a, fmt.Errorf("Status unknown for quota: %s", resourceQuota.Name)) } interestingQuotaIndexes = append(interestingQuotaIndexes, i) } if len(interestingQuotaIndexes) == 0 { return quotas, nil } // Usage of some resources cannot be counted in isolation. For example when // the resource represents a number of unique references to external // resource. In such a case an evaluator needs to process other objects in // the same namespace which needs to be known. if accessor, err := meta.Accessor(inputObject); namespace != "" && err == nil { if accessor.GetNamespace() == "" { accessor.SetNamespace(namespace) } } // there is at least one quota that definitely matches our object // as a result, we need to measure the usage of this object for quota // on updates, we need to subtract the previous measured usage // if usage shows no change, just return since it has no impact on quota deltaUsage := evaluator.Usage(inputObject) if admission.Update == op { prevItem := a.GetOldObject() if prevItem == nil { return nil, admission.NewForbidden(a, fmt.Errorf("Unable to get previous usage since prior version of object was not found")) } // if we can definitively determine that this is not a case of "create on update", // then charge based on the delta. Otherwise, bill the maximum metadata, err := meta.Accessor(prevItem) if err == nil && len(metadata.GetResourceVersion()) > 0 { prevUsage := evaluator.Usage(prevItem) deltaUsage = quota.Subtract(deltaUsage, prevUsage) } } if quota.IsZero(deltaUsage) { return quotas, nil } for _, index := range interestingQuotaIndexes { resourceQuota := quotas[index] hardResources := quota.ResourceNames(resourceQuota.Status.Hard) requestedUsage := quota.Mask(deltaUsage, hardResources) newUsage := quota.Add(resourceQuota.Status.Used, requestedUsage) maskedNewUsage := quota.Mask(newUsage, quota.ResourceNames(requestedUsage)) if allowed, exceeded := quota.LessThanOrEqual(maskedNewUsage, resourceQuota.Status.Hard); !allowed { failedRequestedUsage := quota.Mask(requestedUsage, exceeded) failedUsed := quota.Mask(resourceQuota.Status.Used, exceeded) failedHard := quota.Mask(resourceQuota.Status.Hard, exceeded) return nil, admission.NewForbidden(a, fmt.Errorf("Exceeded quota: %s, requested: %s, used: %s, limited: %s", resourceQuota.Name, prettyPrint(failedRequestedUsage), prettyPrint(failedUsed), prettyPrint(failedHard))) } // update to the new usage number quotas[index].Status.Used = newUsage } return quotas, nil }
// Admit makes admission decisions that enforce restrictions on adding // project-scoped role-bindings. In order for a role binding to be permitted, // each subject in the binding must be matched by some rolebinding restriction // in the namespace. func (q *restrictUsersAdmission) Admit(a admission.Attributes) (err error) { // We only care about rolebindings and policybindings; ignore anything else. switch a.GetResource().GroupResource() { case authorizationapi.Resource("rolebindings"): case authorizationapi.Resource("policybindings"): default: return nil } // Ignore all operations that correspond to subresource actions. if len(a.GetSubresource()) != 0 { return nil } ns := a.GetNamespace() // Ignore cluster-level resources. if len(ns) == 0 { return nil } var subjects, oldSubjects []kapi.ObjectReference obj, oldObj := a.GetObject(), a.GetOldObject() switch a.GetResource().GroupResource() { case authorizationapi.Resource("rolebindings"): rolebinding, ok := obj.(*authorizationapi.RoleBinding) if !ok { return admission.NewForbidden(a, fmt.Errorf("wrong object type for new rolebinding: %t", obj)) } subjects = rolebinding.Subjects if len(subjects) == 0 { return nil } if oldObj != nil { oldrolebinding, ok := oldObj.(*authorizationapi.RoleBinding) if !ok { return admission.NewForbidden(a, fmt.Errorf("wrong object type for old rolebinding: %t", oldObj)) } oldSubjects = oldrolebinding.Subjects } glog.V(4).Infof("Handling rolebinding %s/%s", rolebinding.Namespace, rolebinding.Name) case authorizationapi.Resource("policybindings"): policybinding, ok := obj.(*authorizationapi.PolicyBinding) if !ok { return admission.NewForbidden(a, fmt.Errorf("wrong object type for new policybinding: %t", obj)) } for _, rolebinding := range policybinding.RoleBindings { subjects = append(subjects, rolebinding.Subjects...) } if len(subjects) == 0 { return nil } if oldObj != nil { oldpolicybinding, ok := oldObj.(*authorizationapi.PolicyBinding) if !ok { return admission.NewForbidden(a, fmt.Errorf("wrong object type for old policybinding: %t", oldObj)) } for _, rolebinding := range oldpolicybinding.RoleBindings { oldSubjects = append(oldSubjects, rolebinding.Subjects...) } } glog.V(4).Infof("Handling policybinding %s/%s", policybinding.Namespace, policybinding.Name) } newSubjects := objectReferenceDelta(oldSubjects, subjects) if len(newSubjects) == 0 { glog.V(4).Infof("No new subjects; admitting") return nil } // TODO: Cache rolebinding restrictions. roleBindingRestrictionList, err := q.oclient.RoleBindingRestrictions(ns). List(kapi.ListOptions{}) if err != nil { return admission.NewForbidden(a, err) } if len(roleBindingRestrictionList.Items) == 0 { glog.V(4).Infof("No rolebinding restrictions specified; admitting") return nil } if !q.groupCache.Running() { return admission.NewForbidden(a, errors.New("groupCache not running")) } checkers := []SubjectChecker{} for _, rbr := range roleBindingRestrictionList.Items { checker, err := NewSubjectChecker(&rbr.Spec) if err != nil { return admission.NewForbidden(a, err) } checkers = append(checkers, checker) } roleBindingRestrictionContext, err := NewRoleBindingRestrictionContext(ns, q.kclient, q.oclient, q.groupCache) if err != nil { return admission.NewForbidden(a, err) } checker := NewUnionSubjectChecker(checkers) errs := []error{} for _, subject := range newSubjects { allowed, err := checker.Allowed(subject, roleBindingRestrictionContext) if err != nil { errs = append(errs, err) } if !allowed { errs = append(errs, fmt.Errorf("rolebindings to %s %q are not allowed in project %q", subject.Kind, subject.Name, ns)) } } if len(errs) != 0 { return admission.NewForbidden(a, kerrors.NewAggregate(errs)) } glog.V(4).Infof("All new subjects are allowed; admitting") return nil }