diff --git a/hack/.golint_failures b/hack/.golint_failures index 0871b07a4326e..7c5c63055c772 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -28,6 +28,7 @@ pkg/apis/apps/v1 pkg/apis/apps/v1beta1 pkg/apis/apps/v1beta2 pkg/apis/apps/validation +pkg/apis/auditregistration/v1alpha1 pkg/apis/authentication pkg/apis/authentication/v1 pkg/apis/authentication/v1beta1 @@ -283,6 +284,7 @@ pkg/registry/apps/replicaset/storage pkg/registry/apps/rest pkg/registry/apps/statefulset pkg/registry/apps/statefulset/storage +pkg/registry/auditregistration/rest pkg/registry/authentication/rest pkg/registry/authentication/tokenreview pkg/registry/authorization/localsubjectaccessreview @@ -453,6 +455,7 @@ staging/src/k8s.io/api/admissionregistration/v1beta1 staging/src/k8s.io/api/apps/v1 staging/src/k8s.io/api/apps/v1beta1 staging/src/k8s.io/api/apps/v1beta2 +staging/src/k8s.io/api/auditregistration/v1alpha1 staging/src/k8s.io/api/authentication/v1 staging/src/k8s.io/api/authentication/v1beta1 staging/src/k8s.io/api/authorization/v1 @@ -555,7 +558,6 @@ staging/src/k8s.io/apiserver/pkg/apis/audit staging/src/k8s.io/apiserver/pkg/apis/audit/v1 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/config/v1alpha1 staging/src/k8s.io/apiserver/pkg/apis/example staging/src/k8s.io/apiserver/pkg/apis/example/v1 diff --git a/hack/lib/init.sh b/hack/lib/init.sh index a9ef8415f41ed..2a676f804a09b 100755 --- a/hack/lib/init.sh +++ b/hack/lib/init.sh @@ -62,6 +62,7 @@ admission.k8s.io/v1beta1 \ apps/v1beta1 \ apps/v1beta2 \ apps/v1 \ +auditregistration.k8s.io/v1alpha1 \ authentication.k8s.io/v1 \ authentication.k8s.io/v1beta1 \ authorization.k8s.io/v1 \ diff --git a/hack/update-generated-protobuf-dockerized.sh b/hack/update-generated-protobuf-dockerized.sh index 798f832fc8184..612cfba3648cb 100755 --- a/hack/update-generated-protobuf-dockerized.sh +++ b/hack/update-generated-protobuf-dockerized.sh @@ -78,6 +78,7 @@ PACKAGES=( k8s.io/api/admissionregistration/v1alpha1 k8s.io/api/admissionregistration/v1beta1 k8s.io/api/admission/v1beta1 + k8s.io/api/auditregistration/v1alpha1 k8s.io/api/networking/v1 k8s.io/metrics/pkg/apis/metrics/v1alpha1 k8s.io/metrics/pkg/apis/metrics/v1beta1 diff --git a/pkg/api/testapi/testapi.go b/pkg/api/testapi/testapi.go index 31d419620c9aa..32b81d29d0e7d 100644 --- a/pkg/api/testapi/testapi.go +++ b/pkg/api/testapi/testapi.go @@ -37,6 +37,7 @@ import ( "k8s.io/kubernetes/pkg/apis/admission" "k8s.io/kubernetes/pkg/apis/admissionregistration" "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/auditregistration" "k8s.io/kubernetes/pkg/apis/authorization" "k8s.io/kubernetes/pkg/apis/autoscaling" "k8s.io/kubernetes/pkg/apis/batch" @@ -57,6 +58,7 @@ import ( _ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admissionregistration/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" + _ "k8s.io/kubernetes/pkg/apis/auditregistration/install" _ "k8s.io/kubernetes/pkg/apis/authentication/install" _ "k8s.io/kubernetes/pkg/apis/authorization/install" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" @@ -267,6 +269,12 @@ func init() { externalGroupVersion: externalGroupVersion, } } + if _, ok := Groups[auditregistration.GroupName]; !ok { + externalGroupVersion := schema.GroupVersion{Group: auditregistration.GroupName, Version: legacyscheme.Scheme.PrioritizedVersionsForGroup(auditregistration.GroupName)[0].Version} + Groups[auditregistration.GroupName] = TestGroup{ + externalGroupVersion: externalGroupVersion, + } + } Default = Groups[api.GroupName] Autoscaling = Groups[autoscaling.GroupName] diff --git a/pkg/api/testing/defaulting_test.go b/pkg/api/testing/defaulting_test.go index 58d9dc487c25c..504535772729c 100644 --- a/pkg/api/testing/defaulting_test.go +++ b/pkg/api/testing/defaulting_test.go @@ -136,6 +136,8 @@ func TestDefaulting(t *testing.T) { {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingWebhookConfigurationList"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "MutatingWebhookConfiguration"}: {}, {Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "MutatingWebhookConfigurationList"}: {}, + {Group: "auditregistration.k8s.io", Version: "v1alpha1", Kind: "AuditSink"}: {}, + {Group: "auditregistration.k8s.io", Version: "v1alpha1", Kind: "AuditSinkList"}: {}, {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicy"}: {}, {Group: "networking.k8s.io", Version: "v1", Kind: "NetworkPolicyList"}: {}, {Group: "storage.k8s.io", Version: "v1beta1", Kind: "StorageClass"}: {}, diff --git a/pkg/api/testing/fuzzer.go b/pkg/api/testing/fuzzer.go index 2a2bb3175ba4a..65b5f42bf907e 100644 --- a/pkg/api/testing/fuzzer.go +++ b/pkg/api/testing/fuzzer.go @@ -29,6 +29,7 @@ import ( runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" admissionregistrationfuzzer "k8s.io/kubernetes/pkg/apis/admissionregistration/fuzzer" appsfuzzer "k8s.io/kubernetes/pkg/apis/apps/fuzzer" + auditregistrationfuzzer "k8s.io/kubernetes/pkg/apis/auditregistration/fuzzer" autoscalingfuzzer "k8s.io/kubernetes/pkg/apis/autoscaling/fuzzer" batchfuzzer "k8s.io/kubernetes/pkg/apis/batch/fuzzer" certificatesfuzzer "k8s.io/kubernetes/pkg/apis/certificates/fuzzer" @@ -101,6 +102,7 @@ var FuzzerFuncs = fuzzer.MergeFuzzerFuncs( policyfuzzer.Funcs, certificatesfuzzer.Funcs, admissionregistrationfuzzer.Funcs, + auditregistrationfuzzer.Funcs, storagefuzzer.Funcs, networkingfuzzer.Funcs, ) diff --git a/pkg/apis/admissionregistration/validation/validation.go b/pkg/apis/admissionregistration/validation/validation.go index 14d3d799bf466..9174bb0835a5e 100644 --- a/pkg/apis/admissionregistration/validation/validation.go +++ b/pkg/apis/admissionregistration/validation/validation.go @@ -240,6 +240,7 @@ func validateWebhookClientConfig(fldPath *field.Path, cc *admissionregistration. return allErrors } +// note: this has copy/paste inheritance in auditregistration func validateWebhookService(fldPath *field.Path, svc *admissionregistration.ServiceReference) field.ErrorList { var allErrors field.ErrorList diff --git a/pkg/apis/auditregistration/doc.go b/pkg/apis/auditregistration/doc.go new file mode 100644 index 0000000000000..1046b5a62be11 --- /dev/null +++ b/pkg/apis/auditregistration/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2018 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 +// +groupName=auditregistration.k8s.io + +package auditregistration // import "k8s.io/kubernetes/pkg/apis/auditregistration" diff --git a/pkg/apis/auditregistration/fuzzer/fuzzer.go b/pkg/apis/auditregistration/fuzzer/fuzzer.go new file mode 100644 index 0000000000000..eb141540fe597 --- /dev/null +++ b/pkg/apis/auditregistration/fuzzer/fuzzer.go @@ -0,0 +1,38 @@ +/* +Copyright 2018 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" + + runtimeserializer "k8s.io/apimachinery/pkg/runtime/serializer" + "k8s.io/kubernetes/pkg/apis/auditregistration" +) + +// Funcs returns the fuzzer functions for the auditregistration api group. +var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { + return []interface{}{ + func(obj *auditregistration.AuditSink, c fuzz.Continue) { + c.FuzzNoCustom(obj) + v := int64(1) + obj.Spec.Webhook.Throttle = &auditregistration.WebhookThrottleConfig{ + QPS: &v, + Burst: &v, + } + }, + } +} diff --git a/pkg/apis/auditregistration/install/install.go b/pkg/apis/auditregistration/install/install.go new file mode 100644 index 0000000000000..ffb905cd131de --- /dev/null +++ b/pkg/apis/auditregistration/install/install.go @@ -0,0 +1,38 @@ +/* +Copyright 2018 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 adds the experimental API group, making it available as +// an option to all of the API encoding/decoding machinery. +package install + +import ( + "k8s.io/apimachinery/pkg/runtime" + utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/apis/auditregistration" + "k8s.io/kubernetes/pkg/apis/auditregistration/v1alpha1" +) + +func init() { + Install(legacyscheme.Scheme) +} + +// Install registers the API group and adds types to a scheme +func Install(scheme *runtime.Scheme) { + utilruntime.Must(auditregistration.AddToScheme(scheme)) + utilruntime.Must(v1alpha1.AddToScheme(scheme)) + utilruntime.Must(scheme.SetVersionPriority(v1alpha1.SchemeGroupVersion)) +} diff --git a/pkg/apis/auditregistration/register.go b/pkg/apis/auditregistration/register.go new file mode 100644 index 0000000000000..ebaa38109482a --- /dev/null +++ b/pkg/apis/auditregistration/register.go @@ -0,0 +1,53 @@ +/* +Copyright 2018 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 auditregistration + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName is the group name use in this package +const GroupName = "auditregistration.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: runtime.APIVersionInternal} + +// Kind takes an unqualified kind and returns a Group qualified GroupKind +func Kind(kind string) schema.GroupKind { + return SchemeGroupVersion.WithKind(kind).GroupKind() +} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + // SchemeBuilder for audit registration + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) + // AddToScheme audit registration + AddToScheme = SchemeBuilder.AddToScheme +) + +func addKnownTypes(scheme *runtime.Scheme) error { + scheme.AddKnownTypes(SchemeGroupVersion, + &AuditSink{}, + &AuditSinkList{}, + ) + return nil +} diff --git a/pkg/apis/auditregistration/types.go b/pkg/apis/auditregistration/types.go new file mode 100644 index 0000000000000..8362483d21e79 --- /dev/null +++ b/pkg/apis/auditregistration/types.go @@ -0,0 +1,197 @@ +/* +Copyright 2018 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:openapi-gen=true + +package auditregistration + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// 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 and watches). + LevelRequestResponse Level = "RequestResponse" +) + +// Stage defines the stages in request handling during which audit events may be generated. +type Stage string + +// Valid audit stages. +const ( + // The stage for events generated after the audit handler receives the request, but before it + // is delegated down the handler chain. + StageRequestReceived = "RequestReceived" + // The stage for events generated after 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 after the response body has been completed, and no more bytes + // will be sent. + StageResponseComplete = "ResponseComplete" + // The stage for events generated when a panic occurred. + StagePanic = "Panic" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuditSink represents a cluster level sink for audit data +type AuditSink struct { + metav1.TypeMeta + + // +optional + metav1.ObjectMeta + + // Spec defines the audit sink spec + Spec AuditSinkSpec +} + +// AuditSinkSpec is the spec for the audit sink object +type AuditSinkSpec struct { + // Policy defines the policy for selecting which events should be sent to the backend + // required + Policy Policy + + // Webhook to send events + // required + Webhook Webhook +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuditSinkList is a list of a audit sink items. +type AuditSinkList struct { + metav1.TypeMeta + + // +optional + metav1.ListMeta + + // List of audit configurations. + Items []AuditSink +} + +// Policy defines the configuration of how audit events are logged +type Policy struct { + // The Level that all requests are recorded at. + // available options: None, Metadata, Request, RequestResponse + // required + Level Level + + // Stages is a list of stages for which events are created. + // +optional + Stages []Stage +} + +// Webhook holds the configuration of the webhooks +type Webhook struct { + // Throttle holds the options for throttling the webhook + // +optional + Throttle *WebhookThrottleConfig + + // ClientConfig holds the connection parameters for the webhook + // required + ClientConfig WebhookClientConfig +} + +// WebhookThrottleConfig holds the configuration for throttling +type WebhookThrottleConfig struct { + // QPS maximum number of batches per second + // default 10 QPS + // +optional + QPS *int64 + + // Burst is the maximum number of events sent at the same moment + // default 15 QPS + // +optional + Burst *int64 +} + +// WebhookClientConfig contains the information to make a connection with the webhook +type WebhookClientConfig struct { + // `url` gives the location of the webhook, in standard URL form + // (`[scheme://]host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `host` should not refer to a service running in the cluster; use + // the `service` field instead. The host might be resolved via external + // DNS in some apiservers (e.g., `kube-apiserver` cannot resolve + // in-cluster DNS as that would be a layering violation). `host` may + // also be an IP address. + // + // Please note that using `localhost` or `127.0.0.1` as a `host` is + // risky unless you take great care to run this webhook on all hosts + // which run an apiserver which might need to make calls to this + // webhook. Such installs are likely to be non-portable, i.e., not easy + // to turn up in a new cluster. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A path is optional, and if present may be any string permissible in + // a URL. You may use the path to pass an arbitrary string to the + // webhook, for example, a cluster identifier. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + URL *string + + // `service` is a reference to the service for this webhook. Either + // `service` or `url` must be specified. + // + // If the webhook is running within the cluster, then you should use `service`. + // + // Port 443 will be used if it is open, otherwise it is an error. + // + // +optional + Service *ServiceReference + + // `caBundle` is a PEM encoded CA bundle which will be used to validate + // the webhook's server certificate. + // defaults to the apiservers CA bundle for the endpoint type + // +optional + CABundle []byte +} + +// ServiceReference holds a reference to Service.legacy.k8s.io +type ServiceReference struct { + // `namespace` is the namespace of the service. + // Required + Namespace string + + // `name` is the name of the service. + // Required + Name string + + // `path` is an optional URL path which will be sent in any request to + // this service. + // +optional + Path *string +} diff --git a/pkg/apis/auditregistration/v1alpha1/defaults.go b/pkg/apis/auditregistration/v1alpha1/defaults.go new file mode 100644 index 0000000000000..1884a3907928c --- /dev/null +++ b/pkg/apis/auditregistration/v1alpha1/defaults.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 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 v1alpha1 + +import ( + auditregistrationv1alpha1 "k8s.io/api/auditregistration/v1alpha1" + "k8s.io/apimachinery/pkg/runtime" + utilpointer "k8s.io/utils/pointer" +) + +const ( + // DefaultQPS is the default QPS value + DefaultQPS = int64(10) + // DefaultBurst is the default burst value + DefaultBurst = int64(15) +) + +// DefaultThrottle is a default throttle config +func DefaultThrottle() *auditregistrationv1alpha1.WebhookThrottleConfig { + return &auditregistrationv1alpha1.WebhookThrottleConfig{ + QPS: utilpointer.Int64Ptr(DefaultQPS), + Burst: utilpointer.Int64Ptr(DefaultBurst), + } +} + +func addDefaultingFuncs(scheme *runtime.Scheme) error { + return RegisterDefaults(scheme) +} + +// SetDefaults_AuditSink sets defaults if the audit sink isn't present +func SetDefaults_AuditSink(obj *auditregistrationv1alpha1.AuditSink) { + if obj.Spec.Webhook.Throttle != nil { + if obj.Spec.Webhook.Throttle.QPS == nil { + obj.Spec.Webhook.Throttle.QPS = utilpointer.Int64Ptr(DefaultQPS) + } + if obj.Spec.Webhook.Throttle.Burst == nil { + obj.Spec.Webhook.Throttle.Burst = utilpointer.Int64Ptr(DefaultBurst) + } + } else { + obj.Spec.Webhook.Throttle = DefaultThrottle() + } +} diff --git a/pkg/apis/auditregistration/v1alpha1/defaults_test.go b/pkg/apis/auditregistration/v1alpha1/defaults_test.go new file mode 100644 index 0000000000000..acfc7fba8a192 --- /dev/null +++ b/pkg/apis/auditregistration/v1alpha1/defaults_test.go @@ -0,0 +1,165 @@ +/* +Copyright 2018 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 v1alpha1_test + +import ( + "reflect" + "testing" + + auditregistrationv1alpha1 "k8s.io/api/auditregistration/v1alpha1" + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/kubernetes/pkg/api/legacyscheme" + _ "k8s.io/kubernetes/pkg/apis/auditregistration/install" + . "k8s.io/kubernetes/pkg/apis/auditregistration/v1alpha1" + utilpointer "k8s.io/utils/pointer" +) + +func TestSetDefaultAuditSink(t *testing.T) { + defaultURL := "http://test" + tests := []struct { + original *auditregistrationv1alpha1.AuditSink + expected *auditregistrationv1alpha1.AuditSink + }{ + { // Missing Throttle + original: &auditregistrationv1alpha1.AuditSink{ + Spec: auditregistrationv1alpha1.AuditSinkSpec{ + Policy: auditregistrationv1alpha1.Policy{ + Level: auditregistrationv1alpha1.LevelMetadata, + }, + Webhook: auditregistrationv1alpha1.Webhook{ + ClientConfig: auditregistrationv1alpha1.WebhookClientConfig{ + URL: &defaultURL, + }, + }, + }, + }, + expected: &auditregistrationv1alpha1.AuditSink{ + Spec: auditregistrationv1alpha1.AuditSinkSpec{ + Policy: auditregistrationv1alpha1.Policy{ + Level: auditregistrationv1alpha1.LevelMetadata, + }, + Webhook: auditregistrationv1alpha1.Webhook{ + Throttle: DefaultThrottle(), + ClientConfig: auditregistrationv1alpha1.WebhookClientConfig{ + URL: &defaultURL, + }, + }, + }, + }, + }, + { // Missing QPS + original: &auditregistrationv1alpha1.AuditSink{ + Spec: auditregistrationv1alpha1.AuditSinkSpec{ + Policy: auditregistrationv1alpha1.Policy{ + Level: auditregistrationv1alpha1.LevelMetadata, + }, + Webhook: auditregistrationv1alpha1.Webhook{ + Throttle: &auditregistrationv1alpha1.WebhookThrottleConfig{ + Burst: utilpointer.Int64Ptr(1), + }, + ClientConfig: auditregistrationv1alpha1.WebhookClientConfig{ + URL: &defaultURL, + }, + }, + }, + }, + expected: &auditregistrationv1alpha1.AuditSink{ + Spec: auditregistrationv1alpha1.AuditSinkSpec{ + Policy: auditregistrationv1alpha1.Policy{ + Level: auditregistrationv1alpha1.LevelMetadata, + }, + Webhook: auditregistrationv1alpha1.Webhook{ + Throttle: &auditregistrationv1alpha1.WebhookThrottleConfig{ + QPS: DefaultThrottle().QPS, + Burst: utilpointer.Int64Ptr(1), + }, + ClientConfig: auditregistrationv1alpha1.WebhookClientConfig{ + URL: &defaultURL, + }, + }, + }, + }, + }, + { // Missing Burst + original: &auditregistrationv1alpha1.AuditSink{ + Spec: auditregistrationv1alpha1.AuditSinkSpec{ + Policy: auditregistrationv1alpha1.Policy{ + Level: auditregistrationv1alpha1.LevelMetadata, + }, + Webhook: auditregistrationv1alpha1.Webhook{ + Throttle: &auditregistrationv1alpha1.WebhookThrottleConfig{ + QPS: utilpointer.Int64Ptr(1), + }, + ClientConfig: auditregistrationv1alpha1.WebhookClientConfig{ + URL: &defaultURL, + }, + }, + }, + }, + expected: &auditregistrationv1alpha1.AuditSink{ + Spec: auditregistrationv1alpha1.AuditSinkSpec{ + Policy: auditregistrationv1alpha1.Policy{ + Level: auditregistrationv1alpha1.LevelMetadata, + }, + Webhook: auditregistrationv1alpha1.Webhook{ + Throttle: &auditregistrationv1alpha1.WebhookThrottleConfig{ + QPS: utilpointer.Int64Ptr(1), + Burst: DefaultThrottle().Burst, + }, + ClientConfig: auditregistrationv1alpha1.WebhookClientConfig{ + URL: &defaultURL, + }, + }, + }, + }, + }, + } + + for i, test := range tests { + original := test.original + expected := test.expected + obj2 := roundTrip(t, runtime.Object(original)) + got, ok := obj2.(*auditregistrationv1alpha1.AuditSink) + if !ok { + t.Fatalf("(%d) unexpected object: %v", i, obj2) + } + if !apiequality.Semantic.DeepEqual(got.Spec, expected.Spec) { + t.Errorf("(%d) got different than expected\ngot:\n\t%+v\nexpected:\n\t%+v", i, got.Spec, expected.Spec) + } + } +} + +func roundTrip(t *testing.T, obj runtime.Object) runtime.Object { + data, err := runtime.Encode(legacyscheme.Codecs.LegacyCodec(SchemeGroupVersion), obj) + if err != nil { + t.Errorf("%v\n %#v", err, obj) + return nil + } + obj2, err := runtime.Decode(legacyscheme.Codecs.UniversalDecoder(), data) + if err != nil { + t.Errorf("%v\nData: %s\nSource: %#v", err, string(data), obj) + return nil + } + obj3 := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(runtime.Object) + err = legacyscheme.Scheme.Convert(obj2, obj3, nil) + if err != nil { + t.Errorf("%v\nSource: %#v", err, obj2) + return nil + } + return obj3 +} diff --git a/pkg/apis/auditregistration/v1alpha1/doc.go b/pkg/apis/auditregistration/v1alpha1/doc.go new file mode 100644 index 0000000000000..606c457c30274 --- /dev/null +++ b/pkg/apis/auditregistration/v1alpha1/doc.go @@ -0,0 +1,24 @@ +/* +Copyright 2018 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:conversion-gen=k8s.io/kubernetes/pkg/apis/auditregistration +// +k8s:conversion-gen-external-types=k8s.io/api/auditregistration/v1alpha1 +// +k8s:defaulter-gen=TypeMeta +// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/auditregistration/v1alpha1 + +// +groupName=auditregistration.k8s.io + +package v1alpha1 // import "k8s.io/kubernetes/pkg/apis/auditregistration/v1alpha1" diff --git a/pkg/apis/auditregistration/v1alpha1/register.go b/pkg/apis/auditregistration/v1alpha1/register.go new file mode 100644 index 0000000000000..a74b162b6ad26 --- /dev/null +++ b/pkg/apis/auditregistration/v1alpha1/register.go @@ -0,0 +1,46 @@ +/* +Copyright 2018 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 v1alpha1 + +import ( + auditregistrationv1alpha1 "k8s.io/api/auditregistration/v1alpha1" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// GroupName for audit registration +const GroupName = "auditregistration.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// Resource takes an unqualified resource and returns a Group qualified GroupResource +func Resource(resource string) schema.GroupResource { + return SchemeGroupVersion.WithResource(resource).GroupResource() +} + +var ( + localSchemeBuilder = &auditregistrationv1alpha1.SchemeBuilder + // AddToScheme audit registration + 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(addDefaultingFuncs) +} diff --git a/pkg/apis/auditregistration/validation/validation.go b/pkg/apis/auditregistration/validation/validation.go new file mode 100644 index 0000000000000..693a657418189 --- /dev/null +++ b/pkg/apis/auditregistration/validation/validation.go @@ -0,0 +1,200 @@ +/* +Copyright 2018 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 validation + +import ( + "fmt" + "net/url" + "strings" + + genericvalidation "k8s.io/apimachinery/pkg/api/validation" + "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/apimachinery/pkg/util/validation" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/auditregistration" +) + +// ValidateAuditSink validates the AuditSinks +func ValidateAuditSink(as *auditregistration.AuditSink) field.ErrorList { + allErrs := genericvalidation.ValidateObjectMeta(&as.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) + allErrs = append(allErrs, ValidateAuditSinkSpec(as.Spec, field.NewPath("spec"))...) + return allErrs +} + +// ValidateAuditSinkSpec validates the sink spec for audit +func ValidateAuditSinkSpec(s auditregistration.AuditSinkSpec, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + allErrs = append(allErrs, ValidatePolicy(s.Policy, field.NewPath("policy"))...) + allErrs = append(allErrs, ValidateWebhook(s.Webhook, field.NewPath("webhook"))...) + return allErrs +} + +// ValidateWebhook validates the webhook +func ValidateWebhook(w auditregistration.Webhook, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if w.Throttle != nil { + allErrs = append(allErrs, ValidateWebhookThrottleConfig(w.Throttle, fldPath.Child("throttle"))...) + } + allErrs = append(allErrs, ValidateWebhookClientConfig(&w.ClientConfig, fldPath.Child("clientConfig"))...) + return allErrs +} + +// ValidateWebhookThrottleConfig validates the throttle config +func ValidateWebhookThrottleConfig(c *auditregistration.WebhookThrottleConfig, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + if c.QPS != nil && *c.QPS <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("qps"), c.QPS, "qps must be a positive number")) + } + if c.Burst != nil && *c.Burst <= 0 { + allErrs = append(allErrs, field.Invalid(fldPath.Child("burst"), c.Burst, "burst must be a positive number")) + } + return allErrs +} + +// ValidateWebhookClientConfig validates the WebhookClientConfig +// note: this is largely copy/paste inheritance from admissionregistration with subtle changes +func ValidateWebhookClientConfig(cc *auditregistration.WebhookClientConfig, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + if (cc.URL == nil) == (cc.Service == nil) { + allErrors = append(allErrors, field.Required(fldPath.Child("url"), "exactly one of url or service is required")) + } + + if cc.URL != nil { + const form = "; desired format: https://host[/path]" + if u, err := url.Parse(*cc.URL); err != nil { + allErrors = append(allErrors, field.Required(fldPath.Child("url"), "url must be a valid URL: "+err.Error()+form)) + } else { + if len(u.Host) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.Host, "host must be provided"+form)) + } + if u.User != nil { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.User.String(), "user information is not permitted in the URL")) + } + if len(u.Fragment) != 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.Fragment, "fragments are not permitted in the URL")) + } + if len(u.RawQuery) != 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("url"), u.RawQuery, "query parameters are not permitted in the URL")) + } + } + } + + if cc.Service != nil { + allErrors = append(allErrors, validateWebhookService(cc.Service, fldPath.Child("service"))...) + } + return allErrors +} + +// note: this is copy/paste inheritance from admissionregistration +func validateWebhookService(svc *auditregistration.ServiceReference, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + + if len(svc.Name) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("name"), "service name is required")) + } + + if len(svc.Namespace) == 0 { + allErrors = append(allErrors, field.Required(fldPath.Child("namespace"), "service namespace is required")) + } + + if svc.Path == nil { + return allErrors + } + + // TODO: replace below with url.Parse + verifying that host is empty? + + urlPath := *svc.Path + if urlPath == "/" || len(urlPath) == 0 { + return allErrors + } + if urlPath == "//" { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "segment[0] may not be empty")) + return allErrors + } + + if !strings.HasPrefix(urlPath, "/") { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, "must start with a '/'")) + } + + urlPathToCheck := urlPath[1:] + if strings.HasSuffix(urlPathToCheck, "/") { + urlPathToCheck = urlPathToCheck[:len(urlPathToCheck)-1] + } + steps := strings.Split(urlPathToCheck, "/") + for i, step := range steps { + if len(step) == 0 { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d] may not be empty", i))) + continue + } + failures := validation.IsDNS1123Subdomain(step) + for _, failure := range failures { + allErrors = append(allErrors, field.Invalid(fldPath.Child("path"), urlPath, fmt.Sprintf("segment[%d]: %v", i, failure))) + } + } + + return allErrors +} + +// ValidatePolicy validates the audit policy +func ValidatePolicy(policy auditregistration.Policy, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + allErrs = append(allErrs, validateStages(policy.Stages, fldPath.Child("stages"))...) + allErrs = append(allErrs, validateLevel(policy.Level, fldPath.Child("level"))...) + if policy.Level != auditregistration.LevelNone && len(policy.Stages) == 0 { + return field.ErrorList{field.Required(fldPath.Child("stages"), "")} + } + return allErrs +} + +var validLevels = sets.NewString( + string(auditregistration.LevelNone), + string(auditregistration.LevelMetadata), + string(auditregistration.LevelRequest), + string(auditregistration.LevelRequestResponse), +) + +var validStages = sets.NewString( + string(auditregistration.StageRequestReceived), + string(auditregistration.StageResponseStarted), + string(auditregistration.StageResponseComplete), + string(auditregistration.StagePanic), +) + +func validateLevel(level auditregistration.Level, fldPath *field.Path) field.ErrorList { + if string(level) == "" { + return field.ErrorList{field.Required(fldPath, "")} + } + if !validLevels.Has(string(level)) { + return field.ErrorList{field.NotSupported(fldPath, level, validLevels.List())} + } + return nil +} + +func validateStages(stages []auditregistration.Stage, fldPath *field.Path) field.ErrorList { + var allErrs field.ErrorList + for i, stage := range stages { + if !validStages.Has(string(stage)) { + allErrs = append(allErrs, field.Invalid(fldPath.Index(i), string(stage), "allowed stages are "+strings.Join(validStages.List(), ","))) + } + } + return allErrs +} + +// ValidateAuditSinkUpdate validates an update to the object +func ValidateAuditSinkUpdate(newC, oldC *auditregistration.AuditSink) field.ErrorList { + return ValidateAuditSink(newC) +} diff --git a/pkg/apis/auditregistration/validation/validation_test.go b/pkg/apis/auditregistration/validation/validation_test.go new file mode 100644 index 0000000000000..522fb246ce824 --- /dev/null +++ b/pkg/apis/auditregistration/validation/validation_test.go @@ -0,0 +1,324 @@ +/* +Copyright 2018 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 validation + +import ( + "strings" + "testing" + + "github.com/stretchr/testify/require" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/kubernetes/pkg/apis/auditregistration" +) + +func TestValidateAuditSink(t *testing.T) { + testQPS := int64(10) + testURL := "http://localhost" + testCases := []struct { + name string + conf auditregistration.AuditSink + numErr int + }{ + { + name: "should pass full config", + conf: auditregistration.AuditSink{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myconf", + }, + Spec: auditregistration.AuditSinkSpec{ + Policy: auditregistration.Policy{ + Level: auditregistration.LevelRequest, + Stages: []auditregistration.Stage{ + auditregistration.StageRequestReceived, + }, + }, + Webhook: auditregistration.Webhook{ + Throttle: &auditregistration.WebhookThrottleConfig{ + QPS: &testQPS, + }, + ClientConfig: auditregistration.WebhookClientConfig{ + URL: &testURL, + }, + }, + }, + }, + numErr: 0, + }, + { + name: "should fail no policy", + conf: auditregistration.AuditSink{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myconf", + }, + Spec: auditregistration.AuditSinkSpec{ + Webhook: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: &testURL, + }, + }, + }, + }, + numErr: 1, + }, + { + name: "should fail no webhook", + conf: auditregistration.AuditSink{ + ObjectMeta: metav1.ObjectMeta{ + Name: "myconf", + }, + Spec: auditregistration.AuditSinkSpec{ + Policy: auditregistration.Policy{ + Level: auditregistration.LevelMetadata, + Stages: []auditregistration.Stage{ + auditregistration.StageRequestReceived, + }, + }, + }, + }, + numErr: 1, + }, + } + + for _, test := range testCases { + t.Run(test.name, func(t *testing.T) { + errs := ValidateAuditSink(&test.conf) + require.Len(t, errs, test.numErr) + }) + } +} + +func TestValidatePolicy(t *testing.T) { + successCases := []auditregistration.Policy{} + successCases = append(successCases, auditregistration.Policy{ // Policy with omitStages and level + Level: auditregistration.LevelRequest, + Stages: []auditregistration.Stage{ + auditregistration.Stage("RequestReceived"), + auditregistration.Stage("ResponseStarted"), + }, + }) + successCases = append(successCases, auditregistration.Policy{Level: auditregistration.LevelNone}) // Policy with none level only + + for i, policy := range successCases { + if errs := ValidatePolicy(policy, field.NewPath("policy")); len(errs) != 0 { + t.Errorf("[%d] Expected policy %#v to be valid: %v", i, policy, errs) + } + } + + errorCases := []auditregistration.Policy{} + errorCases = append(errorCases, auditregistration.Policy{}) // Empty policy // Policy with missing level + errorCases = append(errorCases, auditregistration.Policy{Stages: []auditregistration.Stage{ // Policy with invalid stages + auditregistration.Stage("Bad")}}) + errorCases = append(errorCases, auditregistration.Policy{Level: auditregistration.Level("invalid")}) // Policy with bad level + errorCases = append(errorCases, auditregistration.Policy{Level: auditregistration.LevelMetadata}) // Policy without stages + + for i, policy := range errorCases { + if errs := ValidatePolicy(policy, field.NewPath("policy")); len(errs) == 0 { + t.Errorf("[%d] Expected policy %#v to be invalid!", i, policy) + } + } +} + +func strPtr(s string) *string { return &s } + +func TestValidateWebhookConfiguration(t *testing.T) { + tests := []struct { + name string + config auditregistration.Webhook + expectedError string + }{ + { + name: "both service and URL missing", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{}, + }, + expectedError: `exactly one of`, + }, + { + name: "both service and URL provided", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + }, + URL: strPtr("example.com/k8s/webhook"), + }, + }, + expectedError: `webhook.clientConfig.url: Required value: exactly one of url or service is required`, + }, + { + name: "blank URL", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: strPtr(""), + }, + }, + expectedError: `webhook.clientConfig.url: Invalid value: "": host must be provided`, + }, + { + name: "missing host", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: strPtr("https:///fancy/webhook"), + }, + }, + expectedError: `host must be provided`, + }, + { + name: "fragment", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: strPtr("https://example.com/#bookmark"), + }, + }, + expectedError: `"bookmark": fragments are not permitted`, + }, + { + name: "query", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: strPtr("https://example.com?arg=value"), + }, + }, + expectedError: `"arg=value": query parameters are not permitted`, + }, + { + name: "user", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: strPtr("https://harry.potter@example.com/"), + }, + }, + expectedError: `"harry.potter": user information is not permitted`, + }, + { + name: "just totally wrong", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"), + }, + }, + expectedError: `host must be provided`, + }, + { + name: "path must start with slash", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("foo/"), + }, + }, + }, + expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`, + }, + { + name: "path accepts slash", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/"), + }, + }, + }, + expectedError: ``, + }, + { + name: "path accepts no trailing slash", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo"), + }, + }, + }, + expectedError: ``, + }, + { + name: "path fails //", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("//"), + }, + }, + }, + expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`, + }, + { + name: "path no empty step", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo//bar/"), + }, + }, + }, + expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, + }, { + name: "path no empty step 2", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/foo/bar//"), + }, + }, + }, + expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`, + }, + { + name: "path no non-subdomain", + config: auditregistration.Webhook{ + ClientConfig: auditregistration.WebhookClientConfig{ + Service: &auditregistration.ServiceReference{ + Namespace: "ns", + Name: "n", + Path: strPtr("/apis/foo.bar/v1alpha1/--bad"), + }, + }, + }, + expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a DNS-1123 subdomain`, + }, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + errs := ValidateWebhook(test.config, field.NewPath("webhook")) + err := errs.ToAggregate() + if err != nil { + if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" { + t.Errorf("expected to contain \nerr: %s \ngot: %s", e, a) + } + } else { + if test.expectedError != "" { + t.Errorf("unexpected no error, expected to contain %s", test.expectedError) + } + } + }) + } +} diff --git a/pkg/master/import_known_versions.go b/pkg/master/import_known_versions.go index 42477cc4b172a..46e9a33d0d809 100644 --- a/pkg/master/import_known_versions.go +++ b/pkg/master/import_known_versions.go @@ -21,6 +21,7 @@ import ( _ "k8s.io/kubernetes/pkg/apis/admission/install" _ "k8s.io/kubernetes/pkg/apis/admissionregistration/install" _ "k8s.io/kubernetes/pkg/apis/apps/install" + _ "k8s.io/kubernetes/pkg/apis/auditregistration/install" _ "k8s.io/kubernetes/pkg/apis/authentication/install" _ "k8s.io/kubernetes/pkg/apis/authorization/install" _ "k8s.io/kubernetes/pkg/apis/autoscaling/install" diff --git a/pkg/master/master.go b/pkg/master/master.go index 37c672c709470..bda9c0bcb51db 100644 --- a/pkg/master/master.go +++ b/pkg/master/master.go @@ -29,6 +29,7 @@ import ( appsv1 "k8s.io/api/apps/v1" appsv1beta1 "k8s.io/api/apps/v1beta1" appsv1beta2 "k8s.io/api/apps/v1beta2" + auditregistrationv1alpha1 "k8s.io/api/auditregistration/v1alpha1" authenticationv1 "k8s.io/api/authentication/v1" authenticationv1beta1 "k8s.io/api/authentication/v1beta1" authorizationapiv1 "k8s.io/api/authorization/v1" @@ -83,6 +84,7 @@ import ( // RESTStorage installers admissionregistrationrest "k8s.io/kubernetes/pkg/registry/admissionregistration/rest" appsrest "k8s.io/kubernetes/pkg/registry/apps/rest" + auditregistrationrest "k8s.io/kubernetes/pkg/registry/auditregistration/rest" authenticationrest "k8s.io/kubernetes/pkg/registry/authentication/rest" authorizationrest "k8s.io/kubernetes/pkg/registry/authorization/rest" autoscalingrest "k8s.io/kubernetes/pkg/registry/autoscaling/rest" @@ -346,6 +348,7 @@ func (c completedConfig) New(delegationTarget genericapiserver.DelegationTarget) // TODO: describe the priority all the way down in the RESTStorageProviders and plumb it back through the various discovery // handlers that we have. restStorageProviders := []RESTStorageProvider{ + auditregistrationrest.RESTStorageProvider{}, authenticationrest.RESTStorageProvider{Authenticator: c.GenericConfig.Authentication.Authenticator}, authorizationrest.RESTStorageProvider{Authorizer: c.GenericConfig.Authorization.Authorizer, RuleResolver: c.GenericConfig.RuleResolver}, autoscalingrest.RESTStorageProvider{}, @@ -505,6 +508,7 @@ func DefaultAPIResourceConfigSource() *serverstorage.ResourceConfig { ) // disable alpha versions explicitly so we have a full list of what's possible to serve ret.DisableVersions( + auditregistrationv1alpha1.SchemeGroupVersion, admissionregistrationv1alpha1.SchemeGroupVersion, batchapiv2alpha1.SchemeGroupVersion, rbacv1alpha1.SchemeGroupVersion, diff --git a/pkg/registry/auditregistration/auditsink/doc.go b/pkg/registry/auditregistration/auditsink/doc.go new file mode 100644 index 0000000000000..09f36e9aa81b6 --- /dev/null +++ b/pkg/registry/auditregistration/auditsink/doc.go @@ -0,0 +1,17 @@ +/* +Copyright 2018 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 auditsink // import "k8s.io/kubernetes/pkg/registry/auditregistration/auditsink" diff --git a/pkg/registry/auditregistration/auditsink/storage/storage.go b/pkg/registry/auditregistration/auditsink/storage/storage.go new file mode 100644 index 0000000000000..f0f071407a75b --- /dev/null +++ b/pkg/registry/auditregistration/auditsink/storage/storage.go @@ -0,0 +1,51 @@ +/* +Copyright 2018 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 storage + +import ( + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/kubernetes/pkg/apis/auditregistration" + auditstrategy "k8s.io/kubernetes/pkg/registry/auditregistration/auditsink" +) + +// REST implements a RESTStorage for audit sink against etcd +type REST struct { + *genericregistry.Store +} + +// NewREST returns a RESTStorage object that will work against audit sinks +func NewREST(optsGetter generic.RESTOptionsGetter) *REST { + store := &genericregistry.Store{ + NewFunc: func() runtime.Object { return &auditregistration.AuditSink{} }, + NewListFunc: func() runtime.Object { return &auditregistration.AuditSinkList{} }, + ObjectNameFunc: func(obj runtime.Object) (string, error) { + return obj.(*auditregistration.AuditSink).Name, nil + }, + DefaultQualifiedResource: auditregistration.Resource("auditsinks"), + + CreateStrategy: auditstrategy.Strategy, + UpdateStrategy: auditstrategy.Strategy, + DeleteStrategy: auditstrategy.Strategy, + } + options := &generic.StoreOptions{RESTOptions: optsGetter} + if err := store.CompleteWithOptions(options); err != nil { + panic(err) // TODO: Propagate error up + } + return &REST{store} +} diff --git a/pkg/registry/auditregistration/auditsink/strategy.go b/pkg/registry/auditregistration/auditsink/strategy.go new file mode 100644 index 0000000000000..23c85ccbca647 --- /dev/null +++ b/pkg/registry/auditregistration/auditsink/strategy.go @@ -0,0 +1,89 @@ +/* +Copyright 2018 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 auditsink + +import ( + "context" + "reflect" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/validation/field" + "k8s.io/apiserver/pkg/storage/names" + "k8s.io/kubernetes/pkg/api/legacyscheme" + audit "k8s.io/kubernetes/pkg/apis/auditregistration" + "k8s.io/kubernetes/pkg/apis/auditregistration/validation" +) + +// auditSinkStrategy implements verification logic for AuditSink. +type auditSinkStrategy struct { + runtime.ObjectTyper + names.NameGenerator +} + +// Strategy is the default logic that applies when creating and updating AuditSink objects. +var Strategy = auditSinkStrategy{legacyscheme.Scheme, names.SimpleNameGenerator} + +// NamespaceScoped returns false because all AuditSink's need to be cluster scoped +func (auditSinkStrategy) NamespaceScoped() bool { + return false +} + +// PrepareForCreate clears the status of an AuditSink before creation. +func (auditSinkStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) { + ic := obj.(*audit.AuditSink) + ic.Generation = 1 +} + +// PrepareForUpdate clears fields that are not allowed to be set by end users on update. +func (auditSinkStrategy) PrepareForUpdate(ctx context.Context, obj, old runtime.Object) { + newIC := obj.(*audit.AuditSink) + oldIC := old.(*audit.AuditSink) + + // Any changes to the policy or backend increment the generation number + // See metav1.ObjectMeta description for more information on Generation. + if !reflect.DeepEqual(oldIC.Spec, newIC.Spec) { + newIC.Generation = oldIC.Generation + 1 + } +} + +// Validate validates a new auditSink. +func (auditSinkStrategy) Validate(ctx context.Context, obj runtime.Object) field.ErrorList { + ic := obj.(*audit.AuditSink) + return validation.ValidateAuditSink(ic) +} + +// Canonicalize normalizes the object after validation. +func (auditSinkStrategy) Canonicalize(obj runtime.Object) { +} + +// AllowCreateOnUpdate is true for auditSink; this means you may create one with a PUT request. +func (auditSinkStrategy) AllowCreateOnUpdate() bool { + return false +} + +// ValidateUpdate is the default update validation for an end user. +func (auditSinkStrategy) ValidateUpdate(ctx context.Context, obj, old runtime.Object) field.ErrorList { + validationErrorList := validation.ValidateAuditSink(obj.(*audit.AuditSink)) + updateErrorList := validation.ValidateAuditSinkUpdate(obj.(*audit.AuditSink), old.(*audit.AuditSink)) + return append(validationErrorList, updateErrorList...) +} + +// AllowUnconditionalUpdate is the default update policy for auditSink objects. Status update should +// only be allowed if version match. +func (auditSinkStrategy) AllowUnconditionalUpdate() bool { + return false +} diff --git a/pkg/registry/auditregistration/rest/storage_auditregistration.go b/pkg/registry/auditregistration/rest/storage_auditregistration.go new file mode 100644 index 0000000000000..4bc5e884969db --- /dev/null +++ b/pkg/registry/auditregistration/rest/storage_auditregistration.go @@ -0,0 +1,53 @@ +/* +Copyright 2018 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 rest + +import ( + auditv1alpha1 "k8s.io/api/auditregistration/v1alpha1" + "k8s.io/apiserver/pkg/registry/generic" + "k8s.io/apiserver/pkg/registry/rest" + genericapiserver "k8s.io/apiserver/pkg/server" + serverstorage "k8s.io/apiserver/pkg/server/storage" + "k8s.io/kubernetes/pkg/api/legacyscheme" + auditstorage "k8s.io/kubernetes/pkg/registry/auditregistration/auditsink/storage" +) + +// RESTStorageProvider is a REST storage provider for audit.k8s.io +type RESTStorageProvider struct{} + +// NewRESTStorage returns a RESTStorageProvider +func (p RESTStorageProvider) NewRESTStorage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) (genericapiserver.APIGroupInfo, bool) { + apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(auditv1alpha1.GroupName, legacyscheme.Scheme, legacyscheme.ParameterCodec, legacyscheme.Codecs) + + if apiResourceConfigSource.VersionEnabled(auditv1alpha1.SchemeGroupVersion) { + apiGroupInfo.VersionedResourcesStorageMap[auditv1alpha1.SchemeGroupVersion.Version] = p.v1alpha1Storage(apiResourceConfigSource, restOptionsGetter) + } + return apiGroupInfo, true +} + +func (p RESTStorageProvider) v1alpha1Storage(apiResourceConfigSource serverstorage.APIResourceConfigSource, restOptionsGetter generic.RESTOptionsGetter) map[string]rest.Storage { + storage := map[string]rest.Storage{} + s := auditstorage.NewREST(restOptionsGetter) + storage["auditsinks"] = s + + return storage +} + +// GroupName is the group name for the storage provider +func (p RESTStorageProvider) GroupName() string { + return auditv1alpha1.GroupName +} diff --git a/staging/src/k8s.io/api/auditregistration/OWNERS b/staging/src/k8s.io/api/auditregistration/OWNERS new file mode 100644 index 0000000000000..df96f16bf05b0 --- /dev/null +++ b/staging/src/k8s.io/api/auditregistration/OWNERS @@ -0,0 +1,5 @@ +reviewers: +- lavalamp +- sttts +- tallclair +- pbarker \ No newline at end of file diff --git a/staging/src/k8s.io/api/auditregistration/v1alpha1/doc.go b/staging/src/k8s.io/api/auditregistration/v1alpha1/doc.go new file mode 100644 index 0000000000000..c0d184a99843d --- /dev/null +++ b/staging/src/k8s.io/api/auditregistration/v1alpha1/doc.go @@ -0,0 +1,22 @@ +/* +Copyright 2018 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 +// +k8s:openapi-gen=true + +// +groupName=auditregistration.k8s.io + +package v1alpha1 // import "k8s.io/api/auditregistration/v1alpha1" diff --git a/staging/src/k8s.io/api/auditregistration/v1alpha1/register.go b/staging/src/k8s.io/api/auditregistration/v1alpha1/register.go new file mode 100644 index 0000000000000..d6271608f00c3 --- /dev/null +++ b/staging/src/k8s.io/api/auditregistration/v1alpha1/register.go @@ -0,0 +1,56 @@ +/* +Copyright 2018 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 v1alpha1 + +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 = "auditregistration.k8s.io" + +// SchemeGroupVersion is group version used to register these objects +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} + +// 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, + &AuditSink{}, + &AuditSinkList{}, + ) + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) + return nil +} diff --git a/staging/src/k8s.io/api/auditregistration/v1alpha1/types.go b/staging/src/k8s.io/api/auditregistration/v1alpha1/types.go new file mode 100644 index 0000000000000..a7ef9d13fc5b7 --- /dev/null +++ b/staging/src/k8s.io/api/auditregistration/v1alpha1/types.go @@ -0,0 +1,195 @@ +/* +Copyright 2018 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:openapi-gen=true + +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// 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 and watches). + LevelRequestResponse Level = "RequestResponse" +) + +// Stage defines the stages in request handling during which audit events may be generated. +type Stage string + +// Valid audit stages. +const ( + // The stage for events generated after the audit handler receives the request, but before it + // is delegated down the handler chain. + StageRequestReceived = "RequestReceived" + // The stage for events generated after 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 after the response body has been completed, and no more bytes + // will be sent. + StageResponseComplete = "ResponseComplete" + // The stage for events generated when a panic occurred. + StagePanic = "Panic" +) + +// +genclient +// +genclient:nonNamespaced +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuditSink represents a cluster level audit sink +type AuditSink struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // Spec defines the audit configuration spec + Spec AuditSinkSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` +} + +// AuditSinkSpec holds the spec for the audit sink +type AuditSinkSpec struct { + // Policy defines the policy for selecting which events should be sent to the webhook + // required + Policy Policy `json:"policy" protobuf:"bytes,1,opt,name=policy"` + + // Webhook to send events + // required + Webhook Webhook `json:"webhook" protobuf:"bytes,2,opt,name=webhook"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// AuditSinkList is a list of AuditSink items. +type AuditSinkList struct { + metav1.TypeMeta `json:",inline"` + // +optional + metav1.ListMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` + + // List of audit configurations. + Items []AuditSink `json:"items" protobuf:"bytes,2,rep,name=items"` +} + +// Policy defines the configuration of how audit events are logged +type Policy struct { + // The Level that all requests are recorded at. + // available options: None, Metadata, Request, RequestResponse + // required + Level Level `json:"level" protobuf:"bytes,1,opt,name=level"` + + // Stages is a list of stages for which events are created. + // +optional + Stages []Stage `json:"stages" protobuf:"bytes,2,opt,name=stages"` +} + +// Webhook holds the configuration of the webhook +type Webhook struct { + // Throttle holds the options for throttling the webhook + // +optional + Throttle *WebhookThrottleConfig `json:"throttle,omitempty" protobuf:"bytes,1,opt,name=throttle"` + + // ClientConfig holds the connection parameters for the webhook + // required + ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` +} + +// WebhookThrottleConfig holds the configuration for throttling events +type WebhookThrottleConfig struct { + // ThrottleQPS maximum number of batches per second + // default 10 QPS + // +optional + QPS *int64 `json:"qps,omitempty" protobuf:"bytes,1,opt,name=qps"` + + // ThrottleBurst is the maximum number of events sent at the same moment + // default 15 QPS + // +optional + Burst *int64 `json:"burst,omitempty" protobuf:"bytes,2,opt,name=burst"` +} + +// WebhookClientConfig contains the information to make a connection with the webhook +type WebhookClientConfig struct { + // `url` gives the location of the webhook, in standard URL form + // (`[scheme://]host:port/path`). Exactly one of `url` or `service` + // must be specified. + // + // The `host` should not refer to a service running in the cluster; use + // the `service` field instead. The host might be resolved via external + // DNS in some apiservers (e.g., `kube-apiserver` cannot resolve + // in-cluster DNS as that would be a layering violation). `host` may + // also be an IP address. + // + // Please note that using `localhost` or `127.0.0.1` as a `host` is + // risky unless you take great care to run this webhook on all hosts + // which run an apiserver which might need to make calls to this + // webhook. Such installs are likely to be non-portable, i.e., not easy + // to turn up in a new cluster. + // + // The scheme must be "https"; the URL must begin with "https://". + // + // A path is optional, and if present may be any string permissible in + // a URL. You may use the path to pass an arbitrary string to the + // webhook, for example, a cluster identifier. + // + // Attempting to use a user or basic auth e.g. "user:password@" is not + // allowed. Fragments ("#...") and query parameters ("?...") are not + // allowed, either. + // + // +optional + URL *string `json:"url,omitempty" protobuf:"bytes,1,opt,name=url"` + + // `service` is a reference to the service for this webhook. Either + // `service` or `url` must be specified. + // + // If the webhook is running within the cluster, then you should use `service`. + // + // Port 443 will be used if it is open, otherwise it is an error. + // + // +optional + Service *ServiceReference `json:"service" protobuf:"bytes,2,opt,name=service"` + + // `caBundle` is a PEM encoded CA bundle which will be used to validate + // the webhook's server certificate. + // defaults to the apiservers CA bundle for the endpoint type + // +optional + CABundle []byte `json:"caBundle" protobuf:"bytes,3,opt,name=caBundle"` +} + +// ServiceReference holds a reference to Service.legacy.k8s.io +type ServiceReference struct { + // `namespace` is the namespace of the service. + // Required + Namespace string `json:"namespace" protobuf:"bytes,1,opt,name=namespace"` + + // `name` is the name of the service. + // Required + Name string `json:"name" protobuf:"bytes,2,opt,name=name"` + + // `path` is an optional URL path which will be sent in any request to + // this service. + // +optional + Path *string `json:"path,omitempty" protobuf:"bytes,3,opt,name=path"` +} diff --git a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go index f80aba01ffec4..397317f23b2f3 100644 --- a/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go +++ b/staging/src/k8s.io/apiserver/pkg/apis/audit/validation/validation.go @@ -24,6 +24,7 @@ import ( "k8s.io/apiserver/pkg/apis/audit" ) +// ValidatePolicy validates the audit policy func ValidatePolicy(policy *audit.Policy) field.ErrorList { var allErrs field.ErrorList allErrs = append(allErrs, validateOmitStages(policy.OmitStages, field.NewPath("omitStages"))...) diff --git a/test/integration/apiserver/print_test.go b/test/integration/apiserver/print_test.go index be5c2c389200f..5411492c90613 100644 --- a/test/integration/apiserver/print_test.go +++ b/test/integration/apiserver/print_test.go @@ -26,6 +26,7 @@ import ( "testing" "time" + auditregv1alpha1 "k8s.io/api/auditregistration/v1alpha1" batchv2alpha1 "k8s.io/api/batch/v2alpha1" rbacv1alpha1 "k8s.io/api/rbac/v1alpha1" schedulerapi "k8s.io/api/scheduling/v1beta1" @@ -130,11 +131,13 @@ var missingHanlders = sets.NewString( "VolumeAttachment", "PriorityClass", "PodPreset", + "AuditSink", ) func TestServerSidePrint(t *testing.T) { s, _, closeFn := setup(t, // additional groupversions needed for the test to run + auditregv1alpha1.SchemeGroupVersion, batchv2alpha1.SchemeGroupVersion, rbacv1alpha1.SchemeGroupVersion, settingsv1alpha1.SchemeGroupVersion, diff --git a/test/integration/framework/master_utils.go b/test/integration/framework/master_utils.go index 829550dbbf1b8..ad464082987b1 100644 --- a/test/integration/framework/master_utils.go +++ b/test/integration/framework/master_utils.go @@ -28,6 +28,7 @@ import ( "github.com/pborman/uuid" apps "k8s.io/api/apps/v1beta1" + auditreg "k8s.io/api/auditregistration/v1alpha1" autoscaling "k8s.io/api/autoscaling/v1" certificates "k8s.io/api/certificates/v1beta1" "k8s.io/api/core/v1" @@ -290,6 +291,10 @@ func NewMasterConfig() *master.Config { schema.GroupResource{Group: storage.GroupName, Resource: serverstorage.AllResources}, "", ns) + storageFactory.SetSerializer( + schema.GroupResource{Group: auditreg.GroupName, Resource: serverstorage.AllResources}, + "", + ns) genericConfig := genericapiserver.NewConfig(legacyscheme.Codecs) kubeVersion := version.Get()