mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-08-01 07:47:56 +00:00
drop managed fields from audit entries
drop the managed fields of the objects from the audit entries when we are logging request and response bodies.
This commit is contained in:
parent
7ea7c2029f
commit
bbc5934831
@ -18,6 +18,7 @@ package audit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"reflect"
|
"reflect"
|
||||||
@ -111,7 +112,8 @@ func LogImpersonatedUser(ae *auditinternal.Event, user user.Info) {
|
|||||||
|
|
||||||
// LogRequestObject fills in the request object into an audit event. The passed runtime.Object
|
// LogRequestObject fills in the request object into an audit event. The passed runtime.Object
|
||||||
// will be converted to the given gv.
|
// will be converted to the given gv.
|
||||||
func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, objGV schema.GroupVersion, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) {
|
func LogRequestObject(ctx context.Context, obj runtime.Object, objGV schema.GroupVersion, gvr schema.GroupVersionResource, subresource string, s runtime.NegotiatedSerializer) {
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -151,6 +153,16 @@ func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, objGV schema.
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldOmitManagedFields(ctx) {
|
||||||
|
copy, ok, err := copyWithoutManagedFields(obj)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("error while dropping managed fields from the request for %q error: %v", reflect.TypeOf(obj).Name(), err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
obj = copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(audit): hook into the serializer to avoid double conversion
|
// TODO(audit): hook into the serializer to avoid double conversion
|
||||||
var err error
|
var err error
|
||||||
ae.RequestObject, err = encodeObject(obj, objGV, s)
|
ae.RequestObject, err = encodeObject(obj, objGV, s)
|
||||||
@ -162,7 +174,8 @@ func LogRequestObject(ae *auditinternal.Event, obj runtime.Object, objGV schema.
|
|||||||
}
|
}
|
||||||
|
|
||||||
// LogRequestPatch fills in the given patch as the request object into an audit event.
|
// LogRequestPatch fills in the given patch as the request object into an audit event.
|
||||||
func LogRequestPatch(ae *auditinternal.Event, patch []byte) {
|
func LogRequestPatch(ctx context.Context, patch []byte) {
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
if ae == nil || ae.Level.Less(auditinternal.LevelRequest) {
|
if ae == nil || ae.Level.Less(auditinternal.LevelRequest) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -175,7 +188,8 @@ func LogRequestPatch(ae *auditinternal.Event, patch []byte) {
|
|||||||
|
|
||||||
// LogResponseObject fills in the response object into an audit event. The passed runtime.Object
|
// LogResponseObject fills in the response object into an audit event. The passed runtime.Object
|
||||||
// will be converted to the given gv.
|
// will be converted to the given gv.
|
||||||
func LogResponseObject(ae *auditinternal.Event, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) {
|
func LogResponseObject(ctx context.Context, obj runtime.Object, gv schema.GroupVersion, s runtime.NegotiatedSerializer) {
|
||||||
|
ae := AuditEventFrom(ctx)
|
||||||
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -191,6 +205,17 @@ func LogResponseObject(ae *auditinternal.Event, obj runtime.Object, gv schema.Gr
|
|||||||
if ae.Level.Less(auditinternal.LevelRequestResponse) {
|
if ae.Level.Less(auditinternal.LevelRequestResponse) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if shouldOmitManagedFields(ctx) {
|
||||||
|
copy, ok, err := copyWithoutManagedFields(obj)
|
||||||
|
if err != nil {
|
||||||
|
klog.Warningf("error while dropping managed fields from the response for %q error: %v", reflect.TypeOf(obj).Name(), err)
|
||||||
|
}
|
||||||
|
if ok {
|
||||||
|
obj = copy
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TODO(audit): hook into the serializer to avoid double conversion
|
// TODO(audit): hook into the serializer to avoid double conversion
|
||||||
var err error
|
var err error
|
||||||
ae.ResponseObject, err = encodeObject(obj, gv, s)
|
ae.ResponseObject, err = encodeObject(obj, gv, s)
|
||||||
@ -242,3 +267,72 @@ func maybeTruncateUserAgent(req *http.Request) string {
|
|||||||
|
|
||||||
return ua
|
return ua
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// copyWithoutManagedFields will make a deep copy of the specified object and
|
||||||
|
// will discard the managed fields from the copy.
|
||||||
|
// The specified object is expected to be a meta.Object or a "list".
|
||||||
|
// The specified object obj is treated as readonly and hence not mutated.
|
||||||
|
// On return, an error is set if the function runs into any error while
|
||||||
|
// removing the managed fields, the boolean value is true if the copy has
|
||||||
|
// been made successfully, otherwise false.
|
||||||
|
func copyWithoutManagedFields(obj runtime.Object) (runtime.Object, bool, error) {
|
||||||
|
isAccessor := true
|
||||||
|
if _, err := meta.Accessor(obj); err != nil {
|
||||||
|
isAccessor = false
|
||||||
|
}
|
||||||
|
isList := meta.IsListType(obj)
|
||||||
|
_, isTable := obj.(*metav1.Table)
|
||||||
|
if !isAccessor && !isList && !isTable {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO a deep copy isn't really needed here, figure out how we can reliably
|
||||||
|
// use shallow copy here to omit the manageFields.
|
||||||
|
copy := obj.DeepCopyObject()
|
||||||
|
|
||||||
|
if isAccessor {
|
||||||
|
if err := removeManagedFields(copy); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isList {
|
||||||
|
if err := meta.EachListItem(copy, removeManagedFields); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if isTable {
|
||||||
|
table := copy.(*metav1.Table)
|
||||||
|
for i := range table.Rows {
|
||||||
|
rowObj := table.Rows[i].Object
|
||||||
|
if err := removeManagedFields(rowObj.Object); err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return copy, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func removeManagedFields(obj runtime.Object) error {
|
||||||
|
if obj == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
accessor, err := meta.Accessor(obj)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
accessor.SetManagedFields(nil)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func shouldOmitManagedFields(ctx context.Context) bool {
|
||||||
|
if auditContext := AuditContextFrom(ctx); auditContext != nil {
|
||||||
|
return auditContext.RequestAuditConfig.OmitManagedFields
|
||||||
|
}
|
||||||
|
|
||||||
|
// If we can't decide, return false to maintain current behavior which is
|
||||||
|
// to retain the manage fields in the audit.
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
@ -18,11 +18,17 @@ package audit
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
corev1 "k8s.io/api/core/v1"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
|
|
||||||
|
"github.com/google/go-cmp/cmp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogAnnotation(t *testing.T) {
|
func TestLogAnnotation(t *testing.T) {
|
||||||
@ -54,3 +60,184 @@ func TestMaybeTruncateUserAgent(t *testing.T) {
|
|||||||
req.Header.Set("User-Agent", ua)
|
req.Header.Set("User-Agent", ua)
|
||||||
assert.NotEqual(t, ua, maybeTruncateUserAgent(req))
|
assert.NotEqual(t, ua, maybeTruncateUserAgent(req))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCopyWithoutManagedFields(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
object runtime.Object
|
||||||
|
expected runtime.Object
|
||||||
|
ok bool
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "object specified is not a meta.Accessor or a list or a table",
|
||||||
|
object: &metav1.Status{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "object specified is a meta.Accessor and has managed fields",
|
||||||
|
object: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||||
|
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
{Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "bar",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "object specified is a list and its items have managed fields",
|
||||||
|
object: &corev1.PodList{
|
||||||
|
Items: []corev1.Pod{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||||
|
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
{Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||||
|
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
{Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &corev1.PodList{
|
||||||
|
Items: []corev1.Pod{
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "object specified is a Table and objects in its rows have managed fields",
|
||||||
|
object: &metav1.Table{
|
||||||
|
Rows: []metav1.TableRow{
|
||||||
|
{
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||||
|
{Manager: "a", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
{Manager: "b", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
ManagedFields: []metav1.ManagedFieldsEntry{
|
||||||
|
{Manager: "c", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
{Manager: "d", Operation: metav1.ManagedFieldsOperationUpdate, Time: &metav1.Time{Time: time.Now()}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
// add an empty row to make sure we don't panic
|
||||||
|
{
|
||||||
|
Object: runtime.RawExtension{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: &metav1.Table{
|
||||||
|
Rows: []metav1.TableRow{
|
||||||
|
{
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "foo",
|
||||||
|
Namespace: "ns1",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: runtime.RawExtension{
|
||||||
|
Object: &corev1.Pod{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: "bar",
|
||||||
|
Namespace: "ns2",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Object: runtime.RawExtension{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ok: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
original := test.object.DeepCopyObject()
|
||||||
|
objectGot, ok, err := copyWithoutManagedFields(test.object)
|
||||||
|
|
||||||
|
if test.err != err {
|
||||||
|
t.Errorf("expected error: %v, but got: %v", test.err, err)
|
||||||
|
}
|
||||||
|
if test.ok != ok {
|
||||||
|
t.Errorf("expected ok: %t, but got: %t", test.ok, ok)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case test.expected == nil:
|
||||||
|
if objectGot != nil {
|
||||||
|
t.Errorf("expected the returned object to be nil, but got %#v", objectGot)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// verify that a deep copy of the specified object is made before mutating it.
|
||||||
|
if expected, actual := reflect.ValueOf(test.object), reflect.ValueOf(objectGot); expected.Pointer() == actual.Pointer() {
|
||||||
|
t.Error("expected the returned object to be a deep copy of the input object")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !cmp.Equal(test.expected, objectGot) {
|
||||||
|
t.Errorf("expected and actual do not match, diff: %s", cmp.Diff(test.expected, objectGot))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// we always expect the original object to be unchanged.
|
||||||
|
if !cmp.Equal(original, test.object) {
|
||||||
|
t.Errorf("the original object has mutated, diff: %s", cmp.Diff(original, test.object))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -143,7 +143,7 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
|
|||||||
|
|
||||||
ae := audit.AuditEventFrom(ctx)
|
ae := audit.AuditEventFrom(ctx)
|
||||||
admit = admission.WithAudit(admit, ae)
|
admit = admission.WithAudit(admit, ae)
|
||||||
audit.LogRequestObject(ae, obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
||||||
|
|
||||||
userInfo, _ := request.UserFrom(ctx)
|
userInfo, _ := request.UserFrom(ctx)
|
||||||
|
|
||||||
|
@ -103,9 +103,8 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
|
|||||||
}
|
}
|
||||||
trace.Step("Decoded delete options")
|
trace.Step("Decoded delete options")
|
||||||
|
|
||||||
ae := audit.AuditEventFrom(ctx)
|
|
||||||
objGV := gvk.GroupVersion()
|
objGV := gvk.GroupVersion()
|
||||||
audit.LogRequestObject(ae, obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
||||||
trace.Step("Recorded the audit event")
|
trace.Step("Recorded the audit event")
|
||||||
} else {
|
} else {
|
||||||
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||||
@ -250,9 +249,8 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
ae := audit.AuditEventFrom(ctx)
|
|
||||||
objGV := gvk.GroupVersion()
|
objGV := gvk.GroupVersion()
|
||||||
audit.LogRequestObject(ae, obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
||||||
} else {
|
} else {
|
||||||
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
if err := metainternalversionscheme.ParameterCodec.DecodeParameters(req.URL.Query(), scope.MetaGroupVersion, options); err != nil {
|
||||||
err = errors.NewBadRequest(err.Error())
|
err = errors.NewBadRequest(err.Error())
|
||||||
|
@ -126,7 +126,7 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
|
|||||||
ae := audit.AuditEventFrom(ctx)
|
ae := audit.AuditEventFrom(ctx)
|
||||||
admit = admission.WithAudit(admit, ae)
|
admit = admission.WithAudit(admit, ae)
|
||||||
|
|
||||||
audit.LogRequestPatch(ae, patchBytes)
|
audit.LogRequestPatch(req.Context(), patchBytes)
|
||||||
trace.Step("Recorded the audit event")
|
trace.Step("Recorded the audit event")
|
||||||
|
|
||||||
baseContentType := runtime.ContentTypeJSON
|
baseContentType := runtime.ContentTypeJSON
|
||||||
|
@ -267,9 +267,7 @@ func WriteObjectNegotiated(s runtime.NegotiatedSerializer, restrictions negotiat
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if ae := audit.AuditEventFrom(req.Context()); ae != nil {
|
audit.LogResponseObject(req.Context(), object, gv, s)
|
||||||
audit.LogResponseObject(ae, object, gv, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
encoder := s.EncoderForVersion(serializer.Serializer, gv)
|
encoder := s.EncoderForVersion(serializer.Serializer, gv)
|
||||||
SerializeObject(serializer.MediaType, encoder, w, req, statusCode, object)
|
SerializeObject(serializer.MediaType, encoder, w, req, statusCode, object)
|
||||||
|
@ -119,7 +119,7 @@ func UpdateResource(r rest.Updater, scope *RequestScope, admit admission.Interfa
|
|||||||
trace.Step("Conversion done")
|
trace.Step("Conversion done")
|
||||||
|
|
||||||
ae := audit.AuditEventFrom(ctx)
|
ae := audit.AuditEventFrom(ctx)
|
||||||
audit.LogRequestObject(ae, obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
audit.LogRequestObject(req.Context(), obj, objGV, scope.Resource, scope.Subresource, scope.Serializer)
|
||||||
admit = admission.WithAudit(admit, ae)
|
admit = admission.WithAudit(admit, ae)
|
||||||
|
|
||||||
if err := checkName(obj, name, namespace, scope.Namer); err != nil {
|
if err := checkName(obj, name, namespace, scope.Namer); err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user