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 (p *provision) Admit(a admission.Attributes) (err error) { gvk, err := api.RESTMapper.KindFor(a.GetResource()) if err != nil { return admission.NewForbidden(a, err) } mapping, err := api.RESTMapper.RESTMapping(gvk.GroupKind(), gvk.Version) if err != nil { return admission.NewForbidden(a, err) } if mapping.Scope.Name() != meta.RESTScopeNameNamespace { return nil } namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: a.GetNamespace(), Namespace: "", }, Status: api.NamespaceStatus{}, } _, exists, err := p.store.Get(namespace) if err != nil { return admission.NewForbidden(a, err) } if exists { return nil } _, err = p.client.Namespaces().Create(namespace) if err != nil && !errors.IsAlreadyExists(err) { return admission.NewForbidden(a, err) } return nil }
// SupportsAttributes is a helper that returns true if the resource is supported by the plugin. // Implements the LimitRangerActions interface. func (a *imageLimitRangerPlugin) SupportsAttributes(attr kadmission.Attributes) bool { if attr.GetSubresource() != "" { return false } return attr.GetKind().GroupKind() == imageapi.Kind("ImageStreamMapping") }
// 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)) }
// 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 }
// SupportsAttributes ignores all calls that do not deal with pod resources since that is // all this supports now. Also ignores any call that has a subresource defined. func (d *DefaultLimitRangerActions) SupportsAttributes(a admission.Attributes) bool { if a.GetSubresource() != "" { return false } return a.GetKind().GroupKind() == api.Kind("Pod") }
func (e *quotaEvaluator) Evaluate(a admission.Attributes) error { e.init.Do(func() { go e.run() }) // if we do not know how to evaluate use for this kind, just ignore evaluators := e.registry.Evaluators() evaluator, found := evaluators[a.GetKind().GroupKind()] if !found { return nil } // for this kind, check if the operation could mutate any quota resources // if no resources tracked by quota are impacted, then just return op := a.GetOperation() operationResources := evaluator.OperationResources(op) if len(operationResources) == 0 { return nil } waiter := newAdmissionWaiter(a) e.addWork(waiter) // wait for completion or timeout select { case <-waiter.finished: case <-time.After(10 * time.Second): return fmt.Errorf("timeout") } return waiter.result }
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 will deny any SecurityContext that defines options that were not previously available in the api.Container // struct (Capabilities and Privileged) 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")) } 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 }
// 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 enforces that a namespace must exist in order to associate content with it. // Admit enforces that a namespace that is terminating cannot accept new content being associated with it. func (e *lifecycle) Admit(a admission.Attributes) (err error) { if len(a.GetNamespace()) == 0 { return nil } // always allow a SAR request through, the SAR will return information about // the ability to take action on the object, no need to verify it here. if isSubjectAccessReview(a) { return nil } groupMeta, err := registered.Group(a.GetKind().Group) if err != nil { return err } mapping, err := groupMeta.RESTMapper.RESTMapping(a.GetKind().GroupKind()) if err != nil { glog.V(4).Infof("Ignoring life-cycle enforcement for resource %v; no associated default version and kind could be found.", a.GetResource()) return nil } if mapping.Scope.Name() != meta.RESTScopeNameNamespace { return nil } if !e.cache.Running() { return admission.NewForbidden(a, err) } namespace, err := e.cache.GetNamespace(a.GetNamespace()) if err != nil { return admission.NewForbidden(a, err) } // in case of concurrency issues, we will retry this logic numRetries := 10 interval := time.Duration(rand.Int63n(90)+int64(10)) * time.Millisecond for retry := 1; retry <= numRetries; retry++ { // associate this namespace with openshift _, err = projectutil.Associate(e.client, namespace) if err == nil { break } // we have exhausted all reasonable efforts to retry so give up now if retry == numRetries { return admission.NewForbidden(a, err) } // get the latest namespace for the next pass in case of resource version updates time.Sleep(interval) // it's possible the namespace actually was deleted, so just forbid if this occurs namespace, err = e.client.Core().Namespaces().Get(a.GetNamespace()) if err != nil { return admission.NewForbidden(a, err) } } return nil }
// Admit makes admission decisions while enforcing quota func (q *quotaAdmission) Admit(a admission.Attributes) (err error) { // ignore all operations that correspond to sub-resource actions if a.GetSubresource() != "" { return nil } return q.evaluator.Evaluate(a) }
func (a *buildByStrategy) checkAccess(strategy buildapi.BuildStrategy, subjectAccessReview *authorizationapi.LocalSubjectAccessReview, attr admission.Attributes) error { resp, err := a.client.LocalSubjectAccessReviews(attr.GetNamespace()).Create(subjectAccessReview) if err != nil { return admission.NewForbidden(attr, err) } if !resp.Allowed { return notAllowed(strategy, attr) } return nil }
func (a *buildByStrategy) checkAccess(strategyType buildapi.BuildStrategyType, subjectAccessReview *authorizationapi.SubjectAccessReview, attr admission.Attributes) error { resp, err := a.client.SubjectAccessReviews(attr.GetNamespace()).Create(subjectAccessReview) if err != nil { return err } if !resp.Allowed { return notAllowed(strategyType, attr) } return nil }
func (a *buildByStrategy) checkBuildConfigAuthorization(buildConfig *buildapi.BuildConfig, attr admission.Attributes) error { strategyType := buildConfig.Spec.Strategy.Type subjectAccessReview := &authorizationapi.SubjectAccessReview{ Verb: "create", Resource: resourceForStrategyType(strategyType), User: attr.GetUserInfo().GetName(), Groups: util.NewStringSet(attr.GetUserInfo().GetGroups()...), Content: runtime.EmbeddedObject{Object: buildConfig}, ResourceName: resourceName(buildConfig.ObjectMeta), } return a.checkAccess(strategyType, subjectAccessReview, attr) }
// IsBuildPod returns true if a pod is a pod generated for a Build func IsBuildPod(a admission.Attributes) bool { if a.GetResource() != kapi.Resource("pods") { return false } if len(a.GetSubresource()) != 0 { return false } pod, err := GetPod(a) if err != nil { return false } return hasBuildAnnotation(pod) && hasBuildEnvVar(pod) }
// Admit makes admission decisions while enforcing quota func (q *quotaAdmission) Admit(a admission.Attributes) (err error) { startTime := time.Now() defer func() { duration := time.Now().Sub(startTime) runningTotal = duration + runningTotal }() // ignore all operations that correspond to sub-resource actions if a.GetSubresource() != "" { return nil } return q.evaluator.Evaluate(a) }
// 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) { // 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 != 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 }
func (e *exists) Admit(a admission.Attributes) (err error) { // if we're here, then we've already passed authentication, so we're allowed to do what we're trying to do // if we're here, then the API server has found a route, which means that if we have a non-empty namespace // its a namespaced resource. if len(a.GetNamespace()) == 0 || a.GetKind().GroupKind() == api.Kind("Namespace") { return nil } namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: a.GetNamespace(), Namespace: "", }, Status: api.NamespaceStatus{}, } _, exists, err := e.store.Get(namespace) if err != nil { return errors.NewInternalError(err) } if exists { return nil } // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not _, err = e.client.Core().Namespaces().Get(a.GetNamespace()) if err != nil { if errors.IsNotFound(err) { return err } return errors.NewInternalError(err) } 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 }
func (e *exists) Admit(a admission.Attributes) (err error) { defaultVersion, kind, err := api.RESTMapper.VersionAndKindForResource(a.GetResource()) if err != nil { return admission.NewForbidden(a, err) } mapping, err := api.RESTMapper.RESTMapping(kind, defaultVersion) if err != nil { return admission.NewForbidden(a, err) } if mapping.Scope.Name() != meta.RESTScopeNameNamespace { return nil } namespace := &api.Namespace{ ObjectMeta: api.ObjectMeta{ Name: a.GetNamespace(), Namespace: "", }, Status: api.NamespaceStatus{}, } _, exists, err := e.store.Get(namespace) if err != nil { return admission.NewForbidden(a, err) } if exists { return nil } // in case of latency in our caches, make a call direct to storage to verify that it truly exists or not _, err = e.client.Namespaces().Get(a.GetNamespace()) if err != nil { return admission.NewForbidden(a, fmt.Errorf("Namespace %s does not exist", a.GetNamespace())) } return nil }
func (a *buildByStrategy) checkBuildAuthorization(build *buildapi.Build, attr admission.Attributes) error { strategy := build.Spec.Strategy subjectAccessReview := &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.AuthorizationAttributes{ Verb: "create", Resource: resourceForStrategyType(strategy), Content: runtime.EmbeddedObject{Object: build}, ResourceName: resourceName(build.ObjectMeta), }, User: attr.GetUserInfo().GetName(), Groups: sets.NewString(attr.GetUserInfo().GetGroups()...), } return a.checkAccess(strategy, subjectAccessReview, attr) }
// 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 }
func (a *buildByStrategy) Admit(attr admission.Attributes) error { if resource := attr.GetResource(); resource != buildsResource && resource != buildConfigsResource { return nil } switch obj := attr.GetObject().(type) { case *buildapi.Build: return a.checkBuildAuthorization(obj, attr) case *buildapi.BuildConfig: return a.checkBuildConfigAuthorization(obj, attr) case *buildapi.BuildRequest: return a.checkBuildRequestAuthorization(obj, attr) default: return admission.NewForbidden(attr, fmt.Errorf("Unrecognized request object %#v", obj)) } }
// 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 (a *buildByStrategy) checkBuildAuthorization(build *buildapi.Build, attr admission.Attributes) error { strategy := build.Spec.Strategy resource, err := resourceForStrategyType(strategy) if err != nil { return admission.NewForbidden(attr, err) } subjectAccessReview := authorizationapi.AddUserToLSAR(attr.GetUserInfo(), &authorizationapi.LocalSubjectAccessReview{ Action: authorizationapi.AuthorizationAttributes{ Verb: "create", Group: resource.Group, Resource: resource.Resource, Content: build, ResourceName: resourceName(build.ObjectMeta), }, }) return a.checkAccess(strategy, subjectAccessReview, attr) }
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 }