290
vendor/k8s.io/apiserver/plugin/pkg/audit/buffered/buffered.go
generated
vendored
Normal file
290
vendor/k8s.io/apiserver/plugin/pkg/audit/buffered/buffered.go
generated
vendored
Normal file
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
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 buffered
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
// PluginName is the name reported in error metrics.
|
||||
const PluginName = "buffered"
|
||||
|
||||
// BatchConfig represents batching delegate audit backend configuration.
|
||||
type BatchConfig struct {
|
||||
// BufferSize defines a size of the buffering queue.
|
||||
BufferSize int
|
||||
// MaxBatchSize defines maximum size of a batch.
|
||||
MaxBatchSize int
|
||||
// MaxBatchWait indicates the maximum interval between two batches.
|
||||
MaxBatchWait time.Duration
|
||||
|
||||
// ThrottleEnable defines whether throttling will be applied to the batching process.
|
||||
ThrottleEnable bool
|
||||
// ThrottleQPS defines the allowed rate of batches per second sent to the delegate backend.
|
||||
ThrottleQPS float32
|
||||
// ThrottleBurst defines the maximum number of requests sent to the delegate backend at the same moment in case
|
||||
// the capacity defined by ThrottleQPS was not utilized.
|
||||
ThrottleBurst int
|
||||
|
||||
// Whether the delegate backend should be called asynchronously.
|
||||
AsyncDelegate bool
|
||||
}
|
||||
|
||||
type bufferedBackend struct {
|
||||
// The delegate backend that actually exports events.
|
||||
delegateBackend audit.Backend
|
||||
|
||||
// Channel to buffer events before sending to the delegate backend.
|
||||
buffer chan *auditinternal.Event
|
||||
// Maximum number of events in a batch sent to the delegate backend.
|
||||
maxBatchSize int
|
||||
// Amount of time to wait after sending a batch to the delegate backend before sending another one.
|
||||
//
|
||||
// Receiving maxBatchSize events will always trigger sending a batch, regardless of the amount of time passed.
|
||||
maxBatchWait time.Duration
|
||||
|
||||
// Whether the delegate backend should be called asynchronously.
|
||||
asyncDelegate bool
|
||||
|
||||
// Channel to signal that the batching routine has processed all remaining events and exited.
|
||||
// Once `shutdownCh` is closed no new events will be sent to the delegate backend.
|
||||
shutdownCh chan struct{}
|
||||
|
||||
// WaitGroup to control the concurrency of sending batches to the delegate backend.
|
||||
// Worker routine calls Add before sending a batch and
|
||||
// then spawns a routine that calls Done after batch was processed by the delegate backend.
|
||||
// This WaitGroup is used to wait for all sending routines to finish before shutting down audit backend.
|
||||
wg sync.WaitGroup
|
||||
|
||||
// Limits the number of batches sent to the delegate backend per second.
|
||||
throttle flowcontrol.RateLimiter
|
||||
}
|
||||
|
||||
var _ audit.Backend = &bufferedBackend{}
|
||||
|
||||
// NewBackend returns a buffered audit backend that wraps delegate backend.
|
||||
// Buffered backend automatically runs and shuts down the delegate backend.
|
||||
func NewBackend(delegate audit.Backend, config BatchConfig) audit.Backend {
|
||||
var throttle flowcontrol.RateLimiter
|
||||
if config.ThrottleEnable {
|
||||
throttle = flowcontrol.NewTokenBucketRateLimiter(config.ThrottleQPS, config.ThrottleBurst)
|
||||
}
|
||||
return &bufferedBackend{
|
||||
delegateBackend: delegate,
|
||||
buffer: make(chan *auditinternal.Event, config.BufferSize),
|
||||
maxBatchSize: config.MaxBatchSize,
|
||||
maxBatchWait: config.MaxBatchWait,
|
||||
asyncDelegate: config.AsyncDelegate,
|
||||
shutdownCh: make(chan struct{}),
|
||||
wg: sync.WaitGroup{},
|
||||
throttle: throttle,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bufferedBackend) Run(stopCh <-chan struct{}) error {
|
||||
go func() {
|
||||
// Signal that the working routine has exited.
|
||||
defer close(b.shutdownCh)
|
||||
|
||||
b.processIncomingEvents(stopCh)
|
||||
|
||||
// Handle the events that were received after the last buffer
|
||||
// scraping and before this line. Since the buffer is closed, no new
|
||||
// events will come through.
|
||||
allEventsProcessed := false
|
||||
timer := make(chan time.Time)
|
||||
for !allEventsProcessed {
|
||||
allEventsProcessed = func() bool {
|
||||
// Recover from any panic in order to try to process all remaining events.
|
||||
// Note, that in case of a panic, the return value will be false and
|
||||
// the loop execution will continue.
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
events := b.collectEvents(timer, wait.NeverStop)
|
||||
b.processEvents(events)
|
||||
return len(events) == 0
|
||||
}()
|
||||
}
|
||||
}()
|
||||
return b.delegateBackend.Run(stopCh)
|
||||
}
|
||||
|
||||
// Shutdown blocks until stopCh passed to the Run method is closed and all
|
||||
// events added prior to that moment are batched and sent to the delegate backend.
|
||||
func (b *bufferedBackend) Shutdown() {
|
||||
// Wait until the routine spawned in Run method exits.
|
||||
<-b.shutdownCh
|
||||
|
||||
// Wait until all sending routines exit.
|
||||
//
|
||||
// - When b.shutdownCh is closed, we know that the goroutine in Run has terminated.
|
||||
// - This means that processIncomingEvents has terminated.
|
||||
// - Which means that b.buffer is closed and cannot accept any new events anymore.
|
||||
// - Because processEvents is called synchronously from the Run goroutine, the waitgroup has its final value.
|
||||
// Hence wg.Wait will not miss any more outgoing batches.
|
||||
b.wg.Wait()
|
||||
|
||||
b.delegateBackend.Shutdown()
|
||||
}
|
||||
|
||||
// processIncomingEvents runs a loop that collects events from the buffer. When
|
||||
// b.stopCh is closed, processIncomingEvents stops and closes the buffer.
|
||||
func (b *bufferedBackend) processIncomingEvents(stopCh <-chan struct{}) {
|
||||
defer close(b.buffer)
|
||||
|
||||
var (
|
||||
maxWaitChan <-chan time.Time
|
||||
maxWaitTimer *time.Timer
|
||||
)
|
||||
// Only use max wait batching if batching is enabled.
|
||||
if b.maxBatchSize > 1 {
|
||||
maxWaitTimer = time.NewTimer(b.maxBatchWait)
|
||||
maxWaitChan = maxWaitTimer.C
|
||||
defer maxWaitTimer.Stop()
|
||||
}
|
||||
|
||||
for {
|
||||
func() {
|
||||
// Recover from any panics caused by this function so a panic in the
|
||||
// goroutine can't bring down the main routine.
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
if b.maxBatchSize > 1 {
|
||||
maxWaitTimer.Reset(b.maxBatchWait)
|
||||
}
|
||||
b.processEvents(b.collectEvents(maxWaitChan, stopCh))
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// collectEvents attempts to collect some number of events in a batch.
|
||||
//
|
||||
// The following things can cause collectEvents to stop and return the list
|
||||
// of events:
|
||||
//
|
||||
// * Maximum number of events for a batch.
|
||||
// * Timer has passed.
|
||||
// * Buffer channel is closed and empty.
|
||||
// * stopCh is closed.
|
||||
func (b *bufferedBackend) collectEvents(timer <-chan time.Time, stopCh <-chan struct{}) []*auditinternal.Event {
|
||||
var events []*auditinternal.Event
|
||||
|
||||
L:
|
||||
for i := 0; i < b.maxBatchSize; i++ {
|
||||
select {
|
||||
case ev, ok := <-b.buffer:
|
||||
// Buffer channel was closed and no new events will follow.
|
||||
if !ok {
|
||||
break L
|
||||
}
|
||||
events = append(events, ev)
|
||||
case <-timer:
|
||||
// Timer has expired. Send currently accumulated batch.
|
||||
break L
|
||||
case <-stopCh:
|
||||
// Backend has been stopped. Send currently accumulated batch.
|
||||
break L
|
||||
}
|
||||
}
|
||||
|
||||
return events
|
||||
}
|
||||
|
||||
// processEvents process the batch events in a goroutine using delegateBackend's ProcessEvents.
|
||||
func (b *bufferedBackend) processEvents(events []*auditinternal.Event) {
|
||||
if len(events) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO(audit): Should control the number of active goroutines
|
||||
// if one goroutine takes 5 seconds to finish, the number of goroutines can be 5 * defaultBatchThrottleQPS
|
||||
if b.throttle != nil {
|
||||
b.throttle.Accept()
|
||||
}
|
||||
|
||||
if b.asyncDelegate {
|
||||
b.wg.Add(1)
|
||||
go func() {
|
||||
defer b.wg.Done()
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
// Execute the real processing in a goroutine to keep it from blocking.
|
||||
// This lets the batching routine continue draining the queue immediately.
|
||||
b.delegateBackend.ProcessEvents(events...)
|
||||
}()
|
||||
} else {
|
||||
func() {
|
||||
defer runtime.HandleCrash()
|
||||
|
||||
// Execute the real processing in a goroutine to keep it from blocking.
|
||||
// This lets the batching routine continue draining the queue immediately.
|
||||
b.delegateBackend.ProcessEvents(events...)
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (b *bufferedBackend) ProcessEvents(ev ...*auditinternal.Event) bool {
|
||||
// The following mechanism is in place to support the situation when audit
|
||||
// events are still coming after the backend was stopped.
|
||||
var sendErr error
|
||||
var evIndex int
|
||||
|
||||
// If the delegateBackend was shutdown and the buffer channel was closed, an
|
||||
// attempt to add an event to it will result in panic that we should
|
||||
// recover from.
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
sendErr = fmt.Errorf("audit backend shut down")
|
||||
}
|
||||
if sendErr != nil {
|
||||
audit.HandlePluginError(PluginName, sendErr, ev[evIndex:]...)
|
||||
}
|
||||
}()
|
||||
|
||||
for i, e := range ev {
|
||||
evIndex = i
|
||||
// Per the audit.Backend interface these events are reused after being
|
||||
// sent to the Sink. Deep copy and send the copy to the queue.
|
||||
event := e.DeepCopy()
|
||||
|
||||
select {
|
||||
case b.buffer <- event:
|
||||
default:
|
||||
sendErr = fmt.Errorf("audit buffer queue blocked")
|
||||
return true
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *bufferedBackend) String() string {
|
||||
return fmt.Sprintf("%s<%s>", PluginName, b.delegateBackend)
|
||||
}
|
||||
19
vendor/k8s.io/apiserver/plugin/pkg/audit/buffered/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/plugin/pkg/audit/buffered/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 buffered provides an implementation for the audit.Backend interface
|
||||
// that batches incoming audit events and sends batches to the delegate audit.Backend.
|
||||
package buffered // import "k8s.io/apiserver/plugin/pkg/audit/buffered"
|
||||
46
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/defaults.go
generated
vendored
Normal file
46
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/defaults.go
generated
vendored
Normal file
@@ -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 dynamic
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
bufferedplugin "k8s.io/apiserver/plugin/pkg/audit/buffered"
|
||||
)
|
||||
|
||||
const (
|
||||
// Default configuration values for ModeBatch when applied to a dynamic plugin
|
||||
defaultBatchBufferSize = 5000 // Buffer up to 5000 events before starting discarding.
|
||||
defaultBatchMaxSize = 400 // Only send up to 400 events at a time.
|
||||
defaultBatchMaxWait = 30 * time.Second // Send events at least twice a minute.
|
||||
defaultBatchThrottleQPS = 10 // Limit the send rate by 10 QPS.
|
||||
defaultBatchThrottleBurst = 15 // Allow up to 15 QPS burst.
|
||||
)
|
||||
|
||||
// NewDefaultWebhookBatchConfig returns new Batch Config objects populated by default values
|
||||
// for dynamic webhooks
|
||||
func NewDefaultWebhookBatchConfig() *bufferedplugin.BatchConfig {
|
||||
return &bufferedplugin.BatchConfig{
|
||||
BufferSize: defaultBatchBufferSize,
|
||||
MaxBatchSize: defaultBatchMaxSize,
|
||||
MaxBatchWait: defaultBatchMaxWait,
|
||||
ThrottleEnable: true,
|
||||
ThrottleQPS: defaultBatchThrottleQPS,
|
||||
ThrottleBurst: defaultBatchThrottleBurst,
|
||||
AsyncDelegate: true,
|
||||
}
|
||||
}
|
||||
338
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/dynamic.go
generated
vendored
Normal file
338
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/dynamic.go
generated
vendored
Normal file
@@ -0,0 +1,338 @@
|
||||
/*
|
||||
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 dynamic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"k8s.io/klog"
|
||||
|
||||
auditregv1alpha1 "k8s.io/api/auditregistration/v1alpha1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
auditinstall "k8s.io/apiserver/pkg/apis/audit/install"
|
||||
auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
webhook "k8s.io/apiserver/pkg/util/webhook"
|
||||
bufferedplugin "k8s.io/apiserver/plugin/pkg/audit/buffered"
|
||||
auditinformer "k8s.io/client-go/informers/auditregistration/v1alpha1"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
"k8s.io/client-go/tools/record"
|
||||
)
|
||||
|
||||
// PluginName is the name reported in error metrics.
|
||||
const PluginName = "dynamic"
|
||||
|
||||
// Config holds the configuration for the dynamic backend
|
||||
type Config struct {
|
||||
// Informer for the audit sinks
|
||||
Informer auditinformer.AuditSinkInformer
|
||||
// EventConfig holds the configuration for event notifications about the AuditSink API objects
|
||||
EventConfig EventConfig
|
||||
// BufferedConfig is the runtime buffered configuration
|
||||
BufferedConfig *bufferedplugin.BatchConfig
|
||||
// WebhookConfig holds the configuration for outgoing webhooks
|
||||
WebhookConfig WebhookConfig
|
||||
}
|
||||
|
||||
// WebhookConfig holds the configurations for outgoing webhooks
|
||||
type WebhookConfig struct {
|
||||
// AuthInfoResolverWrapper provides the webhook authentication for in-cluster endpoints
|
||||
AuthInfoResolverWrapper webhook.AuthenticationInfoResolverWrapper
|
||||
// ServiceResolver knows how to convert a webhook service reference into an actual location.
|
||||
ServiceResolver webhook.ServiceResolver
|
||||
}
|
||||
|
||||
// EventConfig holds the configurations for sending event notifiations about AuditSink API objects
|
||||
type EventConfig struct {
|
||||
// Sink for emitting events
|
||||
Sink record.EventSink
|
||||
// Source holds the source information about the event emitter
|
||||
Source corev1.EventSource
|
||||
}
|
||||
|
||||
// delegate represents a delegate backend that was created from an audit sink configuration
|
||||
type delegate struct {
|
||||
audit.Backend
|
||||
configuration *auditregv1alpha1.AuditSink
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
// gracefulShutdown will gracefully shutdown the delegate
|
||||
func (d *delegate) gracefulShutdown() {
|
||||
close(d.stopChan)
|
||||
d.Shutdown()
|
||||
}
|
||||
|
||||
// NewBackend returns a backend that dynamically updates its configuration
|
||||
// based on a shared informer.
|
||||
func NewBackend(c *Config) (audit.Backend, error) {
|
||||
eventBroadcaster := record.NewBroadcaster()
|
||||
eventBroadcaster.StartLogging(klog.Infof)
|
||||
eventBroadcaster.StartRecordingToSink(c.EventConfig.Sink)
|
||||
|
||||
scheme := runtime.NewScheme()
|
||||
err := auditregv1alpha1.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
recorder := eventBroadcaster.NewRecorder(scheme, c.EventConfig.Source)
|
||||
|
||||
if c.BufferedConfig == nil {
|
||||
c.BufferedConfig = NewDefaultWebhookBatchConfig()
|
||||
}
|
||||
cm, err := webhook.NewClientManager(auditv1.SchemeGroupVersion, func(s *runtime.Scheme) error {
|
||||
auditinstall.Install(s)
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// TODO: need a way of injecting authentication before beta
|
||||
authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver("")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cm.SetAuthenticationInfoResolver(authInfoResolver)
|
||||
cm.SetServiceResolver(c.WebhookConfig.ServiceResolver)
|
||||
cm.SetAuthenticationInfoResolverWrapper(c.WebhookConfig.AuthInfoResolverWrapper)
|
||||
|
||||
manager := &backend{
|
||||
config: c,
|
||||
delegates: atomic.Value{},
|
||||
delegateUpdateMutex: sync.Mutex{},
|
||||
webhookClientManager: cm,
|
||||
recorder: recorder,
|
||||
}
|
||||
manager.delegates.Store(syncedDelegates{})
|
||||
|
||||
c.Informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{
|
||||
AddFunc: func(obj interface{}) {
|
||||
manager.addSink(obj.(*auditregv1alpha1.AuditSink))
|
||||
},
|
||||
UpdateFunc: func(oldObj, newObj interface{}) {
|
||||
manager.updateSink(oldObj.(*auditregv1alpha1.AuditSink), newObj.(*auditregv1alpha1.AuditSink))
|
||||
},
|
||||
DeleteFunc: func(obj interface{}) {
|
||||
sink, ok := obj.(*auditregv1alpha1.AuditSink)
|
||||
if !ok {
|
||||
tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Couldn't get object from tombstone %#v", obj)
|
||||
return
|
||||
}
|
||||
sink, ok = tombstone.Obj.(*auditregv1alpha1.AuditSink)
|
||||
if !ok {
|
||||
klog.V(2).Infof("Tombstone contained object that is not an AuditSink: %#v", obj)
|
||||
return
|
||||
}
|
||||
}
|
||||
manager.deleteSink(sink)
|
||||
},
|
||||
})
|
||||
|
||||
return manager, nil
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
// delegateUpdateMutex holds an update lock on the delegates
|
||||
delegateUpdateMutex sync.Mutex
|
||||
config *Config
|
||||
delegates atomic.Value
|
||||
webhookClientManager webhook.ClientManager
|
||||
recorder record.EventRecorder
|
||||
}
|
||||
|
||||
type syncedDelegates map[types.UID]*delegate
|
||||
|
||||
// Names returns the names of the delegate configurations
|
||||
func (s syncedDelegates) Names() []string {
|
||||
names := []string{}
|
||||
for _, delegate := range s {
|
||||
names = append(names, delegate.configuration.Name)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
// ProcessEvents proccesses the given events per current delegate map
|
||||
func (b *backend) ProcessEvents(events ...*auditinternal.Event) bool {
|
||||
for _, d := range b.GetDelegates() {
|
||||
d.ProcessEvents(events...)
|
||||
}
|
||||
// Returning true regardless of results, since dynamic audit backends
|
||||
// can never cause apiserver request to fail.
|
||||
return true
|
||||
}
|
||||
|
||||
// Run starts a goroutine that propagates the shutdown signal,
|
||||
// individual delegates are ran as they are created.
|
||||
func (b *backend) Run(stopCh <-chan struct{}) error {
|
||||
go func() {
|
||||
<-stopCh
|
||||
b.stopAllDelegates()
|
||||
}()
|
||||
return nil
|
||||
}
|
||||
|
||||
// stopAllDelegates closes the stopChan for every delegate to enable
|
||||
// goroutines to terminate gracefully. This is a helper method to propagate
|
||||
// the primary stopChan to the current delegate map.
|
||||
func (b *backend) stopAllDelegates() {
|
||||
b.delegateUpdateMutex.Lock()
|
||||
for _, d := range b.GetDelegates() {
|
||||
close(d.stopChan)
|
||||
}
|
||||
}
|
||||
|
||||
// Shutdown calls the shutdown method on all delegates. The stopChan should
|
||||
// be closed before this is called.
|
||||
func (b *backend) Shutdown() {
|
||||
for _, d := range b.GetDelegates() {
|
||||
d.Shutdown()
|
||||
}
|
||||
}
|
||||
|
||||
// GetDelegates retrieves current delegates in a safe manner
|
||||
func (b *backend) GetDelegates() syncedDelegates {
|
||||
return b.delegates.Load().(syncedDelegates)
|
||||
}
|
||||
|
||||
// copyDelegates returns a copied delegate map
|
||||
func (b *backend) copyDelegates() syncedDelegates {
|
||||
c := make(syncedDelegates)
|
||||
for u, s := range b.GetDelegates() {
|
||||
c[u] = s
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// setDelegates sets the current delegates in a safe manner
|
||||
func (b *backend) setDelegates(delegates syncedDelegates) {
|
||||
b.delegates.Store(delegates)
|
||||
}
|
||||
|
||||
// addSink is called by the shared informer when a sink is added
|
||||
func (b *backend) addSink(sink *auditregv1alpha1.AuditSink) {
|
||||
b.delegateUpdateMutex.Lock()
|
||||
defer b.delegateUpdateMutex.Unlock()
|
||||
delegates := b.copyDelegates()
|
||||
if _, ok := delegates[sink.UID]; ok {
|
||||
klog.Errorf("Audit sink %q uid: %s already exists, could not readd", sink.Name, sink.UID)
|
||||
return
|
||||
}
|
||||
d, err := b.createAndStartDelegate(sink)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Could not add audit sink %q: %v", sink.Name, err)
|
||||
klog.Error(msg)
|
||||
b.recorder.Event(sink, corev1.EventTypeWarning, "CreateFailed", msg)
|
||||
return
|
||||
}
|
||||
delegates[sink.UID] = d
|
||||
b.setDelegates(delegates)
|
||||
klog.V(2).Infof("Added audit sink: %s", sink.Name)
|
||||
klog.V(2).Infof("Current audit sinks: %v", delegates.Names())
|
||||
}
|
||||
|
||||
// updateSink is called by the shared informer when a sink is updated.
|
||||
// The new sink is only rebuilt on spec changes. The new sink must not have
|
||||
// the same uid as the previous. The new sink will be started before the old
|
||||
// one is shutdown so no events will be lost
|
||||
func (b *backend) updateSink(oldSink, newSink *auditregv1alpha1.AuditSink) {
|
||||
b.delegateUpdateMutex.Lock()
|
||||
defer b.delegateUpdateMutex.Unlock()
|
||||
delegates := b.copyDelegates()
|
||||
oldDelegate, ok := delegates[oldSink.UID]
|
||||
if !ok {
|
||||
klog.Errorf("Could not update audit sink %q uid: %s, old sink does not exist",
|
||||
oldSink.Name, oldSink.UID)
|
||||
return
|
||||
}
|
||||
|
||||
// check if spec has changed
|
||||
eq := reflect.DeepEqual(oldSink.Spec, newSink.Spec)
|
||||
if eq {
|
||||
delete(delegates, oldSink.UID)
|
||||
delegates[newSink.UID] = oldDelegate
|
||||
b.setDelegates(delegates)
|
||||
} else {
|
||||
d, err := b.createAndStartDelegate(newSink)
|
||||
if err != nil {
|
||||
msg := fmt.Sprintf("Could not update audit sink %q: %v", oldSink.Name, err)
|
||||
klog.Error(msg)
|
||||
b.recorder.Event(newSink, corev1.EventTypeWarning, "UpdateFailed", msg)
|
||||
return
|
||||
}
|
||||
delete(delegates, oldSink.UID)
|
||||
delegates[newSink.UID] = d
|
||||
b.setDelegates(delegates)
|
||||
oldDelegate.gracefulShutdown()
|
||||
}
|
||||
|
||||
klog.V(2).Infof("Updated audit sink: %s", newSink.Name)
|
||||
klog.V(2).Infof("Current audit sinks: %v", delegates.Names())
|
||||
}
|
||||
|
||||
// deleteSink is called by the shared informer when a sink is deleted
|
||||
func (b *backend) deleteSink(sink *auditregv1alpha1.AuditSink) {
|
||||
b.delegateUpdateMutex.Lock()
|
||||
defer b.delegateUpdateMutex.Unlock()
|
||||
delegates := b.copyDelegates()
|
||||
delegate, ok := delegates[sink.UID]
|
||||
if !ok {
|
||||
klog.Errorf("Could not delete audit sink %q uid: %s, does not exist", sink.Name, sink.UID)
|
||||
return
|
||||
}
|
||||
delete(delegates, sink.UID)
|
||||
b.setDelegates(delegates)
|
||||
delegate.gracefulShutdown()
|
||||
klog.V(2).Infof("Deleted audit sink: %s", sink.Name)
|
||||
klog.V(2).Infof("Current audit sinks: %v", delegates.Names())
|
||||
}
|
||||
|
||||
// createAndStartDelegate will build a delegate from an audit sink configuration and run it
|
||||
func (b *backend) createAndStartDelegate(sink *auditregv1alpha1.AuditSink) (*delegate, error) {
|
||||
f := factory{
|
||||
config: b.config,
|
||||
webhookClientManager: b.webhookClientManager,
|
||||
sink: sink,
|
||||
}
|
||||
delegate, err := f.BuildDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
err = delegate.Run(delegate.stopChan)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return delegate, nil
|
||||
}
|
||||
|
||||
// String returns a string representation of the backend
|
||||
func (b *backend) String() string {
|
||||
var delegateStrings []string
|
||||
for _, delegate := range b.GetDelegates() {
|
||||
delegateStrings = append(delegateStrings, fmt.Sprintf("%s", delegate))
|
||||
}
|
||||
return fmt.Sprintf("%s[%s]", PluginName, strings.Join(delegateStrings, ","))
|
||||
}
|
||||
93
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/enforced/enforced.go
generated
vendored
Normal file
93
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/enforced/enforced.go
generated
vendored
Normal file
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
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 enforced
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
ev "k8s.io/apiserver/pkg/audit/event"
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
)
|
||||
|
||||
// PluginName is the name reported in error metrics.
|
||||
const PluginName = "enforced"
|
||||
|
||||
// Backend filters audit events according to the policy
|
||||
// trimming them as necessary to match the level
|
||||
type Backend struct {
|
||||
policyChecker policy.Checker
|
||||
delegateBackend audit.Backend
|
||||
}
|
||||
|
||||
// NewBackend returns an enforced audit backend that wraps delegate backend.
|
||||
// Enforced backend automatically runs and shuts down the delegate backend.
|
||||
func NewBackend(delegate audit.Backend, p policy.Checker) audit.Backend {
|
||||
return &Backend{
|
||||
policyChecker: p,
|
||||
delegateBackend: delegate,
|
||||
}
|
||||
}
|
||||
|
||||
// Run the delegate backend
|
||||
func (b Backend) Run(stopCh <-chan struct{}) error {
|
||||
return b.delegateBackend.Run(stopCh)
|
||||
}
|
||||
|
||||
// Shutdown the delegate backend
|
||||
func (b Backend) Shutdown() {
|
||||
b.delegateBackend.Shutdown()
|
||||
}
|
||||
|
||||
// ProcessEvents enforces policy on a shallow copy of the given event
|
||||
// dropping any sections that don't conform
|
||||
func (b Backend) ProcessEvents(events ...*auditinternal.Event) bool {
|
||||
for _, event := range events {
|
||||
if event == nil {
|
||||
continue
|
||||
}
|
||||
attr, err := ev.NewAttributes(event)
|
||||
if err != nil {
|
||||
audit.HandlePluginError(PluginName, err, event)
|
||||
continue
|
||||
}
|
||||
level, stages := b.policyChecker.LevelAndStages(attr)
|
||||
if level == auditinternal.LevelNone {
|
||||
continue
|
||||
}
|
||||
// make shallow copy before modifying to satisfy interface definition
|
||||
ev := *event
|
||||
e, err := policy.EnforcePolicy(&ev, level, stages)
|
||||
if err != nil {
|
||||
audit.HandlePluginError(PluginName, err, event)
|
||||
continue
|
||||
}
|
||||
if e == nil {
|
||||
continue
|
||||
}
|
||||
b.delegateBackend.ProcessEvents(e)
|
||||
}
|
||||
// Returning true regardless of results, since dynamic audit backends
|
||||
// can never cause apiserver request to fail.
|
||||
return true
|
||||
}
|
||||
|
||||
// String returns a string representation of the backend
|
||||
func (b Backend) String() string {
|
||||
return fmt.Sprintf("%s<%s>", PluginName, b.delegateBackend)
|
||||
}
|
||||
91
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/factory.go
generated
vendored
Normal file
91
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/factory.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
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 dynamic
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
auditregv1alpha1 "k8s.io/api/auditregistration/v1alpha1"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/audit/policy"
|
||||
auditutil "k8s.io/apiserver/pkg/audit/util"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
bufferedplugin "k8s.io/apiserver/plugin/pkg/audit/buffered"
|
||||
enforcedplugin "k8s.io/apiserver/plugin/pkg/audit/dynamic/enforced"
|
||||
webhookplugin "k8s.io/apiserver/plugin/pkg/audit/webhook"
|
||||
)
|
||||
|
||||
// TODO: find a common place for all the default retry backoffs
|
||||
const retryBackoff = 500 * time.Millisecond
|
||||
|
||||
// factory builds a delegate from an AuditSink
|
||||
type factory struct {
|
||||
config *Config
|
||||
webhookClientManager webhook.ClientManager
|
||||
sink *auditregv1alpha1.AuditSink
|
||||
}
|
||||
|
||||
// BuildDelegate creates a delegate from the AuditSink object
|
||||
func (f *factory) BuildDelegate() (*delegate, error) {
|
||||
backend, err := f.buildWebhookBackend()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
backend = f.applyEnforcedOpts(backend)
|
||||
backend = f.applyBufferedOpts(backend)
|
||||
ch := make(chan struct{})
|
||||
return &delegate{
|
||||
Backend: backend,
|
||||
configuration: f.sink,
|
||||
stopChan: ch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *factory) buildWebhookBackend() (audit.Backend, error) {
|
||||
hookClient := auditutil.HookClientConfigForSink(f.sink)
|
||||
client, err := f.webhookClientManager.HookClient(hookClient)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not create webhook client: %v", err)
|
||||
}
|
||||
backend := webhookplugin.NewDynamicBackend(client, retryBackoff)
|
||||
return backend, nil
|
||||
}
|
||||
|
||||
func (f *factory) applyEnforcedOpts(delegate audit.Backend) audit.Backend {
|
||||
pol := policy.ConvertDynamicPolicyToInternal(&f.sink.Spec.Policy)
|
||||
checker := policy.NewChecker(pol)
|
||||
eb := enforcedplugin.NewBackend(delegate, checker)
|
||||
return eb
|
||||
}
|
||||
|
||||
func (f *factory) applyBufferedOpts(delegate audit.Backend) audit.Backend {
|
||||
bc := f.config.BufferedConfig
|
||||
tc := f.sink.Spec.Webhook.Throttle
|
||||
if tc != nil {
|
||||
bc.ThrottleEnable = true
|
||||
if tc.Burst != nil {
|
||||
bc.ThrottleBurst = int(*tc.Burst)
|
||||
}
|
||||
if tc.QPS != nil {
|
||||
bc.ThrottleQPS = float32(*tc.QPS)
|
||||
}
|
||||
} else {
|
||||
bc.ThrottleEnable = false
|
||||
}
|
||||
return bufferedplugin.NewBackend(delegate, *bc)
|
||||
}
|
||||
104
vendor/k8s.io/apiserver/plugin/pkg/audit/log/backend.go
generated
vendored
Normal file
104
vendor/k8s.io/apiserver/plugin/pkg/audit/log/backend.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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 log
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
|
||||
const (
|
||||
// FormatLegacy saves event in 1-line text format.
|
||||
FormatLegacy = "legacy"
|
||||
// FormatJson saves event in structured json format.
|
||||
FormatJson = "json"
|
||||
|
||||
// PluginName is the name of this plugin, to be used in help and logs.
|
||||
PluginName = "log"
|
||||
)
|
||||
|
||||
// AllowedFormats are the formats known by log backend.
|
||||
var AllowedFormats = []string{
|
||||
FormatLegacy,
|
||||
FormatJson,
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
out io.Writer
|
||||
format string
|
||||
groupVersion schema.GroupVersion
|
||||
}
|
||||
|
||||
var _ audit.Backend = &backend{}
|
||||
|
||||
func NewBackend(out io.Writer, format string, groupVersion schema.GroupVersion) audit.Backend {
|
||||
return &backend{
|
||||
out: out,
|
||||
format: format,
|
||||
groupVersion: groupVersion,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) ProcessEvents(events ...*auditinternal.Event) bool {
|
||||
success := true
|
||||
for _, ev := range events {
|
||||
success = b.logEvent(ev) && success
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
func (b *backend) logEvent(ev *auditinternal.Event) bool {
|
||||
line := ""
|
||||
switch b.format {
|
||||
case FormatLegacy:
|
||||
line = audit.EventString(ev) + "\n"
|
||||
case FormatJson:
|
||||
bs, err := runtime.Encode(audit.Codecs.LegacyCodec(b.groupVersion), ev)
|
||||
if err != nil {
|
||||
audit.HandlePluginError(PluginName, err, ev)
|
||||
return false
|
||||
}
|
||||
line = string(bs[:])
|
||||
default:
|
||||
audit.HandlePluginError(PluginName, fmt.Errorf("log format %q is not in list of known formats (%s)",
|
||||
b.format, strings.Join(AllowedFormats, ",")), ev)
|
||||
return false
|
||||
}
|
||||
if _, err := fmt.Fprint(b.out, line); err != nil {
|
||||
audit.HandlePluginError(PluginName, err, ev)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *backend) Run(stopCh <-chan struct{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) Shutdown() {
|
||||
// Nothing to do here.
|
||||
}
|
||||
|
||||
func (b *backend) String() string {
|
||||
return PluginName
|
||||
}
|
||||
19
vendor/k8s.io/apiserver/plugin/pkg/audit/truncate/doc.go
generated
vendored
Normal file
19
vendor/k8s.io/apiserver/plugin/pkg/audit/truncate/doc.go
generated
vendored
Normal file
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
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 truncate provides an implementation for the audit.Backend interface
|
||||
// that truncates audit events and sends them to the delegate audit.Backend.
|
||||
package truncate // import "k8s.io/apiserver/plugin/pkg/audit/truncate"
|
||||
160
vendor/k8s.io/apiserver/plugin/pkg/audit/truncate/truncate.go
generated
vendored
Normal file
160
vendor/k8s.io/apiserver/plugin/pkg/audit/truncate/truncate.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
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 truncate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName is the name reported in error metrics.
|
||||
PluginName = "truncate"
|
||||
|
||||
// annotationKey defines the name of the annotation used to indicate truncation.
|
||||
annotationKey = "audit.k8s.io/truncated"
|
||||
// annotationValue defines the value of the annotation used to indicate truncation.
|
||||
annotationValue = "true"
|
||||
)
|
||||
|
||||
// Config represents truncating backend configuration.
|
||||
type Config struct {
|
||||
// MaxEventSize defines max allowed size of the event. If the event is larger,
|
||||
// truncating will be performed.
|
||||
MaxEventSize int64
|
||||
|
||||
// MaxBatchSize defined max allowed size of the batch of events, passed to the backend.
|
||||
// If the total size of the batch is larger than this number, batch will be split. Actual
|
||||
// size of the serialized request might be slightly higher, on the order of hundreds of bytes.
|
||||
MaxBatchSize int64
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
// The delegate backend that actually exports events.
|
||||
delegateBackend audit.Backend
|
||||
|
||||
// Configuration used for truncation.
|
||||
c Config
|
||||
|
||||
// Encoder used to calculate audit event sizes.
|
||||
e runtime.Encoder
|
||||
}
|
||||
|
||||
var _ audit.Backend = &backend{}
|
||||
|
||||
// NewBackend returns a new truncating backend, using configuration passed in the parameters.
|
||||
// Truncate backend automatically runs and shut downs the delegate backend.
|
||||
func NewBackend(delegateBackend audit.Backend, config Config, groupVersion schema.GroupVersion) audit.Backend {
|
||||
return &backend{
|
||||
delegateBackend: delegateBackend,
|
||||
c: config,
|
||||
e: audit.Codecs.LegacyCodec(groupVersion),
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) ProcessEvents(events ...*auditinternal.Event) bool {
|
||||
var errors []error
|
||||
var impacted []*auditinternal.Event
|
||||
var batch []*auditinternal.Event
|
||||
var batchSize int64
|
||||
success := true
|
||||
for _, event := range events {
|
||||
size, err := b.calcSize(event)
|
||||
// If event was correctly serialized, but the size is more than allowed
|
||||
// and it makes sense to do trimming, i.e. there's a request and/or
|
||||
// response present, try to strip away request and response.
|
||||
if err == nil && size > b.c.MaxEventSize && event.Level.GreaterOrEqual(auditinternal.LevelRequest) {
|
||||
event = truncate(event)
|
||||
size, err = b.calcSize(event)
|
||||
}
|
||||
if err != nil {
|
||||
errors = append(errors, err)
|
||||
impacted = append(impacted, event)
|
||||
continue
|
||||
}
|
||||
if size > b.c.MaxEventSize {
|
||||
errors = append(errors, fmt.Errorf("event is too large even after truncating"))
|
||||
impacted = append(impacted, event)
|
||||
continue
|
||||
}
|
||||
|
||||
if len(batch) > 0 && batchSize+size > b.c.MaxBatchSize {
|
||||
success = b.delegateBackend.ProcessEvents(batch...) && success
|
||||
batch = []*auditinternal.Event{}
|
||||
batchSize = 0
|
||||
}
|
||||
|
||||
batchSize += size
|
||||
batch = append(batch, event)
|
||||
}
|
||||
|
||||
if len(batch) > 0 {
|
||||
success = b.delegateBackend.ProcessEvents(batch...) && success
|
||||
}
|
||||
|
||||
if len(impacted) > 0 {
|
||||
audit.HandlePluginError(PluginName, utilerrors.NewAggregate(errors), impacted...)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
// truncate removed request and response objects from the audit events,
|
||||
// to try and keep at least metadata.
|
||||
func truncate(e *auditinternal.Event) *auditinternal.Event {
|
||||
// Make a shallow copy to avoid copying response/request objects.
|
||||
newEvent := &auditinternal.Event{}
|
||||
*newEvent = *e
|
||||
|
||||
newEvent.RequestObject = nil
|
||||
newEvent.ResponseObject = nil
|
||||
audit.LogAnnotation(newEvent, annotationKey, annotationValue)
|
||||
return newEvent
|
||||
}
|
||||
|
||||
func (b *backend) Run(stopCh <-chan struct{}) error {
|
||||
return b.delegateBackend.Run(stopCh)
|
||||
}
|
||||
|
||||
func (b *backend) Shutdown() {
|
||||
b.delegateBackend.Shutdown()
|
||||
}
|
||||
|
||||
func (b *backend) calcSize(e *auditinternal.Event) (int64, error) {
|
||||
s := &sizer{}
|
||||
if err := b.e.Encode(e, s); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return s.Size, nil
|
||||
}
|
||||
|
||||
func (b *backend) String() string {
|
||||
return fmt.Sprintf("%s<%s>", PluginName, b.delegateBackend)
|
||||
}
|
||||
|
||||
type sizer struct {
|
||||
Size int64
|
||||
}
|
||||
|
||||
func (s *sizer) Write(p []byte) (n int, err error) {
|
||||
s.Size += int64(len(p))
|
||||
return len(p), nil
|
||||
}
|
||||
104
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go
generated
vendored
Normal file
104
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
/*
|
||||
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 webhook implements the audit.Backend interface using HTTP webhooks.
|
||||
package webhook
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/apis/audit/install"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
// PluginName is the name of this plugin, to be used in help and logs.
|
||||
PluginName = "webhook"
|
||||
|
||||
// DefaultInitialBackoff is the default amount of time to wait before
|
||||
// retrying sending audit events through a webhook.
|
||||
DefaultInitialBackoff = 10 * time.Second
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(audit.Scheme)
|
||||
}
|
||||
|
||||
func loadWebhook(configFile string, groupVersion schema.GroupVersion, initialBackoff time.Duration) (*webhook.GenericWebhook, error) {
|
||||
return webhook.NewGenericWebhook(audit.Scheme, audit.Codecs, configFile,
|
||||
[]schema.GroupVersion{groupVersion}, initialBackoff)
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
w *webhook.GenericWebhook
|
||||
name string
|
||||
}
|
||||
|
||||
// NewDynamicBackend returns an audit backend configured from a REST client that
|
||||
// sends events over HTTP to an external service.
|
||||
func NewDynamicBackend(rc *rest.RESTClient, initialBackoff time.Duration) audit.Backend {
|
||||
return &backend{
|
||||
w: &webhook.GenericWebhook{
|
||||
RestClient: rc,
|
||||
InitialBackoff: initialBackoff,
|
||||
},
|
||||
name: fmt.Sprintf("dynamic_%s", PluginName),
|
||||
}
|
||||
}
|
||||
|
||||
// NewBackend returns an audit backend that sends events over HTTP to an external service.
|
||||
func NewBackend(kubeConfigFile string, groupVersion schema.GroupVersion, initialBackoff time.Duration) (audit.Backend, error) {
|
||||
w, err := loadWebhook(kubeConfigFile, groupVersion, initialBackoff)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &backend{w: w, name: PluginName}, nil
|
||||
}
|
||||
|
||||
func (b *backend) Run(stopCh <-chan struct{}) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) Shutdown() {
|
||||
// nothing to do here
|
||||
}
|
||||
|
||||
func (b *backend) ProcessEvents(ev ...*auditinternal.Event) bool {
|
||||
if err := b.processEvents(ev...); err != nil {
|
||||
audit.HandlePluginError(b.String(), err, ev...)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *backend) processEvents(ev ...*auditinternal.Event) error {
|
||||
var list auditinternal.EventList
|
||||
for _, e := range ev {
|
||||
list.Items = append(list.Items, *e)
|
||||
}
|
||||
return b.w.WithExponentialBackoff(func() rest.Result {
|
||||
return b.w.RestClient.Post().Body(&list).Do()
|
||||
}).Error()
|
||||
}
|
||||
|
||||
func (b *backend) String() string {
|
||||
return b.name
|
||||
}
|
||||
Reference in New Issue
Block a user