From 014db64ed2f7c0d6b445527eb384a1b71bf8cff0 Mon Sep 17 00:00:00 2001 From: evalsocket Date: Fri, 10 Jul 2020 11:48:27 -0700 Subject: [PATCH] validation added for deny request for generated resource --- cmd/kyverno/main.go | 2 + pkg/userinfo/roleRef.go | 50 +++++++++++++++++++++++++ pkg/webhooks/server.go | 83 ++++++++++++++++++++++++++--------------- 3 files changed, 104 insertions(+), 31 deletions(-) diff --git a/cmd/kyverno/main.go b/cmd/kyverno/main.go index 172052a873..1dbb7b65fb 100644 --- a/cmd/kyverno/main.go +++ b/cmd/kyverno/main.go @@ -272,6 +272,8 @@ func main() { pInformer.Kyverno().V1().ClusterPolicies(), kubeInformer.Rbac().V1().RoleBindings(), kubeInformer.Rbac().V1().ClusterRoleBindings(), + kubeInformer.Rbac().V1().Roles(), + kubeInformer.Rbac().V1().ClusterRoles(), eventGenerator, pCacheController.Cache, webhookRegistrationClient, diff --git a/pkg/userinfo/roleRef.go b/pkg/userinfo/roleRef.go index 93517dd1c9..ae21fd32fb 100644 --- a/pkg/userinfo/roleRef.go +++ b/pkg/userinfo/roleRef.go @@ -20,6 +20,8 @@ const ( SaPrefix = "system:serviceaccount:" ) +var defaultSuffixs = []string{"system:","kyverno:"} + //GetRoleRef gets the list of roles and cluster roles for the incoming api-request func GetRoleRef(rbLister rbaclister.RoleBindingLister, crbLister rbaclister.ClusterRoleBindingLister, request *v1beta1.AdmissionRequest) (roles []string, clusterRoles []string, err error) { keys := append(request.UserInfo.Groups, request.UserInfo.Username) @@ -127,3 +129,51 @@ func matchUserOrGroup(subject rbacv1.Subject, userInfo authenticationv1.UserInfo return false } + + +//IsRoleAuthorize is role authorize or not +func IsRoleAuthorize(rbLister rbaclister.RoleBindingLister, crbLister rbaclister.ClusterRoleBindingLister,rLister rbaclister.RoleLister, crLister rbaclister.ClusterRoleLister, request *v1beta1.AdmissionRequest) (bool,error) { + + if strings.Contains(request.UserInfo.Username, SaPrefix) { + roles,clusterRoles,err := GetRoleRef(rbLister,crbLister,request) + if err != nil { + return false, err + } + for _,e := range clusterRoles { + role,err := crLister.Get(e); + if err != nil { + return false, err + } + labels := role.GetLabels() + if labels["kubernetes.io/bootstrapping"] == "rbac-defaults" { + return true,nil + } + } + for _,e := range roles { + roleData := strings.Split(e, ":") + role, err := rLister.Roles(roleData[0]).Get(roleData[1]); + if err != nil { + return false, err + } + labels := role.GetLabels() + if labels["kubernetes.io/bootstrapping"] == "rbac-defaults" { + return true, nil + } + } + } else { + // User or Group + var matchedRoles []bool + for _,e := range request.UserInfo.Groups { + for _,defaultSuffix := range defaultSuffixs { + if strings.Contains(e,defaultSuffix) { + matchedRoles = append(matchedRoles, true) + } + } + } + if len(matchedRoles) == len(request.UserInfo.Groups) { + return true,nil + } + } + + return false,nil +} \ No newline at end of file diff --git a/pkg/webhooks/server.go b/pkg/webhooks/server.go index 5e863bf587..123107fca7 100644 --- a/pkg/webhooks/server.go +++ b/pkg/webhooks/server.go @@ -8,6 +8,7 @@ import ( "fmt" "io/ioutil" apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "net/http" "time" @@ -54,6 +55,12 @@ type WebhookServer struct { // list/get role binding resource rbLister rbaclister.RoleBindingLister + // list/get role binding resource + rLister rbaclister.RoleLister + + // list/get role binding resource + crLister rbaclister.ClusterRoleLister + // return true if role bining store has synced atleast once rbSynced cache.InformerSynced @@ -106,6 +113,8 @@ func NewWebhookServer( pInformer kyvernoinformer.ClusterPolicyInformer, rbInformer rbacinformer.RoleBindingInformer, crbInformer rbacinformer.ClusterRoleBindingInformer, + rInformer rbacinformer.RoleInformer, + crInformer rbacinformer.ClusterRoleInformer, eventGen event.Interface, pCache policycache.Interface, webhookRegistrationClient *webhookconfig.WebhookRegistrationClient, @@ -137,8 +146,10 @@ func NewWebhookServer( pLister: pInformer.Lister(), pSynced: pInformer.Informer().HasSynced, rbLister: rbInformer.Lister(), + rLister: rInformer.Lister(), rbSynced: rbInformer.Informer().HasSynced, crbLister: crbInformer.Lister(), + crLister: crInformer.Lister(), crbSynced: crbInformer.Informer().HasSynced, eventGen: eventGen, pCache: pCache, @@ -366,27 +377,25 @@ 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) + logger.V(4).Info("DEBUG","request",request) + 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 request.Operation == v1beta1.Delete || request.Operation == v1beta1.Update { - // convert RAW to unstructured - resource, err := enginutils.ConvertToUnstructured(request.Object.Raw) - 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(), - }, + 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 err != nil { + //TODO: skip applying the admission control ? + logger.Error(err, "failed to convert RAR resource to unstructured format") - 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{ @@ -395,22 +404,35 @@ func (ws *WebhookServer) resourceValidation(request *v1beta1.AdmissionRequest) * }, } } - } - 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", - }, + + 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{ @@ -437,7 +459,6 @@ 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)