func (bs *SourceBuildStrategy) canRunAsRoot(build *buildapi.Build) bool { var rootUser int64 rootUser = 0 pod := &kapi.Pod{ ObjectMeta: kapi.ObjectMeta{ Name: buildapi.GetBuildPodName(build), Namespace: build.Namespace, }, Spec: kapi.PodSpec{ ServiceAccountName: build.Spec.ServiceAccount, Containers: []kapi.Container{ { Name: "sti-build", Image: bs.Image, SecurityContext: &kapi.SecurityContext{ RunAsUser: &rootUser, }, }, }, RestartPolicy: kapi.RestartPolicyNever, }, } userInfo := serviceaccount.UserInfo(build.Namespace, build.Spec.ServiceAccount, "") attrs := admission.NewAttributesRecord(pod, pod, kapi.Kind("Pod").WithVersion(""), pod.Namespace, pod.Name, kapi.Resource("pods").WithVersion(""), "", admission.Create, userInfo) err := bs.AdmissionControl.Admit(attrs) if err != nil { glog.V(2).Infof("Admit for root user returned error: %v", err) } return err == nil }
func validateServiceAccount(client *kclient.Client, ns string, serviceAccount string, hostNetwork bool) error { if !hostNetwork { return nil } // get cluster sccs sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{}) if err != nil { if !errors.IsUnauthorized(err) { return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err) } return nil } // get set of sccs applicable to the service account userInfo := serviceaccount.UserInfo(ns, serviceAccount, "") for _, scc := range sccList.Items { if admission.ConstraintAppliesTo(&scc, userInfo) { switch { case hostNetwork && scc.AllowHostNetwork: return nil } } } return fmt.Errorf("service account %q is not allowed to access the host network on nodes, needs access via a security context constraint", serviceAccount) }
// 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)) }
// Create registers a given new PodSecurityPolicyReview instance to r.registry. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { pspr, ok := obj.(*securityapi.PodSecurityPolicyReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a PodSecurityPolicyReview: %#v", obj)) } if errs := securityvalidation.ValidatePodSecurityPolicyReview(pspr); len(errs) > 0 { return nil, kapierrors.NewInvalid(kapi.Kind("PodSecurityPolicyReview"), "", errs) } ns, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, kapierrors.NewBadRequest("namespace parameter required.") } serviceAccounts, err := getServiceAccounts(pspr.Spec, r.saCache, ns) if err != nil { return nil, kapierrors.NewBadRequest(err.Error()) } if len(serviceAccounts) == 0 { glog.Errorf("No service accounts for namespace %s", ns) return nil, kapierrors.NewBadRequest(fmt.Sprintf("unable to find ServiceAccount for namespace: %s", ns)) } errs := []error{} newStatus := securityapi.PodSecurityPolicyReviewStatus{} for _, sa := range serviceAccounts { userInfo := serviceaccount.UserInfo(ns, sa.Name, "") saConstraints, err := r.sccMatcher.FindApplicableSCCs(userInfo) if err != nil { errs = append(errs, fmt.Errorf("unable to find SecurityContextConstraints for ServiceAccount %s: %v", sa.Name, err)) continue } oscc.DeduplicateSecurityContextConstraints(saConstraints) sort.Sort(oscc.ByPriority(saConstraints)) var namespace *kapi.Namespace for _, constraint := range saConstraints { var ( provider kscc.SecurityContextConstraintsProvider err error ) pspsrs := securityapi.PodSecurityPolicySubjectReviewStatus{} if provider, namespace, err = oscc.CreateProviderFromConstraint(ns, namespace, constraint, r.client); err != nil { errs = append(errs, fmt.Errorf("unable to create provider for service account %s: %v", sa.Name, err)) continue } _, err = podsecuritypolicysubjectreview.FillPodSecurityPolicySubjectReviewStatus(&pspsrs, provider, pspr.Spec.Template.Spec, constraint) if err != nil { glog.Errorf("unable to fill PodSecurityPolicyReviewStatus from constraint %v", err) continue } sapsprs := securityapi.ServiceAccountPodSecurityPolicyReviewStatus{pspsrs, sa.Name} newStatus.AllowedServiceAccounts = append(newStatus.AllowedServiceAccounts, sapsprs) } } if len(errs) > 0 { return nil, kapierrors.NewBadRequest(fmt.Sprintf("%s", kerrors.NewAggregate(errs))) } pspr.Status = newStatus return pspr, nil }
func WithActingAs(handler http.Handler, requestContextMapper api.RequestContextMapper, a authorizer.Authorizer) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { requestedSubject := req.Header.Get("Impersonate-User") if len(requestedSubject) == 0 { handler.ServeHTTP(w, req) return } ctx, exists := requestContextMapper.Get(req) if !exists { forbidden(w, req) return } requestor, exists := api.UserFrom(ctx) if !exists { forbidden(w, req) return } actingAsAttributes := &authorizer.AttributesRecord{ User: requestor, Verb: "impersonate", APIGroup: api.GroupName, Resource: "users", // ResourceName: requestedSubject, ResourceRequest: true, } err := a.Authorize(actingAsAttributes) if err != nil { forbidden(w, req) return } switch { case strings.HasPrefix(requestedSubject, serviceaccount.ServiceAccountUsernamePrefix): namespace, name, err := serviceaccount.SplitUsername(requestedSubject) if err != nil { forbidden(w, req) return } requestContextMapper.Update(req, api.WithUser(ctx, serviceaccount.UserInfo(namespace, name, ""))) default: newUser := &user.DefaultInfo{ Name: requestedSubject, } requestContextMapper.Update(req, api.WithUser(ctx, newUser)) } newCtx, _ := requestContextMapper.Get(req) oldUser, _ := api.UserFrom(ctx) newUser, _ := api.UserFrom(newCtx) httplog.LogOf(req, w).Addf("%v is acting as %v", oldUser, newUser) handler.ServeHTTP(w, req) }) }
// Create registers a given new PodSecurityPolicySubjectReview instance to r.registry. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { pspsr, ok := obj.(*securityapi.PodSecurityPolicySubjectReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a PodSecurityPolicySubjectReview: %#v", obj)) } ns, ok := kapi.NamespaceFrom(ctx) if !ok { return nil, kapierrors.NewBadRequest("namespace parameter required.") } if errs := securityvalidation.ValidatePodSecurityPolicySubjectReview(pspsr); len(errs) > 0 { return nil, kapierrors.NewInvalid(kapi.Kind("PodSecurityPolicySubjectReview"), "", errs) } userInfo := &user.DefaultInfo{Name: pspsr.Spec.User, Groups: pspsr.Spec.Groups} matchedConstraints, err := r.sccMatcher.FindApplicableSCCs(userInfo) if err != nil { return nil, kapierrors.NewBadRequest(fmt.Sprintf("unable to find SecurityContextConstraints: %v", err)) } saName := pspsr.Spec.Template.Spec.ServiceAccountName if len(saName) > 0 { saUserInfo := serviceaccount.UserInfo(ns, saName, "") saConstraints, err := r.sccMatcher.FindApplicableSCCs(saUserInfo) if err != nil { return nil, kapierrors.NewBadRequest(fmt.Sprintf("unable to find SecurityContextConstraints: %v", err)) } matchedConstraints = append(matchedConstraints, saConstraints...) } oscc.DeduplicateSecurityContextConstraints(matchedConstraints) sort.Sort(oscc.ByPriority(matchedConstraints)) var namespace *kapi.Namespace for _, constraint := range matchedConstraints { var ( provider kscc.SecurityContextConstraintsProvider err error ) if provider, namespace, err = oscc.CreateProviderFromConstraint(ns, namespace, constraint, r.client); err != nil { glog.Errorf("Unable to create provider for constraint: %v", err) continue } filled, err := FillPodSecurityPolicySubjectReviewStatus(&pspsr.Status, provider, pspsr.Spec.Template.Spec, constraint) if err != nil { glog.Errorf("unable to fill PodSecurityPolicySubjectReviewStatus from constraint %v", err) continue } if filled { return pspsr, nil } } return pspsr, nil }
func validateServiceAccount(kClient *kclient.Client, ns string, sa string) error { // get cluster sccs sccList, err := kClient.SecurityContextConstraints().List(labels.Everything(), fields.Everything()) if err != nil { return fmt.Errorf("unable to validate service account %v", err) } // get set of sccs applicable to the service account userInfo := serviceaccount.UserInfo(ns, sa, "") for _, scc := range sccList.Items { if admission.ConstraintAppliesTo(&scc, userInfo) { if scc.AllowHostPorts { return nil } } } return fmt.Errorf("unable to validate service account, host ports are forbidden") }
func validateServiceAccount(client *kclient.Client, ns string, serviceAccount string, hostNetwork, hostPorts bool) error { if !hostNetwork && !hostPorts { return nil } // get cluster sccs sccList, err := client.SecurityContextConstraints().List(kapi.ListOptions{}) if err != nil { if !errors.IsUnauthorized(err) { return fmt.Errorf("could not retrieve list of security constraints to verify service account %q: %v", serviceAccount, err) } return nil } // get set of sccs applicable to the service account userInfo := serviceaccount.UserInfo(ns, serviceAccount, "") for _, scc := range sccList.Items { if oscc.ConstraintAppliesTo(&scc, userInfo) { switch { case hostPorts && scc.AllowHostPorts: return nil case hostNetwork && scc.AllowHostNetwork: return nil } } } if hostNetwork { errMsg := "service account %q is not allowed to access the host network on nodes, grant access with oadm policy add-scc-to-user %s -z %s" return fmt.Errorf(errMsg, serviceAccount, bootstrappolicy.SecurityContextConstraintsHostNetwork, serviceAccount) } if hostPorts { errMsg := "service account %q is not allowed to access host ports on nodes, grant access with oadm policy add-scc-to-user %s -z %s" return fmt.Errorf(errMsg, serviceAccount, bootstrappolicy.SecurityContextConstraintsHostNetwork, serviceAccount) } 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 TestPodNodeConstraints(t *testing.T) { ns := kapi.NamespaceDefault tests := []struct { config *api.PodNodeConstraintsConfig resource runtime.Object kind unversioned.GroupKind groupresource unversioned.GroupResource userinfo user.Info reviewResponse *authorizationapi.SubjectAccessReviewResponse expectedResource string expectedErrorMsg string }{ // 0: expect unspecified defaults to not error { config: emptyConfig(), resource: defaultPod(), userinfo: serviceaccount.UserInfo("", "", ""), reviewResponse: reviewResponse(false, ""), expectedResource: "pods/binding", expectedErrorMsg: "", }, // 1: expect nodeSelector to error with user which lacks "pods/binding" access { config: testConfig(), resource: nodeSelectorPod(), userinfo: serviceaccount.UserInfo("", "", ""), reviewResponse: reviewResponse(false, ""), expectedResource: "pods/binding", expectedErrorMsg: "node selection by label(s) [bogus] is prohibited by policy for your role", }, // 2: expect nodeName to fail with user that lacks "pods/binding" access { config: testConfig(), resource: nodeNamePod(), userinfo: serviceaccount.UserInfo("herpy", "derpy", ""), reviewResponse: reviewResponse(false, ""), expectedResource: "pods/binding", expectedErrorMsg: "node selection by nodeName is prohibited by policy for your role", }, // 3: expect nodeName and nodeSelector to fail with user that lacks "pods/binding" access { config: testConfig(), resource: nodeNameNodeSelectorPod(), userinfo: serviceaccount.UserInfo("herpy", "derpy", ""), reviewResponse: reviewResponse(false, ""), expectedResource: "pods/binding", expectedErrorMsg: "node selection by nodeName and label(s) [bogus] is prohibited by policy for your role", }, // 4: expect nodeSelector to succeed with user that has "pods/binding" access { config: testConfig(), resource: nodeSelectorPod(), userinfo: serviceaccount.UserInfo("openshift-infra", "daemonset-controller", ""), reviewResponse: reviewResponse(true, ""), expectedResource: "pods/binding", expectedErrorMsg: "", }, // 5: expect nodeName to succeed with user that has "pods/binding" access { config: testConfig(), resource: nodeNamePod(), userinfo: serviceaccount.UserInfo("openshift-infra", "daemonset-controller", ""), reviewResponse: reviewResponse(true, ""), expectedResource: "pods/binding", expectedErrorMsg: "", }, // 6: expect nil config to bypass admission { config: nil, resource: defaultPod(), userinfo: serviceaccount.UserInfo("", "", ""), reviewResponse: reviewResponse(false, ""), expectedResource: "pods/binding", expectedErrorMsg: "", }, } for i, tc := range tests { var expectedError error errPrefix := fmt.Sprintf("%d", i) prc := NewPodNodeConstraints(tc.config) prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t)) err := prc.(oadmission.Validator).Validate() if err != nil { checkAdmitError(t, err, expectedError, errPrefix) continue } attrs := admission.NewAttributesRecord(tc.resource, nil, kapi.Kind("Pod").WithVersion("version"), ns, "test", kapi.Resource("pods").WithVersion("version"), "", admission.Create, tc.userinfo) if tc.expectedErrorMsg != "" { expectedError = admission.NewForbidden(attrs, fmt.Errorf(tc.expectedErrorMsg)) } err = prc.Admit(attrs) checkAdmitError(t, err, expectedError, errPrefix) } }
func TestPodNodeConstraintsResources(t *testing.T) { ns := kapi.NamespaceDefault testconfigs := []struct { config *api.PodNodeConstraintsConfig userinfo user.Info reviewResponse *authorizationapi.SubjectAccessReviewResponse }{ { config: testConfig(), userinfo: serviceaccount.UserInfo("", "", ""), reviewResponse: reviewResponse(false, ""), }, } testresources := []struct { resource func(bool) runtime.Object kind unversioned.GroupKind groupresource unversioned.GroupResource prefix string }{ { resource: replicationController, kind: kapi.Kind("ReplicationController"), groupresource: kapi.Resource("replicationcontrollers"), prefix: "ReplicationController", }, { resource: deployment, kind: extensions.Kind("Deployment"), groupresource: extensions.Resource("deployments"), prefix: "Deployment", }, { resource: replicaSet, kind: extensions.Kind("ReplicaSet"), groupresource: extensions.Resource("replicasets"), prefix: "ReplicaSet", }, { resource: job, kind: extensions.Kind("Job"), groupresource: extensions.Resource("jobs"), prefix: "Job", }, { resource: job, kind: batch.Kind("Job"), groupresource: batch.Resource("jobs"), prefix: "Job", }, { resource: deploymentConfig, kind: deployapi.Kind("DeploymentConfig"), groupresource: deployapi.Resource("deploymentconfigs"), prefix: "DeploymentConfig", }, { resource: podTemplate, kind: deployapi.Kind("PodTemplate"), groupresource: deployapi.Resource("podtemplates"), prefix: "PodTemplate", }, { resource: podSecurityPolicySubjectReview, kind: securityapi.Kind("PodSecurityPolicySubjectReview"), groupresource: securityapi.Resource("podsecuritypolicysubjectreviews"), prefix: "PodSecurityPolicy", }, { resource: podSecurityPolicySelfSubjectReview, kind: securityapi.Kind("PodSecurityPolicySelfSubjectReview"), groupresource: securityapi.Resource("podsecuritypolicyselfsubjectreviews"), prefix: "PodSecurityPolicy", }, { resource: podSecurityPolicyReview, kind: securityapi.Kind("PodSecurityPolicyReview"), groupresource: securityapi.Resource("podsecuritypolicyreviews"), prefix: "PodSecurityPolicy", }, } testparams := []struct { nodeselector bool expectedErrorMsg string prefix string }{ { nodeselector: true, expectedErrorMsg: "node selection by label(s) [bogus] is prohibited by policy for your role", prefix: "with nodeSelector", }, { nodeselector: false, expectedErrorMsg: "", prefix: "without nodeSelector", }, } testops := []struct { operation admission.Operation }{ { operation: admission.Create, }, { operation: admission.Update, }, } for _, tc := range testconfigs { for _, tr := range testresources { for _, tp := range testparams { for _, top := range testops { var expectedError error errPrefix := fmt.Sprintf("%s; %s; %s", tr.prefix, tp.prefix, top.operation) prc := NewPodNodeConstraints(tc.config) prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t)) err := prc.(oadmission.Validator).Validate() if err != nil { checkAdmitError(t, err, expectedError, errPrefix) continue } attrs := admission.NewAttributesRecord(tr.resource(tp.nodeselector), nil, tr.kind.WithVersion("version"), ns, "test", tr.groupresource.WithVersion("version"), "", top.operation, tc.userinfo) if tp.expectedErrorMsg != "" { expectedError = admission.NewForbidden(attrs, fmt.Errorf(tp.expectedErrorMsg)) } err = prc.Admit(attrs) checkAdmitError(t, err, expectedError, errPrefix) } } } } }
func TestPodNodeConstraintsNonHandledResources(t *testing.T) { ns := kapi.NamespaceDefault errPrefix := "ResourceQuotaTest" var expectedError error prc := NewPodNodeConstraints(testConfig()) prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t)) err := prc.(oadmission.Validator).Validate() if err != nil { checkAdmitError(t, err, expectedError, errPrefix) return } attrs := admission.NewAttributesRecord(resourceQuota(), nil, kapi.Kind("ResourceQuota").WithVersion("version"), ns, "test", kapi.Resource("resourcequotas").WithVersion("version"), "", admission.Create, serviceaccount.UserInfo("", "", "")) err = prc.Admit(attrs) checkAdmitError(t, err, expectedError, errPrefix) }
func TestPodNodeConstraintsPodUpdate(t *testing.T) { ns := kapi.NamespaceDefault var expectedError error errPrefix := "PodUpdate" prc := NewPodNodeConstraints(testConfig()) prc.(oadmission.WantsAuthorizer).SetAuthorizer(fakeAuthorizer(t)) err := prc.(oadmission.Validator).Validate() if err != nil { checkAdmitError(t, err, expectedError, errPrefix) return } attrs := admission.NewAttributesRecord(nodeNamePod(), nodeNamePod(), kapi.Kind("Pod").WithVersion("version"), ns, "test", kapi.Resource("pods").WithVersion("version"), "", admission.Update, serviceaccount.UserInfo("", "", "")) err = prc.Admit(attrs) checkAdmitError(t, err, expectedError, errPrefix) }