mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-25 20:53:33 +00:00
audit & admission: associate annotation with audit level
This commit is contained in:
parent
82a981fc39
commit
318226f340
@ -24,6 +24,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
"k8s.io/apimachinery/pkg/util/validation"
|
"k8s.io/apimachinery/pkg/util/validation"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -42,12 +43,17 @@ type attributesRecord struct {
|
|||||||
|
|
||||||
// other elements are always accessed in single goroutine.
|
// other elements are always accessed in single goroutine.
|
||||||
// But ValidatingAdmissionWebhook add annotations concurrently.
|
// But ValidatingAdmissionWebhook add annotations concurrently.
|
||||||
annotations map[string]string
|
annotations map[string]annotation
|
||||||
annotationsLock sync.RWMutex
|
annotationsLock sync.RWMutex
|
||||||
|
|
||||||
reinvocationContext ReinvocationContext
|
reinvocationContext ReinvocationContext
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type annotation struct {
|
||||||
|
level auditinternal.Level
|
||||||
|
value string
|
||||||
|
}
|
||||||
|
|
||||||
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
|
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
|
||||||
return &attributesRecord{
|
return &attributesRecord{
|
||||||
kind: kind,
|
kind: kind,
|
||||||
@ -111,7 +117,7 @@ func (record *attributesRecord) GetUserInfo() user.Info {
|
|||||||
|
|
||||||
// getAnnotations implements privateAnnotationsGetter.It's a private method used
|
// getAnnotations implements privateAnnotationsGetter.It's a private method used
|
||||||
// by WithAudit decorator.
|
// by WithAudit decorator.
|
||||||
func (record *attributesRecord) getAnnotations() map[string]string {
|
func (record *attributesRecord) getAnnotations(maxLevel auditinternal.Level) map[string]string {
|
||||||
record.annotationsLock.RLock()
|
record.annotationsLock.RLock()
|
||||||
defer record.annotationsLock.RUnlock()
|
defer record.annotationsLock.RUnlock()
|
||||||
|
|
||||||
@ -120,26 +126,36 @@ func (record *attributesRecord) getAnnotations() map[string]string {
|
|||||||
}
|
}
|
||||||
cp := make(map[string]string, len(record.annotations))
|
cp := make(map[string]string, len(record.annotations))
|
||||||
for key, value := range record.annotations {
|
for key, value := range record.annotations {
|
||||||
cp[key] = value
|
if value.level.Less(maxLevel) || value.level == maxLevel {
|
||||||
|
cp[key] = value.value
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return cp
|
return cp
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AddAnnotation adds an annotation to attributesRecord with Metadata audit level
|
||||||
func (record *attributesRecord) AddAnnotation(key, value string) error {
|
func (record *attributesRecord) AddAnnotation(key, value string) error {
|
||||||
|
return record.AddAnnotationWithLevel(key, value, auditinternal.LevelMetadata)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (record *attributesRecord) AddAnnotationWithLevel(key, value string, level auditinternal.Level) error {
|
||||||
if err := checkKeyFormat(key); err != nil {
|
if err := checkKeyFormat(key); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if level.Less(auditinternal.LevelMetadata) {
|
||||||
|
return fmt.Errorf("admission annotations are not allowed to be set at audit level lower than Metadata, key: %q, level: %s", key, level)
|
||||||
|
}
|
||||||
record.annotationsLock.Lock()
|
record.annotationsLock.Lock()
|
||||||
defer record.annotationsLock.Unlock()
|
defer record.annotationsLock.Unlock()
|
||||||
|
|
||||||
if record.annotations == nil {
|
if record.annotations == nil {
|
||||||
record.annotations = make(map[string]string)
|
record.annotations = make(map[string]annotation)
|
||||||
}
|
}
|
||||||
if v, ok := record.annotations[key]; ok && v != value {
|
annotation := annotation{level: level, value: value}
|
||||||
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %q, new value:%q", key, record.annotations[key], value)
|
if v, ok := record.annotations[key]; ok && v != annotation {
|
||||||
|
return fmt.Errorf("admission annotations are not allowd to be overwritten, key:%q, old value: %v, new value: %v", key, record.annotations[key], annotation)
|
||||||
}
|
}
|
||||||
record.annotations[key] = value
|
record.annotations[key] = annotation
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAddAnnotation(t *testing.T) {
|
func TestAddAnnotation(t *testing.T) {
|
||||||
@ -28,13 +29,13 @@ func TestAddAnnotation(t *testing.T) {
|
|||||||
// test AddAnnotation
|
// test AddAnnotation
|
||||||
attr.AddAnnotation("podsecuritypolicy.admission.k8s.io/validate-policy", "privileged")
|
attr.AddAnnotation("podsecuritypolicy.admission.k8s.io/validate-policy", "privileged")
|
||||||
attr.AddAnnotation("podsecuritypolicy.admission.k8s.io/admit-policy", "privileged")
|
attr.AddAnnotation("podsecuritypolicy.admission.k8s.io/admit-policy", "privileged")
|
||||||
annotations := attr.getAnnotations()
|
annotations := attr.getAnnotations(auditinternal.LevelMetadata)
|
||||||
assert.Equal(t, annotations["podsecuritypolicy.admission.k8s.io/validate-policy"], "privileged")
|
assert.Equal(t, annotations["podsecuritypolicy.admission.k8s.io/validate-policy"], "privileged")
|
||||||
|
|
||||||
// test overwrite
|
// test overwrite
|
||||||
assert.Error(t, attr.AddAnnotation("podsecuritypolicy.admission.k8s.io/validate-policy", "privileged-overwrite"),
|
assert.Error(t, attr.AddAnnotation("podsecuritypolicy.admission.k8s.io/validate-policy", "privileged-overwrite"),
|
||||||
"admission annotations should not be allowd to be overwritten")
|
"admission annotations should not be allowd to be overwritten")
|
||||||
annotations = attr.getAnnotations()
|
annotations = attr.getAnnotations(auditinternal.LevelMetadata)
|
||||||
assert.Equal(t, annotations["podsecuritypolicy.admission.k8s.io/validate-policy"], "privileged", "admission annotations should not be overwritten")
|
assert.Equal(t, annotations["podsecuritypolicy.admission.k8s.io/validate-policy"], "privileged", "admission annotations should not be overwritten")
|
||||||
|
|
||||||
// test invalid plugin names
|
// test invalid plugin names
|
||||||
@ -47,7 +48,7 @@ func TestAddAnnotation(t *testing.T) {
|
|||||||
for name, invalidKey := range testCases {
|
for name, invalidKey := range testCases {
|
||||||
err := attr.AddAnnotation(invalidKey, "value-foo")
|
err := attr.AddAnnotation(invalidKey, "value-foo")
|
||||||
assert.Error(t, err)
|
assert.Error(t, err)
|
||||||
annotations = attr.getAnnotations()
|
annotations = attr.getAnnotations(auditinternal.LevelMetadata)
|
||||||
assert.Equal(t, annotations[invalidKey], "", name+": invalid pluginName is not allowed ")
|
assert.Equal(t, annotations[invalidKey], "", name+": invalid pluginName is not allowed ")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -84,11 +84,18 @@ func ensureAnnotationGetter(a Attributes) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (handler auditHandler) logAnnotations(a Attributes) {
|
func (handler auditHandler) logAnnotations(a Attributes) {
|
||||||
|
if handler.ae == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
switch a := a.(type) {
|
switch a := a.(type) {
|
||||||
case privateAnnotationsGetter:
|
case privateAnnotationsGetter:
|
||||||
audit.LogAnnotations(handler.ae, a.getAnnotations())
|
for key, value := range a.getAnnotations(handler.ae.Level) {
|
||||||
|
audit.LogAnnotation(handler.ae, key, value)
|
||||||
|
}
|
||||||
case AnnotationsGetter:
|
case AnnotationsGetter:
|
||||||
audit.LogAnnotations(handler.ae, a.GetAnnotations())
|
for key, value := range a.GetAnnotations(handler.ae.Level) {
|
||||||
|
audit.LogAnnotation(handler.ae, key, value)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
// this will never happen, because we have already checked it in ensureAnnotationGetter
|
// this will never happen, because we have already checked it in ensureAnnotationGetter
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
"k8s.io/apiserver/pkg/authentication/user"
|
"k8s.io/apiserver/pkg/authentication/user"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -61,8 +62,15 @@ type Attributes interface {
|
|||||||
// "podsecuritypolicy" is the name of the plugin, "admission.k8s.io" is the name of the organization, "admit-policy" is the key name.
|
// "podsecuritypolicy" is the name of the plugin, "admission.k8s.io" is the name of the organization, "admit-policy" is the key name.
|
||||||
// An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned.
|
// An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned.
|
||||||
// Both ValidationInterface and MutationInterface are allowed to add Annotations.
|
// Both ValidationInterface and MutationInterface are allowed to add Annotations.
|
||||||
|
// By default, an annotation gets logged into audit event if the request's audit level is greater or
|
||||||
|
// equal to Metadata.
|
||||||
AddAnnotation(key, value string) error
|
AddAnnotation(key, value string) error
|
||||||
|
|
||||||
|
// AddAnnotationWithLevel sets annotation according to key-value pair with additional intended audit level.
|
||||||
|
// An Annotation gets logged into audit event if the request's audit level is greater or equal to the
|
||||||
|
// intended audit level.
|
||||||
|
AddAnnotationWithLevel(key, value string, level auditinternal.Level) error
|
||||||
|
|
||||||
// GetReinvocationContext tracks the admission request information relevant to the re-invocation policy.
|
// GetReinvocationContext tracks the admission request information relevant to the re-invocation policy.
|
||||||
GetReinvocationContext() ReinvocationContext
|
GetReinvocationContext() ReinvocationContext
|
||||||
}
|
}
|
||||||
@ -85,13 +93,13 @@ type ObjectInterfaces interface {
|
|||||||
|
|
||||||
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
|
// privateAnnotationsGetter is a private interface which allows users to get annotations from Attributes.
|
||||||
type privateAnnotationsGetter interface {
|
type privateAnnotationsGetter interface {
|
||||||
getAnnotations() map[string]string
|
getAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
|
// AnnotationsGetter allows users to get annotations from Attributes. An alternate Attribute should implement
|
||||||
// this interface.
|
// this interface.
|
||||||
type AnnotationsGetter interface {
|
type AnnotationsGetter interface {
|
||||||
GetAnnotations() map[string]string
|
GetAnnotations(maxLevel auditinternal.Level) map[string]string
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy.
|
// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy.
|
||||||
|
@ -29,6 +29,7 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apiserver/pkg/admission"
|
"k8s.io/apiserver/pkg/admission"
|
||||||
webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing"
|
webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestAdmit tests that MutatingWebhook#Admit works as expected
|
// TestAdmit tests that MutatingWebhook#Admit works as expected
|
||||||
@ -108,9 +109,9 @@ func TestAdmit(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if len(tt.ExpectAnnotations) == 0 {
|
if len(tt.ExpectAnnotations) == 0 {
|
||||||
assert.Empty(t, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.")
|
assert.Empty(t, fakeAttr.GetAnnotations(auditinternal.LevelMetadata), tt.Name+": annotations not set as expected.")
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, tt.ExpectAnnotations, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.")
|
assert.Equal(t, tt.ExpectAnnotations, fakeAttr.GetAnnotations(auditinternal.LevelMetadata), tt.Name+": annotations not set as expected.")
|
||||||
}
|
}
|
||||||
reinvocationCtx := fakeAttr.Attributes.GetReinvocationContext()
|
reinvocationCtx := fakeAttr.Attributes.GetReinvocationContext()
|
||||||
reinvocationCtx.SetIsReinvoke()
|
reinvocationCtx.SetIsReinvoke()
|
||||||
|
@ -25,6 +25,7 @@ import (
|
|||||||
|
|
||||||
"k8s.io/apimachinery/pkg/api/errors"
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing"
|
webhooktesting "k8s.io/apiserver/pkg/admission/plugin/webhook/testing"
|
||||||
|
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TestValidate tests that ValidatingWebhook#Validate works as expected
|
// TestValidate tests that ValidatingWebhook#Validate works as expected
|
||||||
@ -86,9 +87,9 @@ func TestValidate(t *testing.T) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(tt.ExpectAnnotations) == 0 {
|
if len(tt.ExpectAnnotations) == 0 {
|
||||||
assert.Empty(t, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.")
|
assert.Empty(t, fakeAttr.GetAnnotations(auditinternal.LevelMetadata), tt.Name+": annotations not set as expected.")
|
||||||
} else {
|
} else {
|
||||||
assert.Equal(t, tt.ExpectAnnotations, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.")
|
assert.Equal(t, tt.ExpectAnnotations, fakeAttr.GetAnnotations(auditinternal.LevelMetadata), tt.Name+": annotations not set as expected.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -230,16 +230,6 @@ func LogAnnotation(ae *auditinternal.Event, key, value string) {
|
|||||||
ae.Annotations[key] = value
|
ae.Annotations[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
// LogAnnotations fills in the Annotations according to the annotations map.
|
|
||||||
func LogAnnotations(ae *auditinternal.Event, annotations map[string]string) {
|
|
||||||
if ae == nil || ae.Level.Less(auditinternal.LevelMetadata) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
for key, value := range annotations {
|
|
||||||
LogAnnotation(ae, key, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// truncate User-Agent if too long, otherwise return it directly.
|
// truncate User-Agent if too long, otherwise return it directly.
|
||||||
func maybeTruncateUserAgent(req *http.Request) string {
|
func maybeTruncateUserAgent(req *http.Request) string {
|
||||||
ua := req.UserAgent()
|
ua := req.UserAgent()
|
||||||
|
Loading…
Reference in New Issue
Block a user