k8s.io/apiserver@v0.31.1/pkg/util/webhook/authentication.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 18 19 import ( 20 "fmt" 21 "net" 22 "net/http" 23 "os" 24 "strconv" 25 "strings" 26 "time" 27 28 "go.opentelemetry.io/otel/trace" 29 30 corev1 "k8s.io/api/core/v1" 31 utilnet "k8s.io/apimachinery/pkg/util/net" 32 "k8s.io/apiserver/pkg/features" 33 egressselector "k8s.io/apiserver/pkg/server/egressselector" 34 "k8s.io/apiserver/pkg/util/feature" 35 "k8s.io/client-go/rest" 36 "k8s.io/client-go/tools/clientcmd" 37 clientcmdapi "k8s.io/client-go/tools/clientcmd/api" 38 tracing "k8s.io/component-base/tracing" 39 ) 40 41 // AuthenticationInfoResolverWrapper can be used to inject Dial function to the 42 // rest.Config generated by the resolver. 43 type AuthenticationInfoResolverWrapper func(AuthenticationInfoResolver) AuthenticationInfoResolver 44 45 // NewDefaultAuthenticationInfoResolverWrapper builds a default authn resolver wrapper 46 func NewDefaultAuthenticationInfoResolverWrapper( 47 proxyTransport *http.Transport, 48 egressSelector *egressselector.EgressSelector, 49 kubeapiserverClientConfig *rest.Config, 50 tp trace.TracerProvider) AuthenticationInfoResolverWrapper { 51 52 webhookAuthResolverWrapper := func(delegate AuthenticationInfoResolver) AuthenticationInfoResolver { 53 return &AuthenticationInfoResolverDelegator{ 54 ClientConfigForFunc: func(hostPort string) (*rest.Config, error) { 55 if hostPort == "kubernetes.default.svc:443" { 56 return kubeapiserverClientConfig, nil 57 } 58 ret, err := delegate.ClientConfigFor(hostPort) 59 if err != nil { 60 return nil, err 61 } 62 if feature.DefaultFeatureGate.Enabled(features.APIServerTracing) { 63 ret.Wrap(tracing.WrapperFor(tp)) 64 } 65 66 if egressSelector != nil { 67 networkContext := egressselector.ControlPlane.AsNetworkContext() 68 var egressDialer utilnet.DialFunc 69 egressDialer, err = egressSelector.Lookup(networkContext) 70 71 if err != nil { 72 return nil, err 73 } 74 75 ret.Dial = egressDialer 76 } 77 return ret, nil 78 }, 79 ClientConfigForServiceFunc: func(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) { 80 if serviceName == "kubernetes" && serviceNamespace == corev1.NamespaceDefault && servicePort == 443 { 81 return kubeapiserverClientConfig, nil 82 } 83 ret, err := delegate.ClientConfigForService(serviceName, serviceNamespace, servicePort) 84 if err != nil { 85 return nil, err 86 } 87 if feature.DefaultFeatureGate.Enabled(features.APIServerTracing) { 88 ret.Wrap(tracing.WrapperFor(tp)) 89 } 90 91 if egressSelector != nil { 92 networkContext := egressselector.Cluster.AsNetworkContext() 93 var egressDialer utilnet.DialFunc 94 egressDialer, err = egressSelector.Lookup(networkContext) 95 if err != nil { 96 return nil, err 97 } 98 99 ret.Dial = egressDialer 100 } else if proxyTransport != nil && proxyTransport.DialContext != nil { 101 ret.Dial = proxyTransport.DialContext 102 } 103 return ret, nil 104 }, 105 } 106 } 107 return webhookAuthResolverWrapper 108 } 109 110 // AuthenticationInfoResolver builds rest.Config base on the server or service 111 // name and service namespace. 112 type AuthenticationInfoResolver interface { 113 // ClientConfigFor builds rest.Config based on the hostPort. 114 ClientConfigFor(hostPort string) (*rest.Config, error) 115 // ClientConfigForService builds rest.Config based on the serviceName and 116 // serviceNamespace. 117 ClientConfigForService(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) 118 } 119 120 // AuthenticationInfoResolverDelegator implements AuthenticationInfoResolver. 121 type AuthenticationInfoResolverDelegator struct { 122 ClientConfigForFunc func(hostPort string) (*rest.Config, error) 123 ClientConfigForServiceFunc func(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) 124 } 125 126 // ClientConfigFor returns client config for given hostPort. 127 func (a *AuthenticationInfoResolverDelegator) ClientConfigFor(hostPort string) (*rest.Config, error) { 128 return a.ClientConfigForFunc(hostPort) 129 } 130 131 // ClientConfigForService returns client config for given service. 132 func (a *AuthenticationInfoResolverDelegator) ClientConfigForService(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) { 133 return a.ClientConfigForServiceFunc(serviceName, serviceNamespace, servicePort) 134 } 135 136 type defaultAuthenticationInfoResolver struct { 137 kubeconfig clientcmdapi.Config 138 } 139 140 // NewDefaultAuthenticationInfoResolver generates an AuthenticationInfoResolver 141 // that builds rest.Config based on the kubeconfig file. kubeconfigFile is the 142 // path to the kubeconfig. 143 func NewDefaultAuthenticationInfoResolver(kubeconfigFile string) (AuthenticationInfoResolver, error) { 144 if len(kubeconfigFile) == 0 { 145 return &defaultAuthenticationInfoResolver{}, nil 146 } 147 148 loadingRules := clientcmd.NewDefaultClientConfigLoadingRules() 149 loadingRules.ExplicitPath = kubeconfigFile 150 loader := clientcmd.NewNonInteractiveDeferredLoadingClientConfig(loadingRules, &clientcmd.ConfigOverrides{}) 151 clientConfig, err := loader.RawConfig() 152 if err != nil { 153 return nil, err 154 } 155 156 return &defaultAuthenticationInfoResolver{kubeconfig: clientConfig}, nil 157 } 158 159 func (c *defaultAuthenticationInfoResolver) ClientConfigFor(hostPort string) (*rest.Config, error) { 160 return c.clientConfig(hostPort) 161 } 162 163 func (c *defaultAuthenticationInfoResolver) ClientConfigForService(serviceName, serviceNamespace string, servicePort int) (*rest.Config, error) { 164 return c.clientConfig(net.JoinHostPort(serviceName+"."+serviceNamespace+".svc", strconv.Itoa(servicePort))) 165 } 166 167 func (c *defaultAuthenticationInfoResolver) clientConfig(target string) (*rest.Config, error) { 168 // exact match 169 if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok { 170 return restConfigFromKubeconfig(authConfig) 171 } 172 173 // star prefixed match 174 serverSteps := strings.Split(target, ".") 175 for i := 1; i < len(serverSteps); i++ { 176 nickName := "*." + strings.Join(serverSteps[i:], ".") 177 if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok { 178 return restConfigFromKubeconfig(authConfig) 179 } 180 } 181 182 // If target included the default https port (443), search again without the port 183 if target, port, err := net.SplitHostPort(target); err == nil && port == "443" { 184 // exact match without port 185 if authConfig, ok := c.kubeconfig.AuthInfos[target]; ok { 186 return restConfigFromKubeconfig(authConfig) 187 } 188 189 // star prefixed match without port 190 serverSteps := strings.Split(target, ".") 191 for i := 1; i < len(serverSteps); i++ { 192 nickName := "*." + strings.Join(serverSteps[i:], ".") 193 if authConfig, ok := c.kubeconfig.AuthInfos[nickName]; ok { 194 return restConfigFromKubeconfig(authConfig) 195 } 196 } 197 } 198 199 // if we're trying to hit the kube-apiserver and there wasn't an explicit config, use the in-cluster config 200 if target == "kubernetes.default.svc:443" { 201 // if we can find an in-cluster-config use that. If we can't, fall through. 202 inClusterConfig, err := rest.InClusterConfig() 203 if err == nil { 204 return setGlobalDefaults(inClusterConfig), nil 205 } 206 } 207 208 // star (default) match 209 if authConfig, ok := c.kubeconfig.AuthInfos["*"]; ok { 210 return restConfigFromKubeconfig(authConfig) 211 } 212 213 // use the current context from the kubeconfig if possible 214 if len(c.kubeconfig.CurrentContext) > 0 { 215 if currContext, ok := c.kubeconfig.Contexts[c.kubeconfig.CurrentContext]; ok { 216 if len(currContext.AuthInfo) > 0 { 217 if currAuth, ok := c.kubeconfig.AuthInfos[currContext.AuthInfo]; ok { 218 return restConfigFromKubeconfig(currAuth) 219 } 220 } 221 } 222 } 223 224 // anonymous 225 return setGlobalDefaults(&rest.Config{}), nil 226 } 227 228 func restConfigFromKubeconfig(configAuthInfo *clientcmdapi.AuthInfo) (*rest.Config, error) { 229 config := &rest.Config{} 230 231 // blindly overwrite existing values based on precedence 232 if len(configAuthInfo.Token) > 0 { 233 config.BearerToken = configAuthInfo.Token 234 config.BearerTokenFile = configAuthInfo.TokenFile 235 } else if len(configAuthInfo.TokenFile) > 0 { 236 tokenBytes, err := os.ReadFile(configAuthInfo.TokenFile) 237 if err != nil { 238 return nil, err 239 } 240 config.BearerToken = string(tokenBytes) 241 config.BearerTokenFile = configAuthInfo.TokenFile 242 } 243 if len(configAuthInfo.Impersonate) > 0 { 244 config.Impersonate = rest.ImpersonationConfig{ 245 UserName: configAuthInfo.Impersonate, 246 UID: configAuthInfo.ImpersonateUID, 247 Groups: configAuthInfo.ImpersonateGroups, 248 Extra: configAuthInfo.ImpersonateUserExtra, 249 } 250 } 251 if len(configAuthInfo.ClientCertificate) > 0 || len(configAuthInfo.ClientCertificateData) > 0 { 252 config.CertFile = configAuthInfo.ClientCertificate 253 config.CertData = configAuthInfo.ClientCertificateData 254 config.KeyFile = configAuthInfo.ClientKey 255 config.KeyData = configAuthInfo.ClientKeyData 256 } 257 if len(configAuthInfo.Username) > 0 || len(configAuthInfo.Password) > 0 { 258 config.Username = configAuthInfo.Username 259 config.Password = configAuthInfo.Password 260 } 261 if configAuthInfo.Exec != nil { 262 config.ExecProvider = configAuthInfo.Exec.DeepCopy() 263 } 264 if configAuthInfo.AuthProvider != nil { 265 return nil, fmt.Errorf("auth provider not supported") 266 } 267 268 return setGlobalDefaults(config), nil 269 } 270 271 func setGlobalDefaults(config *rest.Config) *rest.Config { 272 config.UserAgent = "kube-apiserver-admission" 273 config.Timeout = 30 * time.Second 274 275 return config 276 }