Example #1
0
func (e *exists) Admit(a admission.Attributes) (err error) {
	defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
	if err != nil {
		return err
	}
	mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
	if err != nil {
		return 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 err
	}
	if exists {
		return nil
	}
	obj := a.GetObject()
	name := "Unknown"
	if obj != nil {
		name, _ = meta.NewAccessor().Name(obj)
	}
	return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s does not exist", a.GetNamespace()))
}
Example #2
0
// forbidden renders a simple forbidden error
func forbidden(reason, apiVersion string, w http.ResponseWriter, req *http.Request) {
	// the api version can be empty for two basic reasons:
	// 1. malformed API request
	// 2. not an API request at all
	// In these cases, just assume the latest version that will work better than nothing
	if len(apiVersion) == 0 {
		apiVersion = klatest.Version
	}

	// Reason is an opaque string that describes why access is allowed or forbidden (forbidden by the time we reach here).
	// We don't have direct access to kind or name (not that those apply either in the general case)
	// We create a NewForbidden to stay close the API, but then we override the message to get a serialization
	// that makes sense when a human reads it.
	forbiddenError, _ := kapierror.NewForbidden("", "", errors.New("")).(*kapierror.StatusError)
	forbiddenError.ErrStatus.Message = reason

	// Not all API versions in valid API requests will have a matching codec in kubernetes.  If we can't find one,
	// just default to the latest kube codec.
	codec := klatest.Codec
	if requestedCodec, err := klatest.InterfacesFor(apiVersion); err == nil {
		codec = requestedCodec
	}

	formatted := &bytes.Buffer{}
	output, err := codec.Encode(&forbiddenError.ErrStatus)
	if err != nil {
		fmt.Fprintf(formatted, "%s", forbiddenError.Error())
	} else {
		_ = json.Indent(formatted, output, "", "  ")
	}

	w.Header().Set("Content-Type", restful.MIME_JSON)
	w.WriteHeader(http.StatusForbidden)
	w.Write(formatted.Bytes())
}
// Validate validates a new image stream.
func (s Strategy) Validate(ctx kapi.Context, obj runtime.Object) fielderrors.ValidationErrorList {
	stream := obj.(*api.ImageStream)
	user, ok := kapi.UserFrom(ctx)
	if !ok {
		return fielderrors.ValidationErrorList{kerrors.NewForbidden("imageStream", stream.Name, fmt.Errorf("unable to update an ImageStream without a user on the context"))}
	}
	errs := s.tagVerifier.Verify(nil, stream, user)
	errs = append(errs, s.tagsChanged(nil, stream)...)
	errs = append(errs, validation.ValidateImageStream(stream)...)
	return errs
}
Example #4
0
// List retrieves a list of Projects that match label.
func (s *REST) List(ctx kapi.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
	user, ok := kapi.UserFrom(ctx)
	if !ok {
		return nil, kerrors.NewForbidden("Project", "", fmt.Errorf("unable to list projects without a user on the context"))
	}
	namespaceList, err := s.lister.List(user)
	if err != nil {
		return nil, err
	}
	return convertNamespaceList(namespaceList), nil
}
Example #5
0
// 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")
	}
	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 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
}
Example #7
0
// NewForbidden is a utility function to return a well-formatted admission control error response
func NewForbidden(a Attributes, internalError error) error {
	// do not double wrap an error of same type
	if apierrors.IsForbidden(internalError) {
		return internalError
	}

	name := "Unknown"
	kind := a.GetKind()
	obj := a.GetObject()
	if obj != nil {
		objectMeta, err := api.ObjectMetaFor(obj)
		if err != nil {
			return apierrors.NewForbidden(kind, name, internalError)
		}

		// this is necessary because name object name generation has not occurred yet
		if len(objectMeta.Name) > 0 {
			name = objectMeta.Name
		} else if len(objectMeta.GenerateName) > 0 {
			name = objectMeta.GenerateName
		}
	}
	return apierrors.NewForbidden(kind, name, internalError)
}
Example #8
0
func TestErrors(t *testing.T) {
	o := testclient.NewObjects(kapi.Scheme, kapi.Scheme)
	o.Add(&kapi.List{
		Items: []runtime.Object{
			&(errors.NewNotFound("DeploymentConfigList", "").(*errors.StatusError).ErrStatus),
			&(errors.NewForbidden("DeploymentConfigList", "", nil).(*errors.StatusError).ErrStatus),
		},
	})
	oc, _ := NewFixtureClients(o)
	_, err := oc.DeploymentConfigs("test").List(labels.Everything(), fields.Everything())
	if !errors.IsNotFound(err) {
		t.Fatalf("unexpected error: %v", err)
	}
	t.Logf("error: %#v", err.(*errors.StatusError).Status())
	_, err = oc.DeploymentConfigs("test").List(labels.Everything(), fields.Everything())
	if !errors.IsForbidden(err) {
		t.Fatalf("unexpected error: %v", err)
	}
}
Example #9
0
func TestErrors(t *testing.T) {
	o := NewObjects(api.Scheme, api.Scheme)
	o.Add(&api.List{
		Items: []runtime.Object{
			// This first call to List will return this error
			&(errors.NewNotFound("ServiceList", "").(*errors.StatusError).ErrStatus),
			// The second call to List will return this error
			&(errors.NewForbidden("ServiceList", "", nil).(*errors.StatusError).ErrStatus),
		},
	})
	client := &Fake{ReactFn: ObjectReaction(o, latest.RESTMapper)}
	_, err := client.Services("test").List(labels.Everything())
	if !errors.IsNotFound(err) {
		t.Fatalf("unexpected error: %v", err)
	}
	t.Logf("error: %#v", err.(*errors.StatusError).Status())
	_, err = client.Services("test").List(labels.Everything())
	if !errors.IsForbidden(err) {
		t.Fatalf("unexpected error: %v", err)
	}
}
Example #10
0
func (l *lifecycle) Admit(a admission.Attributes) (err error) {

	// prevent deletion of immortal namespaces
	if a.GetOperation() == admission.Delete {
		if a.GetKind() == "Namespace" && l.immortalNamespaces.Has(a.GetName()) {
			return errors.NewForbidden(a.GetKind(), a.GetName(), fmt.Errorf("namespace can never be deleted"))
		}
		return nil
	}

	defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
	if err != nil {
		return admission.NewForbidden(a, err)
	}
	mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
	if err != nil {
		return admission.NewForbidden(a, err)
	}
	if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
		return nil
	}
	namespaceObj, exists, err := l.store.Get(&api.Namespace{
		ObjectMeta: api.ObjectMeta{
			Name:      a.GetNamespace(),
			Namespace: "",
		},
	})
	if err != nil {
		return admission.NewForbidden(a, err)
	}
	if !exists {
		return nil
	}
	namespace := namespaceObj.(*api.Namespace)
	if namespace.Status.Phase != api.NamespaceTerminating {
		return nil
	}

	return admission.NewForbidden(a, fmt.Errorf("Unable to create new content in namespace %s because it is being terminated.", a.GetNamespace()))
}
Example #11
0
// Get retrieves the item from etcd.
func (r *REST) Get(ctx kapi.Context, name string) (runtime.Object, error) {
	// "~" means the currently authenticated user
	if name == "~" {
		user, ok := kapi.UserFrom(ctx)
		if !ok || user.GetName() == "" {
			return nil, kerrs.NewForbidden("user", "~", errors.New("requests to ~ must be authenticated"))
		}
		name = user.GetName()

		// remove the known virtual groups from the list if they are present
		contextGroups := kutil.NewStringSet(user.GetGroups()...)
		contextGroups.Delete(bootstrappolicy.UnauthenticatedGroup, bootstrappolicy.AuthenticatedGroup)

		if ok, _ := validation.ValidateUserName(name, false); !ok {
			// The user the authentication layer has identified cannot possibly be a persisted user
			// Return an API representation of the virtual user
			return &api.User{ObjectMeta: kapi.ObjectMeta{Name: name}, Groups: contextGroups.List()}, nil
		}

		obj, err := r.Etcd.Get(ctx, name)
		if err == nil {
			return obj, nil
		}

		if !kerrs.IsNotFound(err) {
			return nil, err
		}

		return &api.User{ObjectMeta: kapi.ObjectMeta{Name: name}, Groups: contextGroups.List()}, nil
	}

	if ok, details := validation.ValidateUserName(name, false); !ok {
		return nil, fielderrors.NewFieldInvalid("metadata.name", name, details)
	}

	return r.Etcd.Get(ctx, name)
}
Example #12
0
// 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 deletes
	if a.GetOperation() == "DELETE" {
		return nil
	}

	obj := a.GetObject()
	resource := a.GetResource()
	name := "Unknown"
	if obj != nil {
		name, _ = meta.NewAccessor().Name(obj)
	}

	key := &api.LimitRange{
		ObjectMeta: api.ObjectMeta{
			Namespace: a.GetNamespace(),
			Name:      "",
		},
	}
	items, err := l.indexer.Index("namespace", key)
	if err != nil {
		return apierrors.NewForbidden(a.GetResource(), name, 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 err
		}
	}
	return nil
}
Example #13
0
func (r *REST) List(ctx kapi.Context, label labels.Selector, field fields.Selector) (runtime.Object, error) {
	userInfo, exists := kapi.UserFrom(ctx)
	if !exists {
		return nil, errors.New("a user must be provided")
	}

	// the caller might not have permission to run a subject access review (he has it by default, but it could have been removed).
	// So we'll escalate for the subject access review to determine rights
	accessReview := &authorizationapi.SubjectAccessReview{
		Verb:     "create",
		Resource: "projectrequests",
		User:     userInfo.GetName(),
		Groups:   util.NewStringSet(userInfo.GetGroups()...),
	}
	accessReviewResponse, err := r.openshiftClient.ClusterSubjectAccessReviews().Create(accessReview)
	if err != nil {
		return nil, err
	}
	if accessReviewResponse.Allowed {
		return &kapi.Status{Status: kapi.StatusSuccess}, nil
	}

	forbiddenError, _ := kapierror.NewForbidden("ProjectRequest", "", errors.New("you may not request a new project via this API.")).(*kapierror.StatusError)
	if len(r.message) > 0 {
		forbiddenError.ErrStatus.Message = r.message
		forbiddenError.ErrStatus.Details = &kapi.StatusDetails{
			Kind: "ProjectRequest",
			Causes: []kapi.StatusCause{
				{Message: r.message},
			},
		}
	} else {
		forbiddenError.ErrStatus.Message = "You may not request a new project via this API."
	}
	return nil, forbiddenError
}
// 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
	}
	defaultVersion, kind, err := latest.RESTMapper.VersionAndKindForResource(a.GetResource())
	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
	}
	mapping, err := latest.RESTMapper.RESTMapping(kind, defaultVersion)
	if err != nil {
		return admission.NewForbidden(a, err)
	}
	if mapping.Scope.Name() != meta.RESTScopeNameNamespace {
		return nil
	}

	// we want to allow someone to delete something in case it was phantom created somehow
	if a.GetOperation() == "DELETE" {
		return nil
	}

	name := "Unknown"
	obj := a.GetObject()
	if obj != nil {
		name, _ = meta.NewAccessor().Name(obj)
	}

	projects, err := cache.GetProjectCache()
	if err != nil {
		return admission.NewForbidden(a, err)
	}

	namespace, err := projects.GetNamespaceObject(a.GetNamespace())
	if err != nil {
		return admission.NewForbidden(a, err)
	}

	if a.GetOperation() != "CREATE" {
		return nil
	}

	if namespace.Status.Phase == kapi.NamespaceTerminating && !e.creatableResources.Has(strings.ToLower(a.GetResource())) {
		return apierrors.NewForbidden(kind, name, fmt.Errorf("Namespace %s is terminating", a.GetNamespace()))
	}

	// 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.Namespaces().Get(a.GetNamespace())
		if err != nil {
			return admission.NewForbidden(a, err)
		}
	}
	return nil
}
Example #15
0
// PodLimitFunc enforces that a pod spec does not exceed any limits specified on the supplied limit range
func PodLimitFunc(limitRange *api.LimitRange, resourceName string, obj runtime.Object) error {
	if resourceName != "pods" {
		return nil
	}

	pod := obj.(*api.Pod)

	podCPU := int64(0)
	podMem := int64(0)

	minContainerCPU := int64(0)
	minContainerMem := int64(0)
	maxContainerCPU := int64(0)
	maxContainerMem := int64(0)

	for i := range pod.Spec.Containers {
		container := pod.Spec.Containers[i]
		containerCPU := container.Resources.Limits.Cpu().MilliValue()
		containerMem := container.Resources.Limits.Memory().Value()

		if i == 0 {
			minContainerCPU = containerCPU
			minContainerMem = containerMem
			maxContainerCPU = containerCPU
			maxContainerMem = containerMem
		}

		podCPU = podCPU + container.Resources.Limits.Cpu().MilliValue()
		podMem = podMem + container.Resources.Limits.Memory().Value()

		minContainerCPU = Min(containerCPU, minContainerCPU)
		minContainerMem = Min(containerMem, minContainerMem)
		maxContainerCPU = Max(containerCPU, maxContainerCPU)
		maxContainerMem = Max(containerMem, maxContainerMem)
	}

	for i := range limitRange.Spec.Limits {
		limit := limitRange.Spec.Limits[i]
		for _, minOrMax := range []string{"Min", "Max"} {
			var rl api.ResourceList
			switch minOrMax {
			case "Min":
				rl = limit.Min
			case "Max":
				rl = limit.Max
			}
			for k, v := range rl {
				observed := int64(0)
				enforced := int64(0)
				var err error
				switch k {
				case api.ResourceMemory:
					enforced = v.Value()
					switch limit.Type {
					case api.LimitTypePod:
						observed = podMem
						err = fmt.Errorf("%simum memory usage per pod is %s", minOrMax, v.String())
					case api.LimitTypeContainer:
						observed = maxContainerMem
						err = fmt.Errorf("%simum memory usage per container is %s", minOrMax, v.String())
					}
				case api.ResourceCPU:
					enforced = v.MilliValue()
					switch limit.Type {
					case api.LimitTypePod:
						observed = podCPU
						err = fmt.Errorf("%simum CPU usage per pod is %s, but requested %s", minOrMax, v.String(), resource.NewMilliQuantity(observed, resource.DecimalSI))
					case api.LimitTypeContainer:
						observed = maxContainerCPU
						err = fmt.Errorf("%simum CPU usage per container is %s", minOrMax, v.String())
					}
				}
				switch minOrMax {
				case "Min":
					if observed < enforced {
						return apierrors.NewForbidden(resourceName, pod.Name, err)
					}
				case "Max":
					if observed > enforced {
						return apierrors.NewForbidden(resourceName, pod.Name, err)
					}
				}
			}
		}
	}
	return nil
}
Example #16
0
func (alwaysDeny) Admit(a admission.Attributes) (err error) {
	return apierrors.NewForbidden(a.GetResource(), "", errors.New("Admission control is denying all modifications"))
}