package policy import ( "errors" "fmt" "io" "io/ioutil" "os" "reflect" "text/tabwriter" "github.com/spf13/cobra" kapi "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api/unversioned" kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/util/sets" authorizationapi "github.com/openshift/origin/pkg/authorization/api" "github.com/openshift/origin/pkg/client" "github.com/openshift/origin/pkg/cmd/cli/describe" "github.com/openshift/origin/pkg/cmd/util/clientcmd" ) const CanIRecommendedName = "can-i" type canIOptions struct { AllNamespaces bool ListAll bool Quiet bool IgnoreScopes bool User string Groups []string Scopes []string Namespace string SelfRulesReviewClient client.SelfSubjectRulesReviewsNamespacer RulesReviewClient client.SubjectRulesReviewsNamespacer SARClient client.SubjectAccessReviews Verb string Resource unversioned.GroupVersionResource ResourceName string Out io.Writer } func NewCmdCanI(name, fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { o := &canIOptions{ Out: out, } cmd := &cobra.Command{ Use: name + " VERB RESOURCE [NAME]", Short: "Check whether an action is allowed", Long: "Check whether an action is allowed", Run: func(cmd *cobra.Command, args []string) { if reflect.DeepEqual(args, []string{"educate", "dolphins"}) { fmt.Fprintln(o.Out, "Only liggitt can educate dolphins.") return } if err := o.Complete(f, args); err != nil { kcmdutil.CheckErr(kcmdutil.UsageError(cmd, err.Error())) } allowed, err := o.Run() kcmdutil.CheckErr(err) if o.Quiet && !allowed { os.Exit(2) } }, } cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If true, check the specified action in all namespaces.") cmd.Flags().BoolVar(&o.ListAll, "list", o.ListAll, "If true, list all the actions you can perform in a namespace, cannot be specified with --all-namespaces or a VERB RESOURCE") cmd.Flags().BoolVarP(&o.Quiet, "quiet", "q", o.Quiet, "If true, suppress output and just return the exit code.") cmd.Flags().BoolVar(&o.IgnoreScopes, "ignore-scopes", o.IgnoreScopes, "If true, disregard any scopes present on this request and evaluate considering full permissions.") cmd.Flags().StringSliceVar(&o.Scopes, "scopes", o.Scopes, "Check the specified action using these scopes. By default, the scopes on the current token will be used.") cmd.Flags().StringVar(&o.User, "user", o.User, "Check the specified action using this user instead of your user.") cmd.Flags().StringSliceVar(&o.Groups, "groups", o.Groups, "Check the specified action using these groups instead of your groups.") return cmd } const ( tabwriterMinWidth = 10 tabwriterWidth = 4 tabwriterPadding = 3 tabwriterPadChar = ' ' tabwriterFlags = 0 ) func (o *canIOptions) Complete(f *clientcmd.Factory, args []string) error { if o.ListAll && o.AllNamespaces { return errors.New("--list and --all-namespaces are mutually exclusive") } if o.IgnoreScopes && len(o.Scopes) > 0 { return errors.New("--scopes and --ignore-scopes are mutually exclusive") } switch len(args) { case 3: o.ResourceName = args[2] fallthrough case 2: if o.ListAll { return errors.New("VERB RESOURCE and --list are mutually exclusive") } restMapper, _ := f.Object(false) o.Verb = args[0] o.Resource = resourceFor(restMapper, args[1]) default: if !o.ListAll { return errors.New("you must specify two or three arguments: verb, resource, and optional resourceName") } } var err error oclient, _, _, err := f.Clients() if err != nil { return err } o.SelfRulesReviewClient = oclient o.RulesReviewClient = oclient o.SARClient = oclient o.Namespace = kapi.NamespaceAll if !o.AllNamespaces { o.Namespace, _, err = f.DefaultNamespace() if err != nil { return err } } if o.Quiet { o.Out = ioutil.Discard } return nil } func (o *canIOptions) Run() (bool, error) { if o.ListAll { return true, o.listAllPermissions() } sar := &authorizationapi.SubjectAccessReview{ Action: authorizationapi.Action{ Namespace: o.Namespace, Verb: o.Verb, Group: o.Resource.Group, Resource: o.Resource.Resource, ResourceName: o.ResourceName, }, User: o.User, Groups: sets.NewString(o.Groups...), } if o.IgnoreScopes { sar.Scopes = []string{} } if len(o.Scopes) > 0 { sar.Scopes = o.Scopes } response, err := o.SARClient.SubjectAccessReviews().Create(sar) if err != nil { return false, err } if response.Allowed { fmt.Fprintln(o.Out, "yes") } else { fmt.Fprint(o.Out, "no") if len(response.EvaluationError) > 0 { fmt.Fprintf(o.Out, " - %v", response.EvaluationError) } fmt.Fprintln(o.Out) } return response.Allowed, nil } func (o *canIOptions) listAllPermissions() error { var rulesReviewStatus authorizationapi.SubjectRulesReviewStatus if len(o.User) == 0 && len(o.Groups) == 0 { rulesReview := &authorizationapi.SelfSubjectRulesReview{} if len(o.Scopes) > 0 { rulesReview.Spec.Scopes = o.Scopes } whatCanIDo, err := o.SelfRulesReviewClient.SelfSubjectRulesReviews(o.Namespace).Create(rulesReview) if err != nil { return err } rulesReviewStatus = whatCanIDo.Status } else { rulesReview := &authorizationapi.SubjectRulesReview{ Spec: authorizationapi.SubjectRulesReviewSpec{ User: o.User, Groups: o.Groups, Scopes: o.Scopes, }, } whatCanYouDo, err := o.RulesReviewClient.SubjectRulesReviews(o.Namespace).Create(rulesReview) if err != nil { return err } rulesReviewStatus = whatCanYouDo.Status } writer := tabwriter.NewWriter(o.Out, tabwriterMinWidth, tabwriterWidth, tabwriterPadding, tabwriterPadChar, tabwriterFlags) fmt.Fprint(writer, describe.PolicyRuleHeadings+"\n") for _, rule := range rulesReviewStatus.Rules { describe.DescribePolicyRule(writer, rule, "") } writer.Flush() return nil }