diff --git a/pkg/userinfo/roleRef.go b/pkg/userinfo/roleRef.go index 3ad43d3b34..a53124aac0 100644 --- a/pkg/userinfo/roleRef.go +++ b/pkg/userinfo/roleRef.go @@ -141,16 +141,15 @@ func IsRoleAuthorize(rbLister rbaclister.RoleBindingLister, crbLister rbaclister for _, e := range clusterRoles { if strings.Contains(e, "kyverno:") { return true, nil - } else { - role, err := crLister.Get(e) - if err != nil { - return false, err - } - labels := role.GetLabels() + } + role, err := crLister.Get(e) + if err != nil { + return false, err + } + labels := role.GetLabels() - if labels["kubernetes.io/bootstrapping"] == "rbac-defaults" { - return true, nil - } + if labels["kubernetes.io/bootstrapping"] == "rbac-defaults" { + return true, nil } } for _, e := range roles { @@ -166,26 +165,25 @@ func IsRoleAuthorize(rbLister rbaclister.RoleBindingLister, crbLister rbaclister } } } - } else { - // User or Group - excludeDevelopmentRole := []string{"minikube-user", "kubernetes-admin"} - for _, e := range excludeDevelopmentRole { - if strings.Contains(request.UserInfo.Username, e) { - return false, nil + } + // User or Group + excludeDevelopmentRole := []string{"minikube-user", "kubernetes-admin"} + for _, e := range excludeDevelopmentRole { + if strings.Contains(request.UserInfo.Username, e) { + return false, nil + } + } + var matchedRoles []bool + for _, e := range request.UserInfo.Groups { + for _, defaultSuffix := range defaultSuffixs { + if strings.Contains(e, defaultSuffix) { + matchedRoles = append(matchedRoles, true) + break } } - var matchedRoles []bool - for _, e := range request.UserInfo.Groups { - for _, defaultSuffix := range defaultSuffixs { - if strings.Contains(e, defaultSuffix) { - matchedRoles = append(matchedRoles, true) - break - } - } - } - if len(matchedRoles) == len(request.UserInfo.Groups) { - return true, nil - } + } + if len(matchedRoles) == len(request.UserInfo.Groups) { + return true, nil } return false, nil diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index e80963d078..286bb6941c 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -10,13 +10,13 @@ import ( "net/http" "time" - apierrors "k8s.io/apimachinery/pkg/api/errors" - "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" - "github.com/go-logr/logr" "github.com/julienschmidt/httprouter" v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1" "github.com/nirmata/kyverno/pkg/checker" + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + kyvernoclient "github.com/nirmata/kyverno/pkg/client/clientset/versioned" kyvernoinformer "github.com/nirmata/kyverno/pkg/client/informers/externalversions/kyverno/v1" kyvernolister "github.com/nirmata/kyverno/pkg/client/listers/kyverno/v1" @@ -65,12 +65,18 @@ type WebhookServer struct { // return true if role bining store has synced atleast once rbSynced cache.InformerSynced + // return true if role store has synced atleast once + rSynced cache.InformerSynced + // list/get cluster role binding resource crbLister rbaclister.ClusterRoleBindingLister // return true if cluster role binding store has synced atleast once crbSynced cache.InformerSynced + // return true if cluster role store has synced atleast once + crSynced cache.InformerSynced + // generate events eventGen event.Interface @@ -146,16 +152,19 @@ func NewWebhookServer( tlsConfig.Certificates = []tls.Certificate{pair} ws := &WebhookServer{ - client: client, - kyvernoClient: kyvernoClient, - pLister: pInformer.Lister(), - pSynced: pInformer.Informer().HasSynced, - rbLister: rbInformer.Lister(), - rLister: rInformer.Lister(), - rbSynced: rbInformer.Informer().HasSynced, + client: client, + kyvernoClient: kyvernoClient, + pLister: pInformer.Lister(), + pSynced: pInformer.Informer().HasSynced, + rbLister: rbInformer.Lister(), + rbSynced: rbInformer.Informer().HasSynced, + rLister: rInformer.Lister(), + rSynced: rInformer.Informer().HasSynced, + crbLister: crbInformer.Lister(), crLister: crInformer.Lister(), crbSynced: crbInformer.Informer().HasSynced, + crSynced: crInformer.Informer().HasSynced, eventGen: eventGen, pCache: pCache, webhookRegistrationClient: webhookRegistrationClient, @@ -374,61 +383,19 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1 func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse { logger := ws.log.WithName("resourceValidation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) - checked, err := userinfo.IsRoleAuthorize(ws.rbLister, ws.crbLister, ws.rLister, ws.crLister, request) - if err != nil { - logger.Error(err, "failed to get RBAC infromation for request") - } - if !checked { - if request.Operation == v1beta1.Delete || request.Operation == v1beta1.Update { - // convert RAW to unstructured - var resource *unstructured.Unstructured - if request.Operation == v1beta1.Delete { - resource, err = enginutils.ConvertToUnstructured(request.OldObject.Raw) - } else { - resource, err = enginutils.ConvertToUnstructured(request.Object.Raw) + if request.Operation == v1beta1.Delete || request.Operation == v1beta1.Update { + if err := ws.excludeKyvernoResources(request); err != nil { + return &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Status: "Failure", + Message: err.Error(), + }, } - if err != nil { - //TODO: skip applying the admission control ? - logger.Error(err, "failed to convert RAR resource to unstructured format") - - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Status: "Failure", - Message: err.Error(), - }, - } - } - - oldResource, err := ws.client.GetResource(resource.GetKind(), resource.GetNamespace(), resource.GetName()) - if err != nil { - if !apierrors.IsNotFound(err) { - logger.Error(err, "failed to get resource") - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Status: "Failure", - Message: err.Error(), - }, - } - } - } - labels := oldResource.GetLabels() - if labels != nil { - if labels["app.kubernetes.io/managed-by"] == "kyverno" && labels["app.kubernetes.io/synchronize"] == "enable" { - return &v1beta1.AdmissionResponse{ - Allowed: false, - Result: &metav1.Status{ - Status: "Failure", - Message: "You don't have permission to update resourses that is generated by kyverno", - }, - } - } - } - } } + if !ws.supportMudateValidate { logger.Info("mutate and validate rules are not supported prior to Kubernetes 1.14.0") return &v1beta1.AdmissionResponse{ @@ -458,6 +425,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) * } var roles, clusterRoles []string + var err error // getRoleRef only if policy has roles/clusterroles defined if containRBACinfo(policies) { roles, clusterRoles, err = userinfo.GetRoleRef(ws.rbLister, ws.crbLister, request) @@ -517,7 +485,7 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) * // RunAsync TLS server in separate thread and returns control immediately func (ws *WebhookServer) RunAsync(stopCh <-chan struct{}) { logger := ws.log - if !cache.WaitForCacheSync(stopCh, ws.pSynced, ws.rbSynced, ws.crbSynced) { + if !cache.WaitForCacheSync(stopCh, ws.pSynced, ws.rbSynced, ws.crbSynced, ws.rSynced, ws.crSynced) { logger.Info("failed to sync informer cache") } @@ -584,3 +552,43 @@ func (ws *WebhookServer) bodyToAdmissionReview(request *http.Request, writer htt return admissionReview } + +// excludeKyvernoResources will check resource can have acces or not +func (ws *WebhookServer) excludeKyvernoResources(request *v1beta1.AdmissionRequest) error { + logger := ws.log.WithName("resourceValidation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation) + + checked, err := userinfo.IsRoleAuthorize(ws.rbLister, ws.crbLister, ws.rLister, ws.crLister, request) + if err != nil { + logger.Error(err, "failed to get RBAC infromation for request") + } + + if !checked { + // convert RAW to unstructured + var resource *unstructured.Unstructured + if request.Operation == v1beta1.Delete { + resource, err = enginutils.ConvertToUnstructured(request.OldObject.Raw) + } else { + resource, err = enginutils.ConvertToUnstructured(request.Object.Raw) + } + if err != nil { + logger.Error(err, "failed to convert RAR resource to unstructured format") + return err + } + + oldResource, err := ws.client.GetResource(resource.GetKind(), resource.GetNamespace(), resource.GetName()) + if err != nil { + if !apierrors.IsNotFound(err) { + logger.Error(err, "failed to get resource") + return err + } + } + labels := oldResource.GetLabels() + if labels != nil { + if labels["app.kubernetes.io/managed-by"] == "kyverno" && labels["app.kubernetes.io/synchronize"] == "enable" { + return fmt.Errorf("Resource is managed by Kyverno, can't be changed manually. You can edit generate policy to update this resource") + } + } + + } + return nil +}