// 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)) }
// 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 *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 (r *restrictedEndpointsAdmission) checkAccess(attr kadmission.Attributes) (bool, error) { ctx := kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), attr.GetNamespace()), attr.GetUserInfo()) authzAttr := authorizer.DefaultAuthorizationAttributes{ Verb: "create", Resource: authorizationapi.RestrictedEndpointsResource, APIGroup: kapi.GroupName, ResourceName: attr.GetName(), } allow, _, err := r.authorizer.Authorize(ctx, authzAttr) return allow, err }
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) }
// build LocalSubjectAccessReview struct to validate role via checkAccess func (o *podNodeConstraints) checkPodsBindAccess(attr admission.Attributes) (bool, error) { ctx := kapi.WithUser(kapi.WithNamespace(kapi.NewContext(), attr.GetNamespace()), attr.GetUserInfo()) authzAttr := authorizer.DefaultAuthorizationAttributes{ Verb: "create", Resource: "pods/binding", APIGroup: kapi.GroupName, } if attr.GetResource().GroupResource() == kapi.Resource("pods") { authzAttr.ResourceName = attr.GetName() } allow, _, err := o.authorizer.Authorize(ctx, authzAttr) return allow, err }
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) }
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) }
// 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 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) 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)) }
func (a *jenkinsBootstrapper) Admit(attributes admission.Attributes) error { if a.jenkinsConfig.AutoProvisionEnabled != nil && !*a.jenkinsConfig.AutoProvisionEnabled { return nil } if len(attributes.GetSubresource()) != 0 { return nil } if attributes.GetResource().GroupResource() != buildapi.Resource("buildconfigs") && attributes.GetResource().GroupResource() != buildapi.Resource("builds") { return nil } if !needsJenkinsTemplate(attributes.GetObject()) { return nil } namespace := attributes.GetNamespace() svcName := a.jenkinsConfig.ServiceName if len(svcName) == 0 { return nil } // TODO pull this from a cache. if _, err := a.serviceClient.Services(namespace).Get(svcName); !kapierrors.IsNotFound(err) { // if it isn't a "not found" error, return the error. Either its nil and there's nothing to do or something went really wrong return err } glog.V(3).Infof("Adding new jenkins service %q to the project %q", svcName, namespace) jenkinsTemplate := jenkinscontroller.NewPipelineTemplate(namespace, a.jenkinsConfig, a.openshiftClient) objects, errs := jenkinsTemplate.Process() if len(errs) > 0 { return kutilerrors.NewAggregate(errs) } if !jenkinsTemplate.HasJenkinsService(objects) { return fmt.Errorf("template %s/%s does not contain required service %q", a.jenkinsConfig.TemplateNamespace, a.jenkinsConfig.TemplateName, a.jenkinsConfig.ServiceName) } impersonatingConfig := a.privilegedRESTClientConfig oldWrapTransport := impersonatingConfig.WrapTransport impersonatingConfig.WrapTransport = func(rt http.RoundTripper) http.RoundTripper { return authenticationclient.NewImpersonatingRoundTripper(attributes.GetUserInfo(), oldWrapTransport(rt)) } var bulkErr error bulk := &cmd.Bulk{ Mapper: &resource.Mapper{ RESTMapper: registered.RESTMapper(), ObjectTyper: kapi.Scheme, ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { return client.New(&impersonatingConfig) } return kclient.New(&impersonatingConfig) }), }, Op: cmd.Create, After: func(info *resource.Info, err error) bool { if kapierrors.IsAlreadyExists(err) { return false } if err != nil { bulkErr = err return true } return false }, } // we're intercepting the error we care about using After bulk.Run(objects, namespace) if bulkErr != nil { return bulkErr } glog.V(1).Infof("Jenkins Pipeline service %q created", svcName) return nil }
func (d *sccExecRestrictions) Admit(a admission.Attributes) (err error) { if a.GetOperation() != admission.Connect { return nil } if a.GetResource().GroupResource() != kapi.Resource("pods") { return nil } if a.GetSubresource() != "attach" && a.GetSubresource() != "exec" { return nil } pod, err := d.client.Core().Pods(a.GetNamespace()).Get(a.GetName()) if err != nil { return admission.NewForbidden(a, err) } // TODO, if we want to actually limit who can use which service account, then we'll need to add logic here to make sure that // we're allowed to use the SA the pod is using. Otherwise, user-A creates pod and user-B (who can't use the SA) can exec into it. createAttributes := admission.NewAttributesRecord(pod, pod, kapi.Kind("Pod").WithVersion(""), a.GetNamespace(), a.GetName(), a.GetResource(), "", admission.Create, a.GetUserInfo()) if err := d.constraintAdmission.Admit(createAttributes); err != nil { return admission.NewForbidden(a, err) } return nil }
func (d *sccExecRestrictions) Admit(a admission.Attributes) (err error) { if a.GetOperation() != admission.Connect { return nil } if a.GetResource() != kapi.Resource("pods") { return nil } if a.GetSubresource() != "attach" && a.GetSubresource() != "exec" { return nil } pod, err := d.client.Pods(a.GetNamespace()).Get(a.GetName()) if err != nil { return admission.NewForbidden(a, err) } // create a synthentic admission attribute to check SCC admission status for this pod // clear the SA name, so that any permissions MUST be based on your user's power, not the SAs power. pod.Spec.ServiceAccountName = "" createAttributes := admission.NewAttributesRecord(pod, kapi.Kind("Pod"), a.GetNamespace(), a.GetName(), a.GetResource(), a.GetSubresource(), admission.Create, a.GetUserInfo()) if err := d.constraintAdmission.Admit(createAttributes); err != nil { return admission.NewForbidden(a, err) } return nil }