Skip to content

Commit 7663156

Browse files
Merge pull request #55132 from caesarxuchao/webhook-move-shared-code
Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>. Reorganize admission webhook code ref: kubernetes/enhancements#492 * Moved client and kubeconfig related code to webhook/config; * Moved the rule matcher to webhook/rules; * Left TODOs saying we are going to move some other common utilities; * Other code is moved to webhook/validation. This is to prepare adding the mutating webhook. See kubernetes/kubernetes#54892. Kubernetes-commit: ff7934fdeea38bf6a56c61bbbe15721c4f45023e
2 parents eb9e4a3 + f88f0f1 commit 7663156

24 files changed

+458
-216
lines changed

pkg/admission/BUILD

+3-1
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ filegroup(
7878
"//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs",
7979
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/initialization:all-srcs",
8080
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs",
81-
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:all-srcs",
81+
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs",
82+
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:all-srcs",
83+
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating:all-srcs",
8284
],
8385
tags = ["automanaged"],
8486
)
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = [
6+
"authentication.go",
7+
"client.go",
8+
"errors.go",
9+
"kubeconfig.go",
10+
"serviceresolver.go",
11+
],
12+
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config",
13+
visibility = ["//visibility:public"],
14+
deps = [
15+
"//vendor/github.com/hashicorp/golang-lru:go_default_library",
16+
"//vendor/k8s.io/api/admissionregistration/v1alpha1:go_default_library",
17+
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
18+
"//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library",
19+
"//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library",
20+
"//vendor/k8s.io/client-go/rest:go_default_library",
21+
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
22+
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
23+
],
24+
)
25+
26+
go_test(
27+
name = "go_default_test",
28+
srcs = [
29+
"authentication_test.go",
30+
"serviceresolver_test.go",
31+
],
32+
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/config",
33+
library = ":go_default_library",
34+
deps = [
35+
"//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library",
36+
"//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library",
37+
"//vendor/k8s.io/client-go/rest:go_default_library",
38+
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
39+
],
40+
)
41+
42+
filegroup(
43+
name = "package-srcs",
44+
srcs = glob(["**"]),
45+
tags = ["automanaged"],
46+
visibility = ["//visibility:private"],
47+
)
48+
49+
filegroup(
50+
name = "all-srcs",
51+
srcs = [":package-srcs"],
52+
tags = ["automanaged"],
53+
visibility = ["//visibility:public"],
54+
)

pkg/admission/plugin/webhook/authentication.go renamed to pkg/admission/plugin/webhook/config/authentication.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package webhook
17+
package config
1818

1919
import (
2020
"fmt"
@@ -27,14 +27,19 @@ import (
2727
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
2828
)
2929

30+
// AuthenticationInfoResolverWrapper can be used to inject Dial function to the
31+
// rest.Config generated by the resolver.
3032
type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver
3133

34+
// AuthenticationInfoResolver builds rest.Config base on the server name.
3235
type AuthenticationInfoResolver interface {
3336
ClientConfigFor(server string) (*rest.Config, error)
3437
}
3538

39+
// AuthenticationInfoResolverFunc implements AuthenticationInfoResolver.
3640
type AuthenticationInfoResolverFunc func(server string) (*rest.Config, error)
3741

42+
//ClientConfigFor implements AuthenticationInfoResolver.
3843
func (a AuthenticationInfoResolverFunc) ClientConfigFor(server string) (*rest.Config, error) {
3944
return a(server)
4045
}
@@ -43,7 +48,10 @@ type defaultAuthenticationInfoResolver struct {
4348
kubeconfig clientcmdapi.Config
4449
}
4550

46-
func newDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) {
51+
// NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver
52+
// that builds rest.Config based on the kubeconfig file. kubeconfigFile is the
53+
// path to the kubeconfig.
54+
func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) {
4755
if len(kubeconfigFile) == 0 {
4856
return &defaultAuthenticationInfoResolver{}, nil
4957
}

pkg/admission/plugin/webhook/authentication_test.go renamed to pkg/admission/plugin/webhook/config/authentication_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package webhook
17+
package config
1818

1919
import (
2020
"testing"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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 config
18+
19+
import (
20+
"encoding/json"
21+
"errors"
22+
"fmt"
23+
"net"
24+
"net/url"
25+
26+
lru "github.com/hashicorp/golang-lru"
27+
"k8s.io/api/admissionregistration/v1alpha1"
28+
"k8s.io/apimachinery/pkg/runtime"
29+
utilerrors "k8s.io/apimachinery/pkg/util/errors"
30+
"k8s.io/client-go/rest"
31+
)
32+
33+
const (
34+
defaultCacheSize = 200
35+
)
36+
37+
var (
38+
ErrNeedServiceOrURL = errors.New("webhook configuration must have either service or URL")
39+
)
40+
41+
// ClientManager builds REST clients to talk to webhooks. It caches the clients
42+
// to avoid duplicate creation.
43+
type ClientManager struct {
44+
authInfoResolver AuthenticationInfoResolver
45+
serviceResolver ServiceResolver
46+
negotiatedSerializer runtime.NegotiatedSerializer
47+
cache *lru.Cache
48+
}
49+
50+
// NewClientManager creates a ClientManager.
51+
func NewClientManager() (ClientManager, error) {
52+
cache, err := lru.New(defaultCacheSize)
53+
if err != nil {
54+
return ClientManager{}, err
55+
}
56+
return ClientManager{
57+
cache: cache,
58+
}, nil
59+
}
60+
61+
// SetAuthenticationInfoResolverWrapper sets the
62+
// AuthenticationInfoResolverWrapper.
63+
func (cm *ClientManager) SetAuthenticationInfoResolverWrapper(wrapper AuthenticationInfoResolverWrapper) {
64+
if wrapper != nil {
65+
cm.authInfoResolver = wrapper(cm.authInfoResolver)
66+
}
67+
}
68+
69+
// SetAuthenticationInfoResolver sets the AuthenticationInfoResolver.
70+
func (cm *ClientManager) SetAuthenticationInfoResolver(resolver AuthenticationInfoResolver) {
71+
cm.authInfoResolver = resolver
72+
}
73+
74+
// SetServiceResolver sets the ServiceResolver.
75+
func (cm *ClientManager) SetServiceResolver(sr ServiceResolver) {
76+
if sr != nil {
77+
cm.serviceResolver = sr
78+
}
79+
}
80+
81+
// SetNegotiatedSerializer sets the NegotiatedSerializer.
82+
func (cm *ClientManager) SetNegotiatedSerializer(n runtime.NegotiatedSerializer) {
83+
cm.negotiatedSerializer = n
84+
}
85+
86+
// Validate checks if ClientManager is properly set up.
87+
func (cm *ClientManager) Validate() error {
88+
var errs []error
89+
if cm.negotiatedSerializer == nil {
90+
errs = append(errs, fmt.Errorf("the ClientManager requires a negotiatedSerializer"))
91+
}
92+
if cm.serviceResolver == nil {
93+
errs = append(errs, fmt.Errorf("the ClientManager requires a serviceResolver"))
94+
}
95+
if cm.authInfoResolver == nil {
96+
errs = append(errs, fmt.Errorf("the ClientManager requires an authInfoResolver"))
97+
}
98+
return utilerrors.NewAggregate(errs)
99+
}
100+
101+
// HookClient get a RESTClient from the cache, or constructs one based on the
102+
// webhook configuration.
103+
func (cm *ClientManager) HookClient(h *v1alpha1.Webhook) (*rest.RESTClient, error) {
104+
cacheKey, err := json.Marshal(h.ClientConfig)
105+
if err != nil {
106+
return nil, err
107+
}
108+
if client, ok := cm.cache.Get(string(cacheKey)); ok {
109+
return client.(*rest.RESTClient), nil
110+
}
111+
112+
complete := func(cfg *rest.Config) (*rest.RESTClient, error) {
113+
cfg.TLSClientConfig.CAData = h.ClientConfig.CABundle
114+
cfg.ContentConfig.NegotiatedSerializer = cm.negotiatedSerializer
115+
cfg.ContentConfig.ContentType = runtime.ContentTypeJSON
116+
client, err := rest.UnversionedRESTClientFor(cfg)
117+
if err == nil {
118+
cm.cache.Add(string(cacheKey), client)
119+
}
120+
return client, err
121+
}
122+
123+
if svc := h.ClientConfig.Service; svc != nil {
124+
serverName := svc.Name + "." + svc.Namespace + ".svc"
125+
restConfig, err := cm.authInfoResolver.ClientConfigFor(serverName)
126+
if err != nil {
127+
return nil, err
128+
}
129+
cfg := rest.CopyConfig(restConfig)
130+
host := serverName + ":443"
131+
cfg.Host = "https://" + host
132+
if svc.Path != nil {
133+
cfg.APIPath = *svc.Path
134+
}
135+
cfg.TLSClientConfig.ServerName = serverName
136+
137+
delegateDialer := cfg.Dial
138+
if delegateDialer == nil {
139+
delegateDialer = net.Dial
140+
}
141+
cfg.Dial = func(network, addr string) (net.Conn, error) {
142+
if addr == host {
143+
u, err := cm.serviceResolver.ResolveEndpoint(svc.Namespace, svc.Name)
144+
if err != nil {
145+
return nil, err
146+
}
147+
addr = u.Host
148+
}
149+
return delegateDialer(network, addr)
150+
}
151+
152+
return complete(cfg)
153+
}
154+
155+
if h.ClientConfig.URL == nil {
156+
return nil, &ErrCallingWebhook{WebhookName: h.Name, Reason: ErrNeedServiceOrURL}
157+
}
158+
159+
u, err := url.Parse(*h.ClientConfig.URL)
160+
if err != nil {
161+
return nil, &ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Unparsable URL: %v", err)}
162+
}
163+
164+
restConfig, err := cm.authInfoResolver.ClientConfigFor(u.Host)
165+
if err != nil {
166+
return nil, err
167+
}
168+
169+
cfg := rest.CopyConfig(restConfig)
170+
cfg.Host = u.Host
171+
cfg.APIPath = u.Path
172+
173+
return complete(cfg)
174+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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 config
18+
19+
import "fmt"
20+
21+
// ErrCallingWebhook is returned for transport-layer errors calling webhooks. It
22+
// represents a failure to talk to the webhook, not the webhook rejecting a
23+
// request.
24+
type ErrCallingWebhook struct {
25+
WebhookName string
26+
Reason error
27+
}
28+
29+
func (e *ErrCallingWebhook) Error() string {
30+
if e.Reason != nil {
31+
return fmt.Sprintf("failed calling admission webhook %q: %v", e.WebhookName, e.Reason)
32+
}
33+
return fmt.Sprintf("failed calling admission webhook %q; no further details available", e.WebhookName)
34+
}

pkg/admission/plugin/webhook/config.go renamed to pkg/admission/plugin/webhook/config/kubeconfig.go

+24-1
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,32 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
package webhook
17+
package config
18+
19+
import (
20+
"io"
21+
22+
"k8s.io/apimachinery/pkg/util/yaml"
23+
)
1824

1925
// AdmissionConfig holds config data that is unique to each API server.
2026
type AdmissionConfig struct {
27+
// KubeConfigFile is the path to the kubeconfig file.
2128
KubeConfigFile string `json:"kubeConfigFile"`
2229
}
30+
31+
// LoadConfig extract the KubeConfigFile from configFile
32+
func LoadConfig(configFile io.Reader) (string, error) {
33+
var kubeconfigFile string
34+
if configFile != nil {
35+
// TODO: move this to a versioned configuration file format
36+
var config AdmissionConfig
37+
d := yaml.NewYAMLOrJSONDecoder(configFile, 4096)
38+
err := d.Decode(&config)
39+
if err != nil {
40+
return "", err
41+
}
42+
kubeconfigFile = config.KubeConfigFile
43+
}
44+
return kubeconfigFile, nil
45+
}

pkg/admission/plugin/webhook/serviceresolver.go renamed to pkg/admission/plugin/webhook/config/serviceresolver.go

+10-2
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,25 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// Package webhook checks a webhook for configured operation admission
18-
package webhook
17+
package config
1918

2019
import (
2120
"errors"
2221
"fmt"
2322
"net/url"
2423
)
2524

25+
// ServiceResolver knows how to convert a service reference into an actual location.
26+
type ServiceResolver interface {
27+
ResolveEndpoint(namespace, name string) (*url.URL, error)
28+
}
29+
2630
type defaultServiceResolver struct{}
2731

32+
func NewDefaultServiceResolver() ServiceResolver {
33+
return &defaultServiceResolver{}
34+
}
35+
2836
// ResolveEndpoint constructs a service URL from a given namespace and name
2937
// note that the name and namespace are required and by default all created addresses use HTTPS scheme.
3038
// for example:

pkg/admission/plugin/webhook/serviceresolver_test.go renamed to pkg/admission/plugin/webhook/config/serviceresolver_test.go

+1-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ See the License for the specific language governing permissions and
1414
limitations under the License.
1515
*/
1616

17-
// Package webhook checks a webhook for configured operation admission
18-
package webhook
17+
package config
1918

2019
import (
2120
"fmt"

0 commit comments

Comments
 (0)