diff --git a/hack/.golint_failures b/hack/.golint_failures index afb6b3270fb..8ac1b784f45 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -607,6 +607,7 @@ staging/src/k8s.io/apiserver/pkg/apis/apiserver staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1 staging/src/k8s.io/apiserver/pkg/apis/audit staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1 +staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1 staging/src/k8s.io/apiserver/pkg/apis/audit/validation staging/src/k8s.io/apiserver/pkg/apis/example staging/src/k8s.io/apiserver/pkg/apis/example/v1 diff --git a/pkg/generated/openapi/BUILD b/pkg/generated/openapi/BUILD index 030d5bc4b9d..c322229aa26 100644 --- a/pkg/generated/openapi/BUILD +++ b/pkg/generated/openapi/BUILD @@ -51,6 +51,7 @@ openapi_library( "k8s.io/apimachinery/pkg/util/intstr", "k8s.io/apimachinery/pkg/version", "k8s.io/apiserver/pkg/apis/audit/v1alpha1", + "k8s.io/apiserver/pkg/apis/audit/v1beta1", "k8s.io/apiserver/pkg/apis/example/v1", "k8s.io/client-go/pkg/version", "k8s.io/kube-aggregator/pkg/apis/apiregistration/v1beta1", diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD index 085a82ce5d5..b6556662a8c 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/BUILD @@ -34,8 +34,10 @@ filegroup( name = "all-srcs", srcs = [ ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/audit/install:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/audit/v1alpha1:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1:all-srcs", "//staging/src/k8s.io/apiserver/pkg/apis/audit/validation:all-srcs", ], tags = ["automanaged"], diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/BUILD new file mode 100644 index 00000000000..e7ab48f51ee --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/BUILD @@ -0,0 +1,27 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["fuzzer.go"], + visibility = ["//visibility:public"], + deps = [ + "//vendor/github.com/google/gofuzz:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/fuzzer.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/fuzzer.go new file mode 100644 index 00000000000..24b06367350 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/fuzzer.go @@ -0,0 +1,53 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package fuzzer + +import ( + fuzz "github.com/google/gofuzz" + + "k8s.io/apimachinery/pkg/runtime" + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/apiserver/pkg/apis/audit" +) + +// Funcs returns the fuzzer functions for the audit api group. +func Funcs(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(e *audit.Event, c fuzz.Continue) { + switch c.RandBool() { + case true: + e.RequestObject = nil + case false: + e.RequestObject = &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{APIVersion: "", Kind: ""}, + Raw: []byte(`{"apiVersion":"","kind":"Pod","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, + } + } + switch c.RandBool() { + case true: + e.ResponseObject = nil + case false: + e.ResponseObject = &runtime.Unknown{ + TypeMeta: runtime.TypeMeta{APIVersion: "", Kind: ""}, + Raw: []byte(`{"apiVersion":"","kind":"Pod","someKey":"someValue"}`), + ContentType: runtime.ContentTypeJSON, + } + } + }, + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD index 8f1d8076406..76f39a88093 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/BUILD @@ -3,6 +3,7 @@ package(default_visibility = ["//visibility:public"]) load( "@io_bazel_rules_go//go:def.bzl", "go_library", + "go_test", ) go_library( @@ -15,6 +16,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", ], ) @@ -30,3 +32,13 @@ filegroup( srcs = [":package-srcs"], tags = ["automanaged"], ) + +go_test( + name = "go_default_test", + srcs = ["roundtrip_test.go"], + library = ":go_default_library", + deps = [ + "//vendor/k8s.io/apimachinery/pkg/api/testing/roundtrip:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/fuzzer:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/install.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/install.go index fe648935466..6acf7c5d12d 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/install.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/install.go @@ -25,6 +25,7 @@ import ( "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apiserver/pkg/apis/audit" "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + "k8s.io/apiserver/pkg/apis/audit/v1beta1" ) // Install registers the API group and adds types to a scheme @@ -32,12 +33,13 @@ func Install(groupFactoryRegistry announced.APIGroupFactoryRegistry, registry *r if err := announced.NewGroupMetaFactory( &announced.GroupMetaFactoryArgs{ GroupName: audit.GroupName, - VersionPreferenceOrder: []string{v1alpha1.SchemeGroupVersion.Version}, + VersionPreferenceOrder: []string{v1beta1.SchemeGroupVersion.Version, v1alpha1.SchemeGroupVersion.Version}, // Any Kind that is not namespaced must be cluster scoped. RootScopedKinds: sets.NewString("Event", "Policy"), AddInternalObjectsToScheme: audit.AddToScheme, }, announced.VersionToSchemeFunc{ + v1beta1.SchemeGroupVersion.Version: v1beta1.AddToScheme, v1alpha1.SchemeGroupVersion.Version: v1alpha1.AddToScheme, }, ).Announce(groupFactoryRegistry).RegisterAndEnable(registry, scheme); err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/install/roundtrip_test.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/roundtrip_test.go new file mode 100644 index 00000000000..b3199e7b5d1 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/install/roundtrip_test.go @@ -0,0 +1,28 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package install + +import ( + "testing" + + "k8s.io/apimachinery/pkg/api/testing/roundtrip" + "k8s.io/apiserver/pkg/apis/audit/fuzzer" +) + +func TestRoundTrip(t *testing.T) { + roundtrip.RoundTripTestForAPIGroup(t, Install, fuzzer.Funcs) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD new file mode 100644 index 00000000000..33f57f137eb --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/BUILD @@ -0,0 +1,44 @@ +package(default_visibility = ["//visibility:public"]) + +licenses(["notice"]) + +load( + "@io_bazel_rules_go//go:def.bzl", + "go_library", +) + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "register.go", + "types.go", + "zz_generated.conversion.go", + "zz_generated.deepcopy.go", + "zz_generated.defaults.go", + ], + tags = ["automanaged"], + deps = [ + "//vendor/k8s.io/api/authentication/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/conversion:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/types:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go new file mode 100644 index 00000000000..d95306bbfd8 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go @@ -0,0 +1,23 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// +k8s:deepcopy-gen=package,register +// +k8s:conversion-gen=k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/apis/audit +// +k8s:openapi-gen=true +// +k8s:defaulter-gen=TypeMeta + +// +groupName=audit.k8s.io +package v1beta1 // import "k8s.io/apiserver/pkg/apis/audit/v1beta1" diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/register.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/register.go new file mode 100644 index 00000000000..1bb1a50f294 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/register.go @@ -0,0 +1,58 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "audit.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + SchemeBuilder runtime.SchemeBuilder + localSchemeBuilder = &SchemeBuilder + AddToScheme = localSchemeBuilder.AddToScheme +) + +func init() { + // We only register manually written functions here. The registration of the + // generated functions takes place in the generated files. The separation + // makes the code compile even when the generated files are missing. + localSchemeBuilder.Register(addKnownTypes) +} + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &Event{}, + &EventList{}, + &Policy{}, + &PolicyList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go new file mode 100644 index 00000000000..1ecac858854 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go @@ -0,0 +1,234 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1beta1 + +import ( + authnv1 "k8s.io/api/authentication/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/types" +) + +// Header keys used by the audit system. +const ( + // Header to hold the audit ID as the request is propagated through the serving hierarchy. The + // Audit-ID header should be set by the first server to receive the request (e.g. the federation + // server or kube-aggregator). + HeaderAuditID = "Audit-ID" +) + +// Level defines the amount of information logged during auditing +type Level string + +// Valid audit levels +const ( + // LevelNone disables auditing + LevelNone Level = "None" + // LevelMetadata provides the basic level of auditing. + LevelMetadata Level = "Metadata" + // LevelRequest provides Metadata level of auditing, and additionally + // logs the request object (does not apply for non-resource requests). + LevelRequest Level = "Request" + // LevelRequestResponse provides Request level of auditing, and additionally + // logs the response object (does not apply for non-resource requests). + LevelRequestResponse Level = "RequestResponse" +) + +// Stage defines the stages in request handling that audit events may be generated. +type Stage string + +// Valid audit stages. +const ( + // The stage for events generated as soon as the audit handler receives the request, and before it + // is delegated down the handler chain. + StageRequestReceived = "RequestReceived" + // The stage for events generated once the response headers are sent, but before the response body + // is sent. This stage is only generated for long-running requests (e.g. watch). + StageResponseStarted = "ResponseStarted" + // The stage for events generated once the response body has been completed, and no more bytes + // will be sent. + StageResponseComplete = "ResponseComplete" + // The stage for events generated when a panic occured. + StagePanic = "Panic" +) + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Event captures all the information that can be included in an API audit log. +type Event struct { + metav1.TypeMeta `json:",inline"` + // ObjectMeta is included for interoperability with API infrastructure. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // AuditLevel at which event was generated + Level Level `json:"level" protobuf:"bytes,2,opt,name=level,casttype=Level"` + + // Time the request reached the apiserver. + Timestamp metav1.Time `json:"timestamp" protobuf:"bytes,3,opt,name=timestamp"` + // Unique audit ID, generated for each request. + AuditID types.UID `json:"auditID" protobuf:"bytes,4,opt,name=auditID,casttype=k8s.io/apimachinery/pkg/types.UID"` + // Stage of the request handling when this event instance was generated. + Stage Stage `json:"stage" protobuf:"bytes,5,opt,name=stage,casttype=Stage"` + + // RequestURI is the request URI as sent by the client to a server. + RequestURI string `json:"requestURI" protobuf:"bytes,6,opt,name=requestURI"` + // Verb is the kubernetes verb associated with the request. + // For non-resource requests, this is the lower-cased HTTP method. + Verb string `json:"verb" protobuf:"bytes,7,opt,name=verb"` + // Authenticated user information. + User authnv1.UserInfo `json:"user" protobuf:"bytes,8,opt,name=user"` + // Impersonated user information. + // +optional + ImpersonatedUser *authnv1.UserInfo `json:"impersonatedUser,omitempty" protobuf:"bytes,9,opt,name=impersonatedUser"` + // Source IPs, from where the request originated and intermediate proxies. + // +optional + SourceIPs []string `json:"sourceIPs,omitempty" protobuf:"bytes,10,rep,name=sourceIPs"` + // Object reference this request is targeted at. + // Does not apply for List-type requests, or non-resource requests. + // +optional + ObjectRef *ObjectReference `json:"objectRef,omitempty" protobuf:"bytes,11,opt,name=objectRef"` + // The response status, populated even when the ResponseObject is not a Status type. + // For successful responses, this will only include the Code and StatusSuccess. + // For non-status type error responses, this will be auto-populated with the error Message. + // +optional + ResponseStatus *metav1.Status `json:"responseStatus,omitempty" protobuf:"bytes,12,opt,name=responseStatus"` + + // API object from the request, in JSON format. The RequestObject is recorded as-is in the request + // (possibly re-encoded as JSON), prior to version conversion, defaulting, admission or + // merging. It is an external versioned object type, and may not be a valid object on its own. + // Omitted for non-resource requests. Only logged at Request Level and higher. + // +optional + RequestObject *runtime.Unknown `json:"requestObject,omitempty" protobuf:"bytes,13,opt,name=requestObject"` + // API object returned in the response, in JSON. The ResponseObject is recorded after conversion + // to the external type, and serialized as JSON. Omitted for non-resource requests. Only logged + // at Response Level. + // +optional + ResponseObject *runtime.Unknown `json:"responseObject,omitempty" protobuf:"bytes,14,opt,name=responseObject"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// EventList is a list of audit Events. +type EventList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Items []Event `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Policy defines the configuration of audit logging, and the rules for how different request +// categories are logged. +type Policy struct { + metav1.TypeMeta `json:",inline"` + // ObjectMeta is included for interoperability with API infrastructure. + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Rules specify the audit Level a request should be recorded at. + // A request may match multiple rules, in which case the FIRST matching rule is used. + // The default audit level is None, but can be overridden by a catch-all rule at the end of the list. + // PolicyRules are strictly ordered. + Rules []PolicyRule `json:"rules" protobuf:"bytes,2,rep,name=rules"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// PolicyList is a list of audit Policies. +type PolicyList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + Items []Policy `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// PolicyRule maps requests based off metadata to an audit Level. +// Requests must match the rules of every field (an intersection of rules). +type PolicyRule struct { + // The Level that requests matching this rule are recorded at. + Level Level `json:"level" protobuf:"bytes,1,opt,name=level,casttype=Level"` + + // The users (by authenticated user name) this rule applies to. + // An empty list implies every user. + // +optional + Users []string `json:"users,omitempty" protobuf:"bytes,2,rep,name=users"` + // The user groups this rule applies to. A user is considered matching + // if it is a member of any of the UserGroups. + // An empty list implies every user group. + // +optional + UserGroups []string `json:"userGroups,omitempty" protobuf:"bytes,3,rep,name=userGroups"` + + // The verbs that match this rule. + // An empty list implies every verb. + // +optional + Verbs []string `json:"verbs,omitempty" protobuf:"bytes,4,rep,name=verbs"` + + // Rules can apply to API resources (such as "pods" or "secrets"), + // non-resource URL paths (such as "/api"), or neither, but not both. + // If neither is specified, the rule is treated as a default for all URLs. + + // Resources that this rule matches. An empty list implies all kinds in all API groups. + // +optional + Resources []GroupResources `json:"resources,omitempty" protobuf:"bytes,5,rep,name=resources"` + // Namespaces that this rule matches. + // The empty string "" matches non-namespaced resources. + // An empty list implies every namespace. + // +optional + Namespaces []string `json:"namespaces,omitempty" protobuf:"bytes,6,rep,name=namespaces"` + + // NonResourceURLs is a set of URL paths that should be audited. + // *s are allowed, but only as the full, final step in the path. + // Examples: + // "/metrics" - Log requests for apiserver metrics + // "/healthz*" - Log all health checks + // +optional + NonResourceURLs []string `json:"nonResourceURLs,omitempty" protobuf:"bytes,7,rep,name=nonResourceURLs"` +} + +// GroupResources represents resource kinds in an API group. +type GroupResources struct { + // Group is the name of the API group that contains the resources. + // The empty string represents the core API group. + // +optional + Group string `json:"group,omitempty" protobuf:"bytes,1,opt,name=group"` + // Resources is a list of resources within the API group. + // Any empty list implies every resource kind in the API group. + // +optional + Resources []string `json:"resources,omitempty" protobuf:"bytes,2,rep,name=resources"` +} + +// ObjectReference contains enough information to let you inspect or modify the referred object. +type ObjectReference struct { + // +optional + Resource string `json:"resource,omitempty" protobuf:"bytes,1,opt,name=resource"` + // +optional + Namespace string `json:"namespace,omitempty" protobuf:"bytes,2,opt,name=namespace"` + // +optional + Name string `json:"name,omitempty" protobuf:"bytes,3,opt,name=name"` + // +optional + UID types.UID `json:"uid,omitempty" protobuf:"bytes,4,opt,name=uid,casttype=k8s.io/apimachinery/pkg/types.UID"` + // +optional + APIVersion string `json:"apiVersion,omitempty" protobuf:"bytes,5,opt,name=apiVersion"` + // +optional + ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"` + // +optional + Subresource string `json:"subresource,omitempty" protobuf:"bytes,7,opt,name=subresource"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go new file mode 100644 index 00000000000..78617dba620 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.conversion.go @@ -0,0 +1,272 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file was autogenerated by conversion-gen. Do not edit it manually! + +package v1beta1 + +import ( + authentication_v1 "k8s.io/api/authentication/v1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + types "k8s.io/apimachinery/pkg/types" + audit "k8s.io/apiserver/pkg/apis/audit" + unsafe "unsafe" +) + +func init() { + localSchemeBuilder.Register(RegisterConversions) +} + +// RegisterConversions adds conversion functions to the given scheme. +// Public to allow building arbitrary schemes. +func RegisterConversions(scheme *runtime.Scheme) error { + return scheme.AddGeneratedConversionFuncs( + Convert_v1beta1_Event_To_audit_Event, + Convert_audit_Event_To_v1beta1_Event, + Convert_v1beta1_EventList_To_audit_EventList, + Convert_audit_EventList_To_v1beta1_EventList, + Convert_v1beta1_GroupResources_To_audit_GroupResources, + Convert_audit_GroupResources_To_v1beta1_GroupResources, + Convert_v1beta1_ObjectReference_To_audit_ObjectReference, + Convert_audit_ObjectReference_To_v1beta1_ObjectReference, + Convert_v1beta1_Policy_To_audit_Policy, + Convert_audit_Policy_To_v1beta1_Policy, + Convert_v1beta1_PolicyList_To_audit_PolicyList, + Convert_audit_PolicyList_To_v1beta1_PolicyList, + Convert_v1beta1_PolicyRule_To_audit_PolicyRule, + Convert_audit_PolicyRule_To_v1beta1_PolicyRule, + ) +} + +func autoConvert_v1beta1_Event_To_audit_Event(in *Event, out *audit.Event, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Level = audit.Level(in.Level) + out.Timestamp = in.Timestamp + out.AuditID = types.UID(in.AuditID) + out.Stage = audit.Stage(in.Stage) + out.RequestURI = in.RequestURI + out.Verb = in.Verb + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.User, &out.User, 0); err != nil { + return err + } + out.ImpersonatedUser = (*audit.UserInfo)(unsafe.Pointer(in.ImpersonatedUser)) + out.SourceIPs = *(*[]string)(unsafe.Pointer(&in.SourceIPs)) + out.ObjectRef = (*audit.ObjectReference)(unsafe.Pointer(in.ObjectRef)) + out.ResponseStatus = (*v1.Status)(unsafe.Pointer(in.ResponseStatus)) + out.RequestObject = (*runtime.Unknown)(unsafe.Pointer(in.RequestObject)) + out.ResponseObject = (*runtime.Unknown)(unsafe.Pointer(in.ResponseObject)) + return nil +} + +// Convert_v1beta1_Event_To_audit_Event is an autogenerated conversion function. +func Convert_v1beta1_Event_To_audit_Event(in *Event, out *audit.Event, s conversion.Scope) error { + return autoConvert_v1beta1_Event_To_audit_Event(in, out, s) +} + +func autoConvert_audit_Event_To_v1beta1_Event(in *audit.Event, out *Event, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Level = Level(in.Level) + out.Timestamp = in.Timestamp + out.AuditID = types.UID(in.AuditID) + out.Stage = Stage(in.Stage) + out.RequestURI = in.RequestURI + out.Verb = in.Verb + // TODO: Inefficient conversion - can we improve it? + if err := s.Convert(&in.User, &out.User, 0); err != nil { + return err + } + out.ImpersonatedUser = (*authentication_v1.UserInfo)(unsafe.Pointer(in.ImpersonatedUser)) + out.SourceIPs = *(*[]string)(unsafe.Pointer(&in.SourceIPs)) + out.ObjectRef = (*ObjectReference)(unsafe.Pointer(in.ObjectRef)) + out.ResponseStatus = (*v1.Status)(unsafe.Pointer(in.ResponseStatus)) + out.RequestObject = (*runtime.Unknown)(unsafe.Pointer(in.RequestObject)) + out.ResponseObject = (*runtime.Unknown)(unsafe.Pointer(in.ResponseObject)) + return nil +} + +// Convert_audit_Event_To_v1beta1_Event is an autogenerated conversion function. +func Convert_audit_Event_To_v1beta1_Event(in *audit.Event, out *Event, s conversion.Scope) error { + return autoConvert_audit_Event_To_v1beta1_Event(in, out, s) +} + +func autoConvert_v1beta1_EventList_To_audit_EventList(in *EventList, out *audit.EventList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]audit.Event)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_EventList_To_audit_EventList is an autogenerated conversion function. +func Convert_v1beta1_EventList_To_audit_EventList(in *EventList, out *audit.EventList, s conversion.Scope) error { + return autoConvert_v1beta1_EventList_To_audit_EventList(in, out, s) +} + +func autoConvert_audit_EventList_To_v1beta1_EventList(in *audit.EventList, out *EventList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items == nil { + out.Items = make([]Event, 0) + } else { + out.Items = *(*[]Event)(unsafe.Pointer(&in.Items)) + } + return nil +} + +// Convert_audit_EventList_To_v1beta1_EventList is an autogenerated conversion function. +func Convert_audit_EventList_To_v1beta1_EventList(in *audit.EventList, out *EventList, s conversion.Scope) error { + return autoConvert_audit_EventList_To_v1beta1_EventList(in, out, s) +} + +func autoConvert_v1beta1_GroupResources_To_audit_GroupResources(in *GroupResources, out *audit.GroupResources, s conversion.Scope) error { + out.Group = in.Group + out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) + return nil +} + +// Convert_v1beta1_GroupResources_To_audit_GroupResources is an autogenerated conversion function. +func Convert_v1beta1_GroupResources_To_audit_GroupResources(in *GroupResources, out *audit.GroupResources, s conversion.Scope) error { + return autoConvert_v1beta1_GroupResources_To_audit_GroupResources(in, out, s) +} + +func autoConvert_audit_GroupResources_To_v1beta1_GroupResources(in *audit.GroupResources, out *GroupResources, s conversion.Scope) error { + out.Group = in.Group + out.Resources = *(*[]string)(unsafe.Pointer(&in.Resources)) + return nil +} + +// Convert_audit_GroupResources_To_v1beta1_GroupResources is an autogenerated conversion function. +func Convert_audit_GroupResources_To_v1beta1_GroupResources(in *audit.GroupResources, out *GroupResources, s conversion.Scope) error { + return autoConvert_audit_GroupResources_To_v1beta1_GroupResources(in, out, s) +} + +func autoConvert_v1beta1_ObjectReference_To_audit_ObjectReference(in *ObjectReference, out *audit.ObjectReference, s conversion.Scope) error { + out.Resource = in.Resource + out.Namespace = in.Namespace + out.Name = in.Name + out.UID = types.UID(in.UID) + out.APIVersion = in.APIVersion + out.ResourceVersion = in.ResourceVersion + out.Subresource = in.Subresource + return nil +} + +// Convert_v1beta1_ObjectReference_To_audit_ObjectReference is an autogenerated conversion function. +func Convert_v1beta1_ObjectReference_To_audit_ObjectReference(in *ObjectReference, out *audit.ObjectReference, s conversion.Scope) error { + return autoConvert_v1beta1_ObjectReference_To_audit_ObjectReference(in, out, s) +} + +func autoConvert_audit_ObjectReference_To_v1beta1_ObjectReference(in *audit.ObjectReference, out *ObjectReference, s conversion.Scope) error { + out.Resource = in.Resource + out.Namespace = in.Namespace + out.Name = in.Name + out.UID = types.UID(in.UID) + out.APIVersion = in.APIVersion + out.ResourceVersion = in.ResourceVersion + out.Subresource = in.Subresource + return nil +} + +// Convert_audit_ObjectReference_To_v1beta1_ObjectReference is an autogenerated conversion function. +func Convert_audit_ObjectReference_To_v1beta1_ObjectReference(in *audit.ObjectReference, out *ObjectReference, s conversion.Scope) error { + return autoConvert_audit_ObjectReference_To_v1beta1_ObjectReference(in, out, s) +} + +func autoConvert_v1beta1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + out.Rules = *(*[]audit.PolicyRule)(unsafe.Pointer(&in.Rules)) + return nil +} + +// Convert_v1beta1_Policy_To_audit_Policy is an autogenerated conversion function. +func Convert_v1beta1_Policy_To_audit_Policy(in *Policy, out *audit.Policy, s conversion.Scope) error { + return autoConvert_v1beta1_Policy_To_audit_Policy(in, out, s) +} + +func autoConvert_audit_Policy_To_v1beta1_Policy(in *audit.Policy, out *Policy, s conversion.Scope) error { + out.ObjectMeta = in.ObjectMeta + if in.Rules == nil { + out.Rules = make([]PolicyRule, 0) + } else { + out.Rules = *(*[]PolicyRule)(unsafe.Pointer(&in.Rules)) + } + return nil +} + +// Convert_audit_Policy_To_v1beta1_Policy is an autogenerated conversion function. +func Convert_audit_Policy_To_v1beta1_Policy(in *audit.Policy, out *Policy, s conversion.Scope) error { + return autoConvert_audit_Policy_To_v1beta1_Policy(in, out, s) +} + +func autoConvert_v1beta1_PolicyList_To_audit_PolicyList(in *PolicyList, out *audit.PolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + out.Items = *(*[]audit.Policy)(unsafe.Pointer(&in.Items)) + return nil +} + +// Convert_v1beta1_PolicyList_To_audit_PolicyList is an autogenerated conversion function. +func Convert_v1beta1_PolicyList_To_audit_PolicyList(in *PolicyList, out *audit.PolicyList, s conversion.Scope) error { + return autoConvert_v1beta1_PolicyList_To_audit_PolicyList(in, out, s) +} + +func autoConvert_audit_PolicyList_To_v1beta1_PolicyList(in *audit.PolicyList, out *PolicyList, s conversion.Scope) error { + out.ListMeta = in.ListMeta + if in.Items == nil { + out.Items = make([]Policy, 0) + } else { + out.Items = *(*[]Policy)(unsafe.Pointer(&in.Items)) + } + return nil +} + +// Convert_audit_PolicyList_To_v1beta1_PolicyList is an autogenerated conversion function. +func Convert_audit_PolicyList_To_v1beta1_PolicyList(in *audit.PolicyList, out *PolicyList, s conversion.Scope) error { + return autoConvert_audit_PolicyList_To_v1beta1_PolicyList(in, out, s) +} + +func autoConvert_v1beta1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *audit.PolicyRule, s conversion.Scope) error { + out.Level = audit.Level(in.Level) + out.Users = *(*[]string)(unsafe.Pointer(&in.Users)) + out.UserGroups = *(*[]string)(unsafe.Pointer(&in.UserGroups)) + out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs)) + out.Resources = *(*[]audit.GroupResources)(unsafe.Pointer(&in.Resources)) + out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) + out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) + return nil +} + +// Convert_v1beta1_PolicyRule_To_audit_PolicyRule is an autogenerated conversion function. +func Convert_v1beta1_PolicyRule_To_audit_PolicyRule(in *PolicyRule, out *audit.PolicyRule, s conversion.Scope) error { + return autoConvert_v1beta1_PolicyRule_To_audit_PolicyRule(in, out, s) +} + +func autoConvert_audit_PolicyRule_To_v1beta1_PolicyRule(in *audit.PolicyRule, out *PolicyRule, s conversion.Scope) error { + out.Level = Level(in.Level) + out.Users = *(*[]string)(unsafe.Pointer(&in.Users)) + out.UserGroups = *(*[]string)(unsafe.Pointer(&in.UserGroups)) + out.Verbs = *(*[]string)(unsafe.Pointer(&in.Verbs)) + out.Resources = *(*[]GroupResources)(unsafe.Pointer(&in.Resources)) + out.Namespaces = *(*[]string)(unsafe.Pointer(&in.Namespaces)) + out.NonResourceURLs = *(*[]string)(unsafe.Pointer(&in.NonResourceURLs)) + return nil +} + +// Convert_audit_PolicyRule_To_v1beta1_PolicyRule is an autogenerated conversion function. +func Convert_audit_PolicyRule_To_v1beta1_PolicyRule(in *audit.PolicyRule, out *PolicyRule, s conversion.Scope) error { + return autoConvert_audit_PolicyRule_To_v1beta1_PolicyRule(in, out, s) +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..28c841d3b87 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.deepcopy.go @@ -0,0 +1,336 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file was autogenerated by deepcopy-gen. Do not edit it manually! + +package v1beta1 + +import ( + v1 "k8s.io/api/authentication/v1" + meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + conversion "k8s.io/apimachinery/pkg/conversion" + runtime "k8s.io/apimachinery/pkg/runtime" + reflect "reflect" +) + +func init() { + SchemeBuilder.Register(RegisterDeepCopies) +} + +// RegisterDeepCopies adds deep-copy functions to the given scheme. Public +// to allow building arbitrary schemes. +// +// Deprecated: deepcopy registration will go away when static deepcopy is fully implemented. +func RegisterDeepCopies(scheme *runtime.Scheme) error { + return scheme.AddGeneratedDeepCopyFuncs( + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*Event).DeepCopyInto(out.(*Event)) + return nil + }, InType: reflect.TypeOf(&Event{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*EventList).DeepCopyInto(out.(*EventList)) + return nil + }, InType: reflect.TypeOf(&EventList{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*GroupResources).DeepCopyInto(out.(*GroupResources)) + return nil + }, InType: reflect.TypeOf(&GroupResources{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*ObjectReference).DeepCopyInto(out.(*ObjectReference)) + return nil + }, InType: reflect.TypeOf(&ObjectReference{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*Policy).DeepCopyInto(out.(*Policy)) + return nil + }, InType: reflect.TypeOf(&Policy{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*PolicyList).DeepCopyInto(out.(*PolicyList)) + return nil + }, InType: reflect.TypeOf(&PolicyList{})}, + conversion.GeneratedDeepCopyFunc{Fn: func(in interface{}, out interface{}, c *conversion.Cloner) error { + in.(*PolicyRule).DeepCopyInto(out.(*PolicyRule)) + return nil + }, InType: reflect.TypeOf(&PolicyRule{})}, + ) +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Event) DeepCopyInto(out *Event) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + in.Timestamp.DeepCopyInto(&out.Timestamp) + in.User.DeepCopyInto(&out.User) + if in.ImpersonatedUser != nil { + in, out := &in.ImpersonatedUser, &out.ImpersonatedUser + if *in == nil { + *out = nil + } else { + *out = new(v1.UserInfo) + (*in).DeepCopyInto(*out) + } + } + if in.SourceIPs != nil { + in, out := &in.SourceIPs, &out.SourceIPs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ObjectRef != nil { + in, out := &in.ObjectRef, &out.ObjectRef + if *in == nil { + *out = nil + } else { + *out = new(ObjectReference) + **out = **in + } + } + if in.ResponseStatus != nil { + in, out := &in.ResponseStatus, &out.ResponseStatus + if *in == nil { + *out = nil + } else { + *out = new(meta_v1.Status) + (*in).DeepCopyInto(*out) + } + } + if in.RequestObject != nil { + in, out := &in.RequestObject, &out.RequestObject + if *in == nil { + *out = nil + } else { + *out = new(runtime.Unknown) + (*in).DeepCopyInto(*out) + } + } + if in.ResponseObject != nil { + in, out := &in.ResponseObject, &out.ResponseObject + if *in == nil { + *out = nil + } else { + *out = new(runtime.Unknown) + (*in).DeepCopyInto(*out) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Event. +func (in *Event) DeepCopy() *Event { + if in == nil { + return nil + } + out := new(Event) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Event) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *EventList) DeepCopyInto(out *EventList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Event, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EventList. +func (in *EventList) DeepCopy() *EventList { + if in == nil { + return nil + } + out := new(EventList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *EventList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *GroupResources) DeepCopyInto(out *GroupResources) { + *out = *in + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GroupResources. +func (in *GroupResources) DeepCopy() *GroupResources { + if in == nil { + return nil + } + out := new(GroupResources) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ObjectReference) DeepCopyInto(out *ObjectReference) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ObjectReference. +func (in *ObjectReference) DeepCopy() *ObjectReference { + if in == nil { + return nil + } + out := new(ObjectReference) + in.DeepCopyInto(out) + return out +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Policy) DeepCopyInto(out *Policy) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]PolicyRule, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Policy. +func (in *Policy) DeepCopy() *Policy { + if in == nil { + return nil + } + out := new(Policy) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Policy) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyList) DeepCopyInto(out *PolicyList) { + *out = *in + out.TypeMeta = in.TypeMeta + out.ListMeta = in.ListMeta + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Policy, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyList. +func (in *PolicyList) DeepCopy() *PolicyList { + if in == nil { + return nil + } + out := new(PolicyList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *PolicyList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } else { + return nil + } +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *PolicyRule) DeepCopyInto(out *PolicyRule) { + *out = *in + if in.Users != nil { + in, out := &in.Users, &out.Users + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.UserGroups != nil { + in, out := &in.UserGroups, &out.UserGroups + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Verbs != nil { + in, out := &in.Verbs, &out.Verbs + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.Resources != nil { + in, out := &in.Resources, &out.Resources + *out = make([]GroupResources, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.Namespaces != nil { + in, out := &in.Namespaces, &out.Namespaces + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.NonResourceURLs != nil { + in, out := &in.NonResourceURLs, &out.NonResourceURLs + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new PolicyRule. +func (in *PolicyRule) DeepCopy() *PolicyRule { + if in == nil { + return nil + } + out := new(PolicyRule) + in.DeepCopyInto(out) + return out +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go new file mode 100644 index 00000000000..e24e70be38b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/zz_generated.defaults.go @@ -0,0 +1,32 @@ +// +build !ignore_autogenerated + +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +// This file was autogenerated by defaulter-gen. Do not edit it manually! + +package v1beta1 + +import ( + runtime "k8s.io/apimachinery/pkg/runtime" +) + +// RegisterDefaults adds defaulters functions to the given scheme. +// Public to allow building arbitrary schemes. +// All generated defaulters are covering - they call all nested defaulters. +func RegisterDefaults(scheme *runtime.Scheme) error { + return nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/audit/BUILD b/staging/src/k8s.io/apiserver/pkg/audit/BUILD index 9229b141bba..9d97b56f553 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/audit/BUILD @@ -30,6 +30,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD b/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD index 949a10af20c..a0e731f624a 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/BUILD @@ -20,6 +20,7 @@ go_test( "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/user:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", + "//vendor/k8s.io/apiserver/plugin/pkg/audit/webhook:go_default_library", ], ) @@ -34,6 +35,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/validation:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/authorization/authorizer:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go index 2fcce4da750..1d2d502f769 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" auditinternal "k8s.io/apiserver/pkg/apis/audit" auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" "k8s.io/apiserver/pkg/apis/audit/validation" "k8s.io/apiserver/pkg/audit" @@ -38,16 +39,10 @@ func LoadPolicyFromFile(filePath string) (*auditinternal.Policy, error) { return nil, fmt.Errorf("failed to read file path %q: %+v", filePath, err) } - policyVersioned := &auditv1alpha1.Policy{} - - decoder := audit.Codecs.UniversalDecoder(auditv1alpha1.SchemeGroupVersion) - if err := runtime.DecodeInto(decoder, policyDef, policyVersioned); err != nil { - return nil, fmt.Errorf("failed decoding file %q: %v", filePath, err) - } - policy := &auditinternal.Policy{} - if err := audit.Scheme.Convert(policyVersioned, policy, nil); err != nil { - return nil, fmt.Errorf("failed converting policy: %v", err) + decoder := audit.Codecs.UniversalDecoder(auditv1beta1.SchemeGroupVersion, auditv1alpha1.SchemeGroupVersion) + if err := runtime.DecodeInto(decoder, policyDef, policy); err != nil { + return nil, fmt.Errorf("failed decoding file %q: %v", filePath, err) } if err := validation.ValidatePolicy(policy); err != nil { diff --git a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go index be76364f59c..b61e5131700 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/policy/reader_test.go @@ -24,12 +24,36 @@ import ( "k8s.io/apimachinery/pkg/util/diff" "k8s.io/apiserver/pkg/apis/audit" + // import to call webhook's init() function to register audit.Policy to schema + _ "k8s.io/apiserver/plugin/pkg/audit/webhook" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const policyDef = ` +const policyDefV1alpha1 = ` +apiVersion: audit.k8s.io/v1beta1 +kind: Policy +rules: + - level: None + nonResourceURLs: + - /healthz* + - /version + - level: RequestResponse + users: ["tim"] + userGroups: ["testers", "developers"] + verbs: ["patch", "delete", "create"] + resources: + - group: "" + - group: "rbac.authorization.k8s.io" + resources: ["clusterroles", "clusterrolebindings"] + namespaces: ["default", "kube-system"] + - level: Metadata +` + +const policyDefV1beta1 = ` +apiVersion: audit.k8s.io/v1beta1 +kind: Policy rules: - level: None nonResourceURLs: @@ -66,13 +90,32 @@ var expectedPolicy = &audit.Policy{ }}, } -func TestParser(t *testing.T) { +func TestParserV1alpha1(t *testing.T) { // Create a policy file. f, err := ioutil.TempFile("", "policy.yaml") require.NoError(t, err) defer os.Remove(f.Name()) - _, err = f.WriteString(policyDef) + _, err = f.WriteString(policyDefV1alpha1) + require.NoError(t, err) + require.NoError(t, f.Close()) + + policy, err := LoadPolicyFromFile(f.Name()) + require.NoError(t, err) + + assert.Len(t, policy.Rules, 3) // Sanity check. + if !reflect.DeepEqual(policy, expectedPolicy) { + t.Errorf("Unexpected policy! Diff:\n%s", diff.ObjectDiff(policy, expectedPolicy)) + } +} + +func TestParserV1beta1(t *testing.T) { + // Create a policy file. + f, err := ioutil.TempFile("", "policy.yaml") + require.NoError(t, err) + defer os.Remove(f.Name()) + + _, err = f.WriteString(policyDefV1beta1) require.NoError(t, err) require.NoError(t, f.Close()) diff --git a/staging/src/k8s.io/apiserver/pkg/audit/scheme.go b/staging/src/k8s.io/apiserver/pkg/audit/scheme.go index 1ab6f5bd7c5..61c32f5264b 100644 --- a/staging/src/k8s.io/apiserver/pkg/audit/scheme.go +++ b/staging/src/k8s.io/apiserver/pkg/audit/scheme.go @@ -23,6 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + "k8s.io/apiserver/pkg/apis/audit/v1beta1" ) var Scheme = runtime.NewScheme() @@ -31,4 +32,5 @@ var Codecs = serializer.NewCodecFactory(Scheme) func init() { v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) v1alpha1.AddToScheme(Scheme) + v1beta1.AddToScheme(Scheme) } diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD index cbcb0833810..a4c43bf3f25 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/BUILD @@ -28,7 +28,7 @@ go_test( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", - "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticator:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go index 0987f34d6de..c6e27981188 100644 --- a/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go +++ b/staging/src/k8s.io/apiserver/pkg/endpoints/filters/audit_test.go @@ -37,7 +37,7 @@ import ( "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/wait" auditinternal "k8s.io/apiserver/pkg/apis/audit" - auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/audit/policy" "k8s.io/apiserver/pkg/authentication/user" @@ -441,7 +441,7 @@ func TestAuditJson(t *testing.T) { verb string auditID string handler func(http.ResponseWriter, *http.Request) - expected []auditv1alpha1.Event + expected []auditv1beta1.Event respHeader bool }{ // short running requests with read-only verb @@ -451,7 +451,7 @@ func TestAuditJson(t *testing.T) { "GET", "", func(http.ResponseWriter, *http.Request) {}, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "get", @@ -474,7 +474,7 @@ func TestAuditJson(t *testing.T) { func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("foo")) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "get", @@ -497,7 +497,7 @@ func TestAuditJson(t *testing.T) { func(w http.ResponseWriter, req *http.Request) { panic("kaboom") }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "get", @@ -519,7 +519,7 @@ func TestAuditJson(t *testing.T) { "PUT", "", func(http.ResponseWriter, *http.Request) {}, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "update", @@ -543,7 +543,7 @@ func TestAuditJson(t *testing.T) { w.Write([]byte("foo")) time.Sleep(delay) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "update", @@ -567,7 +567,7 @@ func TestAuditJson(t *testing.T) { w.WriteHeader(403) w.Write([]byte("foo")) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "update", @@ -590,7 +590,7 @@ func TestAuditJson(t *testing.T) { func(w http.ResponseWriter, req *http.Request) { panic("kaboom") }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "update", @@ -614,7 +614,7 @@ func TestAuditJson(t *testing.T) { w.Write([]byte("foo")) panic("kaboom") }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "update", @@ -636,7 +636,7 @@ func TestAuditJson(t *testing.T) { "GET", "", func(http.ResponseWriter, *http.Request) {}, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -665,7 +665,7 @@ func TestAuditJson(t *testing.T) { func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("foo")) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -694,7 +694,7 @@ func TestAuditJson(t *testing.T) { func(http.ResponseWriter, *http.Request) { time.Sleep(delay) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -724,7 +724,7 @@ func TestAuditJson(t *testing.T) { time.Sleep(delay) w.WriteHeader(403) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -753,7 +753,7 @@ func TestAuditJson(t *testing.T) { func(w http.ResponseWriter, req *http.Request) { w.Write([]byte("foo")) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -783,7 +783,7 @@ func TestAuditJson(t *testing.T) { w.WriteHeader(403) w.Write([]byte("foo")) }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -812,7 +812,7 @@ func TestAuditJson(t *testing.T) { func(w http.ResponseWriter, req *http.Request) { panic("kaboom") }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -836,7 +836,7 @@ func TestAuditJson(t *testing.T) { w.Write([]byte("foo")) panic("kaboom") }, - []auditv1alpha1.Event{ + []auditv1beta1.Event{ { Stage: auditinternal.StageRequestReceived, Verb: "watch", @@ -892,8 +892,8 @@ func TestAuditJson(t *testing.T) { expectedID := types.UID("") for i, expect := range test.expected { // decode events back to check json elements. - event := &auditv1alpha1.Event{} - decoder := audit.Codecs.UniversalDecoder(auditv1alpha1.SchemeGroupVersion) + event := &auditv1beta1.Event{} + decoder := audit.Codecs.UniversalDecoder(auditv1beta1.SchemeGroupVersion) if err := runtime.DecodeInto(decoder, []byte(line[i]), event); err != nil { t.Errorf("failed decoding line %s: %v", line[i], err) continue diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/BUILD b/staging/src/k8s.io/apiserver/pkg/server/options/BUILD index 0f6d6e8c606..f2fd351eee5 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/server/options/BUILD @@ -51,6 +51,7 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission:go_default_library", "//vendor/k8s.io/apiserver/pkg/admission/initializer:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit/policy:go_default_library", "//vendor/k8s.io/apiserver/pkg/authentication/authenticatorfactory:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/audit.go b/staging/src/k8s.io/apiserver/pkg/server/options/audit.go index 99c0e1789e3..9e673848bea 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/audit.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/audit.go @@ -25,6 +25,8 @@ import ( "github.com/spf13/pflag" "gopkg.in/natefinch/lumberjack.v2" + "k8s.io/apimachinery/pkg/runtime/schema" + auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/audit/policy" "k8s.io/apiserver/pkg/features" @@ -237,7 +239,8 @@ func (o *AuditWebhookOptions) applyTo(c *server.Config) error { return nil } - webhook, err := pluginwebhook.NewBackend(o.ConfigFile, o.Mode) + // TODO: switch to beta + webhook, err := pluginwebhook.NewBackend(o.ConfigFile, o.Mode, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) if err != nil { return fmt.Errorf("initializing audit webhook: %v", err) } diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/BUILD b/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/BUILD index 2c038e88226..cb5c5b1fb03 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/BUILD +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/BUILD @@ -11,7 +11,7 @@ go_library( deps = [ "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", - "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/backend.go b/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/backend.go index 66660ff2b8c..c93ec07bf46 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/backend.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/log/backend.go @@ -23,7 +23,7 @@ import ( "k8s.io/apimachinery/pkg/runtime" auditinternal "k8s.io/apiserver/pkg/apis/audit" - auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" "k8s.io/apiserver/pkg/audit" ) @@ -66,7 +66,8 @@ func (b *backend) logEvent(ev *auditinternal.Event) { case FormatLegacy: line = audit.EventString(ev) + "\n" case FormatJson: - bs, err := runtime.Encode(audit.Codecs.LegacyCodec(auditv1alpha1.SchemeGroupVersion), ev) + // TODO(audit): figure out a general way to let the client choose their preferred version + bs, err := runtime.Encode(audit.Codecs.LegacyCodec(auditv1beta1.SchemeGroupVersion), ev) if err != nil { audit.HandlePluginError("log", err, ev) return diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/BUILD b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/BUILD index 3fc7cd48af5..f27eb13cea4 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/BUILD +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/BUILD @@ -8,15 +8,20 @@ load( go_test( name = "go_default_test", - srcs = ["webhook_test.go"], + srcs = [ + "webhook_test.go", + "webhook_v1alpha1_test.go", + ], library = ":go_default_library", deps = [ "//vendor/github.com/stretchr/testify/assert:go_default_library", "//vendor/github.com/stretchr/testify/require:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit:go_default_library", "//vendor/k8s.io/client-go/tools/clientcmd/api/v1:go_default_library", ], @@ -35,6 +40,7 @@ go_library( "//vendor/k8s.io/apiserver/pkg/apis/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/install:go_default_library", "//vendor/k8s.io/apiserver/pkg/apis/audit/v1alpha1:go_default_library", + "//vendor/k8s.io/apiserver/pkg/apis/audit/v1beta1:go_default_library", "//vendor/k8s.io/apiserver/pkg/audit:go_default_library", "//vendor/k8s.io/apiserver/pkg/util/webhook:go_default_library", ], diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go index b3844a39984..8d7f6b3a4e7 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go @@ -33,6 +33,7 @@ import ( auditinternal "k8s.io/apiserver/pkg/apis/audit" "k8s.io/apiserver/pkg/apis/audit/install" auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" "k8s.io/apiserver/pkg/audit" "k8s.io/apiserver/pkg/util/webhook" ) @@ -70,12 +71,12 @@ const pluginName = "webhook" // NewBackend returns an audit backend that sends events over HTTP to an external service. // The mode indicates the caching behavior of the webhook. Either blocking (ModeBlocking) // or buffered with batch POSTs (ModeBatch). -func NewBackend(kubeConfigFile string, mode string) (audit.Backend, error) { +func NewBackend(kubeConfigFile string, mode string, groupVersions []schema.GroupVersion) (audit.Backend, error) { switch mode { case ModeBatch: - return newBatchWebhook(kubeConfigFile) + return newBatchWebhook(kubeConfigFile, groupVersions) case ModeBlocking: - return newBlockingWebhook(kubeConfigFile) + return newBlockingWebhook(kubeConfigFile, groupVersions) default: return nil, fmt.Errorf("webhook mode %q is not in list of known modes (%s)", mode, strings.Join(AllowedModes, ",")) @@ -87,24 +88,25 @@ var ( // // Can we make these passable to NewGenericWebhook? groupFactoryRegistry = make(announced.APIGroupFactoryRegistry) - groupVersions = []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion} - registry = registered.NewOrDie("") + // TODO(audit): figure out a general way to let the client choose their preferred version + registry = registered.NewOrDie("") ) func init() { - registry.RegisterVersions(groupVersions) - if err := registry.EnableVersions(groupVersions...); err != nil { - panic(fmt.Sprintf("failed to enable version %v", groupVersions)) + allGVs := []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion, auditv1beta1.SchemeGroupVersion} + registry.RegisterVersions(allGVs) + if err := registry.EnableVersions(allGVs...); err != nil { + panic(fmt.Sprintf("failed to enable version %v", allGVs)) } install.Install(groupFactoryRegistry, registry, audit.Scheme) } -func loadWebhook(configFile string) (*webhook.GenericWebhook, error) { +func loadWebhook(configFile string, groupVersions []schema.GroupVersion) (*webhook.GenericWebhook, error) { return webhook.NewGenericWebhook(registry, audit.Codecs, configFile, groupVersions, 0) } -func newBlockingWebhook(configFile string) (*blockingBackend, error) { - w, err := loadWebhook(configFile) +func newBlockingWebhook(configFile string, groupVersions []schema.GroupVersion) (*blockingBackend, error) { + w, err := loadWebhook(configFile, groupVersions) if err != nil { return nil, err } @@ -139,8 +141,8 @@ func (b *blockingBackend) processEvents(ev ...*auditinternal.Event) error { return b.w.RestClient.Post().Body(&list).Do().Error() } -func newBatchWebhook(configFile string) (*batchBackend, error) { - w, err := loadWebhook(configFile) +func newBatchWebhook(configFile string, groupVersions []schema.GroupVersion) (*batchBackend, error) { + w, err := loadWebhook(configFile, groupVersions) if err != nil { return nil, err } diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_test.go b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_test.go index 80dec8ef9c0..d5af0e3e394 100644 --- a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_test.go +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_test.go @@ -24,6 +24,7 @@ import ( "net/http" "net/http/httptest" "os" + "reflect" "sync" "sync/atomic" "testing" @@ -33,19 +34,22 @@ import ( "github.com/stretchr/testify/require" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/serializer/json" auditinternal "k8s.io/apiserver/pkg/apis/audit" - auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" + auditv1beta1 "k8s.io/apiserver/pkg/apis/audit/v1beta1" "k8s.io/apiserver/pkg/audit" "k8s.io/client-go/tools/clientcmd/api/v1" ) // newWebhookHandler returns a handler which recieves webhook events and decodes the // request body. The caller passes a callback which is called on each webhook POST. -func newWebhookHandler(t *testing.T, cb func(events *auditv1alpha1.EventList)) http.Handler { +// The object passed to cb is of the same type as list. +func newWebhookHandler(t *testing.T, list runtime.Object, cb func(events runtime.Object)) http.Handler { s := json.NewSerializer(json.DefaultMetaFactory, audit.Scheme, audit.Scheme, false) return &testWebhookHandler{ t: t, + list: list, onEvents: cb, serializer: s, } @@ -54,7 +58,8 @@ func newWebhookHandler(t *testing.T, cb func(events *auditv1alpha1.EventList)) h type testWebhookHandler struct { t *testing.T - onEvents func(events *auditv1alpha1.EventList) + list runtime.Object + onEvents func(events runtime.Object) serializer runtime.Serializer } @@ -66,15 +71,14 @@ func (t *testWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { return fmt.Errorf("read webhook request body: %v", err) } - obj, _, err := t.serializer.Decode(body, nil, &auditv1alpha1.EventList{}) + obj, _, err := t.serializer.Decode(body, nil, t.list.DeepCopyObject()) if err != nil { return fmt.Errorf("decode request body: %v", err) } - list, ok := obj.(*auditv1alpha1.EventList) - if !ok { - return fmt.Errorf("expected *v1alpha1.EventList got %T", obj) + if reflect.TypeOf(obj).Elem() != reflect.TypeOf(t.list).Elem() { + return fmt.Errorf("expected %T, got %T", t.list, obj) } - t.onEvents(list) + t.onEvents(obj) return nil }() @@ -87,15 +91,15 @@ func (t *testWebhookHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) } -func newTestBlockingWebhook(t *testing.T, endpoint string) *blockingBackend { - return newWebhook(t, endpoint, ModeBlocking).(*blockingBackend) +func newTestBlockingWebhook(t *testing.T, endpoint string, groupVersions []schema.GroupVersion) *blockingBackend { + return newWebhook(t, endpoint, ModeBlocking, groupVersions).(*blockingBackend) } -func newTestBatchWebhook(t *testing.T, endpoint string) *batchBackend { - return newWebhook(t, endpoint, ModeBatch).(*batchBackend) +func newTestBatchWebhook(t *testing.T, endpoint string, groupVersions []schema.GroupVersion) *batchBackend { + return newWebhook(t, endpoint, ModeBatch, groupVersions).(*batchBackend) } -func newWebhook(t *testing.T, endpoint string, mode string) audit.Backend { +func newWebhook(t *testing.T, endpoint string, mode string, groupVersions []schema.GroupVersion) audit.Backend { config := v1.Config{ Clusters: []v1.NamedCluster{ {Cluster: v1.Cluster{Server: endpoint, InsecureSkipTLSVerify: true}}, @@ -112,7 +116,7 @@ func newWebhook(t *testing.T, endpoint string, mode string) audit.Backend { // NOTE(ericchiang): Do we need to use a proper serializer? require.NoError(t, stdjson.NewEncoder(f).Encode(config), "writing kubeconfig") - backend, err := NewBackend(f.Name(), mode) + backend, err := NewBackend(f.Name(), mode, groupVersions) require.NoError(t, err, "initializing backend") return backend @@ -122,12 +126,12 @@ func TestWebhook(t *testing.T) { gotEvents := false defer func() { require.True(t, gotEvents, "no events received") }() - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { gotEvents = true })) defer s.Close() - backend := newTestBlockingWebhook(t, s.URL) + backend := newTestBlockingWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) // Ensure this doesn't return a serialization error. event := &auditinternal.Event{} @@ -151,12 +155,12 @@ func TestBatchWebhookMaxEvents(t *testing.T) { } got := make(chan int, 2) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { - got <- len(events.Items) + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { + got <- len(events.(*auditv1beta1.EventList).Items) })) defer s.Close() - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) backend.ProcessEvents(events...) @@ -183,12 +187,12 @@ func TestBatchWebhookStopCh(t *testing.T) { expected := len(events) got := make(chan int, 2) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { - got <- len(events.Items) + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { + got <- len(events.(*auditv1beta1.EventList).Items) })) defer s.Close() - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) backend.ProcessEvents(events...) stopCh := make(chan struct{}) @@ -209,12 +213,12 @@ func TestBatchWebhookProcessEventsAfterStop(t *testing.T) { } got := make(chan struct{}) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { close(got) })) defer s.Close() - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) stopCh := make(chan struct{}) backend.Run(stopCh) @@ -233,13 +237,13 @@ func TestBatchWebhookShutdown(t *testing.T) { got := make(chan struct{}) contReqCh := make(chan struct{}) shutdownCh := make(chan struct{}) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { close(got) <-contReqCh })) defer s.Close() - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) backend.ProcessEvents(events...) go func() { @@ -278,12 +282,12 @@ func TestBatchWebhookEmptyBuffer(t *testing.T) { expected := len(events) got := make(chan int, 2) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { - got <- len(events.Items) + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { + got <- len(events.(*auditv1beta1.EventList).Items) })) defer s.Close() - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) stopCh := make(chan struct{}) timer := make(chan time.Time, 1) @@ -311,12 +315,12 @@ func TestBatchBufferFull(t *testing.T) { for i := range events { events[i] = &auditinternal.Event{} } - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { // Do nothing. })) defer s.Close() - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) // Make sure this doesn't block. backend.ProcessEvents(events...) @@ -344,7 +348,8 @@ func TestBatchRun(t *testing.T) { close(done) }() - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(obj runtime.Object) { + events := obj.(*auditv1beta1.EventList) atomic.AddInt64(got, int64(len(events.Items))) wg.Add(-len(events.Items)) })) @@ -353,7 +358,7 @@ func TestBatchRun(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) // Test the Run codepath. E.g. that the spawned goroutines behave correctly. backend.Run(stopCh) @@ -377,8 +382,8 @@ func TestBatchConcurrentRequests(t *testing.T) { wg := new(sync.WaitGroup) wg.Add(len(events)) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { - wg.Add(-len(events.Items)) + s := httptest.NewServer(newWebhookHandler(t, &auditv1beta1.EventList{}, func(events runtime.Object) { + wg.Add(-len(events.(*auditv1beta1.EventList).Items)) // Since the webhook makes concurrent requests, blocking on the webhook response // shouldn't block the webhook from sending more events. @@ -391,7 +396,7 @@ func TestBatchConcurrentRequests(t *testing.T) { stopCh := make(chan struct{}) defer close(stopCh) - backend := newTestBatchWebhook(t, s.URL) + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1beta1.SchemeGroupVersion}) backend.Run(stopCh) backend.ProcessEvents(events...) diff --git a/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_v1alpha1_test.go b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_v1alpha1_test.go new file mode 100644 index 00000000000..99613804121 --- /dev/null +++ b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook_v1alpha1_test.go @@ -0,0 +1,290 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package webhook + +import ( + "net/http/httptest" + "sync" + "sync/atomic" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" + auditinternal "k8s.io/apiserver/pkg/apis/audit" + auditv1alpha1 "k8s.io/apiserver/pkg/apis/audit/v1alpha1" +) + +func TestBatchWebhookMaxEventsV1Alpha1(t *testing.T) { + nRest := 10 + events := make([]*auditinternal.Event, defaultBatchMaxSize+nRest) // greater than max size. + for i := range events { + events[i] = &auditinternal.Event{} + } + + got := make(chan int, 2) + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + got <- len(events.(*auditv1alpha1.EventList).Items) + })) + defer s.Close() + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + + backend.ProcessEvents(events...) + + stopCh := make(chan struct{}) + timer := make(chan time.Time, 1) + + backend.sendBatchEvents(backend.collectEvents(stopCh, timer)) + require.Equal(t, defaultBatchMaxSize, <-got, "did not get batch max size") + + go func() { + waitForEmptyBuffer(backend) // wait for the buffer to empty + timer <- time.Now() // Trigger the wait timeout + }() + + backend.sendBatchEvents(backend.collectEvents(stopCh, timer)) + require.Equal(t, nRest, <-got, "failed to get the rest of the events") +} + +func TestBatchWebhookStopChV1Alpha1(t *testing.T) { + events := make([]*auditinternal.Event, 1) // less than max size. + for i := range events { + events[i] = &auditinternal.Event{} + } + + expected := len(events) + got := make(chan int, 2) + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + got <- len(events.(*auditv1alpha1.EventList).Items) + })) + defer s.Close() + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + backend.ProcessEvents(events...) + + stopCh := make(chan struct{}) + timer := make(chan time.Time) + + go func() { + waitForEmptyBuffer(backend) + close(stopCh) // stop channel has stopped + }() + backend.sendBatchEvents(backend.collectEvents(stopCh, timer)) + require.Equal(t, expected, <-got, "get queued events after timer expires") +} + +func TestBatchWebhookProcessEventsAfterStopV1Alpha1(t *testing.T) { + events := make([]*auditinternal.Event, 1) // less than max size. + for i := range events { + events[i] = &auditinternal.Event{} + } + + got := make(chan struct{}) + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + close(got) + })) + defer s.Close() + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + stopCh := make(chan struct{}) + + backend.Run(stopCh) + close(stopCh) + <-backend.shutdownCh + backend.ProcessEvents(events...) + assert.Equal(t, 0, len(backend.buffer), "processed events after the backed has been stopped") +} + +func TestBatchWebhookShutdownV1Alpha1(t *testing.T) { + events := make([]*auditinternal.Event, 1) + for i := range events { + events[i] = &auditinternal.Event{} + } + + got := make(chan struct{}) + contReqCh := make(chan struct{}) + shutdownCh := make(chan struct{}) + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + close(got) + <-contReqCh + })) + defer s.Close() + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + backend.ProcessEvents(events...) + + go func() { + // Assume stopCh was closed. + close(backend.buffer) + backend.sendBatchEvents(backend.collectLastEvents()) + }() + + <-got + + go func() { + close(backend.shutdownCh) + backend.Shutdown() + close(shutdownCh) + }() + + // Wait for some time in case there's a bug that allows for the Shutdown + // method to exit before all requests has been completed. + time.Sleep(1 * time.Second) + select { + case <-shutdownCh: + t.Fatal("Backend shut down before all requests finished") + default: + // Continue. + } + + close(contReqCh) + <-shutdownCh +} + +func TestBatchWebhookEmptyBufferV1Alpha1(t *testing.T) { + events := make([]*auditinternal.Event, 1) // less than max size. + for i := range events { + events[i] = &auditinternal.Event{} + } + + expected := len(events) + got := make(chan int, 2) + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + got <- len(events.(*auditv1alpha1.EventList).Items) + })) + defer s.Close() + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + + stopCh := make(chan struct{}) + timer := make(chan time.Time, 1) + + timer <- time.Now() // Timer is done. + + // Buffer is empty, no events have been queued. This should exit but send no events. + backend.sendBatchEvents(backend.collectEvents(stopCh, timer)) + + // Send additional events after the sendBatchEvents has been called. + backend.ProcessEvents(events...) + go func() { + waitForEmptyBuffer(backend) + timer <- time.Now() + }() + + backend.sendBatchEvents(backend.collectEvents(stopCh, timer)) + + // Make sure we didn't get a POST with zero events. + require.Equal(t, expected, <-got, "expected one event") +} + +func TestBatchBufferFullV1Alpha1(t *testing.T) { + events := make([]*auditinternal.Event, defaultBatchBufferSize+1) // More than buffered size + for i := range events { + events[i] = &auditinternal.Event{} + } + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + // Do nothing. + })) + defer s.Close() + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + + // Make sure this doesn't block. + backend.ProcessEvents(events...) +} + +func TestBatchRunV1Alpha1(t *testing.T) { + + // Divisable by max batch size so we don't have to wait for a minute for + // the test to finish. + events := make([]*auditinternal.Event, defaultBatchMaxSize*3) + for i := range events { + events[i] = &auditinternal.Event{} + } + + got := new(int64) + want := len(events) + + wg := new(sync.WaitGroup) + wg.Add(want) + done := make(chan struct{}) + + go func() { + wg.Wait() + // When the expected number of events have been received, close the channel. + close(done) + }() + + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(obj runtime.Object) { + events := obj.(*auditv1alpha1.EventList) + atomic.AddInt64(got, int64(len(events.Items))) + wg.Add(-len(events.Items)) + })) + defer s.Close() + + stopCh := make(chan struct{}) + defer close(stopCh) + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + + // Test the Run codepath. E.g. that the spawned goroutines behave correctly. + backend.Run(stopCh) + + backend.ProcessEvents(events...) + + select { + case <-done: + // Received all the events. + case <-time.After(2 * time.Minute): + t.Errorf("expected %d events got %d", want, atomic.LoadInt64(got)) + } +} + +func TestBatchConcurrentRequestsV1Alpha1(t *testing.T) { + events := make([]*auditinternal.Event, defaultBatchBufferSize) // Don't drop events + for i := range events { + events[i] = &auditinternal.Event{} + } + + wg := new(sync.WaitGroup) + wg.Add(len(events)) + + s := httptest.NewServer(newWebhookHandler(t, &auditv1alpha1.EventList{}, func(events runtime.Object) { + wg.Add(-len(events.(*auditv1alpha1.EventList).Items)) + + // Since the webhook makes concurrent requests, blocking on the webhook response + // shouldn't block the webhook from sending more events. + // + // Wait for all responses to be received before sending the response. + wg.Wait() + })) + defer s.Close() + + stopCh := make(chan struct{}) + defer close(stopCh) + + backend := newTestBatchWebhook(t, s.URL, []schema.GroupVersion{auditv1alpha1.SchemeGroupVersion}) + backend.Run(stopCh) + + backend.ProcessEvents(events...) + // Wait for the webhook to receive all events. + wg.Wait() +}