| /* |
| 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 |
| |
| import ( |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "strings" |
| "time" |
| |
| corev1 "k8s.io/api/core/v1" |
| "k8s.io/client-go/rest" |
| "k8s.io/client-go/tools/clientcmd" |
| clientcmdapi "k8s.io/client-go/tools/clientcmd/api" |
| ) |
| |
| // AuthenticationInfoResolverWrapper can be used to inject Dial function to the |
| // rest.Config generated by the resolver. |
| type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver |
| |
| // NewDefaultAuthenticationInfoResolverWrapper builds a default authn resolver wrapper |
| func NewDefaultAuthenticationInfoResolverWrapper( |
| proxyTransport *http.Transport, |
| kubeapiserverClientConfig *rest.Config) AuthenticationInfoResolverWrapper { |
| |
| webhookAuthResolverWrapper := func(delegate AuthenticationInfoResolver) AuthenticationInfoResolver { |
| return &AuthenticationInfoResolverDelegator{ |
| ClientConfigForFunc: func(server string) (*rest.Config, error) { |
| if server == "kubernetes.default.svc" { |
| return kubeapiserverClientConfig, nil |
| } |
| return delegate.ClientConfigFor(server) |
| }, |
| ClientConfigForServiceFunc: func(serviceName, serviceNamespace string) (*rest.Config, error) { |
| if serviceName == "kubernetes" && serviceNamespace == corev1.NamespaceDefault { |
| return kubeapiserverClientConfig, nil |
| } |
| ret, err := delegate.ClientConfigForService(serviceName, serviceNamespace) |
| if err != nil { |
| return nil, err |
| } |
| if proxyTransport != nil && proxyTransport.DialContext != nil { |
| ret.Dial = proxyTransport.DialContext |
| } |
| return ret, err |
| }, |
| } |
| } |
| return webhookAuthResolverWrapper |
| } |
| |
| // AuthenticationInfoResolver builds rest.Config base on the server or service |
| // name and service namespace. |
| type AuthenticationInfoResolver interface { |
| // ClientConfigFor builds rest.Config based on the server. |
| ClientConfigFor(server string) (*rest.Config, error) |
| // ClientConfigForService builds rest.Config based on the serviceName and |
| // serviceNamespace. |
| ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) |
| } |
| |
| // AuthenticationInfoResolverDelegator implements AuthenticationInfoResolver. |
| type AuthenticationInfoResolverDelegator struct { |
| ClientConfigForFunc func(server string) (*rest.Config, error) |
| ClientConfigForServiceFunc func(serviceName, serviceNamespace string) (*rest.Config, error) |
| } |
| |
| // ClientConfigFor returns client config for given server. |
| func (a *AuthenticationInfoResolverDelegator) ClientConfigFor(server string) (*rest.Config, error) { |
| return a.ClientConfigForFunc(server) |
| } |
| |
| // ClientConfigForService returns client config for given service. |
| func (a *AuthenticationInfoResolverDelegator) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) { |
| return a.ClientConfigForServiceFunc(serviceName, serviceNamespace) |
| } |
| |
| type defaultAuthenticationInfoResolver struct { |
| kubeconfig clientcmdapi.Config |
| } |
| |
| // NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver |
| // that builds rest.Config based on the kubeconfig file. kubeconfigFile is the |
| // path to the kubeconfig. |
| func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) { |
| if len(kubeconfigFile) == 0 { |
| return &defaultAuthenticationInfoResolver{}, nil |
| } |
| |
| loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() |
| loadingRules.ExplicitPath = kubeconfigFile |
| loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) |
| clientConfig, err := loader.RawConfig() |
| if err != nil { |
| return nil, err |
| } |
| |
| return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil |
| } |
| |
| func (c *defaultAuthenticationInfoResolver) ClientConfigFor(server string) (*rest.Config, error) { |
| return c.clientConfig(server) |
| } |
| |
| func (c *defaultAuthenticationInfoResolver) ClientConfigForService(serviceName, serviceNamespace string) (*rest.Config, error) { |
| return c.clientConfig(serviceName + "." + serviceNamespace + ".svc") |
| } |
| |
| func (c *defaultAuthenticationInfoResolver) clientConfig(target string) (*rest.Config, error) { |
| // exact match |
| if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok { |
| return restConfigFromKubeconfig(authConfig) |
| } |
| |
| // star prefixed match |
| serverSteps := strings.Split(target, ".") |
| for i := 1; i < len(serverSteps); i++ { |
| nickName := "*." + strings.Join(serverSteps[i:], ".") |
| if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok { |
| return restConfigFromKubeconfig(authConfig) |
| } |
| } |
| |
| // if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config |
| if target == "kubernetes.default.svc" { |
| // if we can find an in-cluster-config use that. If we can't, fall through. |
| inClusterConfig, err := rest.InClusterConfig() |
| if err == nil { |
| return setGlobalDefaults(inClusterConfig), nil |
| } |
| } |
| |
| // star (default) match |
| if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok { |
| return restConfigFromKubeconfig(authConfig) |
| } |
| |
| // use the current context from the kubeconfig if possible |
| if len(c.kubeconfig.CurrentContext) > 0 { |
| if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok { |
| if len(currContext.AuthInfo) > 0 { |
| if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok { |
| return restConfigFromKubeconfig(currAuth) |
| } |
| } |
| } |
| } |
| |
| // anonymous |
| return setGlobalDefaults(&rest.Config{}), nil |
| } |
| |
| func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) { |
| config := &rest.Config{} |
| |
| // blindly overwrite existing values based on precedence |
| if len(configAuthInfo.Token) > 0 { |
| config.BearerToken = configAuthInfo.Token |
| } else if len(configAuthInfo.TokenFile) > 0 { |
| tokenBytes, err := ioutil.ReadFile(configAuthInfo.TokenFile) |
| if err != nil { |
| return nil, err |
| } |
| config.BearerToken = string(tokenBytes) |
| config.BearerTokenFile = configAuthInfo.TokenFile |
| } |
| if len(configAuthInfo.Impersonate) > 0 { |
| config.Impersonate = rest.ImpersonationConfig{ |
| UserName: configAuthInfo.Impersonate, |
| Groups: configAuthInfo.ImpersonateGroups, |
| Extra: configAuthInfo.ImpersonateUserExtra, |
| } |
| } |
| if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 { |
| config.CertFile = configAuthInfo.ClientCertificate |
| config.CertData = configAuthInfo.ClientCertificateData |
| config.KeyFile = configAuthInfo.ClientKey |
| config.KeyData = configAuthInfo.ClientKeyData |
| } |
| if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 { |
| config.Username = configAuthInfo.Username |
| config.Password = configAuthInfo.Password |
| } |
| if configAuthInfo.AuthProvider != nil { |
| return nil, fmt.Errorf("auth provider not supported") |
| } |
| |
| return setGlobalDefaults(config), nil |
| } |
| |
| func setGlobalDefaults(config *rest.Config) *rest.Config { |
| config.UserAgent = "kube-apiserver-admission" |
| config.Timeout = 30 * time.Second |
| |
| return config |
| } |