github.com/oam-dev/kubevela@v1.9.11/references/cli/auth.go (about) 1 /* 2 Copyright 2022 The KubeVela Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cli 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 24 "github.com/spf13/cobra" 25 "golang.org/x/term" 26 corev1 "k8s.io/api/core/v1" 27 kerrors "k8s.io/apimachinery/pkg/api/errors" 28 apitypes "k8s.io/apimachinery/pkg/types" 29 "k8s.io/client-go/kubernetes" 30 "k8s.io/client-go/tools/clientcmd" 31 "k8s.io/kubectl/pkg/util/i18n" 32 "k8s.io/kubectl/pkg/util/templates" 33 34 "github.com/oam-dev/kubevela/apis/types" 35 "github.com/oam-dev/kubevela/pkg/auth" 36 velacmd "github.com/oam-dev/kubevela/pkg/cmd" 37 cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util" 38 "github.com/oam-dev/kubevela/pkg/multicluster" 39 "github.com/oam-dev/kubevela/pkg/utils/util" 40 ) 41 42 // AuthCommandGroup commands for create resources or configuration 43 func AuthCommandGroup(f velacmd.Factory, order string, streams util.IOStreams) *cobra.Command { 44 cmd := &cobra.Command{ 45 Use: "auth", 46 Short: i18n.T("Manage identity and authorizations."), 47 Annotations: map[string]string{ 48 types.TagCommandType: types.TypePlatform, 49 types.TagCommandOrder: order, 50 }, 51 } 52 cmd.AddCommand(NewGenKubeConfigCommand(f, streams)) 53 cmd.AddCommand(NewListPrivilegesCommand(f, streams)) 54 cmd.AddCommand(NewGrantPrivilegesCommand(f, streams)) 55 return cmd 56 } 57 58 // GenKubeConfigOptions options for create kubeconfig 59 type GenKubeConfigOptions struct { 60 auth.Identity 61 util.IOStreams 62 } 63 64 // Complete . 65 func (opt *GenKubeConfigOptions) Complete(f velacmd.Factory, cmd *cobra.Command) { 66 if opt.Identity.ServiceAccount != "" { 67 opt.Identity.ServiceAccountNamespace = velacmd.GetNamespace(f, cmd) 68 } 69 opt.Regularize() 70 } 71 72 // Validate . 73 func (opt *GenKubeConfigOptions) Validate() error { 74 return opt.Identity.Validate() 75 } 76 77 // Run . 78 func (opt *GenKubeConfigOptions) Run(f velacmd.Factory) error { 79 ctx := context.Background() 80 cli, err := kubernetes.NewForConfig(f.Config()) 81 if err != nil { 82 return err 83 } 84 cfg, err := clientcmd.NewDefaultPathOptions().GetStartingConfig() 85 if err != nil { 86 return err 87 } 88 cfg, err = auth.GenerateKubeConfig(ctx, cli, cfg, opt.IOStreams.ErrOut, auth.KubeConfigWithIdentityGenerateOption(opt.Identity)) 89 if err != nil { 90 return err 91 } 92 bs, err := clientcmd.Write(*cfg) 93 if err != nil { 94 return err 95 } 96 _, err = opt.Out.Write(bs) 97 return err 98 } 99 100 var ( 101 genKubeConfigLong = templates.LongDesc(i18n.T(` 102 Generate kubeconfig for user 103 104 Generate a new kubeconfig with specified identity. By default, the generated kubeconfig 105 will reuse the certificate-authority-data in the cluster config from the current used 106 kubeconfig. All contexts, clusters and users that are not in use will not be included 107 in the generated kubeconfig. 108 109 To generate a new kubeconfig for given user and groups, use the --user and --group flag. 110 Multiple --group flags is allowed. The group kubevela:client is added to the groups by 111 default. The identity in the current kubeconfig should be able to approve 112 CertificateSigningRequest in the kubernetes cluster. See 113 https://kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/ 114 for details. 115 116 To generate a kubeconfig based on existing ServiceAccount in your cluster, use the 117 --serviceaccount flag. The corresponding secret token and ca data will be embedded in 118 the generated kubeconfig, which allows you to act as the serviceaccount.`)) 119 120 generateKubeConfigExample = templates.Examples(i18n.T(` 121 # Generate a kubeconfig with provided user 122 vela auth gen-kubeconfig --user new-user 123 124 # Generate a kubeconfig with provided user and group 125 vela auth gen-kubeconfig --user new-user --group kubevela:developer 126 127 # Generate a kubeconfig with provided user and groups 128 vela auth gen-kubeconfig --user new-user --group kubevela:developer --group my-org:my-team 129 130 # Generate a kubeconfig with provided serviceaccount 131 vela auth gen-kubeconfig --serviceaccount default -n demo`)) 132 ) 133 134 // NewGenKubeConfigCommand generate kubeconfig for given user and groups 135 func NewGenKubeConfigCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 136 o := &GenKubeConfigOptions{IOStreams: streams} 137 cmd := &cobra.Command{ 138 Use: "gen-kubeconfig", 139 DisableFlagsInUseLine: true, 140 Short: i18n.T("Generate kubeconfig for user"), 141 Long: genKubeConfigLong, 142 Example: generateKubeConfigExample, 143 Annotations: map[string]string{ 144 types.TagCommandType: types.TypeCD, 145 }, 146 Args: cobra.ExactArgs(0), 147 Run: func(cmd *cobra.Command, args []string) { 148 o.Complete(f, cmd) 149 cmdutil.CheckErr(o.Validate()) 150 cmdutil.CheckErr(o.Run(f)) 151 }, 152 } 153 cmd.Flags().StringVarP(&o.User, "user", "u", o.User, "The user of the generated kubeconfig. If set, an X509-based kubeconfig will be intended to create. It will be embedded as the Subject in the X509 certificate.") 154 cmd.Flags().StringSliceVarP(&o.Groups, "group", "g", o.Groups, "The groups of the generated kubeconfig. This flag only works when `--user` is set. It will be embedded as the Organization in the X509 certificate.") 155 cmd.Flags().StringVarP(&o.ServiceAccount, "serviceaccount", "", o.ServiceAccount, "The serviceaccount of the generated kubeconfig. If set, a kubeconfig will be generated based on the secret token of the serviceaccount. Cannot be set when `--user` presents.") 156 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 157 "serviceaccount", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 158 if strings.TrimSpace(o.User) != "" { 159 return nil, cobra.ShellCompDirectiveNoFileComp 160 } 161 namespace := velacmd.GetNamespace(f, cmd) 162 return velacmd.GetServiceAccountForCompletion(cmd.Context(), f, namespace, toComplete) 163 })) 164 165 return velacmd.NewCommandBuilder(f, cmd). 166 WithNamespaceFlag(velacmd.UsageOption("The namespace of the serviceaccount. This flag only works when `--serviceaccount` is set.")). 167 WithStreams(streams). 168 WithResponsiveWriter(). 169 Build() 170 } 171 172 // ListPrivilegesOptions options for list privileges 173 type ListPrivilegesOptions struct { 174 auth.Identity 175 KubeConfig string 176 Clusters []string 177 util.IOStreams 178 } 179 180 // Complete . 181 func (opt *ListPrivilegesOptions) Complete(f velacmd.Factory, cmd *cobra.Command) { 182 if opt.KubeConfig != "" { 183 identity, err := auth.ReadIdentityFromKubeConfig(opt.KubeConfig) 184 cmdutil.CheckErr(err) 185 opt.Identity = *identity 186 } 187 if opt.Identity.ServiceAccount != "" { 188 opt.Identity.ServiceAccountNamespace = velacmd.GetNamespace(f, cmd) 189 } 190 opt.Clusters = velacmd.GetClusters(cmd) 191 opt.Regularize() 192 } 193 194 // Validate . 195 func (opt *ListPrivilegesOptions) Validate(f velacmd.Factory, cmd *cobra.Command) error { 196 if err := opt.Identity.Validate(); err != nil { 197 return err 198 } 199 for _, cluster := range opt.Clusters { 200 if _, err := multicluster.NewClusterClient(f.Client()).Get(cmd.Context(), cluster); err != nil { 201 return fmt.Errorf("failed to find cluster %s: %w", cluster, err) 202 } 203 } 204 return nil 205 } 206 207 // Run . 208 func (opt *ListPrivilegesOptions) Run(f velacmd.Factory, cmd *cobra.Command) error { 209 ctx := cmd.Context() 210 m, err := auth.ListPrivileges(ctx, f.Client(), opt.Clusters, &opt.Identity) 211 if err != nil { 212 return err 213 } 214 width, _, err := term.GetSize(0) 215 if err != nil { 216 width = 80 217 } 218 _, _ = opt.Out.Write([]byte(auth.PrettyPrintPrivileges(&opt.Identity, m, opt.Clusters, uint(width)-40))) 219 return nil 220 } 221 222 var ( 223 listPrivilegesLong = templates.LongDesc(i18n.T(` 224 List privileges for user 225 226 List privileges that user has in clusters. Use --user/--group to check the privileges 227 for specified user and group. They can be jointly configured to see the union of 228 privileges. Use --serviceaccount and -n/--namespace to see the privileges for 229 ServiceAccount. You can also use --kubeconfig to use the identity inside implicitly. 230 The privileges will be shown in tree format. 231 232 This command supports listing privileges across multiple clusters, by using --cluster. 233 If not set, the control plane will be used. This feature requires cluster-gateway to be 234 properly setup to use. 235 236 The privileges are collected through listing all ClusterRoleBinding and RoleBinding, 237 following the Kubernetes RBAC Authorization. Other authorization mechanism is not supported 238 now. See https://kubernetes.io/docs/reference/access-authn-authz/rbac/ for details. 239 240 The ClusterRoleBinding and RoleBinding that matches the specified identity will be 241 tracked. Related ClusterRoles and Roles are retrieved and the contained PolicyRules are 242 demonstrated.`)) 243 244 listPrivilegesExample = templates.Examples(i18n.T(` 245 # List privileges for User alice in the control plane 246 vela auth list-privileges --user alice 247 248 # List privileges for Group org:dev-team in the control plane 249 vela auth list-privileges --group org:dev-team 250 251 # List privileges for User bob with Groups org:dev-team and org:test-team in the control plane and managed cluster example-cluster 252 vela auth list-privileges --user bob --group org:dev-team --group org:test-team --cluster local --cluster example-cluster 253 254 # List privileges for ServiceAccount example-sa in demo namespace in multiple managed clusters 255 vela auth list-privileges --serviceaccount example-sa -n demo --cluster cluster-1 --cluster cluster-2 256 257 # List privileges for identity in kubeconfig 258 vela auth list-privileges --kubeconfig ./example.kubeconfig --cluster local --cluster cluster-1`)) 259 ) 260 261 // NewListPrivilegesCommand list privileges for given identity 262 func NewListPrivilegesCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 263 o := &ListPrivilegesOptions{IOStreams: streams} 264 cmd := &cobra.Command{ 265 Use: "list-privileges", 266 DisableFlagsInUseLine: true, 267 Short: i18n.T("List privileges for user/group/serviceaccount"), 268 Long: listPrivilegesLong, 269 Example: listPrivilegesExample, 270 Annotations: map[string]string{ 271 types.TagCommandType: types.TypeCD, 272 }, 273 Args: cobra.ExactArgs(0), 274 Run: func(cmd *cobra.Command, args []string) { 275 o.Complete(f, cmd) 276 cmdutil.CheckErr(o.Validate(f, cmd)) 277 cmdutil.CheckErr(o.Run(f, cmd)) 278 }, 279 } 280 cmd.Flags().StringVarP(&o.User, "user", "u", o.User, "The user to list privileges.") 281 cmd.Flags().StringSliceVarP(&o.Groups, "group", "g", o.Groups, "The group to list privileges. Can be set together with --user.") 282 cmd.Flags().StringVarP(&o.ServiceAccount, "serviceaccount", "", o.ServiceAccount, "The serviceaccount to list privileges. Cannot be set with --user and --group.") 283 cmd.Flags().StringVarP(&o.KubeConfig, "kubeconfig", "", o.KubeConfig, "The kubeconfig to list privileges. If set, it will override all the other identity flags.") 284 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 285 "serviceaccount", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 286 if strings.TrimSpace(o.User) != "" { 287 return nil, cobra.ShellCompDirectiveNoFileComp 288 } 289 namespace := velacmd.GetNamespace(f, cmd) 290 return velacmd.GetServiceAccountForCompletion(cmd.Context(), f, namespace, toComplete) 291 })) 292 293 return velacmd.NewCommandBuilder(f, cmd). 294 WithNamespaceFlag(velacmd.UsageOption("The namespace of the serviceaccount. This flag only works when `--serviceaccount` is set.")). 295 WithClusterFlag(velacmd.UsageOption("The cluster to list privileges. If not set, the command will list privileges in the control plane.")). 296 WithStreams(streams). 297 WithResponsiveWriter(). 298 Build() 299 } 300 301 // GrantPrivilegesOptions options for grant privileges 302 type GrantPrivilegesOptions struct { 303 auth.Identity 304 KubeConfig string 305 GrantNamespaces []string 306 GrantClusters []string 307 ReadOnly bool 308 CreateNamespace bool 309 310 util.IOStreams 311 } 312 313 // Complete . 314 func (opt *GrantPrivilegesOptions) Complete(f velacmd.Factory, cmd *cobra.Command) { 315 if opt.KubeConfig != "" { 316 identity, err := auth.ReadIdentityFromKubeConfig(opt.KubeConfig) 317 cmdutil.CheckErr(err) 318 opt.Identity = *identity 319 opt.Identity.Groups = nil 320 } 321 if opt.Identity.ServiceAccount != "" { 322 opt.Identity.ServiceAccountNamespace = velacmd.GetNamespace(f, cmd) 323 } 324 opt.Regularize() 325 if len(opt.GrantClusters) == 0 { 326 opt.GrantClusters = []string{types.ClusterLocalName} 327 } 328 } 329 330 // Validate . 331 func (opt *GrantPrivilegesOptions) Validate(f velacmd.Factory, cmd *cobra.Command) error { 332 if opt.User == "" && len(opt.Groups) == 0 && opt.ServiceAccount == "" { 333 return fmt.Errorf("at least one idenity (user/group/serviceaccount) should be set") 334 } 335 for _, cluster := range opt.GrantClusters { 336 if _, err := multicluster.NewClusterClient(f.Client()).Get(cmd.Context(), cluster); err != nil { 337 return fmt.Errorf("failed to find cluster %s: %w", cluster, err) 338 } 339 if !opt.CreateNamespace { 340 for _, namespace := range opt.GrantNamespaces { 341 if err := f.Client().Get(multicluster.ContextWithClusterName(cmd.Context(), cluster), apitypes.NamespacedName{Name: namespace}, &corev1.Namespace{}); err != nil { 342 return fmt.Errorf("failed to find namespace %s in cluster %s: %w", namespace, cluster, err) 343 } 344 } 345 } 346 } 347 return nil 348 } 349 350 // Run . 351 func (opt *GrantPrivilegesOptions) Run(f velacmd.Factory, cmd *cobra.Command) error { 352 ctx := cmd.Context() 353 if opt.CreateNamespace { 354 for _, cluster := range opt.GrantClusters { 355 if _, err := multicluster.NewClusterClient(f.Client()).Get(cmd.Context(), cluster); err != nil { 356 return fmt.Errorf("failed to find cluster %s: %w", cluster, err) 357 } 358 for _, namespace := range opt.GrantNamespaces { 359 _ctx := multicluster.ContextWithClusterName(cmd.Context(), cluster) 360 ns := &corev1.Namespace{} 361 if err := f.Client().Get(_ctx, apitypes.NamespacedName{Name: namespace}, ns); err != nil { 362 if kerrors.IsNotFound(err) { 363 ns.SetName(namespace) 364 if err = f.Client().Create(_ctx, ns); err != nil { 365 return fmt.Errorf("failed to create namespace %s in cluster %s: %w", namespace, cluster, err) 366 } 367 continue 368 } 369 return fmt.Errorf("failed to find namespace %s in cluster %s: %w", namespace, cluster, err) 370 } 371 } 372 } 373 } 374 var privileges []auth.PrivilegeDescription 375 for _, cluster := range opt.GrantClusters { 376 for _, namespace := range opt.GrantNamespaces { 377 privileges = append(privileges, &auth.ScopedPrivilege{Cluster: cluster, Namespace: namespace, ReadOnly: opt.ReadOnly}) 378 } 379 if len(opt.GrantNamespaces) == 0 { 380 privileges = append(privileges, &auth.ScopedPrivilege{Cluster: cluster, ReadOnly: opt.ReadOnly}) 381 } 382 } 383 if err := auth.GrantPrivileges(ctx, f.Client(), privileges, &opt.Identity, opt.IOStreams.Out); err != nil { 384 return err 385 } 386 _, _ = fmt.Fprintf(opt.IOStreams.Out, "Privileges granted.\n") 387 return nil 388 } 389 390 var ( 391 grantPrivilegesLong = templates.LongDesc(i18n.T(` 392 Grant privileges for user 393 394 Grant privileges to user/group/serviceaccount. By using --for-namespace and --for-cluster, 395 you can grant all read/write privileges for all resources in the specified namespace and 396 cluster. If --for-namespace is not set, the privileges will be granted cluster-wide. 397 398 Setting --create-namespace will automatically create namespace if the namespace of the 399 granted privilege does not exists. By default, this flag is not enabled and errors will be 400 returned if the namespace is not found in the corresponding cluster. 401 402 Setting --readonly will only grant read privileges for all resources in the destination. This 403 can be useful if you want to give somebody the privileges to view resources but do not want to 404 allow them to edit any resource. 405 406 If multiple identity information are set, all the identity information will be bond to the 407 intended privileges respectively. 408 409 If --kubeconfig is set, the user/serviceaccount information in the kubeconfig will be used as 410 the identity to grant privileges. Groups will be ignored.`)) 411 412 grantPrivilegesExample = templates.Examples(i18n.T(` 413 # Grant privileges for User alice in the namespace demo of the control plane 414 vela auth grant-privileges --user alice --for-namespace demo 415 416 # Grant privileges for User alice in the namespace demo in cluster-1, create demo namespace if not exist 417 vela auth grant-privileges --user alice --for-namespace demo --for-cluster cluster-1 --create-namespace 418 419 # Grant cluster-scoped privileges for Group org:dev-team in the control plane 420 vela auth grant-privileges --group org:dev-team 421 422 # Grant privileges for Group org:dev-team and org:test-team in the namespace test on the control plane and managed cluster example-cluster 423 vela auth grant-privileges --group org:dev-team --group org:test-team --for-namespace test --for-cluster local --for-cluster example-cluster 424 425 # Grant read privileges for ServiceAccount observer in test namespace on the control plane 426 vela auth grant-privileges --serviceaccount observer -n test --for-namespace test --readonly 427 428 # Grant privileges for identity in kubeconfig in cluster-1 429 vela auth grant-privileges --kubeconfig ./example.kubeconfig --for-cluster cluster-1`)) 430 ) 431 432 // NewGrantPrivilegesCommand grant privileges to given identity 433 func NewGrantPrivilegesCommand(f velacmd.Factory, streams util.IOStreams) *cobra.Command { 434 o := &GrantPrivilegesOptions{IOStreams: streams} 435 cmd := &cobra.Command{ 436 Use: "grant-privileges", 437 DisableFlagsInUseLine: true, 438 Short: i18n.T("Grant privileges for user/group/serviceaccount"), 439 Long: grantPrivilegesLong, 440 Example: grantPrivilegesExample, 441 Annotations: map[string]string{ 442 types.TagCommandType: types.TypeCD, 443 }, 444 Args: cobra.ExactArgs(0), 445 Run: func(cmd *cobra.Command, args []string) { 446 o.Complete(f, cmd) 447 cmdutil.CheckErr(o.Validate(f, cmd)) 448 cmdutil.CheckErr(o.Run(f, cmd)) 449 }, 450 } 451 cmd.Flags().StringVarP(&o.User, "user", "u", o.User, "The user to grant privileges.") 452 cmd.Flags().StringSliceVarP(&o.Groups, "group", "g", o.Groups, "The group to grant privileges.") 453 cmd.Flags().StringVarP(&o.ServiceAccount, "serviceaccount", "", o.ServiceAccount, "The serviceaccount to grant privileges.") 454 cmd.Flags().StringVarP(&o.KubeConfig, "kubeconfig", "", o.KubeConfig, "The kubeconfig to grant privileges. If set, it will override all the other identity flags.") 455 cmd.Flags().StringSliceVarP(&o.GrantClusters, "for-cluster", "", o.GrantClusters, "The clusters privileges to grant. If empty, the control plane will be used.") 456 cmd.Flags().StringSliceVarP(&o.GrantNamespaces, "for-namespace", "", o.GrantNamespaces, "The namespaces privileges to grant. If empty, cluster-scoped privileges will be granted.") 457 cmd.Flags().BoolVarP(&o.ReadOnly, "readonly", "", o.ReadOnly, "If set, only read privileges of resources will be granted. Otherwise, read/write privileges will be granted.") 458 cmd.Flags().BoolVarP(&o.CreateNamespace, "create-namespace", "", o.CreateNamespace, "If set, non-exist namespace will be created automatically.") 459 cmdutil.CheckErr(cmd.RegisterFlagCompletionFunc( 460 "serviceaccount", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { 461 if strings.TrimSpace(o.User) != "" { 462 return nil, cobra.ShellCompDirectiveNoFileComp 463 } 464 namespace := velacmd.GetNamespace(f, cmd) 465 return velacmd.GetServiceAccountForCompletion(cmd.Context(), f, namespace, toComplete) 466 })) 467 468 return velacmd.NewCommandBuilder(f, cmd). 469 WithNamespaceFlag(velacmd.UsageOption("The namespace of the serviceaccount. This flag only works when `--serviceaccount` is set.")). 470 WithStreams(streams). 471 WithResponsiveWriter(). 472 Build() 473 }