2022-05-18 16:14:00 +08:00
/ *
Copyright 2022 The KubeVela Authors .
Licensed under the Apache License , Version 2.0 ( the "License" ) ;
you may not use this file except in compliance with the License .
You may obtain a copy of the License at
http : //www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing , software
distributed under the License is distributed on an "AS IS" BASIS ,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
See the License for the specific language governing permissions and
limitations under the License .
* /
package cli
import (
"context"
2022-05-18 20:57:39 +08:00
"fmt"
2022-05-18 16:14:00 +08:00
"strings"
"github.com/spf13/cobra"
2022-05-18 20:57:39 +08:00
"golang.org/x/term"
2022-05-23 10:47:13 +08:00
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
apitypes "k8s.io/apimachinery/pkg/types"
2022-05-18 16:14:00 +08:00
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"github.com/oam-dev/kubevela/apis/types"
"github.com/oam-dev/kubevela/pkg/auth"
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
2022-05-23 10:47:13 +08:00
"github.com/oam-dev/kubevela/pkg/multicluster"
2022-05-18 16:14:00 +08:00
"github.com/oam-dev/kubevela/pkg/utils/util"
)
// AuthCommandGroup commands for create resources or configuration
2023-04-27 10:02:47 +08:00
func AuthCommandGroup ( f velacmd . Factory , order string , streams util . IOStreams ) * cobra . Command {
2022-05-18 16:14:00 +08:00
cmd := & cobra . Command {
Use : "auth" ,
Short : i18n . T ( "Manage identity and authorizations." ) ,
Annotations : map [ string ] string {
2023-04-27 10:02:47 +08:00
types . TagCommandType : types . TypePlatform ,
types . TagCommandOrder : order ,
2022-05-18 16:14:00 +08:00
} ,
}
cmd . AddCommand ( NewGenKubeConfigCommand ( f , streams ) )
2022-05-18 20:57:39 +08:00
cmd . AddCommand ( NewListPrivilegesCommand ( f , streams ) )
2022-05-23 10:47:13 +08:00
cmd . AddCommand ( NewGrantPrivilegesCommand ( f , streams ) )
2022-05-18 16:14:00 +08:00
return cmd
}
// GenKubeConfigOptions options for create kubeconfig
type GenKubeConfigOptions struct {
2022-05-18 20:57:39 +08:00
auth . Identity
2022-05-18 16:14:00 +08:00
util . IOStreams
}
// Complete .
func ( opt * GenKubeConfigOptions ) Complete ( f velacmd . Factory , cmd * cobra . Command ) {
2022-05-18 20:57:39 +08:00
if opt . Identity . ServiceAccount != "" {
opt . Identity . ServiceAccountNamespace = velacmd . GetNamespace ( f , cmd )
2022-05-18 16:14:00 +08:00
}
2022-05-18 20:57:39 +08:00
opt . Regularize ( )
2022-05-18 16:14:00 +08:00
}
// Validate .
func ( opt * GenKubeConfigOptions ) Validate ( ) error {
2022-05-18 20:57:39 +08:00
return opt . Identity . Validate ( )
2022-05-18 16:14:00 +08:00
}
// Run .
func ( opt * GenKubeConfigOptions ) Run ( f velacmd . Factory ) error {
ctx := context . Background ( )
cli , err := kubernetes . NewForConfig ( f . Config ( ) )
if err != nil {
return err
}
cfg , err := clientcmd . NewDefaultPathOptions ( ) . GetStartingConfig ( )
if err != nil {
return err
}
2022-05-18 20:57:39 +08:00
cfg , err = auth . GenerateKubeConfig ( ctx , cli , cfg , opt . IOStreams . ErrOut , auth . KubeConfigWithIdentityGenerateOption ( opt . Identity ) )
2022-05-18 16:14:00 +08:00
if err != nil {
return err
}
bs , err := clientcmd . Write ( * cfg )
if err != nil {
return err
}
_ , err = opt . Out . Write ( bs )
return err
}
var (
genKubeConfigLong = templates . LongDesc ( i18n . T ( `
Generate kubeconfig for user
Generate a new kubeconfig with specified identity . By default , the generated kubeconfig
will reuse the certificate - authority - data in the cluster config from the current used
kubeconfig . All contexts , clusters and users that are not in use will not be included
in the generated kubeconfig .
To generate a new kubeconfig for given user and groups , use the -- user and -- group flag .
Multiple -- group flags is allowed . The group kubevela : client is added to the groups by
default . The identity in the current kubeconfig should be able to approve
CertificateSigningRequest in the kubernetes cluster . See
https : //kubernetes.io/docs/reference/access-authn-authz/certificate-signing-requests/
for details .
To generate a kubeconfig based on existing ServiceAccount in your cluster , use the
-- serviceaccount flag . The corresponding secret token and ca data will be embedded in
the generated kubeconfig , which allows you to act as the serviceaccount . ` ) )
generateKubeConfigExample = templates . Examples ( i18n . T ( `
# Generate a kubeconfig with provided user
vela auth gen - kubeconfig -- user new - user
# Generate a kubeconfig with provided user and group
vela auth gen - kubeconfig -- user new - user -- group kubevela : developer
# Generate a kubeconfig with provided user and groups
vela auth gen - kubeconfig -- user new - user -- group kubevela : developer -- group my - org : my - team
# Generate a kubeconfig with provided serviceaccount
vela auth gen - kubeconfig -- serviceaccount default - n demo ` ) )
)
// NewGenKubeConfigCommand generate kubeconfig for given user and groups
func NewGenKubeConfigCommand ( f velacmd . Factory , streams util . IOStreams ) * cobra . Command {
o := & GenKubeConfigOptions { IOStreams : streams }
cmd := & cobra . Command {
Use : "gen-kubeconfig" ,
DisableFlagsInUseLine : true ,
Short : i18n . T ( "Generate kubeconfig for user" ) ,
Long : genKubeConfigLong ,
Example : generateKubeConfigExample ,
Annotations : map [ string ] string {
types . TagCommandType : types . TypeCD ,
} ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 0 ) ,
2022-05-18 16:14:00 +08:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
o . Complete ( f , cmd )
cmdutil . CheckErr ( o . Validate ( ) )
cmdutil . CheckErr ( o . Run ( f ) )
} ,
}
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." )
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." )
2022-05-18 20:57:39 +08:00
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." )
2022-05-18 16:14:00 +08:00
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"serviceaccount" , func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if strings . TrimSpace ( o . User ) != "" {
return nil , cobra . ShellCompDirectiveNoFileComp
}
namespace := velacmd . GetNamespace ( f , cmd )
return velacmd . GetServiceAccountForCompletion ( cmd . Context ( ) , f , namespace , toComplete )
} ) )
return velacmd . NewCommandBuilder ( f , cmd ) .
2022-05-20 12:11:58 +08:00
WithNamespaceFlag ( velacmd . UsageOption ( "The namespace of the serviceaccount. This flag only works when `--serviceaccount` is set." ) ) .
2022-05-18 16:14:00 +08:00
WithStreams ( streams ) .
WithResponsiveWriter ( ) .
Build ( )
}
2022-05-18 20:57:39 +08:00
// ListPrivilegesOptions options for list privileges
type ListPrivilegesOptions struct {
auth . Identity
KubeConfig string
Clusters [ ] string
util . IOStreams
}
// Complete .
func ( opt * ListPrivilegesOptions ) Complete ( f velacmd . Factory , cmd * cobra . Command ) {
if opt . KubeConfig != "" {
identity , err := auth . ReadIdentityFromKubeConfig ( opt . KubeConfig )
cmdutil . CheckErr ( err )
opt . Identity = * identity
}
if opt . Identity . ServiceAccount != "" {
opt . Identity . ServiceAccountNamespace = velacmd . GetNamespace ( f , cmd )
}
2022-05-20 12:11:58 +08:00
opt . Clusters = velacmd . GetClusters ( cmd )
2022-05-18 20:57:39 +08:00
opt . Regularize ( )
}
// Validate .
func ( opt * ListPrivilegesOptions ) Validate ( f velacmd . Factory , cmd * cobra . Command ) error {
if err := opt . Identity . Validate ( ) ; err != nil {
return err
}
for _ , cluster := range opt . Clusters {
2023-04-12 20:25:24 +08:00
if _ , err := multicluster . NewClusterClient ( f . Client ( ) ) . Get ( cmd . Context ( ) , cluster ) ; err != nil {
2022-05-18 20:57:39 +08:00
return fmt . Errorf ( "failed to find cluster %s: %w" , cluster , err )
}
}
return nil
}
// Run .
2022-05-23 10:47:13 +08:00
func ( opt * ListPrivilegesOptions ) Run ( f velacmd . Factory , cmd * cobra . Command ) error {
ctx := cmd . Context ( )
2022-05-18 20:57:39 +08:00
m , err := auth . ListPrivileges ( ctx , f . Client ( ) , opt . Clusters , & opt . Identity )
if err != nil {
return err
}
width , _ , err := term . GetSize ( 0 )
if err != nil {
width = 80
}
_ , _ = opt . Out . Write ( [ ] byte ( auth . PrettyPrintPrivileges ( & opt . Identity , m , opt . Clusters , uint ( width ) - 40 ) ) )
return nil
}
var (
listPrivilegesLong = templates . LongDesc ( i18n . T ( `
List privileges for user
List privileges that user has in clusters . Use -- user / -- group to check the privileges
for specified user and group . They can be jointly configured to see the union of
privileges . Use -- serviceaccount and - n / -- namespace to see the privileges for
ServiceAccount . You can also use -- kubeconfig to use the identity inside implicitly .
The privileges will be shown in tree format .
This command supports listing privileges across multiple clusters , by using -- cluster .
If not set , the control plane will be used . This feature requires cluster - gateway to be
properly setup to use .
The privileges are collected through listing all ClusterRoleBinding and RoleBinding ,
following the Kubernetes RBAC Authorization . Other authorization mechanism is not supported
now . See https : //kubernetes.io/docs/reference/access-authn-authz/rbac/ for details.
The ClusterRoleBinding and RoleBinding that matches the specified identity will be
tracked . Related ClusterRoles and Roles are retrieved and the contained PolicyRules are
demonstrated . ` ) )
listPrivilegesExample = templates . Examples ( i18n . T ( `
# List privileges for User alice in the control plane
vela auth list - privileges -- user alice
# List privileges for Group org : dev - team in the control plane
vela auth list - privileges -- group org : dev - team
# List privileges for User bob with Groups org : dev - team and org : test - team in the control plane and managed cluster example - cluster
vela auth list - privileges -- user bob -- group org : dev - team -- group org : test - team -- cluster local -- cluster example - cluster
# List privileges for ServiceAccount example - sa in demo namespace in multiple managed clusters
vela auth list - privileges -- serviceaccount example - sa - n demo -- cluster cluster - 1 -- cluster cluster - 2
# List privileges for identity in kubeconfig
vela auth list - privileges -- kubeconfig . / example . kubeconfig -- cluster local -- cluster cluster - 1 ` ) )
)
// NewListPrivilegesCommand list privileges for given identity
func NewListPrivilegesCommand ( f velacmd . Factory , streams util . IOStreams ) * cobra . Command {
o := & ListPrivilegesOptions { IOStreams : streams }
cmd := & cobra . Command {
Use : "list-privileges" ,
DisableFlagsInUseLine : true ,
Short : i18n . T ( "List privileges for user/group/serviceaccount" ) ,
Long : listPrivilegesLong ,
Example : listPrivilegesExample ,
Annotations : map [ string ] string {
types . TagCommandType : types . TypeCD ,
} ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 0 ) ,
2022-05-18 20:57:39 +08:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
o . Complete ( f , cmd )
cmdutil . CheckErr ( o . Validate ( f , cmd ) )
2022-05-23 10:47:13 +08:00
cmdutil . CheckErr ( o . Run ( f , cmd ) )
2022-05-18 20:57:39 +08:00
} ,
}
cmd . Flags ( ) . StringVarP ( & o . User , "user" , "u" , o . User , "The user to list privileges." )
cmd . Flags ( ) . StringSliceVarP ( & o . Groups , "group" , "g" , o . Groups , "The group to list privileges. Can be set together with --user." )
cmd . Flags ( ) . StringVarP ( & o . ServiceAccount , "serviceaccount" , "" , o . ServiceAccount , "The serviceaccount to list privileges. Cannot be set with --user and --group." )
cmd . Flags ( ) . StringVarP ( & o . KubeConfig , "kubeconfig" , "" , o . KubeConfig , "The kubeconfig to list privileges. If set, it will override all the other identity flags." )
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"serviceaccount" , func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if strings . TrimSpace ( o . User ) != "" {
return nil , cobra . ShellCompDirectiveNoFileComp
}
namespace := velacmd . GetNamespace ( f , cmd )
return velacmd . GetServiceAccountForCompletion ( cmd . Context ( ) , f , namespace , toComplete )
} ) )
return velacmd . NewCommandBuilder ( f , cmd ) .
2022-05-20 12:11:58 +08:00
WithNamespaceFlag ( velacmd . UsageOption ( "The namespace of the serviceaccount. This flag only works when `--serviceaccount` is set." ) ) .
WithClusterFlag ( velacmd . UsageOption ( "The cluster to list privileges. If not set, the command will list privileges in the control plane." ) ) .
2022-05-18 20:57:39 +08:00
WithStreams ( streams ) .
WithResponsiveWriter ( ) .
Build ( )
}
2022-05-23 10:47:13 +08:00
// GrantPrivilegesOptions options for grant privileges
type GrantPrivilegesOptions struct {
auth . Identity
KubeConfig string
GrantNamespaces [ ] string
GrantClusters [ ] string
ReadOnly bool
CreateNamespace bool
util . IOStreams
}
// Complete .
func ( opt * GrantPrivilegesOptions ) Complete ( f velacmd . Factory , cmd * cobra . Command ) {
if opt . KubeConfig != "" {
identity , err := auth . ReadIdentityFromKubeConfig ( opt . KubeConfig )
cmdutil . CheckErr ( err )
opt . Identity = * identity
opt . Identity . Groups = nil
}
if opt . Identity . ServiceAccount != "" {
opt . Identity . ServiceAccountNamespace = velacmd . GetNamespace ( f , cmd )
}
opt . Regularize ( )
if len ( opt . GrantClusters ) == 0 {
opt . GrantClusters = [ ] string { types . ClusterLocalName }
}
}
// Validate .
func ( opt * GrantPrivilegesOptions ) Validate ( f velacmd . Factory , cmd * cobra . Command ) error {
if opt . User == "" && len ( opt . Groups ) == 0 && opt . ServiceAccount == "" {
return fmt . Errorf ( "at least one idenity (user/group/serviceaccount) should be set" )
}
for _ , cluster := range opt . GrantClusters {
2023-04-12 20:25:24 +08:00
if _ , err := multicluster . NewClusterClient ( f . Client ( ) ) . Get ( cmd . Context ( ) , cluster ) ; err != nil {
2022-05-23 10:47:13 +08:00
return fmt . Errorf ( "failed to find cluster %s: %w" , cluster , err )
}
if ! opt . CreateNamespace {
for _ , namespace := range opt . GrantNamespaces {
if err := f . Client ( ) . Get ( multicluster . ContextWithClusterName ( cmd . Context ( ) , cluster ) , apitypes . NamespacedName { Name : namespace } , & corev1 . Namespace { } ) ; err != nil {
return fmt . Errorf ( "failed to find namespace %s in cluster %s: %w" , namespace , cluster , err )
}
}
}
}
return nil
}
// Run .
func ( opt * GrantPrivilegesOptions ) Run ( f velacmd . Factory , cmd * cobra . Command ) error {
ctx := cmd . Context ( )
if opt . CreateNamespace {
for _ , cluster := range opt . GrantClusters {
2023-04-12 20:25:24 +08:00
if _ , err := multicluster . NewClusterClient ( f . Client ( ) ) . Get ( cmd . Context ( ) , cluster ) ; err != nil {
2022-05-23 10:47:13 +08:00
return fmt . Errorf ( "failed to find cluster %s: %w" , cluster , err )
}
for _ , namespace := range opt . GrantNamespaces {
_ctx := multicluster . ContextWithClusterName ( cmd . Context ( ) , cluster )
ns := & corev1 . Namespace { }
if err := f . Client ( ) . Get ( _ctx , apitypes . NamespacedName { Name : namespace } , ns ) ; err != nil {
if kerrors . IsNotFound ( err ) {
ns . SetName ( namespace )
if err = f . Client ( ) . Create ( _ctx , ns ) ; err != nil {
return fmt . Errorf ( "failed to create namespace %s in cluster %s: %w" , namespace , cluster , err )
}
continue
}
return fmt . Errorf ( "failed to find namespace %s in cluster %s: %w" , namespace , cluster , err )
}
}
}
}
var privileges [ ] auth . PrivilegeDescription
for _ , cluster := range opt . GrantClusters {
for _ , namespace := range opt . GrantNamespaces {
privileges = append ( privileges , & auth . ScopedPrivilege { Cluster : cluster , Namespace : namespace , ReadOnly : opt . ReadOnly } )
}
2022-05-24 14:31:32 +08:00
if len ( opt . GrantNamespaces ) == 0 {
privileges = append ( privileges , & auth . ScopedPrivilege { Cluster : cluster , ReadOnly : opt . ReadOnly } )
}
2022-05-23 10:47:13 +08:00
}
if err := auth . GrantPrivileges ( ctx , f . Client ( ) , privileges , & opt . Identity , opt . IOStreams . Out ) ; err != nil {
return err
}
_ , _ = fmt . Fprintf ( opt . IOStreams . Out , "Privileges granted.\n" )
return nil
}
var (
grantPrivilegesLong = templates . LongDesc ( i18n . T ( `
Grant privileges for user
Grant privileges to user / group / serviceaccount . By using -- for - namespace and -- for - cluster ,
you can grant all read / write privileges for all resources in the specified namespace and
cluster . If -- for - namespace is not set , the privileges will be granted cluster - wide .
Setting -- create - namespace will automatically create namespace if the namespace of the
granted privilege does not exists . By default , this flag is not enabled and errors will be
returned if the namespace is not found in the corresponding cluster .
Setting -- readonly will only grant read privileges for all resources in the destination . This
can be useful if you want to give somebody the privileges to view resources but do not want to
allow them to edit any resource .
If multiple identity information are set , all the identity information will be bond to the
intended privileges respectively .
If -- kubeconfig is set , the user / serviceaccount information in the kubeconfig will be used as
the identity to grant privileges . Groups will be ignored . ` ) )
grantPrivilegesExample = templates . Examples ( i18n . T ( `
# Grant privileges for User alice in the namespace demo of the control plane
vela auth grant - privileges -- user alice -- for - namespace demo
# Grant privileges for User alice in the namespace demo in cluster - 1 , create demo namespace if not exist
vela auth grant - privileges -- user alice -- for - namespace demo -- for - cluster cluster - 1 -- create - namespace
# Grant cluster - scoped privileges for Group org : dev - team in the control plane
vela auth grant - privileges -- group org : dev - team
# Grant privileges for Group org : dev - team and org : test - team in the namespace test on the control plane and managed cluster example - cluster
vela auth grant - privileges -- group org : dev - team -- group org : test - team -- for - namespace test -- for - cluster local -- for - cluster example - cluster
# Grant read privileges for ServiceAccount observer in test namespace on the control plane
vela auth grant - privileges -- serviceaccount observer - n test -- for - namespace test -- readonly
# Grant privileges for identity in kubeconfig in cluster - 1
vela auth grant - privileges -- kubeconfig . / example . kubeconfig -- for - cluster cluster - 1 ` ) )
)
// NewGrantPrivilegesCommand grant privileges to given identity
func NewGrantPrivilegesCommand ( f velacmd . Factory , streams util . IOStreams ) * cobra . Command {
o := & GrantPrivilegesOptions { IOStreams : streams }
cmd := & cobra . Command {
Use : "grant-privileges" ,
DisableFlagsInUseLine : true ,
Short : i18n . T ( "Grant privileges for user/group/serviceaccount" ) ,
Long : grantPrivilegesLong ,
Example : grantPrivilegesExample ,
Annotations : map [ string ] string {
types . TagCommandType : types . TypeCD ,
} ,
2022-12-27 16:28:13 +08:00
Args : cobra . ExactArgs ( 0 ) ,
2022-05-23 10:47:13 +08:00
Run : func ( cmd * cobra . Command , args [ ] string ) {
o . Complete ( f , cmd )
cmdutil . CheckErr ( o . Validate ( f , cmd ) )
cmdutil . CheckErr ( o . Run ( f , cmd ) )
} ,
}
cmd . Flags ( ) . StringVarP ( & o . User , "user" , "u" , o . User , "The user to grant privileges." )
cmd . Flags ( ) . StringSliceVarP ( & o . Groups , "group" , "g" , o . Groups , "The group to grant privileges." )
cmd . Flags ( ) . StringVarP ( & o . ServiceAccount , "serviceaccount" , "" , o . ServiceAccount , "The serviceaccount to grant privileges." )
cmd . Flags ( ) . StringVarP ( & o . KubeConfig , "kubeconfig" , "" , o . KubeConfig , "The kubeconfig to grant privileges. If set, it will override all the other identity flags." )
cmd . Flags ( ) . StringSliceVarP ( & o . GrantClusters , "for-cluster" , "" , o . GrantClusters , "The clusters privileges to grant. If empty, the control plane will be used." )
cmd . Flags ( ) . StringSliceVarP ( & o . GrantNamespaces , "for-namespace" , "" , o . GrantNamespaces , "The namespaces privileges to grant. If empty, cluster-scoped privileges will be granted." )
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." )
cmd . Flags ( ) . BoolVarP ( & o . CreateNamespace , "create-namespace" , "" , o . CreateNamespace , "If set, non-exist namespace will be created automatically." )
cmdutil . CheckErr ( cmd . RegisterFlagCompletionFunc (
"serviceaccount" , func ( cmd * cobra . Command , args [ ] string , toComplete string ) ( [ ] string , cobra . ShellCompDirective ) {
if strings . TrimSpace ( o . User ) != "" {
return nil , cobra . ShellCompDirectiveNoFileComp
}
namespace := velacmd . GetNamespace ( f , cmd )
return velacmd . GetServiceAccountForCompletion ( cmd . Context ( ) , f , namespace , toComplete )
} ) )
return velacmd . NewCommandBuilder ( f , cmd ) .
WithNamespaceFlag ( velacmd . UsageOption ( "The namespace of the serviceaccount. This flag only works when `--serviceaccount` is set." ) ) .
WithStreams ( streams ) .
WithResponsiveWriter ( ) .
Build ( )
}