// Admit will deny any pod that defines AntiAffinity topology key other than unversioned.LabelHostname i.e. "kubernetes.io/hostname" // in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution. func (p *plugin) Admit(attributes admission.Attributes) (err error) { if attributes.GetResource().GroupResource() != api.Resource("pods") { return nil } pod, ok := attributes.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } affinity, err := api.GetAffinityFromPodAnnotations(pod.Annotations) if err != nil { // this is validated later return nil } if affinity.PodAntiAffinity != nil { var podAntiAffinityTerms []api.PodAffinityTerm if len(affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 { podAntiAffinityTerms = affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution } // TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution. //if len(affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution) != 0 { // podAntiAffinityTerms = append(podAntiAffinityTerms, affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution...) //} for _, v := range podAntiAffinityTerms { if v.TopologyKey != unversioned.LabelHostname { return apierrors.NewForbidden(attributes.GetResource().GroupResource(), pod.Name, fmt.Errorf("affinity.PodAntiAffinity.RequiredDuringScheduling has TopologyKey %v but only key %v is allowed", v.TopologyKey, unversioned.LabelHostname)) } } } return nil }
// Admit will deny any pod that defines SELinuxOptions or RunAsUser. func (p *plugin) Admit(a admission.Attributes) (err error) { if a.GetResource() != string(api.ResourcePods) { return nil } pod, ok := a.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SupplementalGroups != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.SupplementalGroups is forbidden")) } if pod.Spec.SecurityContext != nil { if pod.Spec.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("pod.Spec.SecurityContext.SELinuxOptions is forbidden")) } if pod.Spec.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("pod.Spec.SecurityContext.RunAsUser is forbidden")) } } for _, v := range pod.Spec.Containers { if v.SecurityContext != nil { if v.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden")) } if v.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden")) } } } 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() != 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 } var errs field.ErrorList switch { // administrator disabled externalIPs case len(svc.Spec.ExternalIPs) > 0 && len(r.admit) == 0: 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 } if networkSlice(r.reject).Contains(ip) || !networkSlice(r.admit).Contains(ip) { 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(), a.GetName(), errs) } return nil }
// 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 will deny pods that have a RunAsUser set that isn't the uid of the user requesting it func (p *plugin) Admit(a admission.Attributes) (err error) { if a.GetResource() != string(api.ResourcePods) { return nil } pod, ok := a.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } user := a.GetUserInfo() if user == nil { return apierrors.NewBadRequest("uidenforcer admission controller can not be used if there is no user set") } for i := 0; i < len(pod.Spec.Containers); i++ { container := &pod.Spec.Containers[i] uid, ok := strconv.ParseInt(user.GetUID(), 10, 32) if ok == nil { if container.SecurityContext == nil { container.SecurityContext = &api.SecurityContext{ RunAsUser: &uid, } } else { container.SecurityContext.RunAsUser = &uid } } else { return apierrors.NewBadRequest("Requesting user's uid is not an integer") } } return nil }
func (a *runOnceDuration) Admit(attributes admission.Attributes) error { switch { case a.config == nil, !a.config.Enabled, attributes.GetResource() != kapi.Resource("pods"), len(attributes.GetSubresource()) > 0: return nil } pod, ok := attributes.GetObject().(*kapi.Pod) if !ok { return admission.NewForbidden(attributes, fmt.Errorf("unexpected object: %#v", attributes.GetObject())) } // Only update pods with a restart policy of Never or OnFailure switch pod.Spec.RestartPolicy { case kapi.RestartPolicyNever, kapi.RestartPolicyOnFailure: // continue default: return nil } appliedProjectOverride, err := a.applyProjectAnnotationOverride(attributes.GetNamespace(), pod) if err != nil { return admission.NewForbidden(attributes, err) } if !appliedProjectOverride && a.config.ActiveDeadlineSecondsOverride != nil { pod.Spec.ActiveDeadlineSeconds = a.config.ActiveDeadlineSecondsOverride } return nil }
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 pod should be admitted based on the requested security context // and the available SCCs. // // 1. Find SCCs for the user. // 2. Find SCCs for the SA. If there is an error retrieving SA SCCs it is not fatal. // 3. Remove duplicates between the user/SA SCCs. // 4. Create the providers, includes setting pre-allocated values if necessary. // 5. Try to generate and validate an SCC with providers. If we find one then admit the pod // with the validated SCC. If we don't find any reject the pod and give all errors from the // failed attempts. func (c *constraint) Admit(a kadmission.Attributes) error { if a.GetResource().Resource != string(kapi.ResourcePods) { return nil } pod, ok := a.GetObject().(*kapi.Pod) // if we can't convert then we don't handle this object so just return if !ok { return nil } // get all constraints that are usable by the user glog.V(4).Infof("getting security context constraints for pod %s (generate: %s) in namespace %s with user info %v", pod.Name, pod.GenerateName, a.GetNamespace(), a.GetUserInfo()) matchedConstraints, err := getMatchingSecurityContextConstraints(c.store, a.GetUserInfo()) if err != nil { return kadmission.NewForbidden(a, err) } // get all constraints that are usable by the SA if len(pod.Spec.ServiceAccountName) > 0 { userInfo := serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") glog.V(4).Infof("getting security context constraints for pod %s (generate: %s) with service account info %v", pod.Name, pod.GenerateName, userInfo) saConstraints, err := getMatchingSecurityContextConstraints(c.store, userInfo) if err != nil { return kadmission.NewForbidden(a, err) } matchedConstraints = append(matchedConstraints, saConstraints...) } // remove duplicate constraints and sort matchedConstraints = deduplicateSecurityContextConstraints(matchedConstraints) sort.Sort(ByPriority(matchedConstraints)) providers, errs := c.createProvidersFromConstraints(a.GetNamespace(), matchedConstraints) logProviders(pod, providers, errs) if len(providers) == 0 { return kadmission.NewForbidden(a, fmt.Errorf("no providers available to validated pod request")) } // all containers in a single pod must validate under a single provider or we will reject the request validationErrs := field.ErrorList{} for _, provider := range providers { if errs := assignSecurityContext(provider, pod, field.NewPath(fmt.Sprintf("provider %s: ", provider.GetSCCName()))); len(errs) > 0 { validationErrs = append(validationErrs, errs...) continue } // the entire pod validated, annotate and accept the pod glog.V(4).Infof("pod %s (generate: %s) validated against provider %s", pod.Name, pod.GenerateName, provider.GetSCCName()) if pod.ObjectMeta.Annotations == nil { pod.ObjectMeta.Annotations = map[string]string{} } pod.ObjectMeta.Annotations[allocator.ValidatedSCCAnnotation] = provider.GetSCCName() return nil } // we didn't validate against any security context constraint provider, reject the pod and give the errors for each attempt glog.V(4).Infof("unable to validate pod %s (generate: %s) against any security context constraint: %v", pod.Name, pod.GenerateName, validationErrs) return kadmission.NewForbidden(a, fmt.Errorf("unable to validate against any security context constraint: %v", validationErrs)) }
func (d *denyExec) Admit(a admission.Attributes) (err error) { connectRequest, ok := a.GetObject().(*rest.ConnectRequest) if !ok { return errors.NewBadRequest("a connect request was received, but could not convert the request object.") } // Only handle exec or attach requests on pods if connectRequest.ResourcePath != "pods/exec" && connectRequest.ResourcePath != "pods/attach" { return nil } pod, err := d.client.Pods(a.GetNamespace()).Get(connectRequest.Name) if err != nil { return admission.NewForbidden(a, err) } if d.hostPID && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostPID { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host pid")) } if d.hostIPC && pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.HostIPC { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a container using host ipc")) } if d.privileged && isPrivileged(pod) { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into or attach to a privileged container")) } return nil }
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace func (l *limitRanger) Admit(a admission.Attributes) (err error) { // Ignore all calls to subresources if a.GetSubresource() != "" { return nil } obj := a.GetObject() name := "Unknown" if obj != nil { name, _ = meta.NewAccessor().Name(obj) if len(name) == 0 { name, _ = meta.NewAccessor().GenerateName(obj) } } key := &api.LimitRange{ ObjectMeta: api.ObjectMeta{ Namespace: a.GetNamespace(), Name: "", }, } items, err := l.indexer.Index("namespace", key) if err != nil { return admission.NewForbidden(a, fmt.Errorf("Unable to %s %v at this time because there was an error enforcing limit ranges", a.GetOperation(), a.GetResource())) } // if there are no items held in our indexer, check our live-lookup LRU, if that misses, do the live lookup to prime it. if len(items) == 0 { lruItemObj, ok := l.liveLookupCache.Get(a.GetNamespace()) if !ok || lruItemObj.(liveLookupEntry).expiry.Before(time.Now()) { liveList, err := l.client.Core().LimitRanges(a.GetNamespace()).List(api.ListOptions{}) if err != nil { return admission.NewForbidden(a, err) } newEntry := liveLookupEntry{expiry: time.Now().Add(l.liveTTL)} for i := range liveList.Items { newEntry.items = append(newEntry.items, &liveList.Items[i]) } l.liveLookupCache.Add(a.GetNamespace(), newEntry) lruItemObj = newEntry } lruEntry := lruItemObj.(liveLookupEntry) for i := range lruEntry.items { items = append(items, lruEntry.items[i]) } } // ensure it meets each prescribed min/max for i := range items { limitRange := items[i].(*api.LimitRange) err = l.limitFunc(limitRange, a.GetResource().Resource, a.GetObject()) if err != nil { return admission.NewForbidden(a, err) } } return nil }
// GetPod returns a pod from an admission attributes object func GetPod(a admission.Attributes) (*kapi.Pod, error) { pod, isPod := a.GetObject().(*kapi.Pod) if !isPod { return nil, admission.NewForbidden(a, fmt.Errorf("unrecognized request object: %#v", a.GetObject())) } return pod, nil }
// extract the PodSpec from the pod templates for each object we care about func (o *podNodeConstraints) getPodSpec(attr admission.Attributes) (kapi.PodSpec, error) { spec, _, err := meta.GetPodSpec(attr.GetObject()) if err != nil { return kapi.PodSpec{}, kapierrors.NewInternalError(err) } return *spec, nil }
// Admit will deny any pod that defines AntiAffinity topology key other than metav1.LabelHostname i.e. "kubernetes.io/hostname" // in requiredDuringSchedulingRequiredDuringExecution and requiredDuringSchedulingIgnoredDuringExecution. func (p *plugin) Admit(attributes admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if len(attributes.GetSubresource()) != 0 || attributes.GetResource().GroupResource() != api.Resource("pods") { return nil } pod, ok := attributes.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } affinity, err := api.GetAffinityFromPodAnnotations(pod.Annotations) if err != nil { glog.V(5).Infof("Invalid Affinity detected, but we will leave handling of this to validation phase") return nil } if affinity != nil && affinity.PodAntiAffinity != nil { var podAntiAffinityTerms []api.PodAffinityTerm if len(affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution) != 0 { podAntiAffinityTerms = affinity.PodAntiAffinity.RequiredDuringSchedulingIgnoredDuringExecution } // TODO: Uncomment this block when implement RequiredDuringSchedulingRequiredDuringExecution. //if len(affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution) != 0 { // podAntiAffinityTerms = append(podAntiAffinityTerms, affinity.PodAntiAffinity.RequiredDuringSchedulingRequiredDuringExecution...) //} for _, v := range podAntiAffinityTerms { if v.TopologyKey != metav1.LabelHostname { return apierrors.NewForbidden(attributes.GetResource().GroupResource(), pod.Name, fmt.Errorf("affinity.PodAntiAffinity.RequiredDuringScheduling has TopologyKey %v but only key %v is allowed", v.TopologyKey, metav1.LabelHostname)) } } } return nil }
// TODO this will need to update when we have pod requests/limits func (a *clusterResourceOverridePlugin) Admit(attr admission.Attributes) error { glog.V(6).Infof("%s admission controller is invoked", api.PluginName) if a.config == nil || attr.GetResource() != kapi.Resource("pods") || attr.GetSubresource() != "" { return nil // not applicable } pod, ok := attr.GetObject().(*kapi.Pod) if !ok { return admission.NewForbidden(attr, fmt.Errorf("unexpected object: %#v", attr.GetObject())) } glog.V(5).Infof("%s is looking at creating pod %s in project %s", api.PluginName, pod.Name, attr.GetNamespace()) // allow annotations on project to override if ns, err := a.ProjectCache.GetNamespace(attr.GetNamespace()); err != nil { glog.Warningf("%s got an error retrieving namespace: %v", api.PluginName, err) return admission.NewForbidden(attr, err) // this should not happen though } else { projectEnabledPlugin, exists := ns.Annotations[clusterResourceOverrideAnnotation] if exists && projectEnabledPlugin != "true" { glog.V(5).Infof("%s is disabled for project %s", api.PluginName, attr.GetNamespace()) return nil // disabled for this project, do nothing } } // Reuse LimitRanger logic to apply limit/req defaults from the project. Ignore validation // errors, assume that LimitRanger will run after this plugin to validate. glog.V(5).Infof("%s: initial pod limits are: %#v", api.PluginName, pod.Spec.Containers[0].Resources) if err := a.LimitRanger.Admit(attr); err != nil { glog.V(5).Infof("%s: error from LimitRanger: %#v", api.PluginName, err) } glog.V(5).Infof("%s: pod limits after LimitRanger are: %#v", api.PluginName, pod.Spec.Containers[0].Resources) for _, container := range pod.Spec.Containers { resources := container.Resources memLimit, memFound := resources.Limits[kapi.ResourceMemory] if memFound && a.config.memoryRequestToLimitRatio.Cmp(zeroDec) != 0 { resources.Requests[kapi.ResourceMemory] = resource.Quantity{ Amount: multiply(memLimit.Amount, a.config.memoryRequestToLimitRatio), Format: resource.BinarySI, } } if memFound && a.config.limitCPUToMemoryRatio.Cmp(zeroDec) != 0 { resources.Limits[kapi.ResourceCPU] = resource.Quantity{ // float math is necessary here as there is no way to create an inf.Dec to represent cpuBaseScaleFactor < 0.001 Amount: multiply(inf.NewDec(int64(float64(memLimit.Value())*cpuBaseScaleFactor), 3), a.config.limitCPUToMemoryRatio), Format: resource.DecimalSI, } } cpuLimit, cpuFound := resources.Limits[kapi.ResourceCPU] if cpuFound && a.config.cpuRequestToLimitRatio.Cmp(zeroDec) != 0 { resources.Requests[kapi.ResourceCPU] = resource.Quantity{ Amount: multiply(cpuLimit.Amount, a.config.cpuRequestToLimitRatio), Format: resource.DecimalSI, } } } glog.V(5).Infof("%s: pod limits after overrides are: %#v", api.PluginName, pod.Spec.Containers[0].Resources) 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 }
func (ir initialResources) Admit(a admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if a.GetSubresource() != "" || a.GetResource() != api.Resource("pods") { return nil } pod, ok := a.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } ir.estimateAndFillResourcesIfNotSet(pod) return nil }
func (a *alwaysPullImages) Admit(attributes admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if len(attributes.GetSubresource()) != 0 || attributes.GetResource() != api.Resource("pods") { return nil } pod, ok := attributes.GetObject().(*api.Pod) if !ok { return apierrors.NewBadRequest("Resource was marked with kind Pod but was unable to be converted") } for i := range pod.Spec.Containers { pod.Spec.Containers[i].ImagePullPolicy = api.PullAlways } return nil }
// extract the PodSpec from the pod templates for each object we care about func (o *podNodeConstraints) getPodSpec(attr admission.Attributes) (kapi.PodSpec, error) { switch r := attr.GetObject().(type) { case *kapi.Pod: return r.Spec, nil case *kapi.PodTemplate: return r.Template.Spec, nil case *kapi.ReplicationController: return r.Spec.Template.Spec, nil case *extensions.Deployment: return r.Spec.Template.Spec, nil case *extensions.ReplicaSet: return r.Spec.Template.Spec, nil case *batch.Job: return r.Spec.Template.Spec, nil case *batch.ScheduledJob: return r.Spec.JobTemplate.Spec.Template.Spec, nil case *batch.JobTemplate: return r.Template.Spec.Template.Spec, nil case *deployapi.DeploymentConfig: return r.Spec.Template.Spec, nil case *securityapi.PodSecurityPolicySubjectReview: return r.Spec.PodSpec, nil case *securityapi.PodSecurityPolicySelfSubjectReview: return r.Spec.PodSpec, nil case *securityapi.PodSecurityPolicyReview: return r.Spec.PodSpec, nil } return kapi.PodSpec{}, kapierrors.NewInternalError(fmt.Errorf("No PodSpec available for supplied admission attribute")) }
func (p *testAdmissionPlugin) Admit(a admission.Attributes) (err error) { obj := a.GetObject() accessor, err := meta.Accessor(obj) if err != nil { return err } labels := accessor.GetLabels() if labels == nil { labels = map[string]string{} } if len(p.labelValue) > 0 { labels[p.name] = p.labelValue } else { labels[p.name] = "default" } accessor.SetLabels(labels) return nil }
func (d *denyExecOnPrivileged) Admit(a admission.Attributes) (err error) { connectRequest, ok := a.GetObject().(*rest.ConnectRequest) if !ok { return errors.NewBadRequest("a connect request was received, but could not convert the request object.") } // Only handle exec requests on pods if connectRequest.ResourcePath != "pods/exec" { return nil } pod, err := d.client.Pods(a.GetNamespace()).Get(connectRequest.Name) if err != nil { return admission.NewForbidden(a, err) } if isPrivileged(pod) { return admission.NewForbidden(a, fmt.Errorf("Cannot exec into a privileged container")) } return nil }
// Admit makes admission decisions while enforcing ownerReference func (q *ownerReferenceAdmission) Admit(a admission.Attributes) (err error) { metadata, err := meta.Accessor(a.GetObject()) if err != nil { // if we don't have object meta, we don't have fields we're trying to control return nil } // TODO if we have an old object, only consider new owner references and finalizers // this is critical when doing an actual authz check. if ownerRefs := metadata.GetOwnerReferences(); len(ownerRefs) > 0 { return admission.NewForbidden(a, fmt.Errorf("ownerReferences are disabled: %v", ownerRefs)) } if finalizers := metadata.GetFinalizers(); len(finalizers) > 0 { return admission.NewForbidden(a, fmt.Errorf("finalizers are disabled: %v", finalizers)) } return nil }
func (l *persistentVolumeLabel) Admit(a admission.Attributes) (err error) { if a.GetResource() != api.Resource("persistentvolumes") { return nil } obj := a.GetObject() if obj == nil { return nil } volume, ok := obj.(*api.PersistentVolume) if !ok { return nil } var volumeLabels map[string]string if volume.Spec.AWSElasticBlockStore != nil { labels, err := l.findAWSEBSLabels(volume) if err != nil { return admission.NewForbidden(a, fmt.Errorf("error querying AWS EBS volume %s: %v", volume.Spec.AWSElasticBlockStore.VolumeID, err)) } volumeLabels = labels } if volume.Spec.GCEPersistentDisk != nil { labels, err := l.findGCEPDLabels(volume) if err != nil { return admission.NewForbidden(a, fmt.Errorf("error querying GCE PD volume %s: %v", volume.Spec.GCEPersistentDisk.PDName, err)) } volumeLabels = labels } if len(volumeLabels) != 0 { if volume.Labels == nil { volume.Labels = make(map[string]string) } for k, v := range volumeLabels { // We (silently) replace labels if they are provided. // This should be OK because they are in the kubernetes.io namespace // i.e. we own them volume.Labels[k] = v } } return nil }
// Admit admits resources into cluster that do not violate any defined LimitRange in the namespace func (l *limitRanger) Admit(a admission.Attributes) (err error) { // Ignore all calls to subresources if a.GetSubresource() != "" { return nil } obj := a.GetObject() resource := a.GetResource() name := "Unknown" if obj != nil { name, _ = meta.NewAccessor().Name(obj) if len(name) == 0 { name, _ = meta.NewAccessor().GenerateName(obj) } } key := &api.LimitRange{ ObjectMeta: api.ObjectMeta{ Namespace: a.GetNamespace(), Name: "", }, } items, err := l.indexer.Index("namespace", key) if err != nil { return admission.NewForbidden(a, fmt.Errorf("Unable to %s %s at this time because there was an error enforcing limit ranges", a.GetOperation(), resource)) } if len(items) == 0 { return nil } // ensure it meets each prescribed min/max for i := range items { limitRange := items[i].(*api.LimitRange) err = l.limitFunc(limitRange, a.GetResource(), a.GetObject()) if err != nil { return admission.NewForbidden(a, err) } } return nil }
// Admit ensures that only a configured number of projects can be requested by a particular user. func (o *projectRequestLimit) Admit(a admission.Attributes) (err error) { if a.GetResource() != projectapi.Resource("projectrequests") { return nil } if _, isProjectRequest := a.GetObject().(*projectapi.ProjectRequest); !isProjectRequest { return nil } userName := a.GetUserInfo().GetName() projectCount, err := o.projectCountByRequester(userName) if err != nil { return err } maxProjects, hasLimit, err := o.maxProjectsByRequester(userName) if err != nil { return err } if hasLimit && projectCount >= maxProjects { return admission.NewForbidden(a, fmt.Errorf("user %s cannot create more than %d project(s).", userName, maxProjects)) } return nil }
// Admit enforces that pod and its project node label selectors matches at least a node in the cluster. func (p *podNodeEnvironment) Admit(a admission.Attributes) (err error) { // ignore anything except create or update of pods if !(a.GetOperation() == admission.Create || a.GetOperation() == admission.Update) { return nil } resource := a.GetResource() if resource != "pods" { return nil } obj := a.GetObject() pod, ok := obj.(*kapi.Pod) if !ok { return nil } name := pod.Name projects, err := projectcache.GetProjectCache() if err != nil { return err } namespace, err := projects.GetNamespaceObject(a.GetNamespace()) if err != nil { return apierrors.NewForbidden(resource, name, err) } projectNodeSelector, err := projects.GetNodeSelectorMap(namespace) if err != nil { return err } if labelselector.Conflicts(projectNodeSelector, pod.Spec.NodeSelector) { return apierrors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its project node label selector")) } // modify pod node selector = project node selector + current pod node selector pod.Spec.NodeSelector = labelselector.Merge(projectNodeSelector, pod.Spec.NodeSelector) return nil }
// Admit enforces that pod and its project node label selectors matches at least a node in the cluster. func (p *podNodeEnvironment) Admit(a admission.Attributes) (err error) { resource := a.GetResource() if resource != "pods" { return nil } if a.GetSubresource() != "" { // only run the checks below on pods proper and not subresources return nil } obj := a.GetObject() pod, ok := obj.(*kapi.Pod) if !ok { return nil } name := pod.Name projects, err := projectcache.GetProjectCache() if err != nil { return err } namespace, err := projects.GetNamespaceObject(a.GetNamespace()) if err != nil { return apierrors.NewForbidden(resource, name, err) } projectNodeSelector, err := projects.GetNodeSelectorMap(namespace) if err != nil { return err } if labelselector.Conflicts(projectNodeSelector, pod.Spec.NodeSelector) { return apierrors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its project node label selector")) } // modify pod node selector = project node selector + current pod node selector pod.Spec.NodeSelector = labelselector.Merge(projectNodeSelector, pod.Spec.NodeSelector) return nil }
// TODO this will need to update when we have pod requests/limits func (a *clusterResourceOverridePlugin) Admit(attr admission.Attributes) error { glog.V(6).Infof("%s admission controller is invoked", api.PluginName) if a.config == nil || attr.GetResource().GroupResource() != kapi.Resource("pods") || attr.GetSubresource() != "" { return nil // not applicable } pod, ok := attr.GetObject().(*kapi.Pod) if !ok { return admission.NewForbidden(attr, fmt.Errorf("unexpected object: %#v", attr.GetObject())) } glog.V(5).Infof("%s is looking at creating pod %s in project %s", api.PluginName, pod.Name, attr.GetNamespace()) // allow annotations on project to override if ns, err := a.ProjectCache.GetNamespace(attr.GetNamespace()); err != nil { glog.Warningf("%s got an error retrieving namespace: %v", api.PluginName, err) return admission.NewForbidden(attr, err) // this should not happen though } else { projectEnabledPlugin, exists := ns.Annotations[clusterResourceOverrideAnnotation] if exists && projectEnabledPlugin != "true" { glog.V(5).Infof("%s is disabled for project %s", api.PluginName, attr.GetNamespace()) return nil // disabled for this project, do nothing } } // Reuse LimitRanger logic to apply limit/req defaults from the project. Ignore validation // errors, assume that LimitRanger will run after this plugin to validate. glog.V(5).Infof("%s: initial pod limits are: %#v", api.PluginName, pod.Spec) if err := a.LimitRanger.Admit(attr); err != nil { glog.V(5).Infof("%s: error from LimitRanger: %#v", api.PluginName, err) } glog.V(5).Infof("%s: pod limits after LimitRanger: %#v", api.PluginName, pod.Spec) for i := range pod.Spec.InitContainers { updateContainerResources(a.config, &pod.Spec.InitContainers[i]) } for i := range pod.Spec.Containers { updateContainerResources(a.config, &pod.Spec.Containers[i]) } glog.V(5).Infof("%s: pod limits after overrides are: %#v", api.PluginName, pod.Spec) return nil }
// Admit attempts to apply the image policy to the incoming resource. func (a *imagePolicyPlugin) Admit(attr admission.Attributes) error { switch attr.GetOperation() { case admission.Create, admission.Update: if len(attr.GetSubresource()) > 0 { return nil } // only create and update are tested, and only on core resources // TODO: scan all resources // TODO: Create a general equivalence map for admission - operation X on subresource Y is equivalent to reduced operation default: return nil } gr := attr.GetResource().GroupResource() if !a.accepter.Covers(gr) { return nil } m, err := meta.GetImageReferenceMutator(attr.GetObject()) if err != nil { return apierrs.NewForbidden(gr, attr.GetName(), fmt.Errorf("unable to apply image policy against objects of type %T: %v", attr.GetObject(), err)) } // load exclusion rules from the namespace cache var excluded sets.String if ns := attr.GetNamespace(); len(ns) > 0 { if ns, err := a.projectCache.GetNamespace(ns); err == nil { if value := ns.Annotations[api.IgnorePolicyRulesAnnotation]; len(value) > 0 { excluded = sets.NewString(strings.Split(value, ",")...) } } } if err := accept(a.accepter, a.resolver, m, attr, excluded); err != nil { return err } return nil }
// Admit sets the default value of a PersistentVolumeClaim's storage class, in case the user did // not provide a value. // // 1. Find available StorageClasses. // 2. Figure which is the default // 3. Write to the PVClaim func (c *claimDefaulterPlugin) Admit(a admission.Attributes) error { if a.GetResource().GroupResource() != api.Resource("persistentvolumeclaims") { return nil } if len(a.GetSubresource()) != 0 { return nil } pvc, ok := a.GetObject().(*api.PersistentVolumeClaim) // if we can't convert then we don't handle this object so just return if !ok { return nil } _, found := pvc.Annotations[classAnnotation] if found { // The user asked for a class. return nil } glog.V(4).Infof("no storage class for claim %s (generate: %s)", pvc.Name, pvc.GenerateName) def, err := getDefaultClass(c.store) if err != nil { return admission.NewForbidden(a, err) } if def == nil { // No default class selected, do nothing about the PVC. return nil } glog.V(4).Infof("defaulting storage class for claim %s (generate: %s) to %s", pvc.Name, pvc.GenerateName, def.Name) if pvc.ObjectMeta.Annotations == nil { pvc.ObjectMeta.Annotations = map[string]string{} } pvc.Annotations[classAnnotation] = def.Name return nil }
func (a *buildByStrategy) Admit(attr admission.Attributes) error { if resource := attr.GetResource(); resource != buildsResource && resource != buildConfigsResource { return nil } var err error switch obj := attr.GetObject().(type) { case *buildapi.Build: err = a.checkBuildAuthorization(obj, attr) case *buildapi.BuildConfig: err = a.checkBuildConfigAuthorization(obj, attr) } return err }