Feat: vela auth grant-privileges (#3943)

Signed-off-by: Somefive <yd219913@alibaba-inc.com>
This commit is contained in:
Somefive
2022-05-23 10:47:13 +08:00
committed by GitHub
parent 7d9c647b1a
commit c5b28cb4b3
9 changed files with 426 additions and 18 deletions

View File

@@ -147,4 +147,7 @@ subjects:
- kind: Group
name: cluster-gateway-accessor
apiGroup: rbac.authorization.k8s.io
- kind: Group
name: kubevela:client
apiGroup: rbac.authorization.k8s.io
{{ end }}

View File

@@ -111,3 +111,18 @@ func (identity *Identity) Validate() error {
}
return nil
}
// Subjects return rbac subjects
func (identity *Identity) Subjects() []rbacv1.Subject {
var subs []rbacv1.Subject
if identity.User != "" {
subs = append(subs, rbacv1.Subject{Kind: rbacv1.UserKind, APIGroup: rbacv1.GroupName, Name: identity.User})
}
for _, group := range identity.Groups {
subs = append(subs, rbacv1.Subject{Kind: rbacv1.GroupKind, APIGroup: rbacv1.GroupName, Name: group})
}
if identity.ServiceAccount != "" {
subs = append(subs, rbacv1.Subject{Kind: rbacv1.ServiceAccountKind, Name: identity.ServiceAccount, Namespace: identity.ServiceAccountNamespace})
}
return subs
}

View File

@@ -18,17 +18,23 @@ package auth
import (
"context"
"fmt"
"io"
"reflect"
"strings"
"sync"
"github.com/gosuri/uitable/util/wordwrap"
"github.com/xlab/treeprint"
rbacv1 "k8s.io/api/rbac/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/utils"
velaerrors "github.com/oam-dev/kubevela/pkg/utils/errors"
"github.com/oam-dev/kubevela/pkg/utils/parallel"
)
@@ -216,3 +222,137 @@ func PrettyPrintPrivileges(identity *Identity, privilegesMap map[string][]Privil
}
return tree.String()
}
// PrivilegeDescription describe the privilege to grant
type PrivilegeDescription interface {
GetCluster() string
GetRoles() []client.Object
GetRoleBinding([]rbacv1.Subject) client.Object
}
const (
// KubeVelaReaderRoleName a role that can read any resources
KubeVelaReaderRoleName = "kubevela:reader"
// KubeVelaWriterRoleName a role that can read/write any resources
KubeVelaWriterRoleName = "kubevela:writer"
)
// ScopedPrivilege includes all resource privileges in the destination
type ScopedPrivilege struct {
Prefix string
Cluster string
Namespace string
ReadOnly bool
}
// GetCluster the cluster of the privilege
func (p *ScopedPrivilege) GetCluster() string {
return p.Cluster
}
// GetRoles the underlying Roles/ClusterRoles for the privilege
func (p *ScopedPrivilege) GetRoles() []client.Object {
if p.ReadOnly {
return []client.Object{&rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: p.Prefix + KubeVelaReaderRoleName},
Rules: []rbacv1.PolicyRule{
{APIGroups: []string{rbacv1.APIGroupAll}, Resources: []string{rbacv1.ResourceAll}, Verbs: []string{"get", "list", "watch"}},
{NonResourceURLs: []string{rbacv1.NonResourceAll}, Verbs: []string{"get", "list", "watch"}},
},
}}
}
return []client.Object{&rbacv1.ClusterRole{
ObjectMeta: metav1.ObjectMeta{Name: p.Prefix + KubeVelaWriterRoleName},
Rules: []rbacv1.PolicyRule{
{APIGroups: []string{rbacv1.APIGroupAll}, Resources: []string{rbacv1.ResourceAll}, Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}},
{NonResourceURLs: []string{rbacv1.NonResourceAll}, Verbs: []string{"get", "list", "watch", "create", "update", "patch", "delete"}},
},
}}
}
// GetRoleBinding the underlying RoleBinding/ClusterRoleBinding for the privilege
func (p *ScopedPrivilege) GetRoleBinding(subs []rbacv1.Subject) client.Object {
var binding client.Object
if p.Namespace == "" {
binding = &rbacv1.ClusterRoleBinding{
RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: KubeVelaReaderRoleName},
Subjects: subs,
}
} else {
binding = &rbacv1.RoleBinding{
RoleRef: rbacv1.RoleRef{Kind: "ClusterRole", APIGroup: rbacv1.GroupName, Name: KubeVelaWriterRoleName},
Subjects: subs,
}
binding.SetNamespace(p.Namespace)
}
if p.ReadOnly {
binding.SetName(p.Prefix + KubeVelaReaderRoleName + ":binding")
} else {
binding.SetName(p.Prefix + KubeVelaWriterRoleName + ":binding")
}
return binding
}
func mergeSubjects(src []rbacv1.Subject, merge []rbacv1.Subject) []rbacv1.Subject {
subs := append([]rbacv1.Subject{}, src...)
for _, sub := range merge {
contains := false
for _, s := range subs {
if reflect.DeepEqual(sub, s) {
contains = true
break
}
}
if !contains {
subs = append(subs, sub)
}
}
return subs
}
// GrantPrivileges grant privileges to identity
func GrantPrivileges(ctx context.Context, cli client.Client, privileges []PrivilegeDescription, identity *Identity, writer io.Writer) error {
subs := identity.Subjects()
if len(subs) == 0 {
return fmt.Errorf("failed to find RBAC subjects in identity")
}
for _, p := range privileges {
_ctx := multicluster.ContextWithClusterName(ctx, p.GetCluster())
for _, role := range p.GetRoles() {
kind, key := "ClusterRole", role.GetName()
if role.GetNamespace() != "" {
kind, key = "Role", role.GetNamespace()+"/"+role.GetName()
}
res, err := utils.CreateOrUpdate(_ctx, cli, role)
if err != nil {
return fmt.Errorf("failed to create/update %s %s: %w", kind, key, err)
}
if res != controllerutil.OperationResultNone {
_, _ = fmt.Fprintf(writer, "%s %s %s.\n", kind, key, res)
}
}
binding := p.GetRoleBinding(subs)
kind, key := "ClusterRoleBinding", binding.GetName()
if binding.GetNamespace() != "" {
kind, key = "RoleBinding", binding.GetNamespace()+"/"+binding.GetName()
}
switch bindingObj := binding.(type) {
case *rbacv1.RoleBinding:
obj := &rbacv1.RoleBinding{}
if err := cli.Get(_ctx, client.ObjectKeyFromObject(bindingObj), obj); err == nil {
bindingObj.Subjects = mergeSubjects(bindingObj.Subjects, obj.Subjects)
}
case *rbacv1.ClusterRoleBinding:
obj := &rbacv1.ClusterRoleBinding{}
if err := cli.Get(_ctx, client.ObjectKeyFromObject(bindingObj), obj); err == nil {
bindingObj.Subjects = mergeSubjects(bindingObj.Subjects, obj.Subjects)
}
}
res, err := utils.CreateOrUpdate(_ctx, cli, binding)
if err != nil {
return fmt.Errorf("failed to create/update %s %s: %w", kind, key, err)
}
_, _ = fmt.Fprintf(writer, "%s %s %s.\n", kind, key, res)
}
return nil
}

View File

@@ -26,12 +26,12 @@ import (
// GetTokenSubject extract the subject field from the jwt token
func GetTokenSubject(token string) (string, error) {
claims := jwt.MapClaims{}
if _, err := jwt.ParseWithClaims(token, claims, nil); err != nil {
return "", err
claims, sub := jwt.MapClaims{}, ""
_, err := jwt.ParseWithClaims(token, claims, nil)
if len(claims) > 0 {
sub, _ = claims["sub"].(string)
}
sub, _ := claims["sub"].(string)
return sub, nil
return sub, err
}
// GetCertificateSubject extract Subject from Certificate

View File

@@ -18,15 +18,18 @@ package utils
import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
authv1 "k8s.io/api/authentication/v1"
corev1 "k8s.io/api/core/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
k8stypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/rest"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/oam-dev/kubevela/pkg/oam/util"
velaerr "github.com/oam-dev/kubevela/pkg/utils/errors"
@@ -128,12 +131,45 @@ func GetCertificateCommonNameAndOrganizationsFromConfig(cfg *rest.Config) (strin
return name.CommonName, name.Organization
}
// GetUserInfoFromConfig extract UserInfo from KubeConfig
func GetUserInfoFromConfig(cfg *rest.Config) *authv1.UserInfo {
if sub := GetServiceAccountSubjectFromConfig(cfg); sub != "" {
return &authv1.UserInfo{Username: sub}
}
if cn, orgs := GetCertificateCommonNameAndOrganizationsFromConfig(cfg); cn != "" {
return &authv1.UserInfo{Username: cn, Groups: orgs}
}
return nil
}
// AutoSetSelfImpersonationInConfig set impersonate username and group to the identity in the original rest config
func AutoSetSelfImpersonationInConfig(cfg *rest.Config) {
if sub := GetServiceAccountSubjectFromConfig(cfg); sub != "" {
cfg.Impersonate.UserName = sub
} else if cn, orgs := GetCertificateCommonNameAndOrganizationsFromConfig(cfg); cn != "" {
cfg.Impersonate.UserName = cn
cfg.Impersonate.Groups = append(cfg.Impersonate.Groups, orgs...)
if userInfo := GetUserInfoFromConfig(cfg); userInfo != nil {
cfg.Impersonate.UserName = userInfo.Username
cfg.Impersonate.Groups = append(cfg.Impersonate.Groups, userInfo.Groups...)
}
}
// CreateOrUpdate create or update a kubernetes object
func CreateOrUpdate(ctx context.Context, cli client.Client, obj client.Object) (controllerutil.OperationResult, error) {
bs, err := json.Marshal(obj)
if err != nil {
return controllerutil.OperationResultNone, err
}
return controllerutil.CreateOrUpdate(ctx, cli, obj, func() error {
createTimestamp := obj.GetCreationTimestamp()
resourceVersion := obj.GetResourceVersion()
deletionTimestamp := obj.GetDeletionTimestamp()
generation := obj.GetGeneration()
managedFields := obj.GetManagedFields()
if e := json.Unmarshal(bs, obj); err != nil {
return e
}
obj.SetCreationTimestamp(createTimestamp)
obj.SetResourceVersion(resourceVersion)
obj.SetDeletionTimestamp(deletionTimestamp)
obj.SetGeneration(generation)
obj.SetManagedFields(managedFields)
return nil
})
}

View File

@@ -24,6 +24,7 @@ import (
"github.com/pkg/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
utilfeature "k8s.io/apiserver/pkg/util/feature"
"k8s.io/klog/v2"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/webhook"
@@ -34,11 +35,13 @@ import (
"github.com/oam-dev/kubevela/pkg/auth"
"github.com/oam-dev/kubevela/pkg/features"
"github.com/oam-dev/kubevela/pkg/oam"
"github.com/oam-dev/kubevela/pkg/utils"
)
// MutatingHandler adding user info to application annotations
type MutatingHandler struct {
Decoder *admission.Decoder
skipUsers []string
Decoder *admission.Decoder
}
var _ admission.Handler = &MutatingHandler{}
@@ -49,7 +52,7 @@ func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) adm
return admission.Patched("")
}
if slices.Contains(req.UserInfo.Groups, common.Group) {
if slices.Contains(req.UserInfo.Groups, common.Group) || slices.Contains(h.skipUsers, req.UserInfo.Username) {
return admission.Patched("")
}
@@ -61,6 +64,7 @@ func (h *MutatingHandler) Handle(ctx context.Context, req admission.Request) adm
if metav1.HasAnnotation(app.ObjectMeta, oam.AnnotationApplicationServiceAccountName) {
return admission.Errored(http.StatusBadRequest, errors.New("service-account annotation is not permitted when authentication enabled"))
}
klog.Infof("[ApplicationMutatingHandler] Setting UserInfo into Application, UserInfo: %v, Application: %s/%s", req.UserInfo, app.GetNamespace(), app.GetName())
auth.SetUserInfoInAnnotation(&app.ObjectMeta, req.UserInfo)
bs, err := json.Marshal(app)
@@ -81,5 +85,12 @@ func (h *MutatingHandler) InjectDecoder(d *admission.Decoder) error {
// RegisterMutatingHandler will register component mutation handler to the webhook
func RegisterMutatingHandler(mgr manager.Manager) {
server := mgr.GetWebhookServer()
server.Register("/mutating-core-oam-dev-v1beta1-applications", &webhook.Admission{Handler: &MutatingHandler{}})
handler := &MutatingHandler{}
if !utilfeature.DefaultMutableFeatureGate.Enabled(features.ControllerAutoImpersonation) {
if userInfo := utils.GetUserInfoFromConfig(mgr.GetConfig()); userInfo != nil {
klog.Infof("[ApplicationMutatingHandler] add skip user %s", userInfo.Username)
handler.skipUsers = []string{userInfo.Username}
}
}
server.Register("/mutating-core-oam-dev-v1beta1-applications", &webhook.Admission{Handler: handler})
}

View File

@@ -23,6 +23,9 @@ import (
"github.com/spf13/cobra"
"golang.org/x/term"
corev1 "k8s.io/api/core/v1"
kerrors "k8s.io/apimachinery/pkg/api/errors"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"k8s.io/kubectl/pkg/util/i18n"
@@ -34,6 +37,7 @@ import (
"github.com/oam-dev/kubevela/pkg/auth"
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
cmdutil "github.com/oam-dev/kubevela/pkg/cmd/util"
"github.com/oam-dev/kubevela/pkg/multicluster"
"github.com/oam-dev/kubevela/pkg/utils/util"
)
@@ -48,6 +52,7 @@ func AuthCommandGroup(f velacmd.Factory, streams util.IOStreams) *cobra.Command
}
cmd.AddCommand(NewGenKubeConfigCommand(f, streams))
cmd.AddCommand(NewListPrivilegesCommand(f, streams))
cmd.AddCommand(NewGrantPrivilegesCommand(f, streams))
return cmd
}
@@ -201,8 +206,8 @@ func (opt *ListPrivilegesOptions) Validate(f velacmd.Factory, cmd *cobra.Command
}
// Run .
func (opt *ListPrivilegesOptions) Run(f velacmd.Factory) error {
ctx := context.Background()
func (opt *ListPrivilegesOptions) Run(f velacmd.Factory, cmd *cobra.Command) error {
ctx := cmd.Context()
m, err := auth.ListPrivileges(ctx, f.Client(), opt.Clusters, &opt.Identity)
if err != nil {
return err
@@ -270,7 +275,7 @@ func NewListPrivilegesCommand(f velacmd.Factory, streams util.IOStreams) *cobra.
Run: func(cmd *cobra.Command, args []string) {
o.Complete(f, cmd)
cmdutil.CheckErr(o.Validate(f, cmd))
cmdutil.CheckErr(o.Run(f))
cmdutil.CheckErr(o.Run(f, cmd))
},
}
cmd.Flags().StringVarP(&o.User, "user", "u", o.User, "The user to list privileges.")
@@ -293,3 +298,174 @@ func NewListPrivilegesCommand(f velacmd.Factory, streams util.IOStreams) *cobra.
WithResponsiveWriter().
Build()
}
// 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 {
if _, err := prismclusterv1alpha1.NewClusterClient(f.Client()).Get(cmd.Context(), cluster); err != nil {
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 {
if _, err := prismclusterv1alpha1.NewClusterClient(f.Client()).Get(cmd.Context(), cluster); err != nil {
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})
}
}
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,
},
Args: cobra.ExactValidArgs(0),
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()
}

View File

@@ -30,7 +30,6 @@ import (
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
"k8s.io/utils/strings/slices"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"github.com/oam-dev/kubevela/apis/types"
velacmd "github.com/oam-dev/kubevela/pkg/cmd"
@@ -151,7 +150,7 @@ func (opt *KubeApplyOptions) Run(f velacmd.Factory, cmd *cobra.Command) error {
if err = copiedObj.UnmarshalJSON(bs); err != nil {
return err
}
res, err := controllerutil.CreateOrPatch(ctx, f.Client(), copiedObj, nil)
res, err := utils.CreateOrUpdate(ctx, f.Client(), copiedObj)
if err != nil {
return err
}

View File

@@ -17,8 +17,14 @@ limitations under the License.
package e2e_multicluster_test
import (
"context"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
metav1 "k8s.io/api/core/v1"
apitypes "k8s.io/apimachinery/pkg/types"
"github.com/oam-dev/kubevela/pkg/multicluster"
)
var _ = Describe("Test multicluster Auth commands", func() {
@@ -58,6 +64,28 @@ var _ = Describe("Test multicluster Auth commands", func() {
Expect(outputs).Should(ContainSubstring("cluster-admin"))
})
It("Test vela grant-privileges for user and create namespace", func() {
_, err := execCommand("auth", "grant-privileges", "--user", "alice", "--for-namespace", "alice", "--create-namespace", "--for-cluster", "local", "--for-cluster", WorkerClusterName)
Expect(err).Should(Succeed())
Expect(k8sClient.Get(multicluster.ContextWithClusterName(context.Background(), "local"), apitypes.NamespacedName{Name: "alice"}, &metav1.Namespace{})).Should(Succeed())
Expect(k8sClient.Get(multicluster.ContextWithClusterName(context.Background(), WorkerClusterName), apitypes.NamespacedName{Name: "alice"}, &metav1.Namespace{})).Should(Succeed())
})
It("Test vela grant-privileges for groups and readonly", func() {
_, err := execCommand("auth", "grant-privileges", "--group", "kubevela:dev-team", "--group", "kubevela:test-team", "--readonly")
Expect(err).Should(Succeed())
})
It("Test vela grant-privileges for serviceaccount", func() {
_, err := execCommand("auth", "grant-privileges", "--serviceaccount", "default", "-n", "default", "--for-namespace", "default")
Expect(err).Should(Succeed())
})
It("Test vela grant-privileges for kubeconfig with cluster-scoped privileges", func() {
_, err := execCommand("auth", "grant-privileges", "--kubeconfig", WorkerClusterKubeConfigPath, "--for-cluster", WorkerClusterName)
Expect(err).Should(Succeed())
})
})
})