46
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/defaults.go
generated
vendored
46
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/defaults.go
generated
vendored
@@ -1,46 +0,0 @@
|
||||
/*
|
||||
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,
|
||||
}
|
||||
}
|
||||
365
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/dynamic.go
generated
vendored
365
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/dynamic.go
generated
vendored
@@ -1,365 +0,0 @@
|
||||
/*
|
||||
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/runtime/schema"
|
||||
"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([]schema.GroupVersion{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{},
|
||||
stopped: false,
|
||||
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
|
||||
stopped bool
|
||||
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()
|
||||
defer b.delegateUpdateMutex.Unlock()
|
||||
if b.stopped {
|
||||
return
|
||||
}
|
||||
b.stopped = true
|
||||
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()
|
||||
if b.stopped {
|
||||
msg := fmt.Sprintf("Could not add audit sink %q uid: %s. Update to all delegates is stopped.", sink.Name, sink.UID)
|
||||
klog.Error(msg)
|
||||
return
|
||||
}
|
||||
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()
|
||||
if b.stopped {
|
||||
msg := fmt.Sprintf("Could not update old audit sink %q to new audit sink %q. Update to all delegates is stopped.", oldSink.Name, newSink.Name)
|
||||
klog.Error(msg)
|
||||
return
|
||||
}
|
||||
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)
|
||||
|
||||
// graceful shutdown in goroutine as to not block
|
||||
go 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()
|
||||
if b.stopped {
|
||||
msg := fmt.Sprintf("Could not delete audit sink %q uid: %s. Update to all delegates is stopped.", sink.Name, sink.UID)
|
||||
klog.Warning(msg)
|
||||
return
|
||||
}
|
||||
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)
|
||||
|
||||
// graceful shutdown in goroutine as to not block
|
||||
go 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
93
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/enforced/enforced.go
generated
vendored
@@ -1,93 +0,0 @@
|
||||
/*
|
||||
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
91
vendor/k8s.io/apiserver/plugin/pkg/audit/dynamic/factory.go
generated
vendored
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
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)
|
||||
}
|
||||
21
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go
generated
vendored
21
vendor/k8s.io/apiserver/plugin/pkg/audit/webhook/webhook.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
auditinternal "k8s.io/apiserver/pkg/apis/audit"
|
||||
"k8s.io/apiserver/pkg/apis/audit/install"
|
||||
"k8s.io/apiserver/pkg/audit"
|
||||
@@ -36,9 +37,9 @@ 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
|
||||
// DefaultInitialBackoffDelay is the default amount of time to wait before
|
||||
// retrying sending audit events through a webhook.
|
||||
DefaultInitialBackoff = 10 * time.Second
|
||||
DefaultInitialBackoffDelay = 10 * time.Second
|
||||
)
|
||||
|
||||
func init() {
|
||||
@@ -61,9 +62,9 @@ func retryOnError(err error) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func loadWebhook(configFile string, groupVersion schema.GroupVersion, initialBackoff time.Duration, customDial utilnet.DialFunc) (*webhook.GenericWebhook, error) {
|
||||
func loadWebhook(configFile string, groupVersion schema.GroupVersion, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*webhook.GenericWebhook, error) {
|
||||
w, err := webhook.NewGenericWebhook(audit.Scheme, audit.Codecs, configFile,
|
||||
[]schema.GroupVersion{groupVersion}, initialBackoff, customDial)
|
||||
[]schema.GroupVersion{groupVersion}, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -79,20 +80,20 @@ type backend struct {
|
||||
|
||||
// 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 {
|
||||
func NewDynamicBackend(rc *rest.RESTClient, retryBackoff wait.Backoff) audit.Backend {
|
||||
return &backend{
|
||||
w: &webhook.GenericWebhook{
|
||||
RestClient: rc,
|
||||
InitialBackoff: initialBackoff,
|
||||
ShouldRetry: retryOnError,
|
||||
RestClient: rc,
|
||||
RetryBackoff: retryBackoff,
|
||||
ShouldRetry: retryOnError,
|
||||
},
|
||||
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, customDial utilnet.DialFunc) (audit.Backend, error) {
|
||||
w, err := loadWebhook(kubeConfigFile, groupVersion, initialBackoff, customDial)
|
||||
func NewBackend(kubeConfigFile string, groupVersion schema.GroupVersion, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (audit.Backend, error) {
|
||||
w, err := loadWebhook(kubeConfigFile, groupVersion, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
54
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook.go
generated
vendored
54
vendor/k8s.io/apiserver/plugin/pkg/authenticator/token/webhook/webhook.go
generated
vendored
@@ -29,15 +29,20 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
authenticationv1client "k8s.io/client-go/kubernetes/typed/authentication/v1"
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const retryBackoff = 500 * time.Millisecond
|
||||
// DefaultRetryBackoff returns the default backoff parameters for webhook retry.
|
||||
func DefaultRetryBackoff() *wait.Backoff {
|
||||
backoff := webhook.DefaultRetryBackoffWithInitialDelay(500 * time.Millisecond)
|
||||
return &backoff
|
||||
}
|
||||
|
||||
// Ensure WebhookTokenAuthenticator implements the authenticator.Token interface.
|
||||
var _ authenticator.Token = (*WebhookTokenAuthenticator)(nil)
|
||||
@@ -48,33 +53,34 @@ type tokenReviewer interface {
|
||||
|
||||
type WebhookTokenAuthenticator struct {
|
||||
tokenReview tokenReviewer
|
||||
initialBackoff time.Duration
|
||||
retryBackoff wait.Backoff
|
||||
implicitAuds authenticator.Audiences
|
||||
requestTimeout time.Duration
|
||||
}
|
||||
|
||||
// NewFromInterface creates a webhook authenticator using the given tokenReview
|
||||
// client. It is recommend to wrap this authenticator with the token cache
|
||||
// authenticator implemented in
|
||||
// k8s.io/apiserver/pkg/authentication/token/cache.
|
||||
func NewFromInterface(tokenReview authenticationv1client.TokenReviewInterface, implicitAuds authenticator.Audiences) (*WebhookTokenAuthenticator, error) {
|
||||
return newWithBackoff(tokenReview, retryBackoff, implicitAuds)
|
||||
func NewFromInterface(tokenReview authenticationv1client.TokenReviewInterface, implicitAuds authenticator.Audiences, retryBackoff wait.Backoff, requestTimeout time.Duration) (*WebhookTokenAuthenticator, error) {
|
||||
return newWithBackoff(tokenReview, retryBackoff, implicitAuds, requestTimeout)
|
||||
}
|
||||
|
||||
// New creates a new WebhookTokenAuthenticator from the provided kubeconfig
|
||||
// file. It is recommend to wrap this authenticator with the token cache
|
||||
// authenticator implemented in
|
||||
// k8s.io/apiserver/pkg/authentication/token/cache.
|
||||
func New(kubeConfigFile string, version string, implicitAuds authenticator.Audiences, customDial utilnet.DialFunc) (*WebhookTokenAuthenticator, error) {
|
||||
tokenReview, err := tokenReviewInterfaceFromKubeconfig(kubeConfigFile, version, customDial)
|
||||
func New(kubeConfigFile string, version string, implicitAuds authenticator.Audiences, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*WebhookTokenAuthenticator, error) {
|
||||
tokenReview, err := tokenReviewInterfaceFromKubeconfig(kubeConfigFile, version, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return newWithBackoff(tokenReview, retryBackoff, implicitAuds)
|
||||
return newWithBackoff(tokenReview, retryBackoff, implicitAuds, time.Duration(0))
|
||||
}
|
||||
|
||||
// newWithBackoff allows tests to skip the sleep.
|
||||
func newWithBackoff(tokenReview tokenReviewer, initialBackoff time.Duration, implicitAuds authenticator.Audiences) (*WebhookTokenAuthenticator, error) {
|
||||
return &WebhookTokenAuthenticator{tokenReview, initialBackoff, implicitAuds}, nil
|
||||
func newWithBackoff(tokenReview tokenReviewer, retryBackoff wait.Backoff, implicitAuds authenticator.Audiences, requestTimeout time.Duration) (*WebhookTokenAuthenticator, error) {
|
||||
return &WebhookTokenAuthenticator{tokenReview, retryBackoff, implicitAuds, requestTimeout}, nil
|
||||
}
|
||||
|
||||
// AuthenticateToken implements the authenticator.Token interface.
|
||||
@@ -99,14 +105,24 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token
|
||||
}
|
||||
var (
|
||||
result *authenticationv1.TokenReview
|
||||
err error
|
||||
auds authenticator.Audiences
|
||||
cancel context.CancelFunc
|
||||
)
|
||||
webhook.WithExponentialBackoff(ctx, w.initialBackoff, func() error {
|
||||
result, err = w.tokenReview.Create(ctx, r, metav1.CreateOptions{})
|
||||
return err
|
||||
}, webhook.DefaultShouldRetry)
|
||||
if err != nil {
|
||||
|
||||
// set a hard timeout if it was defined
|
||||
// if the child has a shorter deadline then it will expire first,
|
||||
// otherwise if the parent has a shorter deadline then the parent will expire and it will be propagate to the child
|
||||
if w.requestTimeout > 0 {
|
||||
ctx, cancel = context.WithTimeout(ctx, w.requestTimeout)
|
||||
defer cancel()
|
||||
}
|
||||
|
||||
// WithExponentialBackoff will return tokenreview create error (tokenReviewErr) if any.
|
||||
if err := webhook.WithExponentialBackoff(ctx, w.retryBackoff, func() error {
|
||||
var tokenReviewErr error
|
||||
result, tokenReviewErr = w.tokenReview.Create(ctx, r, metav1.CreateOptions{})
|
||||
return tokenReviewErr
|
||||
}, webhook.DefaultShouldRetry); err != nil {
|
||||
// An error here indicates bad configuration or an outage. Log for debugging.
|
||||
klog.Errorf("Failed to make webhook authenticator request: %v", err)
|
||||
return nil, false, err
|
||||
@@ -154,7 +170,7 @@ func (w *WebhookTokenAuthenticator) AuthenticateToken(ctx context.Context, token
|
||||
// tokenReviewInterfaceFromKubeconfig builds a client from the specified kubeconfig file,
|
||||
// and returns a TokenReviewInterface that uses that client. Note that the client submits TokenReview
|
||||
// requests to the exact path specified in the kubeconfig file, so arbitrary non-API servers can be targeted.
|
||||
func tokenReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, customDial utilnet.DialFunc) (tokenReviewer, error) {
|
||||
func tokenReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (tokenReviewer, error) {
|
||||
localScheme := runtime.NewScheme()
|
||||
if err := scheme.AddToScheme(localScheme); err != nil {
|
||||
return nil, err
|
||||
@@ -166,7 +182,7 @@ func tokenReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, c
|
||||
if err := localScheme.SetVersionPriority(groupVersions...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, 0, customDial)
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -177,7 +193,7 @@ func tokenReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, c
|
||||
if err := localScheme.SetVersionPriority(groupVersions...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, 0, customDial)
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
46
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go
generated
vendored
46
vendor/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook.go
generated
vendored
@@ -23,7 +23,7 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
authorizationv1 "k8s.io/api/authorization/v1"
|
||||
authorizationv1beta1 "k8s.io/api/authorization/v1beta1"
|
||||
@@ -32,6 +32,7 @@ import (
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/cache"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
@@ -40,11 +41,16 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
retryBackoff = 500 * time.Millisecond
|
||||
// The maximum length of requester-controlled attributes to allow caching.
|
||||
maxControlledAttrCacheSize = 10000
|
||||
)
|
||||
|
||||
// DefaultRetryBackoff returns the default backoff parameters for webhook retry.
|
||||
func DefaultRetryBackoff() *wait.Backoff {
|
||||
backoff := webhook.DefaultRetryBackoffWithInitialDelay(500 * time.Millisecond)
|
||||
return &backoff
|
||||
}
|
||||
|
||||
// Ensure Webhook implements the authorizer.Authorizer interface.
|
||||
var _ authorizer.Authorizer = (*WebhookAuthorizer)(nil)
|
||||
|
||||
@@ -57,12 +63,12 @@ type WebhookAuthorizer struct {
|
||||
responseCache *cache.LRUExpireCache
|
||||
authorizedTTL time.Duration
|
||||
unauthorizedTTL time.Duration
|
||||
initialBackoff time.Duration
|
||||
retryBackoff wait.Backoff
|
||||
decisionOnError authorizer.Decision
|
||||
}
|
||||
|
||||
// NewFromInterface creates a WebhookAuthorizer using the given subjectAccessReview client
|
||||
func NewFromInterface(subjectAccessReview authorizationv1client.SubjectAccessReviewInterface, authorizedTTL, unauthorizedTTL time.Duration) (*WebhookAuthorizer, error) {
|
||||
func NewFromInterface(subjectAccessReview authorizationv1client.SubjectAccessReviewInterface, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff) (*WebhookAuthorizer, error) {
|
||||
return newWithBackoff(subjectAccessReview, authorizedTTL, unauthorizedTTL, retryBackoff)
|
||||
}
|
||||
|
||||
@@ -85,8 +91,8 @@ func NewFromInterface(subjectAccessReview authorizationv1client.SubjectAccessRev
|
||||
//
|
||||
// For additional HTTP configuration, refer to the kubeconfig documentation
|
||||
// https://kubernetes.io/docs/user-guide/kubeconfig-file/.
|
||||
func New(kubeConfigFile string, version string, authorizedTTL, unauthorizedTTL time.Duration, customDial utilnet.DialFunc) (*WebhookAuthorizer, error) {
|
||||
subjectAccessReview, err := subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile, version, customDial)
|
||||
func New(kubeConfigFile string, version string, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*WebhookAuthorizer, error) {
|
||||
subjectAccessReview, err := subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile, version, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -94,13 +100,13 @@ func New(kubeConfigFile string, version string, authorizedTTL, unauthorizedTTL t
|
||||
}
|
||||
|
||||
// newWithBackoff allows tests to skip the sleep.
|
||||
func newWithBackoff(subjectAccessReview subjectAccessReviewer, authorizedTTL, unauthorizedTTL, initialBackoff time.Duration) (*WebhookAuthorizer, error) {
|
||||
func newWithBackoff(subjectAccessReview subjectAccessReviewer, authorizedTTL, unauthorizedTTL time.Duration, retryBackoff wait.Backoff) (*WebhookAuthorizer, error) {
|
||||
return &WebhookAuthorizer{
|
||||
subjectAccessReview: subjectAccessReview,
|
||||
responseCache: cache.NewLRUExpireCache(8192),
|
||||
authorizedTTL: authorizedTTL,
|
||||
unauthorizedTTL: unauthorizedTTL,
|
||||
initialBackoff: initialBackoff,
|
||||
retryBackoff: retryBackoff,
|
||||
decisionOnError: authorizer.DecisionNoOpinion,
|
||||
}, nil
|
||||
}
|
||||
@@ -186,19 +192,17 @@ func (w *WebhookAuthorizer) Authorize(ctx context.Context, attr authorizer.Attri
|
||||
if entry, ok := w.responseCache.Get(string(key)); ok {
|
||||
r.Status = entry.(authorizationv1.SubjectAccessReviewStatus)
|
||||
} else {
|
||||
var (
|
||||
result *authorizationv1.SubjectAccessReview
|
||||
err error
|
||||
)
|
||||
webhook.WithExponentialBackoff(ctx, w.initialBackoff, func() error {
|
||||
result, err = w.subjectAccessReview.Create(ctx, r, metav1.CreateOptions{})
|
||||
return err
|
||||
}, webhook.DefaultShouldRetry)
|
||||
if err != nil {
|
||||
// An error here indicates bad configuration or an outage. Log for debugging.
|
||||
var result *authorizationv1.SubjectAccessReview
|
||||
// WithExponentialBackoff will return SAR create error (sarErr) if any.
|
||||
if err := webhook.WithExponentialBackoff(ctx, w.retryBackoff, func() error {
|
||||
var sarErr error
|
||||
result, sarErr = w.subjectAccessReview.Create(ctx, r, metav1.CreateOptions{})
|
||||
return sarErr
|
||||
}, webhook.DefaultShouldRetry); err != nil {
|
||||
klog.Errorf("Failed to make webhook authorizer request: %v", err)
|
||||
return w.decisionOnError, "", err
|
||||
}
|
||||
|
||||
r.Status = result.Status
|
||||
if shouldCache(attr) {
|
||||
if r.Status.Allowed {
|
||||
@@ -246,7 +250,7 @@ func convertToSARExtra(extra map[string][]string) map[string]authorizationv1.Ext
|
||||
// subjectAccessReviewInterfaceFromKubeconfig builds a client from the specified kubeconfig file,
|
||||
// and returns a SubjectAccessReviewInterface that uses that client. Note that the client submits SubjectAccessReview
|
||||
// requests to the exact path specified in the kubeconfig file, so arbitrary non-API servers can be targeted.
|
||||
func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, customDial utilnet.DialFunc) (subjectAccessReviewer, error) {
|
||||
func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version string, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (subjectAccessReviewer, error) {
|
||||
localScheme := runtime.NewScheme()
|
||||
if err := scheme.AddToScheme(localScheme); err != nil {
|
||||
return nil, err
|
||||
@@ -258,7 +262,7 @@ func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version s
|
||||
if err := localScheme.SetVersionPriority(groupVersions...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, 0, customDial)
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -269,7 +273,7 @@ func subjectAccessReviewInterfaceFromKubeconfig(kubeConfigFile string, version s
|
||||
if err := localScheme.SetVersionPriority(groupVersions...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, 0, customDial)
|
||||
gw, err := webhook.NewGenericWebhook(localScheme, scheme.Codecs, kubeConfigFile, groupVersions, retryBackoff, customDial)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user