apiserver: self-signed in-memory cert for loopback

This commit is contained in:
Dr. Stefan Schimanski 2017-02-22 14:02:31 +01:00 committed by deads2k
parent 20015a7f72
commit 954f7be538
3 changed files with 63 additions and 122 deletions

View File

@ -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
}

View File

@ -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
}

View File

@ -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