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.Core().Pods(a.GetNamespace()).Get(connectRequest.Name, metav1.GetOptions{}) 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 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 := pod.Spec.Affinity 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 }
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)) }
func (ir initialResources) Admit(a admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. if a.GetSubresource() != "" || a.GetResource().GroupResource() != 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 }
// Admit will deny any pod that defines SELinuxOptions or RunAsUser. func (p *plugin) Admit(a admission.Attributes) (err error) { if a.GetSubresource() != "" || a.GetResource().GroupResource() != 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") } if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.SupplementalGroups != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.SupplementalGroups is forbidden")) } if pod.Spec.SecurityContext != nil { if pod.Spec.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("pod.Spec.SecurityContext.SELinuxOptions is forbidden")) } if pod.Spec.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("pod.Spec.SecurityContext.RunAsUser is forbidden")) } } if pod.Spec.SecurityContext != nil && pod.Spec.SecurityContext.FSGroup != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.FSGroup is forbidden")) } for _, v := range pod.Spec.InitContainers { if v.SecurityContext != nil { if v.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden")) } if v.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden")) } } } for _, v := range pod.Spec.Containers { if v.SecurityContext != nil { if v.SecurityContext.SELinuxOptions != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.SELinuxOptions is forbidden")) } if v.SecurityContext.RunAsUser != nil { return apierrors.NewForbidden(a.GetResource().GroupResource(), pod.Name, fmt.Errorf("SecurityContext.RunAsUser is forbidden")) } } } return nil }
func (l *persistentVolumeLabel) Admit(a admission.Attributes) (err error) { if a.GetResource().GroupResource() != 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 }
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().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") } for i := range pod.Spec.InitContainers { pod.Spec.InitContainers[i].ImagePullPolicy = api.PullAlways } for i := range pod.Spec.Containers { pod.Spec.Containers[i].ImagePullPolicy = api.PullAlways } 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 } if storageutil.HasStorageClassAnnotation(pvc.ObjectMeta) { // 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[storageutil.StorageClassAnnotation] = def.Name return nil }
func (a *imagePolicyWebhook) Admit(attributes admission.Attributes) (err error) { // Ignore all calls to subresources or resources other than pods. allowedResources := map[kubeschema.GroupResource]bool{ api.Resource("pods"): true, } if len(attributes.GetSubresource()) != 0 || !allowedResources[attributes.GetResource().GroupResource()] { 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") } // Build list of ImageReviewContainerSpec var imageReviewContainerSpecs []v1alpha1.ImageReviewContainerSpec containers := make([]api.Container, 0, len(pod.Spec.Containers)+len(pod.Spec.InitContainers)) containers = append(containers, pod.Spec.Containers...) containers = append(containers, pod.Spec.InitContainers...) for _, c := range containers { imageReviewContainerSpecs = append(imageReviewContainerSpecs, v1alpha1.ImageReviewContainerSpec{ Image: c.Image, }) } imageReview := v1alpha1.ImageReview{ Spec: v1alpha1.ImageReviewSpec{ Containers: imageReviewContainerSpecs, Annotations: a.filterAnnotations(pod.Annotations), Namespace: attributes.GetNamespace(), }, } if err := a.admitPod(attributes, &imageReview); err != nil { return admission.NewForbidden(attributes, err) } 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) { if !l.actions.SupportsAttributes(a) { return nil } obj := a.GetObject() name := "Unknown" if obj != nil { name, _ = meta.NewAccessor().Name(obj) if len(name) == 0 { name, _ = meta.NewAccessor().GenerateName(obj) } } items, err := l.lister.LimitRanges(a.GetNamespace()).List(labels.Everything()) 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()) { // TODO: If there are multiple operations at the same time and cache has just expired, // this may cause multiple List operations being issued at the same time. // If there is already in-flight List() for a given namespace, we should wait until // it is finished and cache is updated instead of doing the same, also to avoid // throttling - see #22422 for details. 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] if !l.actions.SupportsLimit(limitRange) { continue } err = l.actions.Limit(limitRange, a.GetResource().Resource, a.GetObject()) if err != nil { return admission.NewForbidden(a, err) } } return nil }
// Admit determines if the pod should be admitted based on the requested security context // and the available PSPs. // // 1. Find available PSPs. // 2. Create the providers, includes setting pre-allocated values if necessary. // 3. Try to generate and validate a PSP with providers. If we find one then admit the pod // with the validated PSP. If we don't find any reject the pod and give all errors from the // failed attempts. func (c *podSecurityPolicyPlugin) Admit(a admission.Attributes) error { if a.GetResource().GroupResource() != api.Resource("pods") { return nil } if len(a.GetSubresource()) != 0 { return nil } pod, ok := a.GetObject().(*api.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 pod security policies for pod %s (generate: %s)", pod.Name, pod.GenerateName) var saInfo user.Info if len(pod.Spec.ServiceAccountName) > 0 { saInfo = serviceaccount.UserInfo(a.GetNamespace(), pod.Spec.ServiceAccountName, "") } matchedPolicies, err := c.pspMatcher(c.store, a.GetUserInfo(), saInfo, c.authz) if err != nil { return admission.NewForbidden(a, err) } // if we have no policies and want to succeed then return. Otherwise we'll end up with no // providers and fail with "unable to validate against any pod security policy" below. if len(matchedPolicies) == 0 && !c.failOnNoPolicies { return nil } providers, errs := c.createProvidersFromPolicies(matchedPolicies, pod.Namespace) logProviders(pod, providers, errs) if len(providers) == 0 { return admission.NewForbidden(a, fmt.Errorf("no providers available to validate 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.GetPSPName()))); 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.GetPSPName()) if pod.ObjectMeta.Annotations == nil { pod.ObjectMeta.Annotations = map[string]string{} } pod.ObjectMeta.Annotations[psputil.ValidatedPSPAnnotation] = provider.GetPSPName() return nil } // we didn't validate against any provider, reject the pod and give the errors for each attempt glog.V(4).Infof("unable to validate pod %s (generate: %s) against any pod security policy: %v", pod.Name, pod.GenerateName, validationErrs) return admission.NewForbidden(a, fmt.Errorf("unable to validate against any pod security policy: %v", validationErrs)) }
// checkRequest verifies that the request does not exceed any quota constraint. it returns 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() if !evaluator.Handles(op) { 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, err := evaluator.Matches(&resourceQuota, inputObject) if err != nil { return quotas, err } if !match { continue } hardResources := quota.ResourceNames(resourceQuota.Status.Hard) requiredResources := evaluator.MatchingResources(hardResources) if err := evaluator.Constraints(requiredResources, inputObject); 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, err := evaluator.Usage(inputObject) if err != nil { return quotas, err } // ensure that usage for input object is never negative (this would mean a resource made a negative resource requirement) if negativeUsage := quota.IsNegative(deltaUsage); len(negativeUsage) > 0 { return nil, admission.NewForbidden(a, fmt.Errorf("quota usage is negative for resource(s): %s", prettyPrintResourceNames(negativeUsage))) } 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, innerErr := evaluator.Usage(prevItem) if innerErr != nil { return quotas, innerErr } deltaUsage = quota.Subtract(deltaUsage, prevUsage) } } if quota.IsZero(deltaUsage) { return quotas, nil } outQuotas, err := copyQuotas(quotas) if err != nil { return nil, err } for _, index := range interestingQuotaIndexes { resourceQuota := outQuotas[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 outQuotas[index].Status.Used = newUsage } return outQuotas, nil }
// Admit enforces that pod and its namespace node label selectors matches at least a node in the cluster. func (p *podNodeSelector) Admit(a admission.Attributes) error { resource := a.GetResource().GroupResource() if resource != api.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.(*api.Pod) if !ok { glog.Errorf("expected pod but got %s", a.GetKind().Kind) return nil } if !p.WaitForReady() { return admission.NewForbidden(a, fmt.Errorf("not yet ready to handle request")) } name := pod.Name nsName := a.GetNamespace() var namespace *api.Namespace namespaceObj, exists, err := p.namespaceInformer.GetStore().Get(&api.Namespace{ ObjectMeta: metav1.ObjectMeta{ Name: nsName, Namespace: "", }, }) if err != nil { return errors.NewInternalError(err) } if exists { namespace = namespaceObj.(*api.Namespace) } else { namespace, err = p.defaultGetNamespace(nsName) if err != nil { if errors.IsNotFound(err) { return err } return errors.NewInternalError(err) } } namespaceNodeSelector, err := p.getNodeSelectorMap(namespace) if err != nil { return err } if labels.Conflicts(namespaceNodeSelector, labels.Set(pod.Spec.NodeSelector)) { return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector conflicts with its namespace node label selector")) } whitelist, err := labels.ConvertSelectorToLabelsMap(p.clusterNodeSelectors[namespace.Name]) if err != nil { return err } // Merge pod node selector = namespace node selector + current pod node selector podNodeSelectorLabels := labels.Merge(namespaceNodeSelector, pod.Spec.NodeSelector) // whitelist verification if !labels.AreLabelsInWhiteList(podNodeSelectorLabels, whitelist) { return errors.NewForbidden(resource, name, fmt.Errorf("pod node label selector labels conflict with its namespace whitelist")) } // Updated pod node selector = namespace node selector + current pod node selector pod.Spec.NodeSelector = map[string]string(podNodeSelectorLabels) return nil }
func (s *serviceAccount) Admit(a admission.Attributes) (err error) { if a.GetResource().GroupResource() != api.Resource("pods") { return nil } obj := a.GetObject() if obj == nil { return nil } pod, ok := obj.(*api.Pod) if !ok { return nil } // Don't modify the spec of mirror pods. // That makes the kubelet very angry and confused, and it immediately deletes the pod (because the spec doesn't match) // That said, don't allow mirror pods to reference ServiceAccounts or SecretVolumeSources either if _, isMirrorPod := pod.Annotations[kubelet.ConfigMirrorAnnotationKey]; isMirrorPod { if len(pod.Spec.ServiceAccountName) != 0 { return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference service accounts")) } for _, volume := range pod.Spec.Volumes { if volume.VolumeSource.Secret != nil { return admission.NewForbidden(a, fmt.Errorf("a mirror pod may not reference secrets")) } } return nil } // Set the default service account if needed if len(pod.Spec.ServiceAccountName) == 0 { pod.Spec.ServiceAccountName = DefaultServiceAccountName } // Ensure the referenced service account exists serviceAccount, err := s.getServiceAccount(a.GetNamespace(), pod.Spec.ServiceAccountName) if err != nil { return admission.NewForbidden(a, fmt.Errorf("error looking up service account %s/%s: %v", a.GetNamespace(), pod.Spec.ServiceAccountName, err)) } if serviceAccount == nil { // TODO: convert to a ServerTimeout error (or other error that sends a Retry-After header) return admission.NewForbidden(a, fmt.Errorf("service account %s/%s was not found, retry after the service account is created", a.GetNamespace(), pod.Spec.ServiceAccountName)) } if s.enforceMountableSecrets(serviceAccount) { if err := s.limitSecretReferences(serviceAccount, pod); err != nil { return admission.NewForbidden(a, err) } } if s.MountServiceAccountToken { if err := s.mountServiceAccountToken(serviceAccount, pod); err != nil { if _, ok := err.(errors.APIStatus); ok { return err } return admission.NewForbidden(a, err) } } if len(pod.Spec.ImagePullSecrets) == 0 { pod.Spec.ImagePullSecrets = make([]api.LocalObjectReference, len(serviceAccount.ImagePullSecrets)) copy(pod.Spec.ImagePullSecrets, serviceAccount.ImagePullSecrets) } return nil }