diff --git a/test/e2e/auth/audit.go b/test/e2e/auth/audit.go index 1d6b8ae859..d6b07adfcd 100644 --- a/test/e2e/auth/audit.go +++ b/test/e2e/auth/audit.go @@ -54,23 +54,609 @@ var ( patch, _ = json.Marshal(jsonpatch.Patch{}) ) -var _ = SIGDescribe("Advanced Audit", func() { +// TODO: Get rid of [DisabledForLargeClusters] when feature request #53455 is ready. +var _ = SIGDescribe("Advanced Audit [DisabledForLargeClusters]", func() { f := framework.NewDefaultFramework("audit") + var namespace string BeforeEach(func() { framework.SkipUnlessProviderIs("gce") + namespace = f.Namespace.Name }) - // TODO: Get rid of [DisabledForLargeClusters] when feature request #53455 is ready. - It("should audit API calls [DisabledForLargeClusters]", func() { - namespace := f.Namespace.Name + It("should audit API calls to create, get, update, patch, delete, list, watch pods.", func() { + pod := &apiv1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "audit-pod", + }, + Spec: apiv1.PodSpec{ + Containers: []apiv1.Container{{ + Name: "pause", + Image: imageutils.GetPauseImageName(), + }}, + }, + } + updatePod := func(pod *apiv1.Pod) {} + f.PodClient().CreateSync(pod) + + _, err := f.PodClient().Get(pod.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "failed to get audit-pod") + + podChan, err := f.PodClient().Watch(watchOptions) + framework.ExpectNoError(err, "failed to create watch for pods") + podChan.Stop() + + f.PodClient().Update(pod.Name, updatePod) + + _, err = f.PodClient().List(metav1.ListOptions{}) + framework.ExpectNoError(err, "failed to list pods") + + _, err = f.PodClient().Patch(pod.Name, types.JSONPatchType, patch) + framework.ExpectNoError(err, "failed to patch pod") + + f.PodClient().DeleteSync(pod.Name, &metav1.DeleteOptions{}, framework.DefaultPodDeletionTimeout) + + expectEvents(f, []utils.AuditEvent{ + { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace), + Verb: "create", + Code: 201, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), + Verb: "get", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace), + Verb: "list", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseStarted, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), + Verb: "update", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), + Verb: "patch", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), + Verb: "delete", + Code: 200, + User: auditTestUser, + Resource: "pods", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, + }) + }) + + It("should audit API calls to create, get, update, patch, delete, list, watch deployments.", func() { + podLabels := map[string]string{"name": "audit-deployment-pod"} + d := framework.NewDeployment("audit-deployment", int32(1), podLabels, "redis", imageutils.GetE2EImage(imageutils.Redis), apps.RecreateDeploymentStrategyType) + + _, err := f.ClientSet.AppsV1().Deployments(namespace).Create(d) + framework.ExpectNoError(err, "failed to create audit-deployment") + + _, err = f.ClientSet.AppsV1().Deployments(namespace).Get(d.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "failed to get audit-deployment") + + deploymentChan, err := f.ClientSet.AppsV1().Deployments(namespace).Watch(watchOptions) + framework.ExpectNoError(err, "failed to create watch for deployments") + deploymentChan.Stop() + + _, err = f.ClientSet.AppsV1().Deployments(namespace).Update(d) + framework.ExpectNoError(err, "failed to update audit-deployment") + + _, err = f.ClientSet.AppsV1().Deployments(namespace).Patch(d.Name, types.JSONPatchType, patch) + framework.ExpectNoError(err, "failed to patch deployment") + + _, err = f.ClientSet.AppsV1().Deployments(namespace).List(metav1.ListOptions{}) + framework.ExpectNoError(err, "failed to create list deployments") + + err = f.ClientSet.AppsV1().Deployments(namespace).Delete("audit-deployment", &metav1.DeleteOptions{}) + framework.ExpectNoError(err, "failed to delete deployments") + + expectEvents(f, []utils.AuditEvent{ + { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments", namespace), + Verb: "create", + Code: 201, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), + Verb: "get", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments", namespace), + Verb: "list", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseStarted, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), + Verb: "update", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), + Verb: "patch", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), + Verb: "delete", + Code: 200, + User: auditTestUser, + Resource: "deployments", + Namespace: namespace, + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, + }) + }) + + It("should audit API calls to create, get, update, patch, delete, list, watch configmaps.", func() { + configMap := &apiv1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "audit-configmap", + }, + Data: map[string]string{ + "map-key": "map-value", + }, + } + + _, err := f.ClientSet.CoreV1().ConfigMaps(namespace).Create(configMap) + framework.ExpectNoError(err, "failed to create audit-configmap") + + _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).Get(configMap.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "failed to get audit-configmap") + + configMapChan, err := f.ClientSet.CoreV1().ConfigMaps(namespace).Watch(watchOptions) + framework.ExpectNoError(err, "failed to create watch for config maps") + configMapChan.Stop() + + _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).Update(configMap) + framework.ExpectNoError(err, "failed to update audit-configmap") + + _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).Patch(configMap.Name, types.JSONPatchType, patch) + framework.ExpectNoError(err, "failed to patch configmap") + + _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).List(metav1.ListOptions{}) + framework.ExpectNoError(err, "failed to list config maps") + + err = f.ClientSet.CoreV1().ConfigMaps(namespace).Delete(configMap.Name, &metav1.DeleteOptions{}) + framework.ExpectNoError(err, "failed to delete audit-configmap") + + expectEvents(f, []utils.AuditEvent{ + { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", namespace), + Verb: "create", + Code: 201, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), + Verb: "get", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", namespace), + Verb: "list", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseStarted, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), + Verb: "update", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), + Verb: "patch", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), + Verb: "delete", + Code: 200, + User: auditTestUser, + Resource: "configmaps", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, + }) + }) + + It("should audit API calls to create, get, update, patch, delete, list, watch secrets.", func() { + secret := &apiv1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: "audit-secret", + }, + Data: map[string][]byte{ + "top-secret": []byte("foo-bar"), + }, + } + _, err := f.ClientSet.CoreV1().Secrets(namespace).Create(secret) + framework.ExpectNoError(err, "failed to create audit-secret") + + _, err = f.ClientSet.CoreV1().Secrets(namespace).Get(secret.Name, metav1.GetOptions{}) + framework.ExpectNoError(err, "failed to get audit-secret") + + secretChan, err := f.ClientSet.CoreV1().Secrets(namespace).Watch(watchOptions) + framework.ExpectNoError(err, "failed to create watch for secrets") + secretChan.Stop() + + _, err = f.ClientSet.CoreV1().Secrets(namespace).Update(secret) + framework.ExpectNoError(err, "failed to update audit-secret") + + _, err = f.ClientSet.CoreV1().Secrets(namespace).Patch(secret.Name, types.JSONPatchType, patch) + framework.ExpectNoError(err, "failed to patch secret") + + _, err = f.ClientSet.CoreV1().Secrets(namespace).List(metav1.ListOptions{}) + framework.ExpectNoError(err, "failed to list secrets") + + err = f.ClientSet.CoreV1().Secrets(namespace).Delete(secret.Name, &metav1.DeleteOptions{}) + framework.ExpectNoError(err, "failed to delete audit-secret") + + expectEvents(f, []utils.AuditEvent{ + { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets", namespace), + Verb: "create", + Code: 201, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), + Verb: "get", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets", namespace), + Verb: "list", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseStarted, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), + Verb: "watch", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), + Verb: "update", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), + Verb: "patch", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), + Verb: "delete", + Code: 200, + User: auditTestUser, + Resource: "secrets", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, + }) + }) + + It("should audit API calls to create and delete custom resource definition.", func() { config, err := framework.LoadConfig() framework.ExpectNoError(err, "failed to load config") apiExtensionClient, err := apiextensionclientset.NewForConfig(config) framework.ExpectNoError(err, "failed to initialize apiExtensionClient") + crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, f.DynamicClient) + framework.ExpectNoError(err, "failed to create custom resource definition") + err = fixtures.DeleteCustomResourceDefinition(crd, apiExtensionClient) + framework.ExpectNoError(err, "failed to delete custom resource definition") + + expectEvents(f, []utils.AuditEvent{ + { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions", + Verb: "create", + Code: 201, + User: auditTestUser, + Resource: "customresourcedefinitions", + RequestObject: true, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/%s/v1beta1/%s", crdNamespace, crdName), + Verb: "create", + Code: 201, + User: auditTestUser, + Resource: crdName, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelRequestResponse, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/%s", crd.Name), + Verb: "delete", + Code: 200, + User: auditTestUser, + Resource: "customresourcedefinitions", + RequestObject: false, + ResponseObject: true, + AuthorizeDecision: "allow", + }, { + Level: auditinternal.LevelMetadata, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/apis/%s/v1beta1/%s/setup-instance", crdNamespace, crdName), + Verb: "delete", + Code: 200, + User: auditTestUser, + Resource: crdName, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", + }, + }) + }) + + // test authorizer annotations, RBAC is required. + It("should audit API calls to get a pod with unauthorized user.", func() { + if !framework.IsRBACEnabled(f) { + framework.Skipf("RBAC not enabled.") + } + By("Creating a kubernetes client that impersonates an unauthorized anonymous user") - config, err = framework.LoadConfig() + config, err := framework.LoadConfig() framework.ExpectNoError(err) config.Impersonate = restclient.ImpersonationConfig{ UserName: "system:anonymous", @@ -79,8 +665,31 @@ var _ = SIGDescribe("Advanced Audit", func() { anonymousClient, err := clientset.NewForConfig(config) framework.ExpectNoError(err) + _, err = anonymousClient.CoreV1().Pods(namespace).Get("another-audit-pod", metav1.GetOptions{}) + expectForbidden(err) + + expectEvents(f, []utils.AuditEvent{ + { + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/another-audit-pod", namespace), + Verb: "get", + Code: 403, + User: auditTestUser, + ImpersonatedUser: "system:anonymous", + ImpersonatedGroups: "system:unauthenticated", + Resource: "pods", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "forbid", + }, + }) + }) + + It("should list pods as impersonated user.", func() { By("Creating a kubernetes client that impersonates an authorized user") - config, err = framework.LoadConfig() + config, err := framework.LoadConfig() framework.ExpectNoError(err) config.Impersonate = restclient.ImpersonationConfig{ UserName: "superman", @@ -89,681 +698,49 @@ var _ = SIGDescribe("Advanced Audit", func() { impersonatedClient, err := clientset.NewForConfig(config) framework.ExpectNoError(err) - testCases := []struct { - action func() - events []utils.AuditEvent - }{ - // Create, get, update, patch, delete, list, watch pods. + _, err = impersonatedClient.CoreV1().Pods(namespace).List(metav1.ListOptions{}) + framework.ExpectNoError(err, "failed to list pods") + + expectEvents(f, []utils.AuditEvent{ { - func() { - pod := &apiv1.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "audit-pod", - }, - Spec: apiv1.PodSpec{ - Containers: []apiv1.Container{{ - Name: "pause", - Image: imageutils.GetPauseImageName(), - }}, - }, - } - updatePod := func(pod *apiv1.Pod) {} - - f.PodClient().CreateSync(pod) - - _, err := f.PodClient().Get(pod.Name, metav1.GetOptions{}) - framework.ExpectNoError(err, "failed to get audit-pod") - - podChan, err := f.PodClient().Watch(watchOptions) - framework.ExpectNoError(err, "failed to create watch for pods") - podChan.Stop() - - f.PodClient().Update(pod.Name, updatePod) - - _, err = f.PodClient().List(metav1.ListOptions{}) - framework.ExpectNoError(err, "failed to list pods") - - _, err = f.PodClient().Patch(pod.Name, types.JSONPatchType, patch) - framework.ExpectNoError(err, "failed to patch pod") - - f.PodClient().DeleteSync(pod.Name, &metav1.DeleteOptions{}, framework.DefaultPodDeletionTimeout) - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace), - Verb: "create", - Code: 201, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), - Verb: "get", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace), - Verb: "list", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseStarted, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), - Verb: "update", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), - Verb: "patch", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/audit-pod", namespace), - Verb: "delete", - Code: 200, - User: auditTestUser, - Resource: "pods", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, - }, + Level: auditinternal.LevelRequest, + Stage: auditinternal.StageResponseComplete, + RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace), + Verb: "list", + Code: 200, + User: auditTestUser, + ImpersonatedUser: "superman", + ImpersonatedGroups: "system:masters", + Resource: "pods", + Namespace: namespace, + RequestObject: false, + ResponseObject: false, + AuthorizeDecision: "allow", }, - // Create, get, update, patch, delete, list, watch deployments. - { - func() { - podLabels := map[string]string{"name": "audit-deployment-pod"} - d := framework.NewDeployment("audit-deployment", int32(1), podLabels, "redis", imageutils.GetE2EImage(imageutils.Redis), apps.RecreateDeploymentStrategyType) - - _, err := f.ClientSet.AppsV1().Deployments(namespace).Create(d) - framework.ExpectNoError(err, "failed to create audit-deployment") - - _, err = f.ClientSet.AppsV1().Deployments(namespace).Get(d.Name, metav1.GetOptions{}) - framework.ExpectNoError(err, "failed to get audit-deployment") - - deploymentChan, err := f.ClientSet.AppsV1().Deployments(namespace).Watch(watchOptions) - framework.ExpectNoError(err, "failed to create watch for deployments") - deploymentChan.Stop() - - _, err = f.ClientSet.AppsV1().Deployments(namespace).Update(d) - framework.ExpectNoError(err, "failed to update audit-deployment") - - _, err = f.ClientSet.AppsV1().Deployments(namespace).Patch(d.Name, types.JSONPatchType, patch) - framework.ExpectNoError(err, "failed to patch deployment") - - _, err = f.ClientSet.AppsV1().Deployments(namespace).List(metav1.ListOptions{}) - framework.ExpectNoError(err, "failed to create list deployments") - - err = f.ClientSet.AppsV1().Deployments(namespace).Delete("audit-deployment", &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "failed to delete deployments") - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments", namespace), - Verb: "create", - Code: 201, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), - Verb: "get", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments", namespace), - Verb: "list", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseStarted, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), - Verb: "update", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), - Verb: "patch", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apps/v1/namespaces/%s/deployments/audit-deployment", namespace), - Verb: "delete", - Code: 200, - User: auditTestUser, - Resource: "deployments", - Namespace: namespace, - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, - }, - }, - // Create, get, update, patch, delete, list, watch configmaps. - { - func() { - configMap := &apiv1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "audit-configmap", - }, - Data: map[string]string{ - "map-key": "map-value", - }, - } - - _, err := f.ClientSet.CoreV1().ConfigMaps(namespace).Create(configMap) - framework.ExpectNoError(err, "failed to create audit-configmap") - - _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).Get(configMap.Name, metav1.GetOptions{}) - framework.ExpectNoError(err, "failed to get audit-configmap") - - configMapChan, err := f.ClientSet.CoreV1().ConfigMaps(namespace).Watch(watchOptions) - framework.ExpectNoError(err, "failed to create watch for config maps") - configMapChan.Stop() - - _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).Update(configMap) - framework.ExpectNoError(err, "failed to update audit-configmap") - - _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).Patch(configMap.Name, types.JSONPatchType, patch) - framework.ExpectNoError(err, "failed to patch configmap") - - _, err = f.ClientSet.CoreV1().ConfigMaps(namespace).List(metav1.ListOptions{}) - framework.ExpectNoError(err, "failed to list config maps") - - err = f.ClientSet.CoreV1().ConfigMaps(namespace).Delete(configMap.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "failed to delete audit-configmap") - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", namespace), - Verb: "create", - Code: 201, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), - Verb: "get", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps", namespace), - Verb: "list", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseStarted, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), - Verb: "update", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), - Verb: "patch", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/configmaps/audit-configmap", namespace), - Verb: "delete", - Code: 200, - User: auditTestUser, - Resource: "configmaps", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, - }, - }, - // Create, get, update, patch, delete, list, watch secrets. - { - func() { - secret := &apiv1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: "audit-secret", - }, - Data: map[string][]byte{ - "top-secret": []byte("foo-bar"), - }, - } - _, err := f.ClientSet.CoreV1().Secrets(namespace).Create(secret) - framework.ExpectNoError(err, "failed to create audit-secret") - - _, err = f.ClientSet.CoreV1().Secrets(namespace).Get(secret.Name, metav1.GetOptions{}) - framework.ExpectNoError(err, "failed to get audit-secret") - - secretChan, err := f.ClientSet.CoreV1().Secrets(namespace).Watch(watchOptions) - framework.ExpectNoError(err, "failed to create watch for secrets") - secretChan.Stop() - - _, err = f.ClientSet.CoreV1().Secrets(namespace).Update(secret) - framework.ExpectNoError(err, "failed to update audit-secret") - - _, err = f.ClientSet.CoreV1().Secrets(namespace).Patch(secret.Name, types.JSONPatchType, patch) - framework.ExpectNoError(err, "failed to patch secret") - - _, err = f.ClientSet.CoreV1().Secrets(namespace).List(metav1.ListOptions{}) - framework.ExpectNoError(err, "failed to list secrets") - - err = f.ClientSet.CoreV1().Secrets(namespace).Delete(secret.Name, &metav1.DeleteOptions{}) - framework.ExpectNoError(err, "failed to delete audit-secret") - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets", namespace), - Verb: "create", - Code: 201, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), - Verb: "get", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets", namespace), - Verb: "list", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseStarted, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets?timeout=%ds&timeoutSeconds=%d&watch=true", namespace, watchTestTimeout, watchTestTimeout), - Verb: "watch", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), - Verb: "update", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), - Verb: "patch", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/secrets/audit-secret", namespace), - Verb: "delete", - Code: 200, - User: auditTestUser, - Resource: "secrets", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, - }, - }, - // Create and delete custom resource definition. - { - func() { - crd, err = fixtures.CreateNewCustomResourceDefinition(crd, apiExtensionClient, f.DynamicClient) - framework.ExpectNoError(err, "failed to create custom resource definition") - fixtures.DeleteCustomResourceDefinition(crd, apiExtensionClient) - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: "/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions", - Verb: "create", - Code: 201, - User: auditTestUser, - Resource: "customresourcedefinitions", - RequestObject: true, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/%s/v1beta1/%s", crdNamespace, crdName), - Verb: "create", - Code: 201, - User: auditTestUser, - Resource: crdName, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelRequestResponse, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/apiextensions.k8s.io/v1beta1/customresourcedefinitions/%s", crd.Name), - Verb: "delete", - Code: 200, - User: auditTestUser, - Resource: "customresourcedefinitions", - RequestObject: false, - ResponseObject: true, - AuthorizeDecision: "allow", - }, { - Level: auditinternal.LevelMetadata, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/apis/%s/v1beta1/%s/setup-instance", crdNamespace, crdName), - Verb: "delete", - Code: 200, - User: auditTestUser, - Resource: crdName, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, - }, - }, - // List pods as impersonated user. - { - func() { - _, err = impersonatedClient.CoreV1().Pods(namespace).List(metav1.ListOptions{}) - framework.ExpectNoError(err, "failed to list pods") - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods", namespace), - Verb: "list", - Code: 200, - User: auditTestUser, - ImpersonatedUser: "superman", - ImpersonatedGroups: "system:masters", - Resource: "pods", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "allow", - }, - }, - }, - } - - // test authorizer annotations, RBAC is required. - annotationTestCases := []struct { - action func() - events []utils.AuditEvent - }{ - - // get a pod with unauthorized user - { - func() { - _, err := anonymousClient.CoreV1().Pods(namespace).Get("another-audit-pod", metav1.GetOptions{}) - expectForbidden(err) - }, - []utils.AuditEvent{ - { - Level: auditinternal.LevelRequest, - Stage: auditinternal.StageResponseComplete, - RequestURI: fmt.Sprintf("/api/v1/namespaces/%s/pods/another-audit-pod", namespace), - Verb: "get", - Code: 403, - User: auditTestUser, - ImpersonatedUser: "system:anonymous", - ImpersonatedGroups: "system:unauthenticated", - Resource: "pods", - Namespace: namespace, - RequestObject: false, - ResponseObject: false, - AuthorizeDecision: "forbid", - }, - }, - }, - } - - if framework.IsRBACEnabled(f) { - testCases = append(testCases, annotationTestCases...) - } - expectedEvents := []utils.AuditEvent{} - for _, t := range testCases { - t.action() - expectedEvents = append(expectedEvents, t.events...) - } - - // The default flush timeout is 30 seconds, therefore it should be enough to retry once - // to find all expected events. However, we're waiting for 5 minutes to avoid flakes. - pollingInterval := 30 * time.Second - pollingTimeout := 5 * time.Minute - err = wait.Poll(pollingInterval, pollingTimeout, func() (bool, error) { - // Fetch the log stream. - stream, err := f.ClientSet.CoreV1().RESTClient().Get().AbsPath("/logs/kube-apiserver-audit.log").Stream() - if err != nil { - return false, err - } - defer stream.Close() - missing, err := utils.CheckAuditLines(stream, expectedEvents, v1beta1.SchemeGroupVersion) - if err != nil { - framework.Logf("Failed to observe audit events: %v", err) - } else if len(missing) > 0 { - framework.Logf("Events %#v not found!", missing) - } - return len(missing) == 0, nil }) - framework.ExpectNoError(err, "after %v failed to observe audit events", pollingTimeout) }) + }) + +func expectEvents(f *framework.Framework, expectedEvents []utils.AuditEvent) { + // The default flush timeout is 30 seconds, therefore it should be enough to retry once + // to find all expected events. However, we're waiting for 5 minutes to avoid flakes. + pollingInterval := 30 * time.Second + pollingTimeout := 5 * time.Minute + err := wait.Poll(pollingInterval, pollingTimeout, func() (bool, error) { + // Fetch the log stream. + stream, err := f.ClientSet.CoreV1().RESTClient().Get().AbsPath("/logs/kube-apiserver-audit.log").Stream() + if err != nil { + return false, err + } + defer stream.Close() + missing, err := utils.CheckAuditLines(stream, expectedEvents, v1beta1.SchemeGroupVersion) + if err != nil { + framework.Logf("Failed to observe audit events: %v", err) + } else if len(missing) > 0 { + framework.Logf("Events %#v not found!", missing) + } + return len(missing) == 0, nil + }) + framework.ExpectNoError(err, "after %v failed to observe audit events", pollingTimeout) +}