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:
commit
00ca0fd152
5 changed files with 186 additions and 25 deletions
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue