apiserver: self-signed in-memory cert for loopback
This commit is contained in:
parent
20015a7f72
commit
954f7be538
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue