diff --git a/staging/src/k8s.io/apiserver/pkg/server/config_selfclient.go b/staging/src/k8s.io/apiserver/pkg/server/config_selfclient.go index e96d5d04b90..d53cc2f24c6 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/config_selfclient.go +++ b/staging/src/k8s.io/apiserver/pkg/server/config_selfclient.go @@ -17,16 +17,18 @@ limitations under the License. package server import ( - "bytes" "crypto/x509" - "encoding/pem" "fmt" "net" restclient "k8s.io/client-go/rest" ) -func (s *SecureServingInfo) NewSelfClientConfig(token string) (*restclient.Config, error) { +// LoopbackClientServerNameOverride is passed to the apiserver from the loopback client in order to +// select the loopback certificate via SNI if TLS is used. +const LoopbackClientServerNameOverride = "apiserver-loopback-client" + +func (s *SecureServingInfo) NewLoopbackClientConfig(token string, loopbackCert []byte) (*restclient.Config, error) { if s == nil || (s.Cert == nil && len(s.SNICerts) == 0) { return nil, nil } @@ -41,7 +43,7 @@ func (s *SecureServingInfo) NewSelfClientConfig(token string) (*restclient.Confi host = "localhost" } - clientConfig := &restclient.Config{ + return &restclient.Config{ // Increase QPS limits. The client is currently passed to all admission plugins, // and those can be throttled in case of higher load on apiserver - see #22340 and #22422 // for more details. Once #22422 is fixed, we may want to remove it. @@ -49,60 +51,13 @@ func (s *SecureServingInfo) NewSelfClientConfig(token string) (*restclient.Confi Burst: 100, Host: "https://" + net.JoinHostPort(host, port), BearerToken: token, - } - - // find certificate for host: either explicitly given, from the server cert bundle or one of the SNI certs, - // but only return CA:TRUE certificates. - var derCA []byte - if s.CACert != nil { - derCA = s.CACert.Certificate[0] - } - if derCA == nil && net.ParseIP(host) == nil { - if cert, found := s.SNICerts[host]; found { - chain, err := parseChain(cert.Certificate) - if err != nil { - return nil, fmt.Errorf("failed to parse SNI certificate for host %q: %v", host, err) - } - - if trustedChain(chain) { - return clientConfig, nil - } - - ca, err := findCA(chain) - if err != nil { - return nil, fmt.Errorf("no CA certificate found in SNI server certificate bundle for host %q: %v", host, err) - } - derCA = ca.Raw - } - } - if derCA == nil && s.Cert != nil { - chain, err := parseChain(s.Cert.Certificate) - if err != nil { - return nil, fmt.Errorf("failed to parse server certificate: %v", err) - } - - if (net.ParseIP(host) != nil && certMatchesIP(chain[0], host)) || certMatchesName(chain[0], host) { - if trustedChain(chain) { - return clientConfig, nil - } - - ca, err := findCA(chain) - if err != nil { - return nil, fmt.Errorf("no CA certificate found in server certificate bundle: %v", err) - } - derCA = ca.Raw - } - } - if derCA == nil { - return nil, fmt.Errorf("failed to find certificate which matches %q", host) - } - pemCA := bytes.Buffer{} - if err := pem.Encode(&pemCA, &pem.Block{Type: "CERTIFICATE", Bytes: derCA}); err != nil { - return nil, err - } - clientConfig.CAData = pemCA.Bytes() - - return clientConfig, nil + // override the ServerName to select our loopback certificate via SNI. This name is also + // used by the client to compare the returns server certificate against. + TLSClientConfig: restclient.TLSClientConfig{ + ServerName: LoopbackClientServerNameOverride, + CAData: loopbackCert, + }, + }, nil } func trustedChain(chain []*x509.Certificate) bool { @@ -140,7 +95,7 @@ func findCA(chain []*x509.Certificate) (*x509.Certificate, error) { return nil, fmt.Errorf("no certificate with CA:TRUE found in chain") } -func (s *ServingInfo) NewSelfClientConfig(token string) (*restclient.Config, error) { +func (s *ServingInfo) NewLoopbackClientConfig(token string) (*restclient.Config, error) { if s == nil { return nil, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving.go index fcceb1af57c..58a3da55ba2 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving.go @@ -144,7 +144,18 @@ func (s *SecureServingOptions) ApplyTo(c *server.Config) error { return err } - loopbackClientConfig, err := c.SecureServingInfo.NewSelfClientConfig(uuid.NewRandom().String()) + // create self-signed cert+key with the fake server.LoopbackClientServerNameOverride and + // let the server return it when the loopback client connects. + certPem, keyPem, err := certutil.GenerateSelfSignedCertKey(server.LoopbackClientServerNameOverride, nil, nil) + if err != nil { + return fmt.Errorf("failed to generate self-signed certificate for loopback connection: %v", err) + } + tlsCert, err := tls.X509KeyPair(certPem, keyPem) + if err != nil { + return fmt.Errorf("failed to generate self-signed certificate for loopback connection: %v", err) + } + + secureLoopbackClientConfig, err := c.SecureServingInfo.NewLoopbackClientConfig(uuid.NewRandom().String(), certPem) switch { // if we failed and there's no fallback loopback client config, we need to fail case err != nil && c.LoopbackClientConfig == nil: @@ -154,7 +165,8 @@ func (s *SecureServingOptions) ApplyTo(c *server.Config) error { case err != nil && c.LoopbackClientConfig != nil: default: - c.LoopbackClientConfig = loopbackClientConfig + c.LoopbackClientConfig = secureLoopbackClientConfig + c.SecureServingInfo.SNICerts[server.LoopbackClientServerNameOverride] = &tlsCert } return nil @@ -277,7 +289,7 @@ func (s *ServingOptions) ApplyTo(c *server.Config) error { var err error privilegedLoopbackToken := uuid.NewRandom().String() - if c.LoopbackClientConfig, err = c.InsecureServingInfo.NewSelfClientConfig(privilegedLoopbackToken); err != nil { + if c.LoopbackClientConfig, err = c.InsecureServingInfo.NewLoopbackClientConfig(privilegedLoopbackToken); err != nil { return err } diff --git a/staging/src/k8s.io/apiserver/pkg/server/options/serving_test.go b/staging/src/k8s.io/apiserver/pkg/server/options/serving_test.go index e600bd698d5..a828c56e63a 100644 --- a/staging/src/k8s.io/apiserver/pkg/server/options/serving_test.go +++ b/staging/src/k8s.io/apiserver/pkg/server/options/serving_test.go @@ -34,6 +34,7 @@ import ( "k8s.io/apimachinery/pkg/runtime/serializer" "k8s.io/apimachinery/pkg/version" genericapirequest "k8s.io/apiserver/pkg/endpoints/request" + "k8s.io/apiserver/pkg/server" . "k8s.io/apiserver/pkg/server" utilflag "k8s.io/apiserver/pkg/util/flag" "k8s.io/client-go/discovery" @@ -254,9 +255,9 @@ func TestServerRunWithSNI(t *testing.T) { // passed in the client hello info, "localhost" if unset ServerName string - // optional ip or hostname to pass to NewSelfClientConfig - SelfClientBindAddressOverride string - ExpectSelfClientError bool + // optional ip or hostname to pass to NewLoopbackClientConfig + LoopbackClientBindAddressOverride string + ExpectLoopbackClientError bool }{ "only one cert": { Cert: TestCertSpec{ @@ -338,51 +339,7 @@ func TestServerRunWithSNI(t *testing.T) { ServerName: "www.test.com", }, - "loopback: IP for loopback client on SNI cert": { - Cert: TestCertSpec{ - host: "localhost", - }, - SNICerts: []NamedTestCertSpec{ - { - TestCertSpec: TestCertSpec{ - host: "test.com", - ips: []string{"127.0.0.1"}, - }, - }, - }, - ExpectedCertIndex: -1, - ExpectSelfClientError: true, - }, - "loopback: IP for loopback client on server and SNI cert": { - Cert: TestCertSpec{ - ips: []string{"127.0.0.1"}, - host: "localhost", - }, - SNICerts: []NamedTestCertSpec{ - { - TestCertSpec: TestCertSpec{ - host: "test.com", - ips: []string{"127.0.0.1"}, - }, - }, - }, - ExpectedCertIndex: -1, - }, - "loopback: bind to 0.0.0.0 => loopback uses localhost; localhost on server cert": { - Cert: TestCertSpec{ - host: "localhost", - }, - SNICerts: []NamedTestCertSpec{ - { - TestCertSpec: TestCertSpec{ - host: "test.com", - }, - }, - }, - ExpectedCertIndex: -1, - SelfClientBindAddressOverride: "0.0.0.0", - }, - "loopback: bind to 0.0.0.0 => loopback uses localhost; localhost on SNI cert": { + "loopback: LoopbackClientServerNameOverride not on any cert": { Cert: TestCertSpec{ host: "test.com", }, @@ -393,12 +350,11 @@ func TestServerRunWithSNI(t *testing.T) { }, }, }, - ExpectedCertIndex: 0, - SelfClientBindAddressOverride: "0.0.0.0", + ExpectedCertIndex: 0, }, - "loopback: bind to 0.0.0.0 => loopback uses localhost; localhost on server and SNI cert": { + "loopback: LoopbackClientServerNameOverride on server cert": { Cert: TestCertSpec{ - host: "localhost", + host: server.LoopbackClientServerNameOverride, }, SNICerts: []NamedTestCertSpec{ { @@ -407,8 +363,27 @@ func TestServerRunWithSNI(t *testing.T) { }, }, }, - ExpectedCertIndex: 0, - SelfClientBindAddressOverride: "0.0.0.0", + ExpectedCertIndex: 0, + }, + "loopback: LoopbackClientServerNameOverride on SNI cert": { + Cert: TestCertSpec{ + host: "localhost", + }, + SNICerts: []NamedTestCertSpec{ + { + TestCertSpec: TestCertSpec{ + host: server.LoopbackClientServerNameOverride, + }, + }, + }, + ExpectedCertIndex: -1, + }, + "loopback: bind to 0.0.0.0 => loopback uses localhost": { + Cert: TestCertSpec{ + host: "localhost", + }, + ExpectedCertIndex: -1, + LoopbackClientBindAddressOverride: "0.0.0.0", }, } @@ -554,12 +529,11 @@ NextTest: // check that the loopback client can connect host := "127.0.0.1" - if len(test.SelfClientBindAddressOverride) != 0 { - host = test.SelfClientBindAddressOverride + if len(test.LoopbackClientBindAddressOverride) != 0 { + host = test.LoopbackClientBindAddressOverride } - config.SecureServingInfo.ServingInfo.BindAddress = net.JoinHostPort(host, effectiveSecurePort) - cfg, err := config.SecureServingInfo.NewSelfClientConfig("some-token") - if test.ExpectSelfClientError { + s.LoopbackClientConfig.Host = net.JoinHostPort(host, effectiveSecurePort) + if test.ExpectLoopbackClientError { if err == nil { t.Errorf("%q - expected error creating loopback client config", title) } @@ -569,7 +543,7 @@ NextTest: t.Errorf("%q - failed creating loopback client config: %v", title, err) continue NextTest } - client, err := discovery.NewDiscoveryClientForConfig(cfg) + client, err := discovery.NewDiscoveryClientForConfig(s.LoopbackClientConfig) if err != nil { t.Errorf("%q - failed to create loopback client: %v", title, err) continue NextTest diff --git a/staging/src/k8s.io/client-go/rest/config.go b/staging/src/k8s.io/client-go/rest/config.go index e491d0b1fc9..2a2c03dff13 100644 --- a/staging/src/k8s.io/client-go/rest/config.go +++ b/staging/src/k8s.io/client-go/rest/config.go @@ -83,10 +83,6 @@ type Config struct { // TLSClientConfig contains settings to enable transport layer security TLSClientConfig - // Server should be accessed without verifying the TLS - // certificate. For testing only. - Insecure bool - // UserAgent is an optional field that specifies the caller of this request. UserAgent string @@ -132,6 +128,13 @@ type ImpersonationConfig struct { // TLSClientConfig contains settings to enable transport layer security type TLSClientConfig struct { + // Server should be accessed without verifying the TLS certificate. For testing only. + Insecure bool + // ServerName is passed to the server for SNI and is used in the client to check server + // ceritificates against. If ServerName is empty, the hostname used to contact the + // server is used. + ServerName string + // Server requires TLS client certificate authentication CertFile string // Server requires TLS client certificate authentication @@ -365,11 +368,12 @@ func AnonymousClientConfig(config *Config) *Config { Prefix: config.Prefix, ContentConfig: config.ContentConfig, TLSClientConfig: TLSClientConfig{ - CAFile: config.TLSClientConfig.CAFile, - CAData: config.TLSClientConfig.CAData, + Insecure: config.Insecure, + ServerName: config.ServerName, + CAFile: config.TLSClientConfig.CAFile, + CAData: config.TLSClientConfig.CAData, }, RateLimiter: config.RateLimiter, - Insecure: config.Insecure, UserAgent: config.UserAgent, Transport: config.Transport, WrapTransport: config.WrapTransport, diff --git a/staging/src/k8s.io/client-go/rest/config_test.go b/staging/src/k8s.io/client-go/rest/config_test.go index 41336335379..5b9071d7ac7 100644 --- a/staging/src/k8s.io/client-go/rest/config_test.go +++ b/staging/src/k8s.io/client-go/rest/config_test.go @@ -70,8 +70,10 @@ func TestIsConfigTransportTLS(t *testing.T) { }, { Config: &Config{ - Host: "1.2.3.4:567", - Insecure: true, + Host: "1.2.3.4:567", + TLSClientConfig: TLSClientConfig{ + Insecure: true, + }, }, TransportTLS: true, }, diff --git a/staging/src/k8s.io/client-go/rest/transport.go b/staging/src/k8s.io/client-go/rest/transport.go index 9841f8cc6c6..ba43752bc93 100644 --- a/staging/src/k8s.io/client-go/rest/transport.go +++ b/staging/src/k8s.io/client-go/rest/transport.go @@ -78,13 +78,14 @@ func (c *Config) TransportConfig() (*transport.Config, error) { Transport: c.Transport, WrapTransport: wt, TLS: transport.TLSConfig{ - CAFile: c.CAFile, - CAData: c.CAData, - CertFile: c.CertFile, - CertData: c.CertData, - KeyFile: c.KeyFile, - KeyData: c.KeyData, - Insecure: c.Insecure, + Insecure: c.Insecure, + ServerName: c.ServerName, + CAFile: c.CAFile, + CAData: c.CAData, + CertFile: c.CertFile, + CertData: c.CertData, + KeyFile: c.KeyFile, + KeyData: c.KeyData, }, Username: c.Username, Password: c.Password, diff --git a/staging/src/k8s.io/client-go/transport/config.go b/staging/src/k8s.io/client-go/transport/config.go index eaad99fb2f2..820594ba354 100644 --- a/staging/src/k8s.io/client-go/transport/config.go +++ b/staging/src/k8s.io/client-go/transport/config.go @@ -86,7 +86,8 @@ type TLSConfig struct { CertFile string // Path of the PEM-encoded client certificate. KeyFile string // Path of the PEM-encoded client key. - Insecure bool // Server should be accessed without verifying the certificate. For testing only. + Insecure bool // Server should be accessed without verifying the certificate. For testing only. + ServerName string // Override for the server name passed to the server for SNI and used to verify certificates. CAData []byte // Bytes of the PEM-encoded server trusted root certificates. Supercedes CAFile. CertData []byte // Bytes of the PEM-encoded client certificate. Supercedes CertFile. diff --git a/staging/src/k8s.io/client-go/transport/transport.go b/staging/src/k8s.io/client-go/transport/transport.go index 9c5b9ef3c32..15be0a3e6b1 100644 --- a/staging/src/k8s.io/client-go/transport/transport.go +++ b/staging/src/k8s.io/client-go/transport/transport.go @@ -68,6 +68,7 @@ func TLSConfigFor(c *Config) (*tls.Config, error) { // Can't use TLSv1.1 because of RC4 cipher usage MinVersion: tls.VersionTLS12, InsecureSkipVerify: c.TLS.Insecure, + ServerName: c.TLS.ServerName, } if c.HasCA() { diff --git a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go index c62a62b8f08..025bae6299d 100644 --- a/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go +++ b/staging/src/k8s.io/kube-aggregator/pkg/apiserver/handler_proxy.go @@ -172,8 +172,8 @@ func (r *proxyHandler) updateAPIService(apiService *apiregistrationapi.APIServic r.destinationHost = apiService.Spec.Service.Name + "." + apiService.Spec.Service.Namespace + ".svc" r.restConfig = &restclient.Config{ - Insecure: apiService.Spec.InsecureSkipTLSVerify, TLSClientConfig: restclient.TLSClientConfig{ + Insecure: apiService.Spec.InsecureSkipTLSVerify, CertData: r.proxyClientCert, KeyData: r.proxyClientKey, CAData: apiService.Spec.CABundle,