1
0
Fork 0
mirror of https://github.com/kyverno/kyverno.git synced 2024-12-14 11:57:48 +00:00

Merge pull request #977 from evalsocket/fix-976

Generate Policy from data is not behaving as expected
This commit is contained in:
Jim Bugwadia 2020-07-10 19:08:10 -07:00 committed by GitHub
commit 00ca0fd152
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 186 additions and 25 deletions

View file

@ -281,6 +281,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,

View file

@ -6,7 +6,7 @@ The ```generate``` rule can used to create additional resources when a new resou
The `generate` rule supports `match` and `exclude` blocks, like other rules. Hence, the trigger for applying this rule can be the creation of any resource and its possible to match or exclude API requests based on subjects, roles, etc.
The generate rule triggers during a API CREATE operation and does not support [background processing](/documentation/writing-policies-background.md). To keep resources synchronized across changes you can use `synchronize : true`.
The generate rule is triggered during a API CREATE operation. To keep resources synchronized across changes you can use the `synchronize` property. When `synchronize` is set to `true` the generated resource is kept in-sync with the source resource (which can be defined as part of the policy or may be an existing resource), and generated resources cannot be modified by users. If `synchronize` is set to `false` then users can update or delete the generated resource directly.
This policy sets the Zookeeper and Kafka connection strings for all namespaces.

View file

@ -41,13 +41,25 @@ func (c *Controller) applyGenerate(resource unstructured.Unstructured, gr kyvern
logger := c.log.WithValues("name", gr.Name, "policy", gr.Spec.Policy, "kind", gr.Spec.Resource.Kind, "namespace", gr.Spec.Resource.Namespace, "name", gr.Spec.Resource.Name)
// Get the list of rules to be applied
// get policy
policy, err := c.pLister.Get(gr.Spec.Policy)
if err != nil {
logger.Error(err, "policy not found")
return nil, nil
}
// build context
ctx := context.NewContext()
policy, err := c.pLister.Get(gr.Spec.Policy)
if err != nil {
if apierrors.IsNotFound(err) {
labels := resource.GetLabels()
if labels["app.kubernetes.io/synchronize"] == "enable" {
if err := c.client.DeleteResource(gr.Spec.Resource.Kind, gr.Spec.Resource.Namespace, gr.Spec.Resource.Name, false); err != nil {
logger.V(4).Info("Generated resource is deleted")
return nil, err
}
}
return nil, nil
}
logger.Error(err, "error in getting policy")
return nil, err
}
resourceRaw, err := resource.MarshalJSON()
if err != nil {
logger.Error(err, "failed to marshal resource")
@ -258,6 +270,16 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
// - kyverno.io/generated-by: kind/namespace/name (trigger resource)
manageLabels(newResource, resource)
logger := log.WithValues("genKind", genKind, "genNamespace", genNamespace, "genName", genName)
// Add Synchronize label
label := newResource.GetLabels()
if rule.Generation.Synchronize {
label["app.kubernetes.io/synchronize"] = "enable"
} else {
label["app.kubernetes.io/synchronize"] = "disable"
}
newResource.SetLabels(label)
if mode == Create {
// Reset resource version
newResource.SetResourceVersion("")
@ -271,19 +293,27 @@ func applyRule(log logr.Logger, client *dclient.Client, rule kyverno.Rule, resou
logger.V(4).Info("created new resource")
} else if mode == Update {
if rule.Generation.Synchronize {
logger.V(4).Info("updating existing resource")
// Update the resource
_, err := client.UpdateResource(genKind, genNamespace, newResource, false)
if err != nil {
// Failed to update resource
return noGenResource, err
}
logger.V(4).Info("updated new resource")
label := newResource.GetLabels()
if label != nil {
if label["app.kubernetes.io/synchronize"] == "enable" {
logger.V(4).Info("updating existing resource")
// Update the resource
_, err := client.UpdateResource(genKind, genNamespace, newResource, false)
if err != nil {
logger.Error(err, "updating existing resource")
// Failed to update resource
return noGenResource, err
}
logger.V(4).Info("updated new resource")
} else {
logger.V(4).Info("Synchronize resource is disabled")
}
} else {
logger.V(4).Info("Synchronize resource is disabled")
}
}
return newGenResource, nil

View file

@ -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,63 @@ 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 {
if strings.Contains(e, "kyverno:") {
return true, nil
}
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 !strings.Contains(e, "kyverno:") {
if labels["kubernetes.io/bootstrapping"] == "rbac-defaults" {
return true, nil
}
}
}
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
}
}
}
if len(matchedRoles) == len(request.UserInfo.Groups) {
return true, nil
}
return false, nil
}

View file

@ -14,12 +14,15 @@ import (
"github.com/julienschmidt/httprouter"
v1 "github.com/nirmata/kyverno/pkg/api/kyverno/v1"
"github.com/nirmata/kyverno/pkg/checker"
"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"
"github.com/nirmata/kyverno/pkg/config"
client "github.com/nirmata/kyverno/pkg/dclient"
context2 "github.com/nirmata/kyverno/pkg/engine/context"
enginutils "github.com/nirmata/kyverno/pkg/engine/utils"
"github.com/nirmata/kyverno/pkg/event"
"github.com/nirmata/kyverno/pkg/openapi"
"github.com/nirmata/kyverno/pkg/policycache"
@ -52,15 +55,27 @@ 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
// 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
@ -107,6 +122,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,
@ -134,14 +151,19 @@ func NewWebhookServer(
tlsConfig.Certificates = []tls.Certificate{pair}
ws := &WebhookServer{
client: client,
kyvernoClient: kyvernoClient,
pLister: pInformer.Lister(),
pSynced: pInformer.Informer().HasSynced,
rbLister: rbInformer.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,
@ -239,6 +261,8 @@ func writeResponse(rw http.ResponseWriter, admissionReview *v1beta1.AdmissionRev
func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1beta1.AdmissionResponse {
logger := ws.log.WithName("resourceMutation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
if excludeKyvernoResources(request.Kind.Kind) {
return &v1beta1.AdmissionResponse{
Allowed: true,
@ -248,8 +272,6 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
}
}
logger := ws.log.WithName("resourceMutation").WithValues("uid", request.UID, "kind", request.Kind.Kind, "namespace", request.Namespace, "name", request.Name, "operation", request.Operation)
mutatePolicies := ws.pCache.Get(policycache.Mutate)
validatePolicies := ws.pCache.Get(policycache.ValidateEnforce)
generatePolicies := ws.pCache.Get(policycache.Generate)
@ -269,7 +291,6 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
resource, err := utils.ConvertResource(request.Object.Raw, request.Kind.Group, request.Kind.Version, request.Kind.Kind, request.Namespace)
if err != nil {
logger.Error(err, "failed to convert RAW resource to unstructured format")
return &v1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{
@ -341,6 +362,7 @@ func (ws *WebhookServer) resourceMutation(request *v1beta1.AdmissionRequest) *v1
// Only applied during resource creation and update
// Success -> Generate Request CR created successfully
// Failed -> Failed to create Generate Request CR
if request.Operation == v1beta1.Create || request.Operation == v1beta1.Update {
go ws.HandleGenerate(request.DeepCopy(), generatePolicies, ctx, userRequestInfo)
}
@ -361,6 +383,18 @@ 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)
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 !ws.supportMudateValidate {
logger.Info("mutate and validate rules are not supported prior to Kubernetes 1.14.0")
return &v1beta1.AdmissionResponse{
@ -450,7 +484,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")
}
@ -517,3 +551,36 @@ 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
}
labels := resource.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
}