From f4e8b8f1464e588306d5c1c4ffdc1a6cb1e9313b Mon Sep 17 00:00:00 2001 From: Cao Shufeng Date: Wed, 2 Aug 2017 11:06:23 +0800 Subject: [PATCH] upgrade advanced audit to v1beta1 --- hack/.golint_failures | 1 + .../apiserver/pkg/apis/audit/fuzzer/fuzzer.go | 53 ++++ .../pkg/apis/audit/install/install.go | 4 +- .../pkg/apis/audit/install/roundtrip_test.go | 28 +++ .../apiserver/pkg/apis/audit/v1beta1/doc.go | 23 ++ .../pkg/apis/audit/v1beta1/register.go | 58 +++++ .../apiserver/pkg/apis/audit/v1beta1/types.go | 234 ++++++++++++++++++ .../apiserver/pkg/audit/policy/reader.go | 13 +- .../apiserver/pkg/audit/policy/reader_test.go | 49 +++- .../src/k8s.io/apiserver/pkg/audit/scheme.go | 2 + .../pkg/endpoints/filters/audit_test.go | 40 +-- .../apiserver/plugin/pkg/audit/log/backend.go | 5 +- .../plugin/pkg/audit/webhook/webhook.go | 7 +- .../plugin/pkg/audit/webhook/webhook_test.go | 30 +-- 14 files changed, 494 insertions(+), 53 deletions(-) create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/audit/fuzzer/fuzzer.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/audit/install/roundtrip_test.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/doc.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/register.go create mode 100644 staging/src/k8s.io/apiserver/pkg/apis/audit/v1beta1/types.go diff --git a/hack/.golint_failures b/hack/.golint_failures index d6e06a7d239..b5e39398af5 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -609,6 +609,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/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/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/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/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/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/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/webhook.go b/staging/src/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go index b3844a39984..295fe77312e 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 @@ -32,7 +32,7 @@ import ( "k8s.io/apimachinery/pkg/util/runtime" 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" ) @@ -87,8 +87,9 @@ 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 + groupVersions = []schema.GroupVersion{auditv1beta1.SchemeGroupVersion} + registry = registered.NewOrDie("") ) func init() { 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..a59d0cfdc47 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 @@ -35,14 +35,14 @@ import ( "k8s.io/apimachinery/pkg/runtime" "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 { +func newWebhookHandler(t *testing.T, cb func(events *auditv1beta1.EventList)) http.Handler { s := json.NewSerializer(json.DefaultMetaFactory, audit.Scheme, audit.Scheme, false) return &testWebhookHandler{ t: t, @@ -54,7 +54,7 @@ func newWebhookHandler(t *testing.T, cb func(events *auditv1alpha1.EventList)) h type testWebhookHandler struct { t *testing.T - onEvents func(events *auditv1alpha1.EventList) + onEvents func(events *auditv1beta1.EventList) serializer runtime.Serializer } @@ -66,13 +66,13 @@ 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, &auditv1beta1.EventList{}) if err != nil { return fmt.Errorf("decode request body: %v", err) } - list, ok := obj.(*auditv1alpha1.EventList) + list, ok := obj.(*auditv1beta1.EventList) if !ok { - return fmt.Errorf("expected *v1alpha1.EventList got %T", obj) + return fmt.Errorf("expected *v1beta1.EventList got %T", obj) } t.onEvents(list) return nil @@ -122,7 +122,7 @@ 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, func(events *auditv1beta1.EventList) { gotEvents = true })) defer s.Close() @@ -151,7 +151,7 @@ func TestBatchWebhookMaxEvents(t *testing.T) { } got := make(chan int, 2) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1beta1.EventList) { got <- len(events.Items) })) defer s.Close() @@ -183,7 +183,7 @@ func TestBatchWebhookStopCh(t *testing.T) { expected := len(events) got := make(chan int, 2) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1beta1.EventList) { got <- len(events.Items) })) defer s.Close() @@ -209,7 +209,7 @@ func TestBatchWebhookProcessEventsAfterStop(t *testing.T) { } got := make(chan struct{}) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1beta1.EventList) { close(got) })) defer s.Close() @@ -233,7 +233,7 @@ 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, func(events *auditv1beta1.EventList) { close(got) <-contReqCh })) @@ -278,7 +278,7 @@ func TestBatchWebhookEmptyBuffer(t *testing.T) { expected := len(events) got := make(chan int, 2) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1beta1.EventList) { got <- len(events.Items) })) defer s.Close() @@ -311,7 +311,7 @@ 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, func(events *auditv1beta1.EventList) { // Do nothing. })) defer s.Close() @@ -344,7 +344,7 @@ func TestBatchRun(t *testing.T) { close(done) }() - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1beta1.EventList) { atomic.AddInt64(got, int64(len(events.Items))) wg.Add(-len(events.Items)) })) @@ -377,7 +377,7 @@ func TestBatchConcurrentRequests(t *testing.T) { wg := new(sync.WaitGroup) wg.Add(len(events)) - s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1alpha1.EventList) { + s := httptest.NewServer(newWebhookHandler(t, func(events *auditv1beta1.EventList) { wg.Add(-len(events.Items)) // Since the webhook makes concurrent requests, blocking on the webhook response