diff --git a/cmd/webhook/app/options/options.go b/cmd/webhook/app/options/options.go index a4a6f0c44..37083e32b 100644 --- a/cmd/webhook/app/options/options.go +++ b/cmd/webhook/app/options/options.go @@ -12,21 +12,23 @@ import ( // Config contains the server (the webhook) cert and key. type Options struct { - CertFile string - KeyFile string - KubeConfigFile string - QPS float32 - Burst int + CertFile string + KeyFile string + KubeConfigFile string + QPS float32 + Burst int + SkipOverwriteUserList []string } // NewOptions constructs a new set of default options for webhook. func NewOptions() *Options { return &Options{ - KubeConfigFile: "", - CertFile: "", - KeyFile: "", - QPS: 100.0, - Burst: 200, + KubeConfigFile: "", + CertFile: "", + KeyFile: "", + QPS: 100.0, + Burst: 200, + SkipOverwriteUserList: []string{"system:serviceaccount:open-cluster-management-agent-addon:klusterlet-addon-appmgr"}, } } @@ -42,6 +44,8 @@ func (c *Options) AddFlags(fs *pflag.FlagSet) { "Maximum QPS to the hub server from this webhook.") fs.IntVar(&c.Burst, "max-burst", c.Burst, "Maximum burst for throttle.") + fs.StringSliceVar(&c.SkipOverwriteUserList, "skip-overwrite-user-list", c.SkipOverwriteUserList, + "List of users to skip overwriting of user identity annotations.") } type certificateCacheEntry struct { diff --git a/cmd/webhook/app/start.go b/cmd/webhook/app/start.go index d35b1ea67..198aaac26 100644 --- a/cmd/webhook/app/start.go +++ b/cmd/webhook/app/start.go @@ -37,7 +37,8 @@ func Run(opts *options.Options, stopCh <-chan struct{}) error { informer := informerFactory.Rbac().V1().RoleBindings() mutatingAh := &useridentity.AdmissionHandler{ - Lister: informer.Lister(), + Lister: informer.Lister(), + SkipOverwriteUserList: opts.SkipOverwriteUserList, } validatingAh := &clusterset.AdmissionHandler{ diff --git a/pkg/webhook/useridentity/mutatingWebhook.go b/pkg/webhook/useridentity/mutatingWebhook.go index 69a772e71..789e286aa 100644 --- a/pkg/webhook/useridentity/mutatingWebhook.go +++ b/pkg/webhook/useridentity/mutatingWebhook.go @@ -5,19 +5,22 @@ import ( "fmt" "io" "io/ioutil" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "net/http" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "github.com/mattbaird/jsonpatch" "github.com/open-cluster-management/multicloud-operators-foundation/cmd/webhook/app/options" - "k8s.io/api/admission/v1" + "github.com/open-cluster-management/multicloud-operators-foundation/pkg/utils" + v1 "k8s.io/api/admission/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" rbaclisters "k8s.io/client-go/listers/rbac/v1" "k8s.io/klog/v2" ) type AdmissionHandler struct { - Lister rbaclisters.RoleBindingLister + Lister rbaclisters.RoleBindingLister + SkipOverwriteUserList []string } // toAdmissionResponse is a helper function to create an AdmissionResponse @@ -83,7 +86,8 @@ func (a *AdmissionHandler) serve(w io.Writer, r *http.Request, admit admitFunc) } func (a *AdmissionHandler) mutateResource(ar v1.AdmissionReview) *v1.AdmissionResponse { - klog.V(2).Info("mutating custom resource") + klog.V(4).Info("mutating custom resource") + obj := unstructured.Unstructured{} err := obj.UnmarshalJSON(ar.Request.Object.Raw) if err != nil { @@ -92,6 +96,14 @@ func (a *AdmissionHandler) mutateResource(ar v1.AdmissionReview) *v1.AdmissionRe } annotations := obj.GetAnnotations() + + if utils.ContainsString(a.SkipOverwriteUserList, ar.Request.UserInfo.Username) { + klog.V(4).Infof("Skip add user and group for resource: %+v, name: %+v", ar.Request.Resource.Resource, obj.GetName()) + reviewResponse := v1.AdmissionResponse{} + reviewResponse.Allowed = true + return &reviewResponse + } + resAnnotations := MergeUserIdentityToAnnotations(ar.Request.UserInfo, annotations, obj.GetNamespace(), a.Lister) obj.SetAnnotations(resAnnotations) diff --git a/pkg/webhook/useridentity/mutatingWebhook_test.go b/pkg/webhook/useridentity/mutatingWebhook_test.go index 212dbd7ab..3253d67f4 100644 --- a/pkg/webhook/useridentity/mutatingWebhook_test.go +++ b/pkg/webhook/useridentity/mutatingWebhook_test.go @@ -1,22 +1,25 @@ package useridentity import ( + "testing" + "github.com/stretchr/testify/assert" v1 "k8s.io/api/admission/v1" authenticationv1 "k8s.io/api/authentication/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - "testing" ) func newAdmissionHandler() *AdmissionHandler { return &AdmissionHandler{ - Lister: nil, + Lister: nil, + SkipOverwriteUserList: []string{"system:serviceaccount:open-cluster-management-agent-addon:klusterlet-addon-appmgr"}, } } const ( channelTest = `{"apiVersion":"apps.open-cluster-management.io/v1","kind":"Channel","metadata":{"annotations":{"kubectl.kubernetes.io/last-applied-configuration":"{\"apiVersion\":\"apps.open-cluster-management.io/v1\",\"kind\":\"Channel\",\"metadata\":{\"annotations\":{},\"name\":\"test\",\"namespace\":\"default\"},\"spec\":{\"pathname\":\"https://github.com/open-cluster-management/abc.git\",\"type\":\"Git\"}}\n"},"creationTimestamp":null,"managedFields":[{"apiVersion":"apps.open-cluster-management.io/v1","fieldsType":"FieldsV1","fieldsV1":{"f:metadata":{"f:annotations":{".":{},"f:kubectl.kubernetes.io/last-applied-configuration":{}}},"f:spec":{".":{},"f:pathname":{},"f:type":{}}},"manager":"kubectl-client-side-apply","operation":"Update","time":"2021-03-26T07:23:28Z"}],"name":"test","namespace":"default"},"spec":{"pathname":"https://github.com/open-cluster-management/abc.git","type":"Git"}}` + appsubTest = `{"apiVersion":"apps.open-cluster-management.io/v1","kind": "Subscription","metadata": {"name": "git-sub","namespace": "parentsub","annotations": {"apps.open-cluster-management.io/cluster-admin": "true","apps.open-cluster-management.io/github-path": "test/e2e/github/nestedSubscription"}},"spec": {"channel": "ch-git/git","placement": {"local": "true"}}}` ) func newAdmissionReview() *v1.AdmissionReview { @@ -63,8 +66,55 @@ func newAdmissionReview() *v1.AdmissionReview { } } +func appSubAdmissionReview() *v1.AdmissionReview { + return &v1.AdmissionReview{ + TypeMeta: metav1.TypeMeta{ + Kind: "AdmissionReview", + APIVersion: "admission.k8s.io/v1", + }, + Request: &v1.AdmissionRequest{ + UID: "4d6d1d1f-61a0-49ea-b458-05454ba42ab6", + Kind: metav1.GroupVersionKind{ + Group: "apps.open-cluster-management.io", + Kind: "Subscription", + Version: "v1", + }, + Resource: metav1.GroupVersionResource{ + Group: "apps.open-cluster-management.io", + Version: "v1", + Resource: "subscriptions", + }, + SubResource: "", + RequestKind: &metav1.GroupVersionKind{ + Group: "apps.open-cluster-management.io", + Kind: "Subscription", + Version: "v1", + }, + RequestResource: &metav1.GroupVersionResource{ + Group: "apps.open-cluster-management.io", + Version: "v1", + Resource: "subscriptions", + }, + RequestSubResource: "", + Name: "subtest", + Namespace: "default", + Operation: "CREATE", + UserInfo: authenticationv1.UserInfo{ + Username: "system:serviceaccount:open-cluster-management-agent-addon:klusterlet-addon-appmgr", + }, + Object: runtime.RawExtension{ + Raw: []byte(appsubTest), + }, + }, + Response: nil, + } +} + func TestMutateResource(t *testing.T) { adHandler := newAdmissionHandler() rsp := adHandler.mutateResource(*newAdmissionReview()) assert.True(t, true, len(rsp.Patch) != 0) + + rsp = adHandler.mutateResource(*appSubAdmissionReview()) + assert.True(t, rsp.Patch == nil) }