k8s.io/apiserver@v0.31.1/plugin/pkg/audit/webhook/webhook.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 webhook implements the audit.Backend interface using HTTP webhooks. 18 package webhook 19 20 import ( 21 "context" 22 "fmt" 23 "time" 24 25 "go.opentelemetry.io/otel/attribute" 26 27 "k8s.io/apimachinery/pkg/runtime/schema" 28 utilnet "k8s.io/apimachinery/pkg/util/net" 29 "k8s.io/apimachinery/pkg/util/wait" 30 auditinternal "k8s.io/apiserver/pkg/apis/audit" 31 "k8s.io/apiserver/pkg/apis/audit/install" 32 "k8s.io/apiserver/pkg/audit" 33 "k8s.io/apiserver/pkg/util/webhook" 34 "k8s.io/client-go/rest" 35 "k8s.io/component-base/tracing" 36 ) 37 38 const ( 39 // PluginName is the name of this plugin, to be used in help and logs. 40 PluginName = "webhook" 41 42 // DefaultInitialBackoffDelay is the default amount of time to wait before 43 // retrying sending audit events through a webhook. 44 DefaultInitialBackoffDelay = 10 * time.Second 45 ) 46 47 func init() { 48 install.Install(audit.Scheme) 49 } 50 51 // retryOnError enforces the webhook client to retry requests 52 // on error regardless of its nature. 53 // The default implementation considers a very limited set of 54 // 'retriable' errors, assuming correct use of HTTP codes by 55 // external webhooks. 56 // That may easily lead to dropped audit events. In fact, there is 57 // hardly any error that could be a justified reason NOT to retry 58 // sending audit events if there is even a slight chance that the 59 // receiving service gets back to normal at some point. 60 func retryOnError(err error) bool { 61 if err != nil { 62 return true 63 } 64 return false 65 } 66 67 func loadWebhook(configFile string, groupVersion schema.GroupVersion, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (*webhook.GenericWebhook, error) { 68 clientConfig, err := webhook.LoadKubeconfig(configFile, customDial) 69 if err != nil { 70 return nil, err 71 } 72 w, err := webhook.NewGenericWebhook(audit.Scheme, audit.Codecs, clientConfig, 73 []schema.GroupVersion{groupVersion}, retryBackoff) 74 if err != nil { 75 return nil, err 76 } 77 78 w.ShouldRetry = retryOnError 79 return w, nil 80 } 81 82 type backend struct { 83 w *webhook.GenericWebhook 84 name string 85 } 86 87 // NewDynamicBackend returns an audit backend configured from a REST client that 88 // sends events over HTTP to an external service. 89 func NewDynamicBackend(rc *rest.RESTClient, retryBackoff wait.Backoff) audit.Backend { 90 return &backend{ 91 w: &webhook.GenericWebhook{ 92 RestClient: rc, 93 RetryBackoff: retryBackoff, 94 ShouldRetry: retryOnError, 95 }, 96 name: fmt.Sprintf("dynamic_%s", PluginName), 97 } 98 } 99 100 // NewBackend returns an audit backend that sends events over HTTP to an external service. 101 func NewBackend(kubeConfigFile string, groupVersion schema.GroupVersion, retryBackoff wait.Backoff, customDial utilnet.DialFunc) (audit.Backend, error) { 102 w, err := loadWebhook(kubeConfigFile, groupVersion, retryBackoff, customDial) 103 if err != nil { 104 return nil, err 105 } 106 return &backend{w: w, name: PluginName}, nil 107 } 108 109 func (b *backend) Run(stopCh <-chan struct{}) error { 110 return nil 111 } 112 113 func (b *backend) Shutdown() { 114 // nothing to do here 115 } 116 117 func (b *backend) ProcessEvents(ev ...*auditinternal.Event) bool { 118 if err := b.processEvents(ev...); err != nil { 119 audit.HandlePluginError(b.String(), err, ev...) 120 return false 121 } 122 return true 123 } 124 125 func (b *backend) processEvents(ev ...*auditinternal.Event) error { 126 var list auditinternal.EventList 127 for _, e := range ev { 128 list.Items = append(list.Items, *e) 129 } 130 return b.w.WithExponentialBackoff(context.Background(), func() rest.Result { 131 ctx, span := tracing.Start(context.Background(), "Call Audit Events webhook", 132 attribute.String("name", b.name), 133 attribute.Int("event-count", len(list.Items)), 134 ) 135 // Only log audit webhook traces that exceed a 25ms per object limit plus a 50ms 136 // request overhead allowance. The high per object limit used here is primarily to 137 // allow enough time for the serialization/deserialization of audit events, which 138 // contain nested request and response objects plus additional event fields. 139 defer span.End(time.Duration(50+25*len(list.Items)) * time.Millisecond) 140 return b.w.RestClient.Post().Body(&list).Do(ctx) 141 }).Error() 142 } 143 144 func (b *backend) String() string { 145 return b.name 146 }