From 1ba891611241cfa2a742f84f2ab30afcb9bc35b9 Mon Sep 17 00:00:00 2001 From: Craig Trought Date: Sun, 22 Jan 2023 16:43:47 -0500 Subject: [PATCH] feat: optional flags to emit events in involved object namespace Signed-off-by: Craig Trought --- Makefile | 8 +++ cmd/build/helmify/kustomize-for-helm.yaml | 2 + cmd/build/helmify/static/README.md | 6 +- cmd/build/helmify/static/values.yaml | 2 + config/rbac/role.yaml | 7 +++ manifest_staging/charts/gatekeeper/README.md | 6 +- .../gatekeeper-audit-deployment.yaml | 1 + ...ekeeper-controller-manager-deployment.yaml | 1 + .../gatekeeper-manager-role-clusterrole.yaml | 7 +++ .../charts/gatekeeper/values.yaml | 2 + manifest_staging/deploy/gatekeeper.yaml | 7 +++ pkg/audit/manager.go | 49 ++++++++++------ pkg/audit/manager_test.go | 45 +++++++-------- pkg/controller/config/config_controller.go | 1 + pkg/webhook/common.go | 3 +- pkg/webhook/policy.go | 56 ++++++++++--------- website/docs/customize-startup.md | 10 +++- 17 files changed, 138 insertions(+), 75 deletions(-) diff --git a/Makefile b/Makefile index ca2010e7f7d..9345fe979e7 100644 --- a/Makefile +++ b/Makefile @@ -67,6 +67,7 @@ MANAGER_IMAGE_PATCH := "apiVersion: apps/v1\ \n - --port=8443\ \n - --logtostderr\ \n - --emit-admission-events\ +\n - --admission-events-involved-namespace\ \n - --exempt-namespace=${GATEKEEPER_NAMESPACE}\ \n - --operation=webhook\ \n - --operation=mutation-webhook\ @@ -87,6 +88,7 @@ MANAGER_IMAGE_PATCH := "apiVersion: apps/v1\ \n name: manager\ \n args:\ \n - --emit-audit-events\ +\n - --audit-events-involved-namespace\ \n - --operation=audit\ \n - --operation=status\ \n - --operation=mutation-status\ @@ -190,6 +192,8 @@ e2e-helm-deploy: e2e-helm-install --set postInstall.probeWebhook.enabled=true \ --set emitAdmissionEvents=true \ --set emitAuditEvents=true \ + --set admissionEventsInvolvedNamespace=true \ + --set auditEventsInvolvedNamespace=true \ --set disabledBuiltins={http.send} \ --set logMutations=true \ --set mutationAnnotations=true;\ @@ -201,6 +205,8 @@ e2e-helm-upgrade-init: e2e-helm-install --debug --wait \ --set emitAdmissionEvents=true \ --set emitAuditEvents=true \ + --set admissionEventsInvolvedNamespace=true \ + --set auditEventsInvolvedNamespace=true \ --set postInstall.labelNamespace.enabled=true \ --set postInstall.probeWebhook.enabled=true \ --set disabledBuiltins={http.send} \ @@ -222,6 +228,8 @@ e2e-helm-upgrade: --set postInstall.probeWebhook.enabled=true \ --set emitAdmissionEvents=true \ --set emitAuditEvents=true \ + --set admissionEventsInvolvedNamespace=true \ + --set auditEventsInvolvedNamespace=true \ --set disabledBuiltins={http.send} \ --set logMutations=true \ --set mutationAnnotations=true;\ diff --git a/cmd/build/helmify/kustomize-for-helm.yaml b/cmd/build/helmify/kustomize-for-helm.yaml index 25ae92cf597..da09868d666 100644 --- a/cmd/build/helmify/kustomize-for-helm.yaml +++ b/cmd/build/helmify/kustomize-for-helm.yaml @@ -76,6 +76,7 @@ spec: - --logtostderr - --log-denies={{ .Values.logDenies }} - --emit-admission-events={{ .Values.emitAdmissionEvents }} + - --admission-events-involved-namespace={{ .Values.admissionEventsInvolvedNamespace }} - --log-level={{ (.Values.controllerManager.logLevel | empty | not) | ternary .Values.controllerManager.logLevel .Values.logLevel }} - --exempt-namespace={{ .Release.Namespace }} - --operation=webhook @@ -156,6 +157,7 @@ spec: - --audit-chunk-size={{ .Values.auditChunkSize }} - --audit-match-kind-only={{ .Values.auditMatchKindOnly }} - --emit-audit-events={{ .Values.emitAuditEvents }} + - --audit-events-involved-namespace={{ .Values.auditEventsInvolvedNamespace }} - --operation=audit - --operation=status - HELMSUBST_MUTATION_STATUS_ENABLED_ARG diff --git a/cmd/build/helmify/static/README.md b/cmd/build/helmify/static/README.md index 6e13a55b341..8b3ab950456 100644 --- a/cmd/build/helmify/static/README.md +++ b/cmd/build/helmify/static/README.md @@ -143,8 +143,10 @@ _See [Exempting Namespaces](https://open-policy-agent.github.io/gatekeeper/websi | mutatingWebhookObjectSelector | The label selector to further refine which namespaced resources will be selected by the webhook. Please note that an exemption label means users can circumvent Gatekeeper's mutation webhook unless measures are taken to control how exemption labels can be set. | `{}` | | mutatingWebhookTimeoutSeconds | The timeout for the mutating webhook in seconds | `3` | | mutatingWebhookCustomRules | Custom rules for selecting which API resources trigger the webhook. NOTE: If you change this, ensure all your constraints are still being enforced. | `{}` | -| emitAdmissionEvents | Emit K8s events in gatekeeper namespace for admission violations (alpha feature) | `false` | -| emitAuditEvents | Emit K8s events in gatekeeper namespace for audit violations (alpha feature) | `false` | +| emitAdmissionEvents | Emit K8s events in configurable namespace for admission violations (alpha feature) | `false` | +| emitAuditEvents | Emit K8s events in configurable namespace for audit violations (alpha feature) | `false` | +| auditEventsInvolvedNamespace | Emit admission events for each violation in the involved objects namespace, the default (false) generates events in the namespace gatekeeper is installed in | `false` | +| admissionEventsInvolvedNamespace | Emit admission events for each violation in the involved objects namespace, the default (false) generates events in the namespace gatekeeper is installed in | `false` | | logDenies | Log detailed info on each deny | `false` | | logLevel | Minimum log level | `INFO` | | image.pullPolicy | The image pull policy | `IfNotPresent` | diff --git a/cmd/build/helmify/static/values.yaml b/cmd/build/helmify/static/values.yaml index 364f5ed4794..0b0b34a1339 100644 --- a/cmd/build/helmify/static/values.yaml +++ b/cmd/build/helmify/static/values.yaml @@ -32,6 +32,8 @@ logDenies: false logMutations: false emitAdmissionEvents: false emitAuditEvents: false +admissionEventsInvolvedNamespace: false +auditEventsInvolvedNamespace: false resourceQuota: true postUpgrade: labelNamespace: diff --git a/config/rbac/role.yaml b/config/rbac/role.yaml index a7a893f5933..b81a0171252 100644 --- a/config/rbac/role.yaml +++ b/config/rbac/role.yaml @@ -5,6 +5,13 @@ metadata: creationTimestamp: null name: manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - '*' resources: diff --git a/manifest_staging/charts/gatekeeper/README.md b/manifest_staging/charts/gatekeeper/README.md index 6e13a55b341..8b3ab950456 100644 --- a/manifest_staging/charts/gatekeeper/README.md +++ b/manifest_staging/charts/gatekeeper/README.md @@ -143,8 +143,10 @@ _See [Exempting Namespaces](https://open-policy-agent.github.io/gatekeeper/websi | mutatingWebhookObjectSelector | The label selector to further refine which namespaced resources will be selected by the webhook. Please note that an exemption label means users can circumvent Gatekeeper's mutation webhook unless measures are taken to control how exemption labels can be set. | `{}` | | mutatingWebhookTimeoutSeconds | The timeout for the mutating webhook in seconds | `3` | | mutatingWebhookCustomRules | Custom rules for selecting which API resources trigger the webhook. NOTE: If you change this, ensure all your constraints are still being enforced. | `{}` | -| emitAdmissionEvents | Emit K8s events in gatekeeper namespace for admission violations (alpha feature) | `false` | -| emitAuditEvents | Emit K8s events in gatekeeper namespace for audit violations (alpha feature) | `false` | +| emitAdmissionEvents | Emit K8s events in configurable namespace for admission violations (alpha feature) | `false` | +| emitAuditEvents | Emit K8s events in configurable namespace for audit violations (alpha feature) | `false` | +| auditEventsInvolvedNamespace | Emit admission events for each violation in the involved objects namespace, the default (false) generates events in the namespace gatekeeper is installed in | `false` | +| admissionEventsInvolvedNamespace | Emit admission events for each violation in the involved objects namespace, the default (false) generates events in the namespace gatekeeper is installed in | `false` | | logDenies | Log detailed info on each deny | `false` | | logLevel | Minimum log level | `INFO` | | image.pullPolicy | The image pull policy | `IfNotPresent` | diff --git a/manifest_staging/charts/gatekeeper/templates/gatekeeper-audit-deployment.yaml b/manifest_staging/charts/gatekeeper/templates/gatekeeper-audit-deployment.yaml index 6088a432a3a..a89455fc29b 100644 --- a/manifest_staging/charts/gatekeeper/templates/gatekeeper-audit-deployment.yaml +++ b/manifest_staging/charts/gatekeeper/templates/gatekeeper-audit-deployment.yaml @@ -55,6 +55,7 @@ spec: - --audit-chunk-size={{ .Values.auditChunkSize }} - --audit-match-kind-only={{ .Values.auditMatchKindOnly }} - --emit-audit-events={{ .Values.emitAuditEvents }} + - --audit-events-involved-namespace={{ .Values.auditEventsInvolvedNamespace }} - --operation=audit - --operation=status {{ if not .Values.disableMutation}}- --operation=mutation-status{{- end }} diff --git a/manifest_staging/charts/gatekeeper/templates/gatekeeper-controller-manager-deployment.yaml b/manifest_staging/charts/gatekeeper/templates/gatekeeper-controller-manager-deployment.yaml index b1e5ea7f737..e645fe44d61 100644 --- a/manifest_staging/charts/gatekeeper/templates/gatekeeper-controller-manager-deployment.yaml +++ b/manifest_staging/charts/gatekeeper/templates/gatekeeper-controller-manager-deployment.yaml @@ -54,6 +54,7 @@ spec: - --logtostderr - --log-denies={{ .Values.logDenies }} - --emit-admission-events={{ .Values.emitAdmissionEvents }} + - --admission-events-involved-namespace={{ .Values.admissionEventsInvolvedNamespace }} - --log-level={{ (.Values.controllerManager.logLevel | empty | not) | ternary .Values.controllerManager.logLevel .Values.logLevel }} - --exempt-namespace={{ .Release.Namespace }} - --operation=webhook diff --git a/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml b/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml index 8b32f96014b..a36d6eb97a5 100644 --- a/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml +++ b/manifest_staging/charts/gatekeeper/templates/gatekeeper-manager-role-clusterrole.yaml @@ -11,6 +11,13 @@ metadata: release: '{{ .Release.Name }}' name: gatekeeper-manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - '*' resources: diff --git a/manifest_staging/charts/gatekeeper/values.yaml b/manifest_staging/charts/gatekeeper/values.yaml index 364f5ed4794..0b0b34a1339 100644 --- a/manifest_staging/charts/gatekeeper/values.yaml +++ b/manifest_staging/charts/gatekeeper/values.yaml @@ -32,6 +32,8 @@ logDenies: false logMutations: false emitAdmissionEvents: false emitAuditEvents: false +admissionEventsInvolvedNamespace: false +auditEventsInvolvedNamespace: false resourceQuota: true postUpgrade: labelNamespace: diff --git a/manifest_staging/deploy/gatekeeper.yaml b/manifest_staging/deploy/gatekeeper.yaml index 5b7d55b4d62..d3d95c5be61 100644 --- a/manifest_staging/deploy/gatekeeper.yaml +++ b/manifest_staging/deploy/gatekeeper.yaml @@ -2930,6 +2930,13 @@ metadata: gatekeeper.sh/system: "yes" name: gatekeeper-manager-role rules: +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch - apiGroups: - '*' resources: diff --git a/pkg/audit/manager.go b/pkg/audit/manager.go index 0b3d8d9dbfa..0ae9ac55c96 100644 --- a/pkg/audit/manager.go +++ b/pkg/audit/manager.go @@ -53,14 +53,15 @@ const ( ) var ( - auditInterval = flag.Uint("audit-interval", defaultAuditInterval, "interval to run audit in seconds. defaulted to 60 secs if unspecified, 0 to disable") - constraintViolationsLimit = flag.Uint("constraint-violations-limit", defaultConstraintViolationsLimit, "limit of number of violations per constraint. defaulted to 20 violations if unspecified") - auditChunkSize = flag.Uint64("audit-chunk-size", defaultListLimit, "(alpha) Kubernetes API chunking List results when retrieving cluster resources using discovery client. defaulted to 500 if unspecified") - auditFromCache = flag.Bool("audit-from-cache", false, "pull resources from audit cache when auditing") - emitAuditEvents = flag.Bool("emit-audit-events", false, "(alpha) emit Kubernetes events in gatekeeper namespace with detailed info for each violation from an audit") - auditMatchKindOnly = flag.Bool("audit-match-kind-only", false, "only use kinds specified in all constraints for auditing cluster resources. if kind is not specified in any of the constraints, it will audit all resources (same as setting this flag to false)") - apiCacheDir = flag.String("api-cache-dir", defaultAPICacheDir, "The directory where audit from api server cache are stored, defaults to /tmp/audit") - emptyAuditResults []updateListEntry + auditInterval = flag.Uint("audit-interval", defaultAuditInterval, "interval to run audit in seconds. defaulted to 60 secs if unspecified, 0 to disable") + constraintViolationsLimit = flag.Uint("constraint-violations-limit", defaultConstraintViolationsLimit, "limit of number of violations per constraint. defaulted to 20 violations if unspecified") + auditChunkSize = flag.Uint64("audit-chunk-size", defaultListLimit, "(alpha) Kubernetes API chunking List results when retrieving cluster resources using discovery client. defaulted to 500 if unspecified") + auditFromCache = flag.Bool("audit-from-cache", false, "pull resources from OPA cache when auditing") + emitAuditEvents = flag.Bool("emit-audit-events", false, "(alpha) emit Kubernetes events with detailed info for each violation from an audit") + auditEventsInvolvedNamespace = flag.Bool("audit-events-involved-namespace", false, "emit admission events for each violation in the involved objects namespace, the default (false) generates events in the namespace gatekeeper is installed in") + auditMatchKindOnly = flag.Bool("audit-match-kind-only", false, "only use kinds specified in all constraints for auditing cluster resources. if kind is not specified in any of the constraints, it will audit all resources (same as setting this flag to false)") + apiCacheDir = flag.String("api-cache-dir", defaultAPICacheDir, "The directory where audit from api server cache are stored, defaults to /tmp/audit") + emptyAuditResults []updateListEntry ) // Manager allows us to audit resources periodically. @@ -737,6 +738,8 @@ func (am *Manager) addAuditResponsesToUpdateLists( gvk := r.obj.GroupVersionKind() namespace := r.obj.GetNamespace() name := r.obj.GetName() + uid := r.obj.GetUID() + rv := r.obj.GetResourceVersion() ea := util.EnforcementAction(r.EnforcementAction) // append audit results only if it is below violations limit @@ -760,7 +763,7 @@ func (am *Manager) addAuditResponsesToUpdateLists( totalViolationsPerEnforcementAction[ea]++ logViolation(am.log, r.Constraint, ea, gvk, namespace, name, r.Msg, details, r.obj.GetLabels()) if *emitAuditEvents { - emitEvent(r.Constraint, timestamp, ea, gvk, namespace, name, r.Msg, am.gkNamespace, am.eventRecorder) + emitEvent(r.Constraint, timestamp, ea, gvk, namespace, name, rv, r.Msg, am.gkNamespace, uid, am.eventRecorder) } } return nil @@ -1042,7 +1045,7 @@ func logViolation(l logr.Logger, } func emitEvent(constraint *unstructured.Unstructured, - timestamp string, enforcementAction util.EnforcementAction, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, message, gkNamespace string, + timestamp string, enforcementAction util.EnforcementAction, resourceGroupVersionKind schema.GroupVersionKind, rnamespace, rname, rrv, message, gkNamespace string, ruid types.UID, eventRecorder record.EventRecorder, ) { annotations := map[string]string{ @@ -1061,19 +1064,33 @@ func emitEvent(constraint *unstructured.Unstructured, logging.ResourceNamespace: rnamespace, logging.ResourceName: rname, } + reason := "AuditViolation" - ref := getViolationRef(gkNamespace, resourceGroupVersionKind.Kind, rname, rnamespace, constraint.GetKind(), constraint.GetName(), constraint.GetNamespace()) + enamespace := gkNamespace + if *auditEventsInvolvedNamespace && len(rnamespace) > 0 { + enamespace = rnamespace + } + + ref := getViolationRef(enamespace, resourceGroupVersionKind.Kind, rname, rrv, ruid) - eventRecorder.AnnotatedEventf(ref, annotations, corev1.EventTypeWarning, reason, "Timestamp: %s, Resource Namespace: %s, Constraint: %s, Message: %s", timestamp, rnamespace, constraint.GetName(), message) + if *auditEventsInvolvedNamespace || len(rnamespace) == 0 { + eventRecorder.AnnotatedEventf(ref, annotations, corev1.EventTypeWarning, reason, "Constraint: %s, Message: %s", constraint.GetName(), message) + } else { + eventRecorder.AnnotatedEventf(ref, annotations, corev1.EventTypeWarning, reason, "Resource Namespace: %s, Constraint: %s, Message: %s", rnamespace, constraint.GetName(), message) + } } -func getViolationRef(gkNamespace, rkind, rname, rnamespace, ckind, cname, cnamespace string) *corev1.ObjectReference { - return &corev1.ObjectReference{ +func getViolationRef(enamespace, rkind, rname, rrv string, ruid types.UID) *corev1.ObjectReference { + ref := &corev1.ObjectReference{ Kind: rkind, Name: rname, - UID: types.UID(rkind + "/" + rnamespace + "/" + rname + "/" + ckind + "/" + cnamespace + "/" + cname), - Namespace: gkNamespace, + Namespace: enamespace, + } + if len(ruid) > 0 && len(rrv) > 0 { + ref.UID = ruid + ref.ResourceVersion = rrv } + return ref } // mergeErrors concatenates errs into a single error. None of the original errors diff --git a/pkg/audit/manager_test.go b/pkg/audit/manager_test.go index 38733e7d1d5..56fa0dd82a4 100644 --- a/pkg/audit/manager_test.go +++ b/pkg/audit/manager_test.go @@ -9,6 +9,7 @@ import ( corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + "k8s.io/apimachinery/pkg/types" ) func Test_newNSCache(t *testing.T) { @@ -183,13 +184,11 @@ func Test_nsMapFromObjs(t *testing.T) { func Test_getViolationRef(t *testing.T) { type args struct { - gkNamespace string + enamespace string rkind string rname string - rnamespace string - ckind string - cname string - cnamespace string + rrv string + ruid types.UID } tests := []struct { name string @@ -199,43 +198,41 @@ func Test_getViolationRef(t *testing.T) { { name: "Test case 1", args: args{ - gkNamespace: "default", rkind: "Pod", rname: "my-pod", - rnamespace: "default", - ckind: "LimitRange", - cname: "my-limit-range", - cnamespace: "default", + enamespace: "default", + rrv: "123456", + ruid: "abcde-123456", }, want: &corev1.ObjectReference{ - Kind: "Pod", - Name: "my-pod", - UID: "Pod/default/my-pod/LimitRange/default/my-limit-range", - Namespace: "default", + Kind: "Pod", + Name: "my-pod", + Namespace: "default", + ResourceVersion: "123456", + UID: "abcde-123456", }, }, { name: "Test case 2", args: args{ - gkNamespace: "kube-system", rkind: "Service", + enamespace: "kube-system", rname: "my-service", - rnamespace: "default", - ckind: "PodSecurityPolicy", - cname: "my-pod-security-policy", - cnamespace: "kube-system", + rrv: "123456", + ruid: "abcde-123456", }, want: &corev1.ObjectReference{ - Kind: "Service", - Name: "my-service", - UID: "Service/default/my-service/PodSecurityPolicy/kube-system/my-pod-security-policy", - Namespace: "kube-system", + Kind: "Service", + Name: "my-service", + Namespace: "kube-system", + ResourceVersion: "123456", + UID: "abcde-123456", }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := getViolationRef(tt.args.gkNamespace, tt.args.rkind, tt.args.rname, tt.args.rnamespace, tt.args.ckind, tt.args.cname, tt.args.cnamespace); !reflect.DeepEqual(got, tt.want) { + if got := getViolationRef(tt.args.enamespace, tt.args.rkind, tt.args.rname, tt.args.rrv, tt.args.ruid); !reflect.DeepEqual(got, tt.want) { t.Errorf("getViolationRef() = %v, want %v", got, tt.want) } }) diff --git a/pkg/controller/config/config_controller.go b/pkg/controller/config/config_controller.go index 81baf0c6876..b896a93dcb0 100644 --- a/pkg/controller/config/config_controller.go +++ b/pkg/controller/config/config_controller.go @@ -193,6 +193,7 @@ type ReconcileConfig struct { // +kubebuilder:rbac:groups=policy,resources=podsecuritypolicies,resourceNames=gatekeeper-admin,verbs=use // +kubebuilder:rbac:groups=config.gatekeeper.sh,resources=configs,verbs=get;list;watch;create;update;patch;delete // +kubebuilder:rbac:groups=config.gatekeeper.sh,resources=configs/status,verbs=get;update;patch +// +kubebuilder:rbac:groups="",resources=events,verbs=create;patch; // Reconcile reads that state of the cluster for a Config object and makes changes based on the state read // and what is in the Config.Spec diff --git a/pkg/webhook/common.go b/pkg/webhook/common.go index 63f255b1519..e1c8def5850 100644 --- a/pkg/webhook/common.go +++ b/pkg/webhook/common.go @@ -58,7 +58,8 @@ var ( deserializer = codecs.UniversalDeserializer() disableEnforcementActionValidation = flag.Bool("disable-enforcementaction-validation", false, "disable validation of the enforcementAction field of a constraint") logDenies = flag.Bool("log-denies", false, "log detailed info on each deny") - emitAdmissionEvents = flag.Bool("emit-admission-events", false, "(alpha) emit Kubernetes events in gatekeeper namespace for each admission violation") + emitAdmissionEvents = flag.Bool("emit-admission-events", false, "(alpha) emit Kubernetes events for each admission violation") + admissionEventsInvolvedNamespace = flag.Bool("admission-events-involved-namespace", false, "emit admission events for each violation in the involved objects namespace, the default (false) generates events in the namespace gatekeeper is installed in") tlsMinVersion = flag.String("tls-min-version", "1.3", "minimum version of TLS supported") serviceaccount = fmt.Sprintf("system:serviceaccount:%s:%s", util.GetNamespace(), serviceAccountName) clientCAName = flag.String("client-ca-name", "", "name of the certificate authority bundle to authenticate the Kubernetes API server requests against") diff --git a/pkg/webhook/policy.go b/pkg/webhook/policy.go index 95663e692e1..7a905fda1c8 100644 --- a/pkg/webhook/policy.go +++ b/pkg/webhook/policy.go @@ -229,14 +229,17 @@ func (h *validationHandler) Handle(ctx context.Context, req admission.Request) a func (h *validationHandler) getValidationMessages(res []*rtypes.Result, req *admission.Request) ([]string, []string) { var denyMsgs, warnMsgs []string var resourceName string + obj := &unstructured.Unstructured{} + if len(res) > 0 && (*logDenies || *emitAdmissionEvents) { resourceName = req.AdmissionRequest.Name - if len(resourceName) == 0 && req.AdmissionRequest.Object.Raw != nil { - // On a CREATE operation, the client may omit name and - // rely on the server to generate the name. - obj := &unstructured.Unstructured{} + if req.AdmissionRequest.Object.Raw != nil { if _, _, err := deserializer.Decode(req.AdmissionRequest.Object.Raw, nil, obj); err == nil { - resourceName = obj.GetName() + // On a CREATE operation, the client may omit name and + // rely on the server to generate the name. + if len(resourceName) == 0 { + resourceName = obj.GetName() + } } } } @@ -289,24 +292,19 @@ func (h *validationHandler) getValidationMessages(res []*rtypes.Result, req *adm eventMsg = "Admission webhook \"validation.gatekeeper.sh\" denied request" reason = "FailedAdmission" } - ref := getViolationRef( - h.gkNamespace, - req.AdmissionRequest.Kind.Kind, - resourceName, - req.AdmissionRequest.Namespace, - r.Constraint.GetKind(), - r.Constraint.GetName(), - r.Constraint.GetNamespace()) - h.eventRecorder.AnnotatedEventf( - ref, - annotations, - corev1.EventTypeWarning, - reason, - "%s, Resource Namespace: %s, Constraint: %s, Message: %s", - eventMsg, - req.AdmissionRequest.Namespace, - r.Constraint.GetName(), - r.Msg) + + enamespace := h.gkNamespace + if *admissionEventsInvolvedNamespace && len(req.AdmissionRequest.Namespace) > 0 { + enamespace = req.AdmissionRequest.Namespace + } + + ref := getViolationRef(enamespace, req.AdmissionRequest.Kind.Kind, resourceName, obj.GetResourceVersion(), obj.GetUID()) + + if *admissionEventsInvolvedNamespace || len(req.AdmissionRequest.Namespace) == 0 { + h.eventRecorder.AnnotatedEventf(ref, annotations, corev1.EventTypeWarning, reason, "%s, Constraint: %s, Message: %s", eventMsg, r.Constraint.GetName(), r.Msg) + } else { + h.eventRecorder.AnnotatedEventf(ref, annotations, corev1.EventTypeWarning, reason, "%s, Resource Namespace: %s, Constraint: %s, Message: %s", eventMsg, req.AdmissionRequest.Namespace, r.Constraint.GetName(), r.Msg) + } } if r.EnforcementAction == string(util.Deny) { @@ -604,13 +602,17 @@ func createReviewForResultant(obj *unstructured.Unstructured, ns *corev1.Namespa } } -func getViolationRef(gkNamespace, rkind, rname, rnamespace, ckind, cname, cnamespace string) *corev1.ObjectReference { - return &corev1.ObjectReference{ +func getViolationRef(enamespace, rkind, rname, rrv string, ruid types.UID) *corev1.ObjectReference { + ref := &corev1.ObjectReference{ Kind: rkind, Name: rname, - UID: types.UID(rkind + "/" + rnamespace + "/" + rname + "/" + ckind + "/" + cnamespace + "/" + cname), - Namespace: gkNamespace, + Namespace: enamespace, + } + if len(ruid) > 0 && len(rrv) > 0 { + ref.UID = ruid + ref.ResourceVersion = rrv } + return ref } func AppendValidationWebhookIfEnabled(webhooks []rotator.WebhookInfo) []rotator.WebhookInfo { diff --git a/website/docs/customize-startup.md b/website/docs/customize-startup.md index 9fe4fad10b9..138e764c1e7 100644 --- a/website/docs/customize-startup.md +++ b/website/docs/customize-startup.md @@ -23,11 +23,15 @@ The `--disable-opa-builtin` flag disables specific [OPA built-ins functions](htt ## [Alpha] Emit admission and audit events -The `--emit-admission-events` flag enables the emission of all admission violations as Kubernetes events in the Gatekeeper namespace. This flag is in alpha stage and it is set to `false` by default. +The `--emit-admission-events` flag enables the emission of all admission violations as Kubernetes events. This flag is in alpha stage and it is set to `false` by default. -The `--emit-audit-events` flag enables the emission of all audit violation as Kubernetes events in the Gatekeeper namespace. This flag is in alpha stage and it is set to `false` by default. +The `--emit-audit-events` flag enables the emission of all audit violation as Kubernetes events. This flag is in alpha stage and it is set to `false` by default. -There are three types of events that are emitted by Gatekeeper when the above flags are enabled: +The `--admission-events-involved-namespace` flag controls which namespace admission events will be created in. When set to `true` admission events will be created in the involved objects namespace violating the constraint, if the object has no namespace (ie. cluster scoped resources) they will be created in the namespace gatekeeper is installed in. Setting to `false` will cause all admission events to be created in the gatekeeper namespace. + +The `--audit-events-involved-namespace` flag controls which namespace audit events will be created in. When set to `true` audit events will be created in the involved objects namespace violating the constraint, if the object has no namespace (ie. cluster scoped resources) they will be created in the namespace gatekeeper is installed in. Setting to `false` will cause all audit events to be created in the gatekeeper namespace. + +There are four types of events that are emitted by Gatekeeper when the emit event flags are enabled: | Event | Description | | ------------------ | ----------------------------------------------------------------------- |