build: move e2e dependencies into e2e/go.mod

Several packages are only used while running the e2e suite. These
packages are less important to update, as the they can not influence the
final executable that is part of the Ceph-CSI container-image.

By moving these dependencies out of the main Ceph-CSI go.mod, it is
easier to identify if a reported CVE affects Ceph-CSI, or only the
testing (like most of the Kubernetes CVEs).

Signed-off-by: Niels de Vos <ndevos@ibm.com>
This commit is contained in:
Niels de Vos
2025-03-04 08:57:28 +01:00
committed by mergify[bot]
parent 15da101b1b
commit bec6090996
8047 changed files with 1407827 additions and 3453 deletions

View 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)
}

View 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"

View 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
encoder runtime.Encoder
}
var _ audit.Backend = &backend{}
func NewBackend(out io.Writer, format string, groupVersion schema.GroupVersion) audit.Backend {
return &backend{
out: out,
format: format,
encoder: audit.Codecs.LegacyCodec(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(b.encoder, 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
}

View 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"

View File

@ -0,0 +1,165 @@
/*
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
if newEvent.Annotations == nil {
newEvent.Annotations = make(map[string]string)
}
newEvent.Annotations[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
}

View File

@ -0,0 +1,146 @@
/*
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 (
"context"
"fmt"
"time"
"go.opentelemetry.io/otel/attribute"
"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"
"k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/rest"
"k8s.io/component-base/tracing"
)
const (
// PluginName is the name of this plugin, to be used in help and logs.
PluginName = "webhook"
// DefaultInitialBackoffDelay is the default amount of time to wait before
// retrying sending audit events through a webhook.
DefaultInitialBackoffDelay = 10 * time.Second
)
func init() {
install.Install(audit.Scheme)
}
// retryOnError enforces the webhook client to retry requests
// on error regardless of its nature.
// The default implementation considers a very limited set of
// 'retriable' errors, assuming correct use of HTTP codes by
// external webhooks.
// That may easily lead to dropped audit events. In fact, there is
// hardly any error that could be a justified reason NOT to retry
// sending audit events if there is even a slight chance that the
// receiving service gets back to normal at some point.
func retryOnError(err error) bool {
if err != nil {
return true
}
return false
}
func loadWebhook(configFile string, groupVersion schema.GroupVersion, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*webhook.GenericWebhook, error) {
clientConfig, err := webhook.LoadKubeconfig(configFile, customDial)
if err != nil {
return nil, err
}
w, err := webhook.NewGenericWebhook(audit.Scheme, audit.Codecs, clientConfig,
[]schema.GroupVersion{groupVersion}, retryBackoff)
if err != nil {
return nil, err
}
w.ShouldRetry = retryOnError
return w, nil
}
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, retryBackoff wait.Backoff) audit.Backend {
return &backend{
w: &webhook.GenericWebhook{
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, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (audit.Backend, error) {
w, err := loadWebhook(kubeConfigFile, groupVersion, retryBackoff, customDial)
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(context.Background(), func() rest.Result {
ctx, span := tracing.Start(context.Background(), "Call Audit Events webhook",
attribute.String("name", b.name),
attribute.Int("event-count", len(list.Items)),
)
// Only log audit webhook traces that exceed a 25ms per object limit plus a 50ms
// request overhead allowance. The high per object limit used here is primarily to
// allow enough time for the serialization/deserialization of audit events, which
// contain nested request and response objects plus additional event fields.
defer span.End(time.Duration(50+25*len(list.Items)) * time.Millisecond)
return b.w.RestClient.Post().Body(&list).Do(ctx)
}).Error()
}
func (b *backend) String() string {
return b.name
}