k8s.io/apiserver@v0.31.1/pkg/server/options/audit.go (about)

     1  /*
     2  Copyright 2017 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package options
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"os"
    23  	"path/filepath"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/spf13/pflag"
    28  	"gopkg.in/natefinch/lumberjack.v2"
    29  	"k8s.io/klog/v2"
    30  
    31  	"k8s.io/apimachinery/pkg/runtime/schema"
    32  	utilnet "k8s.io/apimachinery/pkg/util/net"
    33  	"k8s.io/apimachinery/pkg/util/sets"
    34  	auditinternal "k8s.io/apiserver/pkg/apis/audit"
    35  	auditv1 "k8s.io/apiserver/pkg/apis/audit/v1"
    36  	"k8s.io/apiserver/pkg/audit"
    37  	"k8s.io/apiserver/pkg/audit/policy"
    38  	"k8s.io/apiserver/pkg/server"
    39  	"k8s.io/apiserver/pkg/server/egressselector"
    40  	"k8s.io/apiserver/pkg/util/webhook"
    41  	pluginbuffered "k8s.io/apiserver/plugin/pkg/audit/buffered"
    42  	pluginlog "k8s.io/apiserver/plugin/pkg/audit/log"
    43  	plugintruncate "k8s.io/apiserver/plugin/pkg/audit/truncate"
    44  	pluginwebhook "k8s.io/apiserver/plugin/pkg/audit/webhook"
    45  )
    46  
    47  const (
    48  	// Default configuration values for ModeBatch.
    49  	defaultBatchBufferSize = 10000 // Buffer up to 10000 events before starting discarding.
    50  	// These batch parameters are only used by the webhook backend.
    51  	defaultBatchMaxSize       = 400              // Only send up to 400 events at a time.
    52  	defaultBatchMaxWait       = 30 * time.Second // Send events at least twice a minute.
    53  	defaultBatchThrottleQPS   = 10               // Limit the send rate by 10 QPS.
    54  	defaultBatchThrottleBurst = 15               // Allow up to 15 QPS burst.
    55  )
    56  
    57  func appendBackend(existing, newBackend audit.Backend) audit.Backend {
    58  	if existing == nil {
    59  		return newBackend
    60  	}
    61  	if newBackend == nil {
    62  		return existing
    63  	}
    64  	return audit.Union(existing, newBackend)
    65  }
    66  
    67  type AuditOptions struct {
    68  	// Policy configuration file for filtering audit events that are captured.
    69  	// If unspecified, a default is provided.
    70  	PolicyFile string
    71  
    72  	// Plugin options
    73  	LogOptions     AuditLogOptions
    74  	WebhookOptions AuditWebhookOptions
    75  }
    76  
    77  const (
    78  	// ModeBatch indicates that the audit backend should buffer audit events
    79  	// internally, sending batch updates either once a certain number of
    80  	// events have been received or a certain amount of time has passed.
    81  	ModeBatch = "batch"
    82  	// ModeBlocking causes the audit backend to block on every attempt to process
    83  	// a set of events. This causes requests to the API server to wait for the
    84  	// flush before sending a response.
    85  	ModeBlocking = "blocking"
    86  	// ModeBlockingStrict is the same as ModeBlocking, except when there is
    87  	// a failure during audit logging at RequestReceived stage, the whole
    88  	// request to apiserver will fail.
    89  	ModeBlockingStrict = "blocking-strict"
    90  )
    91  
    92  // AllowedModes is the modes known for audit backends.
    93  var AllowedModes = []string{
    94  	ModeBatch,
    95  	ModeBlocking,
    96  	ModeBlockingStrict,
    97  }
    98  
    99  type AuditBatchOptions struct {
   100  	// Should the backend asynchronous batch events to the webhook backend or
   101  	// should the backend block responses?
   102  	//
   103  	// Defaults to asynchronous batch events.
   104  	Mode string
   105  	// Configuration for batching backend. Only used in batch mode.
   106  	BatchConfig pluginbuffered.BatchConfig
   107  }
   108  
   109  type AuditTruncateOptions struct {
   110  	// Whether truncating is enabled or not.
   111  	Enabled bool
   112  
   113  	// Truncating configuration.
   114  	TruncateConfig plugintruncate.Config
   115  }
   116  
   117  // AuditLogOptions determines the output of the structured audit log by default.
   118  type AuditLogOptions struct {
   119  	Path       string
   120  	MaxAge     int
   121  	MaxBackups int
   122  	MaxSize    int
   123  	Format     string
   124  	Compress   bool
   125  
   126  	BatchOptions    AuditBatchOptions
   127  	TruncateOptions AuditTruncateOptions
   128  
   129  	// API group version used for serializing audit events.
   130  	GroupVersionString string
   131  }
   132  
   133  // AuditWebhookOptions control the webhook configuration for audit events.
   134  type AuditWebhookOptions struct {
   135  	ConfigFile     string
   136  	InitialBackoff time.Duration
   137  
   138  	BatchOptions    AuditBatchOptions
   139  	TruncateOptions AuditTruncateOptions
   140  
   141  	// API group version used for serializing audit events.
   142  	GroupVersionString string
   143  }
   144  
   145  func NewAuditOptions() *AuditOptions {
   146  	return &AuditOptions{
   147  		WebhookOptions: AuditWebhookOptions{
   148  			InitialBackoff: pluginwebhook.DefaultInitialBackoffDelay,
   149  			BatchOptions: AuditBatchOptions{
   150  				Mode:        ModeBatch,
   151  				BatchConfig: defaultWebhookBatchConfig(),
   152  			},
   153  			TruncateOptions:    NewAuditTruncateOptions(),
   154  			GroupVersionString: "audit.k8s.io/v1",
   155  		},
   156  		LogOptions: AuditLogOptions{
   157  			Format: pluginlog.FormatJson,
   158  			BatchOptions: AuditBatchOptions{
   159  				Mode:        ModeBlocking,
   160  				BatchConfig: defaultLogBatchConfig(),
   161  			},
   162  			TruncateOptions:    NewAuditTruncateOptions(),
   163  			GroupVersionString: "audit.k8s.io/v1",
   164  		},
   165  	}
   166  }
   167  
   168  func NewAuditTruncateOptions() AuditTruncateOptions {
   169  	return AuditTruncateOptions{
   170  		Enabled: false,
   171  		TruncateConfig: plugintruncate.Config{
   172  			MaxBatchSize: 10 * 1024 * 1024, // 10MB
   173  			MaxEventSize: 100 * 1024,       // 100KB
   174  		},
   175  	}
   176  }
   177  
   178  // Validate checks invalid config combination
   179  func (o *AuditOptions) Validate() []error {
   180  	if o == nil {
   181  		return nil
   182  	}
   183  
   184  	var allErrors []error
   185  	allErrors = append(allErrors, o.LogOptions.Validate()...)
   186  	allErrors = append(allErrors, o.WebhookOptions.Validate()...)
   187  
   188  	return allErrors
   189  }
   190  
   191  func validateBackendMode(pluginName string, mode string) error {
   192  	for _, m := range AllowedModes {
   193  		if m == mode {
   194  			return nil
   195  		}
   196  	}
   197  	return fmt.Errorf("invalid audit %s mode %s, allowed modes are %q", pluginName, mode, strings.Join(AllowedModes, ","))
   198  }
   199  
   200  func validateBackendBatchOptions(pluginName string, options AuditBatchOptions) error {
   201  	if err := validateBackendMode(pluginName, options.Mode); err != nil {
   202  		return err
   203  	}
   204  	if options.Mode != ModeBatch {
   205  		// Don't validate the unused options.
   206  		return nil
   207  	}
   208  	config := options.BatchConfig
   209  	if config.BufferSize <= 0 {
   210  		return fmt.Errorf("invalid audit batch %s buffer size %v, must be a positive number", pluginName, config.BufferSize)
   211  	}
   212  	if config.MaxBatchSize <= 0 {
   213  		return fmt.Errorf("invalid audit batch %s max batch size %v, must be a positive number", pluginName, config.MaxBatchSize)
   214  	}
   215  	if config.ThrottleEnable {
   216  		if config.ThrottleQPS <= 0 {
   217  			return fmt.Errorf("invalid audit batch %s throttle QPS %v, must be a positive number", pluginName, config.ThrottleQPS)
   218  		}
   219  		if config.ThrottleBurst <= 0 {
   220  			return fmt.Errorf("invalid audit batch %s throttle burst %v, must be a positive number", pluginName, config.ThrottleBurst)
   221  		}
   222  	}
   223  	return nil
   224  }
   225  
   226  var knownGroupVersions = []schema.GroupVersion{
   227  	auditv1.SchemeGroupVersion,
   228  }
   229  
   230  func validateGroupVersionString(groupVersion string) error {
   231  	gv, err := schema.ParseGroupVersion(groupVersion)
   232  	if err != nil {
   233  		return err
   234  	}
   235  	if !knownGroupVersion(gv) {
   236  		return fmt.Errorf("invalid group version, allowed versions are %q", knownGroupVersions)
   237  	}
   238  	if gv != auditv1.SchemeGroupVersion {
   239  		klog.Warningf("%q is deprecated and will be removed in a future release, use %q instead", gv, auditv1.SchemeGroupVersion)
   240  	}
   241  	return nil
   242  }
   243  
   244  func knownGroupVersion(gv schema.GroupVersion) bool {
   245  	for _, knownGv := range knownGroupVersions {
   246  		if gv == knownGv {
   247  			return true
   248  		}
   249  	}
   250  	return false
   251  }
   252  
   253  func (o *AuditOptions) AddFlags(fs *pflag.FlagSet) {
   254  	if o == nil {
   255  		return
   256  	}
   257  
   258  	fs.StringVar(&o.PolicyFile, "audit-policy-file", o.PolicyFile,
   259  		"Path to the file that defines the audit policy configuration.")
   260  
   261  	o.LogOptions.AddFlags(fs)
   262  	o.LogOptions.BatchOptions.AddFlags(pluginlog.PluginName, fs)
   263  	o.LogOptions.TruncateOptions.AddFlags(pluginlog.PluginName, fs)
   264  	o.WebhookOptions.AddFlags(fs)
   265  	o.WebhookOptions.BatchOptions.AddFlags(pluginwebhook.PluginName, fs)
   266  	o.WebhookOptions.TruncateOptions.AddFlags(pluginwebhook.PluginName, fs)
   267  }
   268  
   269  func (o *AuditOptions) ApplyTo(
   270  	c *server.Config,
   271  ) error {
   272  	if o == nil {
   273  		return nil
   274  	}
   275  	if c == nil {
   276  		return fmt.Errorf("server config must be non-nil")
   277  	}
   278  
   279  	// 1. Build policy evaluator
   280  	evaluator, err := o.newPolicyRuleEvaluator()
   281  	if err != nil {
   282  		return err
   283  	}
   284  
   285  	// 2. Build log backend
   286  	var logBackend audit.Backend
   287  	w, err := o.LogOptions.getWriter()
   288  	if err != nil {
   289  		return err
   290  	}
   291  	if w != nil {
   292  		if evaluator == nil {
   293  			klog.V(2).Info("No audit policy file provided, no events will be recorded for log backend")
   294  		} else {
   295  			logBackend = o.LogOptions.newBackend(w)
   296  		}
   297  	}
   298  
   299  	// 3. Build webhook backend
   300  	var webhookBackend audit.Backend
   301  	if o.WebhookOptions.enabled() {
   302  		if evaluator == nil {
   303  			klog.V(2).Info("No audit policy file provided, no events will be recorded for webhook backend")
   304  		} else {
   305  			if c.EgressSelector != nil {
   306  				var egressDialer utilnet.DialFunc
   307  				egressDialer, err = c.EgressSelector.Lookup(egressselector.ControlPlane.AsNetworkContext())
   308  				if err != nil {
   309  					return err
   310  				}
   311  				webhookBackend, err = o.WebhookOptions.newUntruncatedBackend(egressDialer)
   312  			} else {
   313  				webhookBackend, err = o.WebhookOptions.newUntruncatedBackend(nil)
   314  			}
   315  			if err != nil {
   316  				return err
   317  			}
   318  		}
   319  	}
   320  
   321  	groupVersion, err := schema.ParseGroupVersion(o.WebhookOptions.GroupVersionString)
   322  	if err != nil {
   323  		return err
   324  	}
   325  
   326  	// 4. Apply dynamic options.
   327  	var dynamicBackend audit.Backend
   328  	if webhookBackend != nil {
   329  		// if only webhook is enabled wrap it in the truncate options
   330  		dynamicBackend = o.WebhookOptions.TruncateOptions.wrapBackend(webhookBackend, groupVersion)
   331  	}
   332  
   333  	// 5. Set the policy rule evaluator
   334  	c.AuditPolicyRuleEvaluator = evaluator
   335  
   336  	// 6. Join the log backend with the webhooks
   337  	c.AuditBackend = appendBackend(logBackend, dynamicBackend)
   338  
   339  	if c.AuditBackend != nil {
   340  		klog.V(2).Infof("Using audit backend: %s", c.AuditBackend)
   341  	}
   342  	return nil
   343  }
   344  
   345  func (o *AuditOptions) newPolicyRuleEvaluator() (audit.PolicyRuleEvaluator, error) {
   346  	if o.PolicyFile == "" {
   347  		return nil, nil
   348  	}
   349  
   350  	p, err := policy.LoadPolicyFromFile(o.PolicyFile)
   351  	if err != nil {
   352  		return nil, fmt.Errorf("loading audit policy file: %v", err)
   353  	}
   354  	return policy.NewPolicyRuleEvaluator(p), nil
   355  }
   356  
   357  func (o *AuditBatchOptions) AddFlags(pluginName string, fs *pflag.FlagSet) {
   358  	fs.StringVar(&o.Mode, fmt.Sprintf("audit-%s-mode", pluginName), o.Mode,
   359  		"Strategy for sending audit events. Blocking indicates sending events should block"+
   360  			" server responses. Batch causes the backend to buffer and write events"+
   361  			" asynchronously. Known modes are "+strings.Join(AllowedModes, ",")+".")
   362  	fs.IntVar(&o.BatchConfig.BufferSize, fmt.Sprintf("audit-%s-batch-buffer-size", pluginName),
   363  		o.BatchConfig.BufferSize, "The size of the buffer to store events before "+
   364  			"batching and writing. Only used in batch mode.")
   365  	fs.IntVar(&o.BatchConfig.MaxBatchSize, fmt.Sprintf("audit-%s-batch-max-size", pluginName),
   366  		o.BatchConfig.MaxBatchSize, "The maximum size of a batch. Only used in batch mode.")
   367  	fs.DurationVar(&o.BatchConfig.MaxBatchWait, fmt.Sprintf("audit-%s-batch-max-wait", pluginName),
   368  		o.BatchConfig.MaxBatchWait, "The amount of time to wait before force writing the "+
   369  			"batch that hadn't reached the max size. Only used in batch mode.")
   370  	fs.BoolVar(&o.BatchConfig.ThrottleEnable, fmt.Sprintf("audit-%s-batch-throttle-enable", pluginName),
   371  		o.BatchConfig.ThrottleEnable, "Whether batching throttling is enabled. Only used in batch mode.")
   372  	fs.Float32Var(&o.BatchConfig.ThrottleQPS, fmt.Sprintf("audit-%s-batch-throttle-qps", pluginName),
   373  		o.BatchConfig.ThrottleQPS, "Maximum average number of batches per second. "+
   374  			"Only used in batch mode.")
   375  	fs.IntVar(&o.BatchConfig.ThrottleBurst, fmt.Sprintf("audit-%s-batch-throttle-burst", pluginName),
   376  		o.BatchConfig.ThrottleBurst, "Maximum number of requests sent at the same "+
   377  			"moment if ThrottleQPS was not utilized before. Only used in batch mode.")
   378  }
   379  
   380  type ignoreErrorsBackend struct {
   381  	audit.Backend
   382  }
   383  
   384  func (i *ignoreErrorsBackend) ProcessEvents(ev ...*auditinternal.Event) bool {
   385  	i.Backend.ProcessEvents(ev...)
   386  	return true
   387  }
   388  
   389  func (i *ignoreErrorsBackend) String() string {
   390  	return fmt.Sprintf("ignoreErrors<%s>", i.Backend)
   391  }
   392  
   393  func (o *AuditBatchOptions) wrapBackend(delegate audit.Backend) audit.Backend {
   394  	if o.Mode == ModeBlockingStrict {
   395  		return delegate
   396  	}
   397  	if o.Mode == ModeBlocking {
   398  		return &ignoreErrorsBackend{Backend: delegate}
   399  	}
   400  	return pluginbuffered.NewBackend(delegate, o.BatchConfig)
   401  }
   402  
   403  func (o *AuditTruncateOptions) Validate(pluginName string) error {
   404  	config := o.TruncateConfig
   405  	if config.MaxEventSize <= 0 {
   406  		return fmt.Errorf("invalid audit truncate %s max event size %v, must be a positive number", pluginName, config.MaxEventSize)
   407  	}
   408  	if config.MaxBatchSize < config.MaxEventSize {
   409  		return fmt.Errorf("invalid audit truncate %s max batch size %v, must be greater than "+
   410  			"max event size (%v)", pluginName, config.MaxBatchSize, config.MaxEventSize)
   411  	}
   412  	return nil
   413  }
   414  
   415  func (o *AuditTruncateOptions) AddFlags(pluginName string, fs *pflag.FlagSet) {
   416  	fs.BoolVar(&o.Enabled, fmt.Sprintf("audit-%s-truncate-enabled", pluginName),
   417  		o.Enabled, "Whether event and batch truncating is enabled.")
   418  	fs.Int64Var(&o.TruncateConfig.MaxBatchSize, fmt.Sprintf("audit-%s-truncate-max-batch-size", pluginName),
   419  		o.TruncateConfig.MaxBatchSize, "Maximum size of the batch sent to the underlying backend. "+
   420  			"Actual serialized size can be several hundreds of bytes greater. If a batch exceeds this limit, "+
   421  			"it is split into several batches of smaller size.")
   422  	fs.Int64Var(&o.TruncateConfig.MaxEventSize, fmt.Sprintf("audit-%s-truncate-max-event-size", pluginName),
   423  		o.TruncateConfig.MaxEventSize, "Maximum size of the audit event sent to the underlying backend. "+
   424  			"If the size of an event is greater than this number, first request and response are removed, and "+
   425  			"if this doesn't reduce the size enough, event is discarded.")
   426  }
   427  
   428  func (o *AuditTruncateOptions) wrapBackend(delegate audit.Backend, gv schema.GroupVersion) audit.Backend {
   429  	if !o.Enabled {
   430  		return delegate
   431  	}
   432  	return plugintruncate.NewBackend(delegate, o.TruncateConfig, gv)
   433  }
   434  
   435  func (o *AuditLogOptions) AddFlags(fs *pflag.FlagSet) {
   436  	fs.StringVar(&o.Path, "audit-log-path", o.Path,
   437  		"If set, all requests coming to the apiserver will be logged to this file.  '-' means standard out.")
   438  	fs.IntVar(&o.MaxAge, "audit-log-maxage", o.MaxAge,
   439  		"The maximum number of days to retain old audit log files based on the timestamp encoded in their filename.")
   440  	fs.IntVar(&o.MaxBackups, "audit-log-maxbackup", o.MaxBackups,
   441  		"The maximum number of old audit log files to retain. Setting a value of 0 will mean there's no restriction on the number of files.")
   442  	fs.IntVar(&o.MaxSize, "audit-log-maxsize", o.MaxSize,
   443  		"The maximum size in megabytes of the audit log file before it gets rotated.")
   444  	fs.StringVar(&o.Format, "audit-log-format", o.Format,
   445  		"Format of saved audits. \"legacy\" indicates 1-line text format for each event."+
   446  			" \"json\" indicates structured json format. Known formats are "+
   447  			strings.Join(pluginlog.AllowedFormats, ",")+".")
   448  	fs.StringVar(&o.GroupVersionString, "audit-log-version", o.GroupVersionString,
   449  		"API group and version used for serializing audit events written to log.")
   450  	fs.BoolVar(&o.Compress, "audit-log-compress", o.Compress, "If set, the rotated log files will be compressed using gzip.")
   451  }
   452  
   453  func (o *AuditLogOptions) Validate() []error {
   454  	// Check whether the log backend is enabled based on the options.
   455  	if !o.enabled() {
   456  		return nil
   457  	}
   458  
   459  	var allErrors []error
   460  
   461  	if err := validateBackendBatchOptions(pluginlog.PluginName, o.BatchOptions); err != nil {
   462  		allErrors = append(allErrors, err)
   463  	}
   464  	if err := o.TruncateOptions.Validate(pluginlog.PluginName); err != nil {
   465  		allErrors = append(allErrors, err)
   466  	}
   467  
   468  	if err := validateGroupVersionString(o.GroupVersionString); err != nil {
   469  		allErrors = append(allErrors, err)
   470  	}
   471  
   472  	// Check log format
   473  	if !sets.NewString(pluginlog.AllowedFormats...).Has(o.Format) {
   474  		allErrors = append(allErrors, fmt.Errorf("invalid audit log format %s, allowed formats are %q", o.Format, strings.Join(pluginlog.AllowedFormats, ",")))
   475  	}
   476  
   477  	// Check validities of MaxAge, MaxBackups and MaxSize of log options, if file log backend is enabled.
   478  	if o.MaxAge < 0 {
   479  		allErrors = append(allErrors, fmt.Errorf("--audit-log-maxage %v can't be a negative number", o.MaxAge))
   480  	}
   481  	if o.MaxBackups < 0 {
   482  		allErrors = append(allErrors, fmt.Errorf("--audit-log-maxbackup %v can't be a negative number", o.MaxBackups))
   483  	}
   484  	if o.MaxSize < 0 {
   485  		allErrors = append(allErrors, fmt.Errorf("--audit-log-maxsize %v can't be a negative number", o.MaxSize))
   486  	}
   487  
   488  	return allErrors
   489  }
   490  
   491  // Check whether the log backend is enabled based on the options.
   492  func (o *AuditLogOptions) enabled() bool {
   493  	return o != nil && o.Path != ""
   494  }
   495  
   496  func (o *AuditLogOptions) getWriter() (io.Writer, error) {
   497  	if !o.enabled() {
   498  		return nil, nil
   499  	}
   500  
   501  	if o.Path == "-" {
   502  		return os.Stdout, nil
   503  	}
   504  
   505  	if err := o.ensureLogFile(); err != nil {
   506  		return nil, fmt.Errorf("ensureLogFile: %w", err)
   507  	}
   508  
   509  	return &lumberjack.Logger{
   510  		Filename:   o.Path,
   511  		MaxAge:     o.MaxAge,
   512  		MaxBackups: o.MaxBackups,
   513  		MaxSize:    o.MaxSize,
   514  		Compress:   o.Compress,
   515  	}, nil
   516  }
   517  
   518  func (o *AuditLogOptions) ensureLogFile() error {
   519  	if err := os.MkdirAll(filepath.Dir(o.Path), 0700); err != nil {
   520  		return err
   521  	}
   522  	mode := os.FileMode(0600)
   523  	f, err := os.OpenFile(o.Path, os.O_CREATE|os.O_APPEND|os.O_RDWR, mode)
   524  	if err != nil {
   525  		return err
   526  	}
   527  	return f.Close()
   528  }
   529  
   530  func (o *AuditLogOptions) newBackend(w io.Writer) audit.Backend {
   531  	groupVersion, _ := schema.ParseGroupVersion(o.GroupVersionString)
   532  	log := pluginlog.NewBackend(w, o.Format, groupVersion)
   533  	log = o.BatchOptions.wrapBackend(log)
   534  	log = o.TruncateOptions.wrapBackend(log, groupVersion)
   535  	return log
   536  }
   537  
   538  func (o *AuditWebhookOptions) AddFlags(fs *pflag.FlagSet) {
   539  	fs.StringVar(&o.ConfigFile, "audit-webhook-config-file", o.ConfigFile,
   540  		"Path to a kubeconfig formatted file that defines the audit webhook configuration.")
   541  	fs.DurationVar(&o.InitialBackoff, "audit-webhook-initial-backoff",
   542  		o.InitialBackoff, "The amount of time to wait before retrying the first failed request.")
   543  	fs.DurationVar(&o.InitialBackoff, "audit-webhook-batch-initial-backoff",
   544  		o.InitialBackoff, "The amount of time to wait before retrying the first failed request.")
   545  	fs.MarkDeprecated("audit-webhook-batch-initial-backoff",
   546  		"Deprecated, use --audit-webhook-initial-backoff instead.")
   547  	fs.StringVar(&o.GroupVersionString, "audit-webhook-version", o.GroupVersionString,
   548  		"API group and version used for serializing audit events written to webhook.")
   549  }
   550  
   551  func (o *AuditWebhookOptions) Validate() []error {
   552  	if !o.enabled() {
   553  		return nil
   554  	}
   555  
   556  	var allErrors []error
   557  	if err := validateBackendBatchOptions(pluginwebhook.PluginName, o.BatchOptions); err != nil {
   558  		allErrors = append(allErrors, err)
   559  	}
   560  	if err := o.TruncateOptions.Validate(pluginwebhook.PluginName); err != nil {
   561  		allErrors = append(allErrors, err)
   562  	}
   563  
   564  	if err := validateGroupVersionString(o.GroupVersionString); err != nil {
   565  		allErrors = append(allErrors, err)
   566  	}
   567  	return allErrors
   568  }
   569  
   570  func (o *AuditWebhookOptions) enabled() bool {
   571  	return o != nil && o.ConfigFile != ""
   572  }
   573  
   574  // newUntruncatedBackend returns a webhook backend without the truncate options applied
   575  // this is done so that the same trucate backend can wrap both the webhook and dynamic backends
   576  func (o *AuditWebhookOptions) newUntruncatedBackend(customDial utilnet.DialFunc) (audit.Backend, error) {
   577  	groupVersion, _ := schema.ParseGroupVersion(o.GroupVersionString)
   578  	webhook, err := pluginwebhook.NewBackend(o.ConfigFile, groupVersion, webhook.DefaultRetryBackoffWithInitialDelay(o.InitialBackoff), customDial)
   579  	if err != nil {
   580  		return nil, fmt.Errorf("initializing audit webhook: %v", err)
   581  	}
   582  	webhook = o.BatchOptions.wrapBackend(webhook)
   583  	return webhook, nil
   584  }
   585  
   586  // defaultWebhookBatchConfig returns the default BatchConfig used by the Webhook backend.
   587  func defaultWebhookBatchConfig() pluginbuffered.BatchConfig {
   588  	return pluginbuffered.BatchConfig{
   589  		BufferSize:   defaultBatchBufferSize,
   590  		MaxBatchSize: defaultBatchMaxSize,
   591  		MaxBatchWait: defaultBatchMaxWait,
   592  
   593  		ThrottleEnable: true,
   594  		ThrottleQPS:    defaultBatchThrottleQPS,
   595  		ThrottleBurst:  defaultBatchThrottleBurst,
   596  
   597  		AsyncDelegate: true,
   598  	}
   599  }
   600  
   601  // defaultLogBatchConfig returns the default BatchConfig used by the Log backend.
   602  func defaultLogBatchConfig() pluginbuffered.BatchConfig {
   603  	return pluginbuffered.BatchConfig{
   604  		BufferSize: defaultBatchBufferSize,
   605  		// Batching is not useful for the log-file backend.
   606  		// MaxBatchWait ignored.
   607  		MaxBatchSize:   1,
   608  		ThrottleEnable: false,
   609  		// Asynchronous log threads just create lock contention.
   610  		AsyncDelegate: false,
   611  	}
   612  }