func describerMap(c *client.Client, kclient kclient.Interface, host string) map[unversioned.GroupKind]kctl.Describer { m := map[unversioned.GroupKind]kctl.Describer{ buildapi.Kind("Build"): &BuildDescriber{c, kclient}, buildapi.Kind("BuildConfig"): &BuildConfigDescriber{c, host}, deployapi.Kind("DeploymentConfig"): &DeploymentConfigDescriber{c, kclient, nil}, authorizationapi.Kind("Identity"): &IdentityDescriber{c}, imageapi.Kind("Image"): &ImageDescriber{c}, imageapi.Kind("ImageStream"): &ImageStreamDescriber{c}, imageapi.Kind("ImageStreamTag"): &ImageStreamTagDescriber{c}, imageapi.Kind("ImageStreamImage"): &ImageStreamImageDescriber{c}, routeapi.Kind("Route"): &RouteDescriber{c, kclient}, projectapi.Kind("Project"): &ProjectDescriber{c, kclient}, templateapi.Kind("Template"): &TemplateDescriber{c, meta.NewAccessor(), kapi.Scheme, nil}, authorizationapi.Kind("Policy"): &PolicyDescriber{c}, authorizationapi.Kind("PolicyBinding"): &PolicyBindingDescriber{c}, authorizationapi.Kind("RoleBinding"): &RoleBindingDescriber{c}, authorizationapi.Kind("Role"): &RoleDescriber{c}, authorizationapi.Kind("ClusterPolicy"): &ClusterPolicyDescriber{c}, authorizationapi.Kind("ClusterPolicyBinding"): &ClusterPolicyBindingDescriber{c}, authorizationapi.Kind("ClusterRoleBinding"): &ClusterRoleBindingDescriber{c}, authorizationapi.Kind("ClusterRole"): &ClusterRoleDescriber{c}, oauthapi.Kind("OAuthAccessToken"): &OAuthAccessTokenDescriber{c}, userapi.Kind("User"): &UserDescriber{c}, userapi.Kind("Group"): &GroupDescriber{c.Groups()}, userapi.Kind("UserIdentityMapping"): &UserIdentityMappingDescriber{c}, quotaapi.Kind("ClusterResourceQuota"): &ClusterQuotaDescriber{c}, quotaapi.Kind("AppliedClusterResourceQuota"): &AppliedClusterQuotaDescriber{c}, } return m }
// Create transforms a LocalSAR into an ClusterSAR that is requesting a namespace. That collapses the code paths. // LocalSubjectAccessReview exists to allow clean expression of policy. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { localSAR, ok := obj.(*authorizationapi.LocalSubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a localSubjectAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateLocalSubjectAccessReview(localSAR); len(errs) > 0 { return nil, kapierrors.NewInvalid(authorizationapi.Kind(localSAR.Kind), "", errs) } if namespace := kapi.NamespaceValue(ctx); len(namespace) == 0 { return nil, kapierrors.NewBadRequest(fmt.Sprintf("namespace is required on this type: %v", namespace)) } else if (len(localSAR.Action.Namespace) > 0) && (namespace != localSAR.Action.Namespace) { return nil, field.Invalid(field.NewPath("namespace"), localSAR.Action.Namespace, fmt.Sprintf("namespace must be: %v", namespace)) } // transform this into a SubjectAccessReview clusterSAR := &authorizationapi.SubjectAccessReview{ Action: localSAR.Action, User: localSAR.User, Groups: localSAR.Groups, Scopes: localSAR.Scopes, } clusterSAR.Action.Namespace = kapi.NamespaceValue(ctx) return r.clusterSARRegistry.CreateSubjectAccessReview(kapi.WithNamespace(ctx, ""), clusterSAR) }
// Create registers a given new ResourceAccessReview instance to r.registry. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { resourceAccessReview, ok := obj.(*authorizationapi.ResourceAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a resourceAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateResourceAccessReview(resourceAccessReview); len(errs) > 0 { return nil, kapierrors.NewInvalid(authorizationapi.Kind(resourceAccessReview.Kind), "", errs) } // if a namespace is present on the request, then the namespace on the on the RAR is overwritten. // This is to support backwards compatibility. To have gotten here in this state, it means that // the authorizer decided that a user could run an RAR against this namespace if namespace := kapi.NamespaceValue(ctx); len(namespace) > 0 { resourceAccessReview.Action.Namespace = namespace } else if err := r.isAllowed(ctx, resourceAccessReview); err != nil { // this check is mutually exclusive to the condition above. localSAR and localRAR both clear the namespace before delegating their calls // We only need to check if the RAR is allowed **again** if the authorizer didn't already approve the request for a legacy call. return nil, err } requestContext := kapi.WithNamespace(ctx, resourceAccessReview.Action.Namespace) attributes := authorizer.ToDefaultAuthorizationAttributes(resourceAccessReview.Action) users, groups, err := r.authorizer.GetAllowedSubjects(requestContext, attributes) response := &authorizationapi.ResourceAccessReviewResponse{ Namespace: resourceAccessReview.Action.Namespace, Users: users, Groups: groups, } if err != nil { response.EvaluationError = err.Error() } return response, nil }
// Create registers a given new ResourceAccessReview instance to r.registry. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a subjectAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) > 0 { return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs) } // if a namespace is present on the request, then the namespace on the on the SAR is overwritten. // This is to support backwards compatibility. To have gotten here in this state, it means that // the authorizer decided that a user could run an SAR against this namespace if namespace := kapi.NamespaceValue(ctx); len(namespace) > 0 { subjectAccessReview.Action.Namespace = namespace } else if err := r.isAllowed(ctx, subjectAccessReview); err != nil { // this check is mutually exclusive to the condition above. localSAR and localRAR both clear the namespace before delegating their calls // We only need to check if the SAR is allowed **again** if the authorizer didn't already approve the request for a legacy call. return nil, err } var userToCheck user.Info if (len(subjectAccessReview.User) == 0) && (len(subjectAccessReview.Groups) == 0) { // if no user or group was specified, use the info from the context ctxUser, exists := kapi.UserFrom(ctx) if !exists { return nil, kapierrors.NewBadRequest("user missing from context") } userToCheck = ctxUser } else { userToCheck = &user.DefaultInfo{ Name: subjectAccessReview.User, Groups: subjectAccessReview.Groups.List(), } } requestContext := kapi.WithNamespace(kapi.WithUser(ctx, userToCheck), subjectAccessReview.Action.Namespace) attributes := authorizer.ToDefaultAuthorizationAttributes(subjectAccessReview.Action) allowed, reason, err := r.authorizer.Authorize(requestContext, attributes) if err != nil { return nil, err } response := &authorizationapi.SubjectAccessReviewResponse{ Namespace: subjectAccessReview.Action.Namespace, Allowed: allowed, Reason: reason, } return response, nil }
func (c *readOnlyClusterPolicyCache) List(options *kapi.ListOptions) (*authorizationapi.ClusterPolicyList, error) { clusterPolicyList := &authorizationapi.ClusterPolicyList{} returnedList := c.indexer.List() matcher := clusterpolicyregistry.Matcher(api.ListOptionsToSelectors(options)) for i := range returnedList { clusterPolicy, castOK := returnedList[i].(*authorizationapi.ClusterPolicy) if !castOK { return clusterPolicyList, errors.NewInvalid(authorizationapi.Kind("ClusterPolicy"), "clusterPolicy", kfield.ErrorList{}) } if matches, err := matcher.Matches(clusterPolicy); err == nil && matches { clusterPolicyList.Items = append(clusterPolicyList.Items, *clusterPolicy) } } return clusterPolicyList, nil }
func (c *readOnlyPolicyCache) List(options *kapi.ListOptions, namespace string) (*authorizationapi.PolicyList, error) { var returnedList []interface{} if namespace == kapi.NamespaceAll { returnedList = c.indexer.List() } else { items, err := c.indexer.Index("namespace", &authorizationapi.Policy{ObjectMeta: kapi.ObjectMeta{Namespace: namespace}}) returnedList = items if err != nil { return &authorizationapi.PolicyList{}, errors.NewInvalid(authorizationapi.Kind("PolicyList"), "policyList", kfield.ErrorList{kfield.Invalid(kfield.NewPath("policyList"), nil, err.Error())}) } } policyList := &authorizationapi.PolicyList{} matcher := policyregistry.Matcher(oapi.ListOptionsToSelectors(options)) for i := range returnedList { policy, castOK := returnedList[i].(*authorizationapi.Policy) if !castOK { return policyList, errors.NewInvalid(authorizationapi.Kind("PolicyList"), "policyList", kfield.ErrorList{}) } if matches, err := matcher.Matches(policy); err == nil && matches { policyList.Items = append(policyList.Items, *policy) } } return policyList, nil }
func (c *readOnlyClusterPolicyCache) Get(name string) (*authorizationapi.ClusterPolicy, error) { keyObj := &authorizationapi.ClusterPolicy{ObjectMeta: kapi.ObjectMeta{Name: name}} key, _ := c.keyFunc(keyObj) item, exists, getErr := c.indexer.GetByKey(key) if getErr != nil { return &authorizationapi.ClusterPolicy{}, getErr } if !exists { existsErr := errors.NewNotFound(authorizationapi.Resource("clusterpolicy"), name) return &authorizationapi.ClusterPolicy{}, existsErr } clusterPolicy, castOK := item.(*authorizationapi.ClusterPolicy) if !castOK { castErr := errors.NewInvalid(authorizationapi.Kind("ClusterPolicy"), name, kfield.ErrorList{}) return &authorizationapi.ClusterPolicy{}, castErr } return clusterPolicy, nil }
func (c *readOnlyPolicyBindingCache) Get(name, namespace string) (*authorizationapi.PolicyBinding, error) { keyObj := &authorizationapi.PolicyBinding{ObjectMeta: kapi.ObjectMeta{Namespace: namespace, Name: name}} key, _ := c.keyFunc(keyObj) item, exists, getErr := c.indexer.GetByKey(key) if getErr != nil { return &authorizationapi.PolicyBinding{}, getErr } if !exists { existsErr := errors.NewNotFound(authorizationapi.Resource("policybinding"), name) return &authorizationapi.PolicyBinding{}, existsErr } policyBinding, castOK := item.(*authorizationapi.PolicyBinding) if !castOK { castErr := errors.NewInvalid(authorizationapi.Kind("PolicyBinding"), name, kfield.ErrorList{}) return &authorizationapi.PolicyBinding{}, castErr } return policyBinding, nil }
// NewFactory creates an object that holds common methods across all OpenShift commands func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory { var restMapper meta.MultiRESTMapper seenGroups := sets.String{} for _, gv := range registered.EnabledVersions() { if seenGroups.Has(gv.Group) { continue } seenGroups.Insert(gv.Group) groupMeta, err := registered.Group(gv.Group) if err != nil { continue } restMapper = meta.MultiRESTMapper(append(restMapper, groupMeta.RESTMapper)) } mapper := ShortcutExpander{RESTMapper: kubectl.ShortcutExpander{RESTMapper: restMapper}} clients := &clientCache{ clients: make(map[string]*client.Client), configs: make(map[string]*kclient.Config), loader: clientConfig, } w := &Factory{ Factory: cmdutil.NewFactory(clientConfig), OpenShiftClientConfig: clientConfig, clients: clients, } w.Object = func() (meta.RESTMapper, runtime.ObjectTyper) { // Output using whatever version was negotiated in the client cache. The // version we decode with may not be the same as what the server requires. if cfg, err := clients.ClientConfigForVersion(nil); err == nil { cmdApiVersion := unversioned.GroupVersion{} if cfg.GroupVersion != nil { cmdApiVersion = *cfg.GroupVersion } return kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}}, api.Scheme } return mapper, api.Scheme } kClientForMapping := w.Factory.ClientForMapping w.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { mappingVersion := mapping.GroupVersionKind.GroupVersion() client, err := clients.ClientForVersion(&mappingVersion) if err != nil { return nil, err } return client.RESTClient, nil } return kClientForMapping(mapping) } // Save original Describer function kDescriberFunc := w.Factory.Describer w.Describer = func(mapping *meta.RESTMapping) (kubectl.Describer, error) { if latest.OriginKind(mapping.GroupVersionKind) { oClient, kClient, err := w.Clients() if err != nil { return nil, fmt.Errorf("unable to create client %s: %v", mapping.GroupVersionKind.Kind, err) } mappingVersion := mapping.GroupVersionKind.GroupVersion() cfg, err := clients.ClientConfigForVersion(&mappingVersion) if err != nil { return nil, fmt.Errorf("unable to load a client %s: %v", mapping.GroupVersionKind.Kind, err) } describer, ok := describe.DescriberFor(mapping.GroupVersionKind.GroupKind(), oClient, kClient, cfg.Host) if !ok { return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind) } return describer, nil } return kDescriberFunc(mapping) } kScalerFunc := w.Factory.Scaler w.Scaler = func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { oc, kc, err := w.Clients() if err != nil { return nil, err } if mapping.GroupVersionKind.GroupKind() == deployapi.Kind("DeploymentConfig") { return deployscaler.NewDeploymentConfigScaler(oc, kc), nil } return kScalerFunc(mapping) } kReaperFunc := w.Factory.Reaper w.Reaper = func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { oc, kc, err := w.Clients() if err != nil { return nil, err } switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): return deployreaper.NewDeploymentConfigReaper(oc, kc), nil case authorizationapi.Kind("Role"): return authorizationreaper.NewRoleReaper(oc, oc), nil case authorizationapi.Kind("ClusterRole"): return authorizationreaper.NewClusterRoleReaper(oc, oc, oc), nil case userapi.Kind("User"): return authenticationreaper.NewUserReaper( client.UsersInterface(oc), client.GroupsInterface(oc), client.ClusterRoleBindingsInterface(oc), client.RoleBindingsNamespacer(oc), kclient.SecurityContextConstraintsInterface(kc), ), nil case userapi.Kind("Group"): return authenticationreaper.NewGroupReaper( client.GroupsInterface(oc), client.ClusterRoleBindingsInterface(oc), client.RoleBindingsNamespacer(oc), kclient.SecurityContextConstraintsInterface(kc), ), nil case buildapi.Kind("BuildConfig"): return buildreaper.NewBuildConfigReaper(oc), nil } return kReaperFunc(mapping) } kGenerators := w.Factory.Generators w.Generators = func(cmdName string) map[string]kubectl.Generator { originGenerators := DefaultGenerators(cmdName) kubeGenerators := kGenerators(cmdName) ret := map[string]kubectl.Generator{} for k, v := range kubeGenerators { ret[k] = v } for k, v := range originGenerators { ret[k] = v } return ret } kPodSelectorForObjectFunc := w.Factory.PodSelectorForObject w.PodSelectorForObject = func(object runtime.Object) (string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return kubectl.MakeLabels(t.Spec.Selector), nil default: return kPodSelectorForObjectFunc(object) } } kMapBasedSelectorForObjectFunc := w.Factory.MapBasedSelectorForObject w.MapBasedSelectorForObject = func(object runtime.Object) (string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return kubectl.MakeLabels(t.Spec.Selector), nil default: return kMapBasedSelectorForObjectFunc(object) } } kPortsForObjectFunc := w.Factory.PortsForObject w.PortsForObject = func(object runtime.Object) ([]string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return getPorts(t.Spec.Template.Spec), nil default: return kPortsForObjectFunc(object) } } kLogsForObjectFunc := w.Factory.LogsForObject w.LogsForObject = func(object, options runtime.Object) (*kclient.Request, error) { oc, _, err := w.Clients() if err != nil { return nil, err } switch t := object.(type) { case *deployapi.DeploymentConfig: dopts, ok := options.(*deployapi.DeploymentLogOptions) if !ok { return nil, errors.New("provided options object is not a DeploymentLogOptions") } return oc.DeploymentLogs(t.Namespace).Get(t.Name, *dopts), nil case *buildapi.Build: bopts, ok := options.(*buildapi.BuildLogOptions) if !ok { return nil, errors.New("provided options object is not a BuildLogOptions") } if bopts.Version != nil { return nil, errors.New("cannot specify a version and a build") } return oc.BuildLogs(t.Namespace).Get(t.Name, *bopts), nil case *buildapi.BuildConfig: bopts, ok := options.(*buildapi.BuildLogOptions) if !ok { return nil, errors.New("provided options object is not a BuildLogOptions") } builds, err := oc.Builds(t.Namespace).List(api.ListOptions{}) if err != nil { return nil, err } builds.Items = buildapi.FilterBuilds(builds.Items, buildapi.ByBuildConfigLabelPredicate(t.Name)) if len(builds.Items) == 0 { return nil, fmt.Errorf("no builds found for %q", t.Name) } if bopts.Version != nil { // If a version has been specified, try to get the logs from that build. desired := buildutil.BuildNameForConfigVersion(t.Name, int(*bopts.Version)) return oc.BuildLogs(t.Namespace).Get(desired, *bopts), nil } sort.Sort(sort.Reverse(buildapi.BuildSliceByCreationTimestamp(builds.Items))) return oc.BuildLogs(t.Namespace).Get(builds.Items[0].Name, *bopts), nil default: return kLogsForObjectFunc(object, options) } } w.Printer = func(mapping *meta.RESTMapping, noHeaders, withNamespace, wide bool, showAll bool, showLabels, absoluteTimestamps bool, columnLabels []string) (kubectl.ResourcePrinter, error) { return describe.NewHumanReadablePrinter(noHeaders, withNamespace, wide, showAll, showLabels, absoluteTimestamps, columnLabels), nil } kCanBeExposed := w.Factory.CanBeExposed w.CanBeExposed = func(kind unversioned.GroupKind) error { if kind == deployapi.Kind("DeploymentConfig") { return nil } return kCanBeExposed(kind) } kCanBeAutoscaled := w.Factory.CanBeAutoscaled w.CanBeAutoscaled = func(kind unversioned.GroupKind) error { if kind == deployapi.Kind("DeploymentConfig") { return nil } return kCanBeAutoscaled(kind) } kAttachablePodForObjectFunc := w.Factory.AttachablePodForObject w.AttachablePodForObject = func(object runtime.Object) (*api.Pod, error) { oc, kc, err := w.Clients() if err != nil { return nil, err } switch t := object.(type) { case *deployapi.DeploymentConfig: var err error var pods *api.PodList for pods == nil || len(pods.Items) == 0 { if t.Status.LatestVersion == 0 { time.Sleep(2 * time.Second) } if t, err = oc.DeploymentConfigs(t.Namespace).Get(t.Name); err != nil { return nil, err } latestDeploymentName := deployutil.LatestDeploymentNameForConfig(t) deployment, err := kc.ReplicationControllers(t.Namespace).Get(latestDeploymentName) if err != nil { if kerrors.IsNotFound(err) { continue } return nil, err } pods, err = kc.Pods(deployment.Namespace).List(api.ListOptions{LabelSelector: labels.SelectorFromSet(deployment.Spec.Selector)}) if err != nil { return nil, err } if len(pods.Items) == 0 { time.Sleep(2 * time.Second) } } var oldestPod *api.Pod for _, pod := range pods.Items { if oldestPod == nil || pod.CreationTimestamp.Before(oldestPod.CreationTimestamp) { oldestPod = &pod } } return oldestPod, nil default: return kAttachablePodForObjectFunc(object) } } kSwaggerSchemaFunc := w.Factory.SwaggerSchema w.Factory.SwaggerSchema = func(gvk unversioned.GroupVersionKind) (*swagger.ApiDeclaration, error) { if !latest.OriginKind(gvk) { return kSwaggerSchemaFunc(gvk) } // TODO: we need to register the OpenShift API under the Kube group, and start returning the OpenShift // group from the scheme. oc, _, err := w.Clients() if err != nil { return nil, err } return w.OriginSwaggerSchema(oc.RESTClient, gvk.GroupVersion()) } w.EditorEnvs = func() []string { return []string{"OC_EDITOR", "EDITOR"} } return w }
// NewFactory creates an object that holds common methods across all OpenShift commands func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory { restMapper := registered.RESTMapper() clients := &clientCache{ clients: make(map[string]*client.Client), configs: make(map[string]*restclient.Config), loader: clientConfig, } w := &Factory{ Factory: cmdutil.NewFactory(clientConfig), OpenShiftClientConfig: clientConfig, clients: clients, ImageResolutionOptions: &imageResolutionOptions{}, } w.Object = func(bool) (meta.RESTMapper, runtime.ObjectTyper) { defaultMapper := ShortcutExpander{RESTMapper: kubectl.ShortcutExpander{RESTMapper: restMapper}} defaultTyper := api.Scheme // Output using whatever version was negotiated in the client cache. The // version we decode with may not be the same as what the server requires. cfg, err := clients.ClientConfigForVersion(nil) if err != nil { return defaultMapper, defaultTyper } cmdApiVersion := unversioned.GroupVersion{} if cfg.GroupVersion != nil { cmdApiVersion = *cfg.GroupVersion } // at this point we've negotiated and can get the client oclient, err := clients.ClientForVersion(nil) if err != nil { return defaultMapper, defaultTyper } cacheDir := computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube"), cfg.Host) cachedDiscoverClient := NewCachedDiscoveryClient(client.NewDiscoveryClient(oclient.RESTClient), cacheDir, time.Duration(10*time.Minute)) // if we can't find the server version or its too old to have Kind information in the discovery doc, skip the discovery RESTMapper // and use our hardcoded levels mapper := registered.RESTMapper() if serverVersion, err := cachedDiscoverClient.ServerVersion(); err == nil && useDiscoveryRESTMapper(serverVersion.GitVersion) { mapper = restmapper.NewDiscoveryRESTMapper(cachedDiscoverClient) } mapper = NewShortcutExpander(cachedDiscoverClient, kubectl.ShortcutExpander{RESTMapper: mapper}) return kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}}, api.Scheme } w.UnstructuredObject = func() (meta.RESTMapper, runtime.ObjectTyper, error) { // load a discovery client from the default config cfg, err := clients.ClientConfigForVersion(nil) if err != nil { return nil, nil, err } dc, err := discovery.NewDiscoveryClientForConfig(cfg) if err != nil { return nil, nil, err } cacheDir := computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube"), cfg.Host) cachedDiscoverClient := NewCachedDiscoveryClient(client.NewDiscoveryClient(dc.RESTClient), cacheDir, time.Duration(10*time.Minute)) // enumerate all group resources groupResources, err := discovery.GetAPIGroupResources(cachedDiscoverClient) if err != nil { return nil, nil, err } // Register unknown APIs as third party for now to make // validation happy. TODO perhaps make a dynamic schema // validator to avoid this. for _, group := range groupResources { for _, version := range group.Group.Versions { gv := unversioned.GroupVersion{Group: group.Group.Name, Version: version.Version} if !registered.IsRegisteredVersion(gv) { registered.AddThirdPartyAPIGroupVersions(gv) } } } // construct unstructured mapper and typer mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured) typer := discovery.NewUnstructuredObjectTyper(groupResources) return NewShortcutExpander(cachedDiscoverClient, kubectl.ShortcutExpander{RESTMapper: mapper}), typer, nil } kClientForMapping := w.Factory.ClientForMapping w.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { mappingVersion := mapping.GroupVersionKind.GroupVersion() client, err := clients.ClientForVersion(&mappingVersion) if err != nil { return nil, err } return client.RESTClient, nil } return kClientForMapping(mapping) } kUnstructuredClientForMapping := w.Factory.UnstructuredClientForMapping w.UnstructuredClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { cfg, err := clientConfig.ClientConfig() if err != nil { return nil, err } if err := client.SetOpenShiftDefaults(cfg); err != nil { return nil, err } cfg.APIPath = "/apis" if mapping.GroupVersionKind.Group == api.GroupName { cfg.APIPath = "/oapi" } gv := mapping.GroupVersionKind.GroupVersion() cfg.ContentConfig = dynamic.ContentConfig() cfg.GroupVersion = &gv return restclient.RESTClientFor(cfg) } return kUnstructuredClientForMapping(mapping) } // Save original Describer function kDescriberFunc := w.Factory.Describer w.Describer = func(mapping *meta.RESTMapping) (kubectl.Describer, error) { if latest.OriginKind(mapping.GroupVersionKind) { oClient, kClient, err := w.Clients() if err != nil { return nil, fmt.Errorf("unable to create client %s: %v", mapping.GroupVersionKind.Kind, err) } mappingVersion := mapping.GroupVersionKind.GroupVersion() cfg, err := clients.ClientConfigForVersion(&mappingVersion) if err != nil { return nil, fmt.Errorf("unable to load a client %s: %v", mapping.GroupVersionKind.Kind, err) } describer, ok := describe.DescriberFor(mapping.GroupVersionKind.GroupKind(), oClient, kClient, cfg.Host) if !ok { return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind) } return describer, nil } return kDescriberFunc(mapping) } kScalerFunc := w.Factory.Scaler w.Scaler = func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { if mapping.GroupVersionKind.GroupKind() == deployapi.Kind("DeploymentConfig") { oc, kc, err := w.Clients() if err != nil { return nil, err } return deploycmd.NewDeploymentConfigScaler(oc, kc), nil } return kScalerFunc(mapping) } kReaperFunc := w.Factory.Reaper w.Reaper = func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): oc, kc, err := w.Clients() if err != nil { return nil, err } return deploycmd.NewDeploymentConfigReaper(oc, kc), nil case authorizationapi.Kind("Role"): oc, _, err := w.Clients() if err != nil { return nil, err } return authorizationreaper.NewRoleReaper(oc, oc), nil case authorizationapi.Kind("ClusterRole"): oc, _, err := w.Clients() if err != nil { return nil, err } return authorizationreaper.NewClusterRoleReaper(oc, oc, oc), nil case userapi.Kind("User"): oc, kc, err := w.Clients() if err != nil { return nil, err } return authenticationreaper.NewUserReaper( client.UsersInterface(oc), client.GroupsInterface(oc), client.ClusterRoleBindingsInterface(oc), client.RoleBindingsNamespacer(oc), kclient.SecurityContextConstraintsInterface(kc), ), nil case userapi.Kind("Group"): oc, kc, err := w.Clients() if err != nil { return nil, err } return authenticationreaper.NewGroupReaper( client.GroupsInterface(oc), client.ClusterRoleBindingsInterface(oc), client.RoleBindingsNamespacer(oc), kclient.SecurityContextConstraintsInterface(kc), ), nil case buildapi.Kind("BuildConfig"): oc, _, err := w.Clients() if err != nil { return nil, err } return buildcmd.NewBuildConfigReaper(oc), nil } return kReaperFunc(mapping) } kGenerators := w.Factory.Generators w.Generators = func(cmdName string) map[string]kubectl.Generator { originGenerators := DefaultGenerators(cmdName) kubeGenerators := kGenerators(cmdName) ret := map[string]kubectl.Generator{} for k, v := range kubeGenerators { ret[k] = v } for k, v := range originGenerators { ret[k] = v } return ret } kMapBasedSelectorForObjectFunc := w.Factory.MapBasedSelectorForObject w.MapBasedSelectorForObject = func(object runtime.Object) (string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return kubectl.MakeLabels(t.Spec.Selector), nil default: return kMapBasedSelectorForObjectFunc(object) } } kPortsForObjectFunc := w.Factory.PortsForObject w.PortsForObject = func(object runtime.Object) ([]string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return getPorts(t.Spec.Template.Spec), nil default: return kPortsForObjectFunc(object) } } kLogsForObjectFunc := w.Factory.LogsForObject w.LogsForObject = func(object, options runtime.Object) (*restclient.Request, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: dopts, ok := options.(*deployapi.DeploymentLogOptions) if !ok { return nil, errors.New("provided options object is not a DeploymentLogOptions") } oc, _, err := w.Clients() if err != nil { return nil, err } return oc.DeploymentLogs(t.Namespace).Get(t.Name, *dopts), nil case *buildapi.Build: bopts, ok := options.(*buildapi.BuildLogOptions) if !ok { return nil, errors.New("provided options object is not a BuildLogOptions") } if bopts.Version != nil { return nil, errors.New("cannot specify a version and a build") } oc, _, err := w.Clients() if err != nil { return nil, err } return oc.BuildLogs(t.Namespace).Get(t.Name, *bopts), nil case *buildapi.BuildConfig: bopts, ok := options.(*buildapi.BuildLogOptions) if !ok { return nil, errors.New("provided options object is not a BuildLogOptions") } oc, _, err := w.Clients() if err != nil { return nil, err } builds, err := oc.Builds(t.Namespace).List(api.ListOptions{}) if err != nil { return nil, err } builds.Items = buildapi.FilterBuilds(builds.Items, buildapi.ByBuildConfigPredicate(t.Name)) if len(builds.Items) == 0 { return nil, fmt.Errorf("no builds found for %q", t.Name) } if bopts.Version != nil { // If a version has been specified, try to get the logs from that build. desired := buildutil.BuildNameForConfigVersion(t.Name, int(*bopts.Version)) return oc.BuildLogs(t.Namespace).Get(desired, *bopts), nil } sort.Sort(sort.Reverse(buildapi.BuildSliceByCreationTimestamp(builds.Items))) return oc.BuildLogs(t.Namespace).Get(builds.Items[0].Name, *bopts), nil default: return kLogsForObjectFunc(object, options) } } // Saves current resource name (or alias if any) in PrintOptions. Once saved, it will not be overwritten by the // kubernetes resource alias look-up, as it will notice a non-empty value in `options.Kind` w.Printer = func(mapping *meta.RESTMapping, options kubectl.PrintOptions) (kubectl.ResourcePrinter, error) { if mapping != nil { options.Kind = mapping.Resource if alias, ok := resourceShortFormFor(mapping.Resource); ok { options.Kind = alias } } return describe.NewHumanReadablePrinter(options), nil } // PrintResourceInfos receives a list of resource infos and prints versioned objects if a generic output format was specified // otherwise, it iterates through info objects, printing each resource with a unique printer for its mapping w.PrintResourceInfos = func(cmd *cobra.Command, infos []*resource.Info, out io.Writer) error { printer, generic, err := cmdutil.PrinterForCommand(cmd) if err != nil { return nil } if !generic { for _, info := range infos { mapping := info.ResourceMapping() printer, err := w.PrinterForMapping(cmd, mapping, false) if err != nil { return err } if err := printer.PrintObj(info.Object, out); err != nil { return nil } } return nil } clientConfig, err := w.ClientConfig() if err != nil { return err } outputVersion, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) if err != nil { return err } object, err := resource.AsVersionedObject(infos, len(infos) != 1, outputVersion, api.Codecs.LegacyCodec(outputVersion)) if err != nil { return err } return printer.PrintObj(object, out) } kCanBeExposed := w.Factory.CanBeExposed w.CanBeExposed = func(kind unversioned.GroupKind) error { if kind == deployapi.Kind("DeploymentConfig") { return nil } return kCanBeExposed(kind) } kCanBeAutoscaled := w.Factory.CanBeAutoscaled w.CanBeAutoscaled = func(kind unversioned.GroupKind) error { if kind == deployapi.Kind("DeploymentConfig") { return nil } return kCanBeAutoscaled(kind) } kAttachablePodForObjectFunc := w.Factory.AttachablePodForObject w.AttachablePodForObject = func(object runtime.Object) (*api.Pod, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: _, kc, err := w.Clients() if err != nil { return nil, err } selector := labels.SelectorFromSet(t.Spec.Selector) f := func(pods []*api.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) } pod, _, err := cmdutil.GetFirstPod(kc, t.Namespace, selector, 1*time.Minute, f) return pod, err default: return kAttachablePodForObjectFunc(object) } } kUpdatePodSpecForObject := w.Factory.UpdatePodSpecForObject w.UpdatePodSpecForObject = func(obj runtime.Object, fn func(*api.PodSpec) error) (bool, error) { switch t := obj.(type) { case *deployapi.DeploymentConfig: template := t.Spec.Template if template == nil { t.Spec.Template = template template = &api.PodTemplateSpec{} } return true, fn(&template.Spec) default: return kUpdatePodSpecForObject(obj, fn) } } kProtocolsForObject := w.Factory.ProtocolsForObject w.ProtocolsForObject = func(object runtime.Object) (map[string]string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return getProtocols(t.Spec.Template.Spec), nil default: return kProtocolsForObject(object) } } kSwaggerSchemaFunc := w.Factory.SwaggerSchema w.Factory.SwaggerSchema = func(gvk unversioned.GroupVersionKind) (*swagger.ApiDeclaration, error) { if !latest.OriginKind(gvk) { return kSwaggerSchemaFunc(gvk) } // TODO: we need to register the OpenShift API under the Kube group, and start returning the OpenShift // group from the scheme. oc, _, err := w.Clients() if err != nil { return nil, err } return w.OriginSwaggerSchema(oc.RESTClient, gvk.GroupVersion()) } w.EditorEnvs = func() []string { return []string{"OC_EDITOR", "EDITOR"} } w.PrintObjectSpecificMessage = func(obj runtime.Object, out io.Writer) {} kPauseObjectFunc := w.Factory.PauseObject w.Factory.PauseObject = func(object runtime.Object) (bool, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: if t.Spec.Paused { return true, nil } t.Spec.Paused = true oc, _, err := w.Clients() if err != nil { return false, err } _, err = oc.DeploymentConfigs(t.Namespace).Update(t) // TODO: Pause the deployer containers. return false, err default: return kPauseObjectFunc(object) } } kResumeObjectFunc := w.Factory.ResumeObject w.Factory.ResumeObject = func(object runtime.Object) (bool, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: if !t.Spec.Paused { return true, nil } t.Spec.Paused = false oc, _, err := w.Clients() if err != nil { return false, err } _, err = oc.DeploymentConfigs(t.Namespace).Update(t) // TODO: Resume the deployer containers. return false, err default: return kResumeObjectFunc(object) } } kResolveImageFunc := w.Factory.ResolveImage w.Factory.ResolveImage = func(image string) (string, error) { options := w.ImageResolutionOptions.(*imageResolutionOptions) if imageutil.IsDocker(options.Source) { return kResolveImageFunc(image) } oc, _, err := w.Clients() if err != nil { return "", err } namespace, _, err := w.DefaultNamespace() if err != nil { return "", err } return imageutil.ResolveImagePullSpec(oc, oc, options.Source, image, namespace) } kHistoryViewerFunc := w.Factory.HistoryViewer w.Factory.HistoryViewer = func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): oc, kc, err := w.Clients() if err != nil { return nil, err } return deploycmd.NewDeploymentConfigHistoryViewer(oc, kc), nil } return kHistoryViewerFunc(mapping) } kRollbackerFunc := w.Factory.Rollbacker w.Factory.Rollbacker = func(mapping *meta.RESTMapping) (kubectl.Rollbacker, error) { switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): oc, _, err := w.Clients() if err != nil { return nil, err } return deploycmd.NewDeploymentConfigRollbacker(oc), nil } return kRollbackerFunc(mapping) } kStatusViewerFunc := w.Factory.StatusViewer w.Factory.StatusViewer = func(mapping *meta.RESTMapping) (kubectl.StatusViewer, error) { oc, _, err := w.Clients() if err != nil { return nil, err } switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): return deploycmd.NewDeploymentConfigStatusViewer(oc), nil } return kStatusViewerFunc(mapping) } return w }
func TestAdmission(t *testing.T) { var ( userAlice = userapi.User{ ObjectMeta: kapi.ObjectMeta{ Name: "Alice", Labels: map[string]string{"foo": "bar"}, }, } userAliceRef = kapi.ObjectReference{ Kind: authorizationapi.UserKind, Name: "Alice", } userBob = userapi.User{ ObjectMeta: kapi.ObjectMeta{Name: "Bob"}, Groups: []string{"group"}, } userBobRef = kapi.ObjectReference{ Kind: authorizationapi.UserKind, Name: "Bob", } group = userapi.Group{ ObjectMeta: kapi.ObjectMeta{ Name: "group", Labels: map[string]string{"baz": "quux"}, }, Users: []string{userBobRef.Name}, } groupRef = kapi.ObjectReference{ Kind: authorizationapi.GroupKind, Name: "group", } serviceaccount = kapi.ServiceAccount{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "serviceaccount", Labels: map[string]string{"xyzzy": "thud"}, }, } serviceaccountRef = kapi.ObjectReference{ Kind: authorizationapi.ServiceAccountKind, Namespace: "namespace", Name: "serviceaccount", } systemuserRef = kapi.ObjectReference{ Kind: authorizationapi.SystemUserKind, Name: "system user", } systemgroupRef = kapi.ObjectReference{ Kind: authorizationapi.SystemGroupKind, Name: "system group", } ) testCases := []struct { name string expectedErr string object runtime.Object oldObject runtime.Object kind unversioned.GroupVersionKind resource unversioned.GroupVersionResource namespace string subresource string objects []runtime.Object }{ { name: "ignore (allow) if subresource is nonempty", object: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{userAliceRef}, }, oldObject: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, }, kind: authorizationapi.Kind("RoleBinding").WithVersion("version"), resource: authorizationapi.Resource("rolebindings").WithVersion("version"), namespace: "namespace", subresource: "subresource", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, }, }, { name: "ignore (allow) cluster-scoped rolebinding", object: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{userAliceRef}, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, oldObject: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, }, kind: authorizationapi.Kind("RoleBinding").WithVersion("version"), resource: authorizationapi.Resource("rolebindings").WithVersion("version"), namespace: "", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, }, }, { name: "allow if the namespace has no rolebinding restrictions", object: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ userAliceRef, userBobRef, groupRef, serviceaccountRef, systemgroupRef, systemuserRef, }, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, oldObject: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, kind: authorizationapi.Kind("RoleBinding").WithVersion("version"), resource: authorizationapi.Resource("rolebindings").WithVersion("version"), namespace: "namespace", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, }, }, { name: "allow if any rolebinding with the subject already exists", object: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ userAliceRef, groupRef, serviceaccountRef, }, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, oldObject: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ userAliceRef, groupRef, serviceaccountRef, }, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, kind: authorizationapi.Kind("RoleBinding").WithVersion("version"), resource: authorizationapi.Resource("rolebindings").WithVersion("version"), namespace: "namespace", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "bogus-matcher", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{}, }, }, }, }, { name: "allow a system user, system group, user, group, or service account in a rolebinding if a literal matches", object: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ systemuserRef, systemgroupRef, userAliceRef, serviceaccountRef, groupRef, }, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, oldObject: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, kind: authorizationapi.Kind("RoleBinding").WithVersion("version"), resource: authorizationapi.Resource("rolebindings").WithVersion("version"), namespace: "namespace", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-system-users", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{ Users: []string{systemuserRef.Name}, }, }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-system-groups", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ GroupRestriction: &authorizationapi.GroupRestriction{ Groups: []string{systemgroupRef.Name}, }, }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-users", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{ Users: []string{userAlice.Name}, }, }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-groups", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ GroupRestriction: &authorizationapi.GroupRestriction{ Groups: []string{group.Name}, }, }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-serviceaccounts", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ ServiceAccountRestriction: &authorizationapi.ServiceAccountRestriction{ ServiceAccounts: []authorizationapi.ServiceAccountReference{ { Name: serviceaccount.Name, Namespace: serviceaccount.Namespace, }, }, }, }, }, }, }, { name: "prohibit user without a matching user literal", expectedErr: fmt.Sprintf("rolebindings to %s %q are not allowed", userAliceRef.Kind, userAliceRef.Name), object: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ userAliceRef, }, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, oldObject: &authorizationapi.RoleBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, RoleRef: kapi.ObjectReference{Namespace: authorizationapi.PolicyName}, }, kind: authorizationapi.Kind("RoleBinding").WithVersion("version"), resource: authorizationapi.Resource("rolebindings").WithVersion("version"), namespace: "namespace", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, &userAlice, &userBob, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-users-bob", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{ Users: []string{userBobRef.Name}, }, }, }, }, }, { name: "allow users in a policybinding if a selector matches", object: &authorizationapi.PolicyBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "policybinding", }, RoleBindings: map[string]*authorizationapi.RoleBinding{ "rolebinding": { ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ userAliceRef, userBobRef, }, RoleRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, }, PolicyRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, oldObject: &authorizationapi.PolicyBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "policybinding", }, RoleBindings: map[string]*authorizationapi.RoleBinding{ "rolebinding": { ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, RoleRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, }, PolicyRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, kind: authorizationapi.Kind("PolicyBinding").WithVersion("version"), resource: authorizationapi.Resource("policybindings").WithVersion("version"), namespace: "namespace", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, &authorizationapi.Policy{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: authorizationapi.PolicyName, }, Roles: map[string]*authorizationapi.Role{ "any": { ObjectMeta: kapi.ObjectMeta{ Namespace: kapi.NamespaceDefault, Name: "any", }, }, }, }, &userAlice, &userBob, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-users-alice", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{ Selectors: []unversioned.LabelSelector{ {MatchLabels: map[string]string{"foo": "bar"}}, }, }, }, }, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-users-bob", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{ Users: []string{userBob.Name}, }, }, }, }, }, { name: "prohibit a user in a policybinding without a matching selector", expectedErr: fmt.Sprintf("rolebindings to %s %q are not allowed", userBobRef.Kind, userBobRef.Name), object: &authorizationapi.PolicyBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "policybinding", }, RoleBindings: map[string]*authorizationapi.RoleBinding{ "rolebinding": { ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{ userAliceRef, userBobRef, }, RoleRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, }, PolicyRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, oldObject: &authorizationapi.PolicyBinding{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "policybinding", }, RoleBindings: map[string]*authorizationapi.RoleBinding{ "rolebinding": { ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: "rolebinding", }, Subjects: []kapi.ObjectReference{}, RoleRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, }, PolicyRef: kapi.ObjectReference{ Namespace: authorizationapi.PolicyName, }, }, kind: authorizationapi.Kind("PolicyBinding").WithVersion("version"), resource: authorizationapi.Resource("policybindings").WithVersion("version"), namespace: "namespace", subresource: "", objects: []runtime.Object{ &kapi.Namespace{ ObjectMeta: kapi.ObjectMeta{ Name: "namespace", }, }, &authorizationapi.Policy{ ObjectMeta: kapi.ObjectMeta{ Namespace: "namespace", Name: authorizationapi.PolicyName, }, Roles: map[string]*authorizationapi.Role{ "any": { ObjectMeta: kapi.ObjectMeta{ Namespace: kapi.NamespaceDefault, Name: "any", }, }, }, }, &userAlice, &userBob, &authorizationapi.RoleBindingRestriction{ ObjectMeta: kapi.ObjectMeta{ Name: "match-users-alice", Namespace: "namespace", }, Spec: authorizationapi.RoleBindingRestrictionSpec{ UserRestriction: &authorizationapi.UserRestriction{ Selectors: []unversioned.LabelSelector{ {MatchLabels: map[string]string{"foo": "bar"}}, }, }, }, }, }, }, } for _, tc := range testCases { kclientset := fake.NewSimpleClientset(tc.objects...) oclient := otestclient.NewSimpleFake(tc.objects...) plugin, err := NewRestrictUsersAdmission(kclientset) if err != nil { t.Errorf("unexpected error initializing admission plugin: %v", err) } plugins := []admission.Interface{plugin} plugin.(oadmission.WantsOpenshiftClient).SetOpenshiftClient(oclient) groupCache := usercache.NewGroupCache(&groupCache{[]userapi.Group{group}}) plugin.(oadmission.WantsGroupCache).SetGroupCache(groupCache) groupCache.Run() err = admission.Validate(plugins) if err != nil { t.Errorf("unexpected error validating admission plugin: %v", err) } attributes := admission.NewAttributesRecord( tc.object, tc.oldObject, tc.kind, tc.namespace, "name", tc.resource, tc.subresource, admission.Create, &user.DefaultInfo{}, ) err = plugin.Admit(attributes) switch { case len(tc.expectedErr) == 0 && err == nil: case len(tc.expectedErr) == 0 && err != nil: t.Errorf("%s: unexpected error: %v", tc.name, err) case len(tc.expectedErr) != 0 && err == nil: t.Errorf("%s: missing error: %v", tc.name, tc.expectedErr) case len(tc.expectedErr) != 0 && err != nil && !strings.Contains(err.Error(), tc.expectedErr): t.Errorf("%s: missing error: expected %v, got %v", tc.name, tc.expectedErr, err) } } }
// NewFactory creates an object that holds common methods across all OpenShift commands func NewFactory(clientConfig kclientcmd.ClientConfig) *Factory { restMapper := registered.RESTMapper() clients := &clientCache{ clients: make(map[string]*client.Client), configs: make(map[string]*restclient.Config), loader: clientConfig, } w := &Factory{ Factory: cmdutil.NewFactory(clientConfig), OpenShiftClientConfig: clientConfig, clients: clients, } w.Object = func(bool) (meta.RESTMapper, runtime.ObjectTyper) { defaultMapper := ShortcutExpander{RESTMapper: kubectl.ShortcutExpander{RESTMapper: restMapper}} defaultTyper := api.Scheme // Output using whatever version was negotiated in the client cache. The // version we decode with may not be the same as what the server requires. cfg, err := clients.ClientConfigForVersion(nil) if err != nil { return defaultMapper, defaultTyper } cmdApiVersion := unversioned.GroupVersion{} if cfg.GroupVersion != nil { cmdApiVersion = *cfg.GroupVersion } // at this point we've negotiated and can get the client oclient, err := clients.ClientForVersion(nil) if err != nil { return defaultMapper, defaultTyper } cacheDir := computeDiscoverCacheDir(filepath.Join(homedir.HomeDir(), ".kube"), cfg.Host) cachedDiscoverClient := NewCachedDiscoveryClient(client.NewDiscoveryClient(oclient.RESTClient), cacheDir, time.Duration(10*time.Minute)) mapper := restmapper.NewDiscoveryRESTMapper(cachedDiscoverClient) mapper = NewShortcutExpander(cachedDiscoverClient, kubectl.ShortcutExpander{RESTMapper: mapper}) return kubectl.OutputVersionMapper{RESTMapper: mapper, OutputVersions: []unversioned.GroupVersion{cmdApiVersion}}, api.Scheme } kClientForMapping := w.Factory.ClientForMapping w.ClientForMapping = func(mapping *meta.RESTMapping) (resource.RESTClient, error) { if latest.OriginKind(mapping.GroupVersionKind) { mappingVersion := mapping.GroupVersionKind.GroupVersion() client, err := clients.ClientForVersion(&mappingVersion) if err != nil { return nil, err } return client.RESTClient, nil } return kClientForMapping(mapping) } // Save original Describer function kDescriberFunc := w.Factory.Describer w.Describer = func(mapping *meta.RESTMapping) (kubectl.Describer, error) { if latest.OriginKind(mapping.GroupVersionKind) { oClient, kClient, err := w.Clients() if err != nil { return nil, fmt.Errorf("unable to create client %s: %v", mapping.GroupVersionKind.Kind, err) } mappingVersion := mapping.GroupVersionKind.GroupVersion() cfg, err := clients.ClientConfigForVersion(&mappingVersion) if err != nil { return nil, fmt.Errorf("unable to load a client %s: %v", mapping.GroupVersionKind.Kind, err) } describer, ok := describe.DescriberFor(mapping.GroupVersionKind.GroupKind(), oClient, kClient, cfg.Host) if !ok { return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind) } return describer, nil } return kDescriberFunc(mapping) } kScalerFunc := w.Factory.Scaler w.Scaler = func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { oc, kc, err := w.Clients() if err != nil { return nil, err } if mapping.GroupVersionKind.GroupKind() == deployapi.Kind("DeploymentConfig") { return deployscaler.NewDeploymentConfigScaler(oc, kc), nil } return kScalerFunc(mapping) } kReaperFunc := w.Factory.Reaper w.Reaper = func(mapping *meta.RESTMapping) (kubectl.Reaper, error) { oc, kc, err := w.Clients() if err != nil { return nil, err } switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): return deployreaper.NewDeploymentConfigReaper(oc, kc), nil case authorizationapi.Kind("Role"): return authorizationreaper.NewRoleReaper(oc, oc), nil case authorizationapi.Kind("ClusterRole"): return authorizationreaper.NewClusterRoleReaper(oc, oc, oc), nil case userapi.Kind("User"): return authenticationreaper.NewUserReaper( client.UsersInterface(oc), client.GroupsInterface(oc), client.ClusterRoleBindingsInterface(oc), client.RoleBindingsNamespacer(oc), kclient.SecurityContextConstraintsInterface(kc), ), nil case userapi.Kind("Group"): return authenticationreaper.NewGroupReaper( client.GroupsInterface(oc), client.ClusterRoleBindingsInterface(oc), client.RoleBindingsNamespacer(oc), kclient.SecurityContextConstraintsInterface(kc), ), nil case buildapi.Kind("BuildConfig"): return buildreaper.NewBuildConfigReaper(oc), nil } return kReaperFunc(mapping) } kGenerators := w.Factory.Generators w.Generators = func(cmdName string) map[string]kubectl.Generator { originGenerators := DefaultGenerators(cmdName) kubeGenerators := kGenerators(cmdName) ret := map[string]kubectl.Generator{} for k, v := range kubeGenerators { ret[k] = v } for k, v := range originGenerators { ret[k] = v } return ret } kMapBasedSelectorForObjectFunc := w.Factory.MapBasedSelectorForObject w.MapBasedSelectorForObject = func(object runtime.Object) (string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return kubectl.MakeLabels(t.Spec.Selector), nil default: return kMapBasedSelectorForObjectFunc(object) } } kPortsForObjectFunc := w.Factory.PortsForObject w.PortsForObject = func(object runtime.Object) ([]string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return getPorts(t.Spec.Template.Spec), nil default: return kPortsForObjectFunc(object) } } kLogsForObjectFunc := w.Factory.LogsForObject w.LogsForObject = func(object, options runtime.Object) (*restclient.Request, error) { oc, _, err := w.Clients() if err != nil { return nil, err } switch t := object.(type) { case *deployapi.DeploymentConfig: dopts, ok := options.(*deployapi.DeploymentLogOptions) if !ok { return nil, errors.New("provided options object is not a DeploymentLogOptions") } return oc.DeploymentLogs(t.Namespace).Get(t.Name, *dopts), nil case *buildapi.Build: bopts, ok := options.(*buildapi.BuildLogOptions) if !ok { return nil, errors.New("provided options object is not a BuildLogOptions") } if bopts.Version != nil { return nil, errors.New("cannot specify a version and a build") } return oc.BuildLogs(t.Namespace).Get(t.Name, *bopts), nil case *buildapi.BuildConfig: bopts, ok := options.(*buildapi.BuildLogOptions) if !ok { return nil, errors.New("provided options object is not a BuildLogOptions") } builds, err := oc.Builds(t.Namespace).List(api.ListOptions{}) if err != nil { return nil, err } builds.Items = buildapi.FilterBuilds(builds.Items, buildapi.ByBuildConfigPredicate(t.Name)) if len(builds.Items) == 0 { return nil, fmt.Errorf("no builds found for %q", t.Name) } if bopts.Version != nil { // If a version has been specified, try to get the logs from that build. desired := buildutil.BuildNameForConfigVersion(t.Name, int(*bopts.Version)) return oc.BuildLogs(t.Namespace).Get(desired, *bopts), nil } sort.Sort(sort.Reverse(buildapi.BuildSliceByCreationTimestamp(builds.Items))) return oc.BuildLogs(t.Namespace).Get(builds.Items[0].Name, *bopts), nil default: return kLogsForObjectFunc(object, options) } } w.Printer = func(mapping *meta.RESTMapping, noHeaders, withNamespace, wide bool, showAll bool, showLabels, absoluteTimestamps bool, columnLabels []string) (kubectl.ResourcePrinter, error) { return describe.NewHumanReadablePrinter(noHeaders, withNamespace, wide, showAll, showLabels, absoluteTimestamps, columnLabels), nil } kCanBeExposed := w.Factory.CanBeExposed w.CanBeExposed = func(kind unversioned.GroupKind) error { if kind == deployapi.Kind("DeploymentConfig") { return nil } return kCanBeExposed(kind) } kCanBeAutoscaled := w.Factory.CanBeAutoscaled w.CanBeAutoscaled = func(kind unversioned.GroupKind) error { if kind == deployapi.Kind("DeploymentConfig") { return nil } return kCanBeAutoscaled(kind) } kAttachablePodForObjectFunc := w.Factory.AttachablePodForObject w.AttachablePodForObject = func(object runtime.Object) (*api.Pod, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: _, kc, err := w.Clients() if err != nil { return nil, err } selector := labels.SelectorFromSet(t.Spec.Selector) f := func(pods []*api.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) } pod, _, err := cmdutil.GetFirstPod(kc, t.Namespace, selector, 1*time.Minute, f) return pod, err default: return kAttachablePodForObjectFunc(object) } } kProtocolsForObject := w.Factory.ProtocolsForObject w.ProtocolsForObject = func(object runtime.Object) (map[string]string, error) { switch t := object.(type) { case *deployapi.DeploymentConfig: return getProtocols(t.Spec.Template.Spec), nil default: return kProtocolsForObject(object) } } kSwaggerSchemaFunc := w.Factory.SwaggerSchema w.Factory.SwaggerSchema = func(gvk unversioned.GroupVersionKind) (*swagger.ApiDeclaration, error) { if !latest.OriginKind(gvk) { return kSwaggerSchemaFunc(gvk) } // TODO: we need to register the OpenShift API under the Kube group, and start returning the OpenShift // group from the scheme. oc, _, err := w.Clients() if err != nil { return nil, err } return w.OriginSwaggerSchema(oc.RESTClient, gvk.GroupVersion()) } w.EditorEnvs = func() []string { return []string{"OC_EDITOR", "EDITOR"} } w.PrintObjectSpecificMessage = func(obj runtime.Object, out io.Writer) {} kPauseObjectFunc := w.Factory.PauseObject w.Factory.PauseObject = func(object runtime.Object) (bool, error) { oc, _, err := w.Clients() if err != nil { return false, err } switch t := object.(type) { case *deployapi.DeploymentConfig: if t.Spec.Paused { return true, nil } t.Spec.Paused = true _, err := oc.DeploymentConfigs(t.Namespace).Update(t) // TODO: Pause the deployer containers. return false, err default: return kPauseObjectFunc(object) } } kResumeObjectFunc := w.Factory.ResumeObject w.Factory.ResumeObject = func(object runtime.Object) (bool, error) { oc, _, err := w.Clients() if err != nil { return false, err } switch t := object.(type) { case *deployapi.DeploymentConfig: if !t.Spec.Paused { return true, nil } t.Spec.Paused = false _, err := oc.DeploymentConfigs(t.Namespace).Update(t) // TODO: Resume the deployer containers. return false, err default: return kResumeObjectFunc(object) } } kHistoryViewerFunc := w.Factory.HistoryViewer w.Factory.HistoryViewer = func(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { oc, kc, err := w.Clients() if err != nil { return nil, err } switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): return deploycmd.NewDeploymentConfigHistoryViewer(oc, kc), nil } return kHistoryViewerFunc(mapping) } kRollbackerFunc := w.Factory.Rollbacker w.Factory.Rollbacker = func(mapping *meta.RESTMapping) (kubectl.Rollbacker, error) { oc, _, err := w.Clients() if err != nil { return nil, err } switch mapping.GroupVersionKind.GroupKind() { case deployapi.Kind("DeploymentConfig"): return deploycmd.NewDeploymentConfigRollbacker(oc), nil } return kRollbackerFunc(mapping) } return w }
// Create registers a given new ResourceAccessReview instance to r.registry. func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) { subjectAccessReview, ok := obj.(*authorizationapi.SubjectAccessReview) if !ok { return nil, kapierrors.NewBadRequest(fmt.Sprintf("not a subjectAccessReview: %#v", obj)) } if errs := authorizationvalidation.ValidateSubjectAccessReview(subjectAccessReview); len(errs) > 0 { return nil, kapierrors.NewInvalid(authorizationapi.Kind(subjectAccessReview.Kind), "", errs) } // if a namespace is present on the request, then the namespace on the on the SAR is overwritten. // This is to support backwards compatibility. To have gotten here in this state, it means that // the authorizer decided that a user could run an SAR against this namespace if namespace := kapi.NamespaceValue(ctx); len(namespace) > 0 { subjectAccessReview.Action.Namespace = namespace } else if err := r.isAllowed(ctx, subjectAccessReview); err != nil { // this check is mutually exclusive to the condition above. localSAR and localRAR both clear the namespace before delegating their calls // We only need to check if the SAR is allowed **again** if the authorizer didn't already approve the request for a legacy call. return nil, err } var userToCheck *user.DefaultInfo if (len(subjectAccessReview.User) == 0) && (len(subjectAccessReview.Groups) == 0) { // if no user or group was specified, use the info from the context ctxUser, exists := kapi.UserFrom(ctx) if !exists { return nil, kapierrors.NewBadRequest("user missing from context") } // make a copy, we don't want to risk changing the original newExtra := map[string][]string{} for k, v := range ctxUser.GetExtra() { if v == nil { newExtra[k] = nil continue } newSlice := make([]string, len(v), len(v)) copy(newSlice, v) newExtra[k] = newSlice } userToCheck = &user.DefaultInfo{ Name: ctxUser.GetName(), Groups: ctxUser.GetGroups(), UID: ctxUser.GetUID(), Extra: newExtra, } } else { userToCheck = &user.DefaultInfo{ Name: subjectAccessReview.User, Groups: subjectAccessReview.Groups.List(), Extra: map[string][]string{}, } } switch { case subjectAccessReview.Scopes == nil: // leave the scopes alone. on a self-sar, this means "use incoming request", on regular-sar it means, "use no scope restrictions" case len(subjectAccessReview.Scopes) == 0: // this always means "use no scope restrictions", so delete them delete(userToCheck.Extra, authorizationapi.ScopesKey) case len(subjectAccessReview.Scopes) > 0: // this always means, "use these scope restrictions", so force the value userToCheck.Extra[authorizationapi.ScopesKey] = subjectAccessReview.Scopes } requestContext := kapi.WithNamespace(kapi.WithUser(ctx, userToCheck), subjectAccessReview.Action.Namespace) attributes := authorizer.ToDefaultAuthorizationAttributes(subjectAccessReview.Action) allowed, reason, err := r.authorizer.Authorize(requestContext, attributes) if err != nil { return nil, err } response := &authorizationapi.SubjectAccessReviewResponse{ Namespace: subjectAccessReview.Action.Namespace, Allowed: allowed, Reason: reason, } return response, nil }