Avoid encoding in LogResponseObject when we are not going to use it
Signed-off-by: Davanum Srinivas <davanum@gmail.com> Kubernetes-commit: e418ee3a92ca6c670d26f775b0f669e8a5fe233c
This commit is contained in:
		
							parent
							
								
									9b509bf53b
								
							
						
					
					
						commit
						bda7e28bbb
					
				|  | @ -171,7 +171,7 @@ func LogRequestPatch(ctx context.Context, patch []byte) { | ||||||
| // will be converted to the given gv.
 | // will be converted to the given gv.
 | ||||||
| func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) { | func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) { | ||||||
| 	ac := AuditContextFrom(WithAuditContext(ctx)) | 	ac := AuditContextFrom(WithAuditContext(ctx)) | ||||||
| 	if ac.GetEventLevel().Less(auditinternal.LevelMetadata) { | 	if ac.GetEventLevel().Less(auditinternal.LevelRequestResponse) { | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -0,0 +1,259 @@ | ||||||
|  | /* | ||||||
|  | Copyright 2025 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 audit | ||||||
|  | 
 | ||||||
|  | import ( | ||||||
|  | 	"context" | ||||||
|  | 	"io" | ||||||
|  | 	"strings" | ||||||
|  | 	"testing" | ||||||
|  | 
 | ||||||
|  | 	corev1 "k8s.io/api/core/v1" | ||||||
|  | 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||||||
|  | 	"k8s.io/apimachinery/pkg/runtime" | ||||||
|  | 	"k8s.io/apimachinery/pkg/runtime/schema" | ||||||
|  | 	"k8s.io/apimachinery/pkg/runtime/serializer" | ||||||
|  | 	auditinternal "k8s.io/apiserver/pkg/apis/audit" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
|  | func TestLogResponseObjectWithPod(t *testing.T) { | ||||||
|  | 	testPod := &corev1.Pod{ | ||||||
|  | 		TypeMeta: metav1.TypeMeta{ | ||||||
|  | 			APIVersion: "v1", | ||||||
|  | 			Kind:       "Pod", | ||||||
|  | 		}, | ||||||
|  | 		ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 			Name:      "test-pod", | ||||||
|  | 			Namespace: "test-namespace", | ||||||
|  | 		}, | ||||||
|  | 		Spec: corev1.PodSpec{ | ||||||
|  | 			Containers: []corev1.Container{ | ||||||
|  | 				{ | ||||||
|  | 					Name:  "test-container", | ||||||
|  | 					Image: "test-image", | ||||||
|  | 				}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	scheme := runtime.NewScheme() | ||||||
|  | 	if err := corev1.AddToScheme(scheme); err != nil { | ||||||
|  | 		t.Fatalf("Failed to add core/v1 to scheme: %v", err) | ||||||
|  | 	} | ||||||
|  | 	codecs := serializer.NewCodecFactory(scheme) | ||||||
|  | 	negotiatedSerializer := codecs.WithoutConversion() | ||||||
|  | 
 | ||||||
|  | 	// Create audit context with RequestResponse level
 | ||||||
|  | 	ctx := WithAuditContext(context.Background()) | ||||||
|  | 	ac := AuditContextFrom(ctx) | ||||||
|  | 
 | ||||||
|  | 	captureSink := &capturingAuditSink{} | ||||||
|  | 	if err := ac.Init(RequestAuditConfig{Level: auditinternal.LevelRequestResponse}, captureSink); err != nil { | ||||||
|  | 		t.Fatalf("Failed to initialize audit context: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	LogResponseObject(ctx, testPod, schema.GroupVersion{Group: "", Version: "v1"}, negotiatedSerializer) | ||||||
|  | 	ac.ProcessEventStage(ctx, auditinternal.StageResponseComplete) | ||||||
|  | 
 | ||||||
|  | 	if len(captureSink.events) != 1 { | ||||||
|  | 		t.Fatalf("Expected one audit event to be captured, got %d", len(captureSink.events)) | ||||||
|  | 	} | ||||||
|  | 	event := captureSink.events[0] | ||||||
|  | 	if event.ResponseObject == nil { | ||||||
|  | 		t.Fatal("Expected ResponseObject to be set, but it was nil") | ||||||
|  | 	} | ||||||
|  | 	if event.ResponseObject.ContentType != runtime.ContentTypeJSON { | ||||||
|  | 		t.Errorf("Expected ContentType to be %q, got %q", runtime.ContentTypeJSON, event.ResponseObject.ContentType) | ||||||
|  | 	} | ||||||
|  | 	if len(event.ResponseObject.Raw) == 0 { | ||||||
|  | 		t.Error("Expected ResponseObject.Raw to contain data, but it was empty") | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	responseJSON := string(event.ResponseObject.Raw) | ||||||
|  | 	expectedFields := []string{"test-pod", "test-namespace", "test-container", "test-image"} | ||||||
|  | 	for _, field := range expectedFields { | ||||||
|  | 		if !strings.Contains(responseJSON, field) { | ||||||
|  | 			t.Errorf("Response should contain %q but didn't. Response: %s", field, responseJSON) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if event.ResponseStatus != nil { | ||||||
|  | 		t.Errorf("Expected ResponseStatus to be nil for regular object, got: %+v", event.ResponseStatus) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestLogResponseObjectWithStatus(t *testing.T) { | ||||||
|  | 	// Create a status object to test ResponseStatus handling
 | ||||||
|  | 	testStatus := &metav1.Status{ | ||||||
|  | 		TypeMeta: metav1.TypeMeta{ | ||||||
|  | 			APIVersion: "v1", | ||||||
|  | 			Kind:       "Status", | ||||||
|  | 		}, | ||||||
|  | 		Status:  "Success", | ||||||
|  | 		Message: "Test status message", | ||||||
|  | 		Reason:  "TestReason", | ||||||
|  | 		Code:    200, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	scheme := runtime.NewScheme() | ||||||
|  | 	err := metav1.AddMetaToScheme(scheme) | ||||||
|  | 	if err != nil { | ||||||
|  | 		t.Fatalf("Failed to add meta to scheme: %v", err) | ||||||
|  | 	} | ||||||
|  | 	scheme.AddKnownTypes(schema.GroupVersion{Version: "v1"}, &metav1.Status{}) | ||||||
|  | 	codecs := serializer.NewCodecFactory(scheme) | ||||||
|  | 	negotiatedSerializer := codecs.WithoutConversion() | ||||||
|  | 
 | ||||||
|  | 	ctx := WithAuditContext(context.Background()) | ||||||
|  | 	ac := AuditContextFrom(ctx) | ||||||
|  | 
 | ||||||
|  | 	captureSink := &capturingAuditSink{} | ||||||
|  | 	if err := ac.Init(RequestAuditConfig{Level: auditinternal.LevelRequestResponse}, captureSink); err != nil { | ||||||
|  | 		t.Fatalf("Failed to initialize audit context: %v", err) | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	LogResponseObject(ctx, testStatus, schema.GroupVersion{Group: "", Version: "v1"}, negotiatedSerializer) | ||||||
|  | 	ac.ProcessEventStage(ctx, auditinternal.StageResponseComplete) | ||||||
|  | 
 | ||||||
|  | 	if len(captureSink.events) != 1 { | ||||||
|  | 		t.Fatalf("Expected one audit event to be captured, got %d", len(captureSink.events)) | ||||||
|  | 	} | ||||||
|  | 	event := captureSink.events[0] | ||||||
|  | 
 | ||||||
|  | 	if event.ResponseObject == nil { | ||||||
|  | 		t.Fatal("Expected ResponseObject to be set, but it was nil") | ||||||
|  | 	} | ||||||
|  | 	if event.ResponseStatus == nil { | ||||||
|  | 		t.Fatal("Expected ResponseStatus to be set for Status object, but it was nil") | ||||||
|  | 	} | ||||||
|  | 	if event.ResponseStatus.Status != "Success" { | ||||||
|  | 		t.Errorf("Expected ResponseStatus.Status to be 'Success', got %q", event.ResponseStatus.Status) | ||||||
|  | 	} | ||||||
|  | 	if event.ResponseStatus.Message != "Test status message" { | ||||||
|  | 		t.Errorf("Expected ResponseStatus.Message to be 'Test status message', got %q", event.ResponseStatus.Message) | ||||||
|  | 	} | ||||||
|  | 	if event.ResponseStatus.Reason != "TestReason" { | ||||||
|  | 		t.Errorf("Expected ResponseStatus.Reason to be 'TestReason', got %q", event.ResponseStatus.Reason) | ||||||
|  | 	} | ||||||
|  | 	if event.ResponseStatus.Code != 200 { | ||||||
|  | 		t.Errorf("Expected ResponseStatus.Code to be 200, got %d", event.ResponseStatus.Code) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func TestLogResponseObjectLevelCheck(t *testing.T) { | ||||||
|  | 	testCases := []struct { | ||||||
|  | 		name         string | ||||||
|  | 		level        auditinternal.Level | ||||||
|  | 		shouldEncode bool | ||||||
|  | 	}{ | ||||||
|  | 		{ | ||||||
|  | 			name:         "None level should not encode", | ||||||
|  | 			level:        auditinternal.LevelNone, | ||||||
|  | 			shouldEncode: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Metadata level should not encode", | ||||||
|  | 			level:        auditinternal.LevelMetadata, | ||||||
|  | 			shouldEncode: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "Request level should not encode", | ||||||
|  | 			level:        auditinternal.LevelRequest, | ||||||
|  | 			shouldEncode: false, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			name:         "RequestResponse level should encode", | ||||||
|  | 			level:        auditinternal.LevelRequestResponse, | ||||||
|  | 			shouldEncode: true, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	for _, tc := range testCases { | ||||||
|  | 		t.Run(tc.name, func(t *testing.T) { | ||||||
|  | 			// Create a test object
 | ||||||
|  | 			testObj := &corev1.Pod{ | ||||||
|  | 				ObjectMeta: metav1.ObjectMeta{ | ||||||
|  | 					Name:      "test-pod", | ||||||
|  | 					Namespace: "test-namespace", | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 
 | ||||||
|  | 			// Create audit context with the specified level
 | ||||||
|  | 			ctx := WithAuditContext(context.Background()) | ||||||
|  | 			ac := AuditContextFrom(ctx) | ||||||
|  | 			ac.Init(RequestAuditConfig{Level: tc.level}, nil) | ||||||
|  | 
 | ||||||
|  | 			// Create a mock serializer that tracks if encoding was attempted
 | ||||||
|  | 			mockSerializer := &mockNegotiatedSerializer{} | ||||||
|  | 
 | ||||||
|  | 			// Call the function under test
 | ||||||
|  | 			LogResponseObject(ctx, testObj, schema.GroupVersion{Group: "", Version: "v1"}, mockSerializer) | ||||||
|  | 
 | ||||||
|  | 			// Check if encoding was attempted as expected
 | ||||||
|  | 			if mockSerializer.encodeCalled != tc.shouldEncode { | ||||||
|  | 				t.Errorf("Expected encoding to be called: %v, but got: %v", tc.shouldEncode, mockSerializer.encodeCalled) | ||||||
|  | 			} | ||||||
|  | 		}) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockNegotiatedSerializer struct { | ||||||
|  | 	encodeCalled bool | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mockNegotiatedSerializer) SupportedMediaTypes() []runtime.SerializerInfo { | ||||||
|  | 	return []runtime.SerializerInfo{ | ||||||
|  | 		{ | ||||||
|  | 			MediaType:        runtime.ContentTypeJSON, | ||||||
|  | 			EncodesAsText:    true, | ||||||
|  | 			Serializer:       nil, | ||||||
|  | 			PrettySerializer: nil, | ||||||
|  | 			StreamSerializer: nil, | ||||||
|  | 		}, | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mockNegotiatedSerializer) EncoderForVersion(serializer runtime.Encoder, gv runtime.GroupVersioner) runtime.Encoder { | ||||||
|  | 	m.encodeCalled = true | ||||||
|  | 	return &mockEncoder{} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (m *mockNegotiatedSerializer) DecoderToVersion(serializer runtime.Decoder, gv runtime.GroupVersioner) runtime.Decoder { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type mockEncoder struct{} | ||||||
|  | 
 | ||||||
|  | func (e *mockEncoder) Encode(obj runtime.Object, w io.Writer) error { | ||||||
|  | 	return nil | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (e *mockEncoder) Identifier() runtime.Identifier { | ||||||
|  | 	return runtime.Identifier("mock") | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | type capturingAuditSink struct { | ||||||
|  | 	events []*auditinternal.Event | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | func (s *capturingAuditSink) ProcessEvents(events ...*auditinternal.Event) bool { | ||||||
|  | 	for _, event := range events { | ||||||
|  | 		eventCopy := event.DeepCopy() | ||||||
|  | 		s.events = append(s.events, eventCopy) | ||||||
|  | 	} | ||||||
|  | 	return true | ||||||
|  | } | ||||||
		Loading…
	
		Reference in New Issue