package requestlimit import ( "fmt" "io" "github.com/golang/glog" "k8s.io/kubernetes/pkg/admission" kapi "k8s.io/kubernetes/pkg/api" clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/serviceaccount" "github.com/openshift/origin/pkg/client" oadmission "github.com/openshift/origin/pkg/cmd/server/admission" configlatest "github.com/openshift/origin/pkg/cmd/server/api/latest" requestlimitapi "github.com/openshift/origin/pkg/project/admission/requestlimit/api" requestlimitapivalidation "github.com/openshift/origin/pkg/project/admission/requestlimit/api/validation" projectapi "github.com/openshift/origin/pkg/project/api" projectcache "github.com/openshift/origin/pkg/project/cache" uservalidation "github.com/openshift/origin/pkg/user/api/validation" ) // allowedTerminatingProjects is the number of projects that are owned by a user, are in terminating state, // and do not count towards the user's limit. const allowedTerminatingProjects = 2 func init() { admission.RegisterPlugin("ProjectRequestLimit", func(client clientset.Interface, config io.Reader) (admission.Interface, error) { pluginConfig, err := readConfig(config) if err != nil { return nil, err } if pluginConfig == nil { glog.Infof("Admission plugin %q is not configured so it will be disabled.", "ProjectRequestLimit") return nil, nil } return NewProjectRequestLimit(pluginConfig) }) } func readConfig(reader io.Reader) (*requestlimitapi.ProjectRequestLimitConfig, error) { obj, err := configlatest.ReadYAML(reader) if err != nil { return nil, err } if obj == nil { return nil, nil } config, ok := obj.(*requestlimitapi.ProjectRequestLimitConfig) if !ok { return nil, fmt.Errorf("unexpected config object: %#v", obj) } errs := requestlimitapivalidation.ValidateProjectRequestLimitConfig(config) if len(errs) > 0 { return nil, errs.ToAggregate() } return config, nil } type projectRequestLimit struct { *admission.Handler client client.Interface config *requestlimitapi.ProjectRequestLimitConfig cache *projectcache.ProjectCache } // ensure that the required Openshift admission interfaces are implemented var _ = oadmission.WantsProjectCache(&projectRequestLimit{}) var _ = oadmission.WantsOpenshiftClient(&projectRequestLimit{}) var _ = oadmission.Validator(&projectRequestLimit{}) // 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 o.config == nil { return nil } if a.GetResource().GroupResource() != 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 } // maxProjectsByRequester returns the maximum number of projects allowed for a given user, whether a limit exists, and an error // if an error occurred. If a limit doesn't exist, the maximum number should be ignored. func (o *projectRequestLimit) maxProjectsByRequester(userName string) (int, bool, error) { // service accounts have a different ruleset, check them if _, _, err := serviceaccount.SplitUsername(userName); err == nil { if o.config.MaxProjectsForServiceAccounts == nil { return 0, false, nil } return *o.config.MaxProjectsForServiceAccounts, true, nil } // if we aren't a valid username, we came in as cert user for certain, use our cert user rules if reasons := uservalidation.ValidateUserName(userName, false); len(reasons) != 0 { if o.config.MaxProjectsForSystemUsers == nil { return 0, false, nil } return *o.config.MaxProjectsForSystemUsers, true, nil } // prevent a user lookup if no limits are configured if len(o.config.Limits) == 0 { return 0, false, nil } user, err := o.client.Users().Get(userName) if err != nil { return 0, false, err } userLabels := labels.Set(user.Labels) for _, limit := range o.config.Limits { selector := labels.Set(limit.Selector).AsSelector() if selector.Matches(userLabels) { if limit.MaxProjects == nil { return 0, false, nil } return *limit.MaxProjects, true, nil } } return 0, false, nil } func (o *projectRequestLimit) projectCountByRequester(userName string) (int, error) { namespaces, err := o.cache.Store.ByIndex("requester", userName) if err != nil { return 0, err } terminatingCount := 0 for _, obj := range namespaces { ns, ok := obj.(*kapi.Namespace) if !ok { return 0, fmt.Errorf("object in cache is not a namespace: %#v", obj) } if ns.Status.Phase == kapi.NamespaceTerminating { terminatingCount++ } } count := len(namespaces) if terminatingCount > allowedTerminatingProjects { count -= allowedTerminatingProjects } else { count -= terminatingCount } return count, nil } func (o *projectRequestLimit) SetOpenshiftClient(client client.Interface) { o.client = client } func (o *projectRequestLimit) SetProjectCache(cache *projectcache.ProjectCache) { o.cache = cache } func (o *projectRequestLimit) Validate() error { if o.client == nil { return fmt.Errorf("ProjectRequestLimit plugin requires an Openshift client") } if o.cache == nil { return fmt.Errorf("ProjectRequestLimit plugin requires a project cache") } return nil } func NewProjectRequestLimit(config *requestlimitapi.ProjectRequestLimitConfig) (admission.Interface, error) { return &projectRequestLimit{ config: config, Handler: admission.NewHandler(admission.Create), }, nil } func projectRequester(ns *kapi.Namespace) string { return ns.Annotations[projectapi.ProjectRequester] }