blob: fdcbdd9e128dfdac782988703ae7f6d91ecab972 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2018 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package generic
18
19import (
20 "context"
21 "fmt"
22 "io"
23
24 "k8s.io/api/admissionregistration/v1beta1"
25 apierrors "k8s.io/apimachinery/pkg/api/errors"
26 "k8s.io/apimachinery/pkg/runtime"
27 "k8s.io/apiserver/pkg/admission"
28 genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
29 "k8s.io/apiserver/pkg/admission/plugin/webhook/config"
30 "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace"
31 "k8s.io/apiserver/pkg/admission/plugin/webhook/rules"
32 "k8s.io/client-go/informers"
33 clientset "k8s.io/client-go/kubernetes"
34)
35
36// Webhook is an abstract admission plugin with all the infrastructure to define Admit or Validate on-top.
37type Webhook struct {
38 *admission.Handler
39
40 sourceFactory sourceFactory
41
42 hookSource Source
43 clientManager *config.ClientManager
44 convertor *convertor
45 namespaceMatcher *namespace.Matcher
46 dispatcher Dispatcher
47}
48
49var (
50 _ genericadmissioninit.WantsExternalKubeClientSet = &Webhook{}
51 _ admission.Interface = &Webhook{}
52)
53
54type sourceFactory func(f informers.SharedInformerFactory) Source
55type dispatcherFactory func(cm *config.ClientManager) Dispatcher
56
57// NewWebhook creates a new generic admission webhook.
58func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) {
59 kubeconfigFile, err := config.LoadConfig(configFile)
60 if err != nil {
61 return nil, err
62 }
63
64 cm, err := config.NewClientManager()
65 if err != nil {
66 return nil, err
67 }
68 authInfoResolver, err := config.NewDefaultAuthenticationInfoResolver(kubeconfigFile)
69 if err != nil {
70 return nil, err
71 }
72 // Set defaults which may be overridden later.
73 cm.SetAuthenticationInfoResolver(authInfoResolver)
74 cm.SetServiceResolver(config.NewDefaultServiceResolver())
75
76 return &Webhook{
77 Handler: handler,
78 sourceFactory: sourceFactory,
79 clientManager: &cm,
80 convertor: &convertor{},
81 namespaceMatcher: &namespace.Matcher{},
82 dispatcher: dispatcherFactory(&cm),
83 }, nil
84}
85
86// SetAuthenticationInfoResolverWrapper sets the
87// AuthenticationInfoResolverWrapper.
88// TODO find a better way wire this, but keep this pull small for now.
89func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper config.AuthenticationInfoResolverWrapper) {
90 a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper)
91}
92
93// SetServiceResolver sets a service resolver for the webhook admission plugin.
94// Passing a nil resolver does not have an effect, instead a default one will be used.
95func (a *Webhook) SetServiceResolver(sr config.ServiceResolver) {
96 a.clientManager.SetServiceResolver(sr)
97}
98
99// SetScheme sets a serializer(NegotiatedSerializer) which is derived from the scheme
100func (a *Webhook) SetScheme(scheme *runtime.Scheme) {
101 if scheme != nil {
102 a.convertor.Scheme = scheme
103 }
104}
105
106// SetExternalKubeClientSet implements the WantsExternalKubeInformerFactory interface.
107// It sets external ClientSet for admission plugins that need it
108func (a *Webhook) SetExternalKubeClientSet(client clientset.Interface) {
109 a.namespaceMatcher.Client = client
110}
111
112// SetExternalKubeInformerFactory implements the WantsExternalKubeInformerFactory interface.
113func (a *Webhook) SetExternalKubeInformerFactory(f informers.SharedInformerFactory) {
114 namespaceInformer := f.Core().V1().Namespaces()
115 a.namespaceMatcher.NamespaceLister = namespaceInformer.Lister()
116 a.hookSource = a.sourceFactory(f)
117 a.SetReadyFunc(func() bool {
118 return namespaceInformer.Informer().HasSynced() && a.hookSource.HasSynced()
119 })
120}
121
122// ValidateInitialization implements the InitializationValidator interface.
123func (a *Webhook) ValidateInitialization() error {
124 if a.hookSource == nil {
125 return fmt.Errorf("kubernetes client is not properly setup")
126 }
127 if err := a.namespaceMatcher.Validate(); err != nil {
128 return fmt.Errorf("namespaceMatcher is not properly setup: %v", err)
129 }
130 if err := a.clientManager.Validate(); err != nil {
131 return fmt.Errorf("clientManager is not properly setup: %v", err)
132 }
133 if err := a.convertor.Validate(); err != nil {
134 return fmt.Errorf("convertor is not properly setup: %v", err)
135 }
136 return nil
137}
138
139// ShouldCallHook makes a decision on whether to call the webhook or not by the attribute.
140func (a *Webhook) ShouldCallHook(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) {
141 var matches bool
142 for _, r := range h.Rules {
143 m := rules.Matcher{Rule: r, Attr: attr}
144 if m.Matches() {
145 matches = true
146 break
147 }
148 }
149 if !matches {
150 return false, nil
151 }
152
153 return a.namespaceMatcher.MatchNamespaceSelector(h, attr)
154}
155
156// Dispatch is called by the downstream Validate or Admit methods.
157func (a *Webhook) Dispatch(attr admission.Attributes) error {
158 if rules.IsWebhookConfigurationResource(attr) {
159 return nil
160 }
161 if !a.WaitForReady() {
162 return admission.NewForbidden(attr, fmt.Errorf("not yet ready to handle request"))
163 }
164 hooks := a.hookSource.Webhooks()
165 ctx := context.TODO()
166
167 var relevantHooks []*v1beta1.Webhook
168 for i := range hooks {
169 call, err := a.ShouldCallHook(&hooks[i], attr)
170 if err != nil {
171 return err
172 }
173 if call {
174 relevantHooks = append(relevantHooks, &hooks[i])
175 }
176 }
177
178 if len(relevantHooks) == 0 {
179 // no matching hooks
180 return nil
181 }
182
183 // convert the object to the external version before sending it to the webhook
184 versionedAttr := VersionedAttributes{
185 Attributes: attr,
186 }
187 if oldObj := attr.GetOldObject(); oldObj != nil {
188 out, err := a.convertor.ConvertToGVK(oldObj, attr.GetKind())
189 if err != nil {
190 return apierrors.NewInternalError(err)
191 }
192 versionedAttr.VersionedOldObject = out
193 }
194 if obj := attr.GetObject(); obj != nil {
195 out, err := a.convertor.ConvertToGVK(obj, attr.GetKind())
196 if err != nil {
197 return apierrors.NewInternalError(err)
198 }
199 versionedAttr.VersionedObject = out
200 }
201 return a.dispatcher.Dispatch(ctx, &versionedAttr, relevantHooks)
202}