Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SNI support to the apiserver #35109

Merged
merged 3 commits into from
Nov 1, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion cmd/kube-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,6 @@ func Run(s *options.ServerRunOptions) error {
}

sharedInformers.Start(wait.NeverStop)
m.GenericAPIServer.PrepareRun().Run()
m.GenericAPIServer.PrepareRun().Run(wait.NeverStop)
return nil
}
12 changes: 11 additions & 1 deletion cmd/kubelet/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -502,9 +502,19 @@ func InitializeTLS(kc *componentconfig.KubeletConfiguration) (*server.TLSOptions
kc.TLSCertFile = path.Join(kc.CertDirectory, "kubelet.crt")
kc.TLSPrivateKeyFile = path.Join(kc.CertDirectory, "kubelet.key")
if !certutil.CanReadCertOrKey(kc.TLSCertFile, kc.TLSPrivateKeyFile) {
if err := certutil.GenerateSelfSignedCert(nodeutil.GetHostname(kc.HostnameOverride), kc.TLSCertFile, kc.TLSPrivateKeyFile, nil, nil); err != nil {
cert, key, err := certutil.GenerateSelfSignedCertKey(nodeutil.GetHostname(kc.HostnameOverride), nil, nil)
if err != nil {
return nil, fmt.Errorf("unable to generate self signed cert: %v", err)
}

if err := certutil.WriteCert(kc.TLSCertFile, cert); err != nil {
return nil, err
}

if err := certutil.WriteKey(kc.TLSPrivateKeyFile, key); err != nil {
return nil, err
}

glog.V(4).Infof("Using self-signed cert (%s, %s)", kc.TLSCertFile, kc.TLSPrivateKeyFile)
}
}
Expand Down
4 changes: 2 additions & 2 deletions examples/apiserver/apiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func NewServerRunOptions() *genericoptions.ServerRunOptions {
return serverOptions
}

func Run(serverOptions *genericoptions.ServerRunOptions) error {
func Run(serverOptions *genericoptions.ServerRunOptions, stopCh <-chan struct{}) error {
// Set ServiceClusterIPRange
_, serviceClusterIPRange, _ := net.ParseCIDR("10.0.0.0/24")
serverOptions.ServiceClusterIPRange = *serviceClusterIPRange
Expand Down Expand Up @@ -105,6 +105,6 @@ func Run(serverOptions *genericoptions.ServerRunOptions) error {
if err := s.InstallAPIGroup(&apiGroupInfo); err != nil {
return fmt.Errorf("Error in installing API: %v", err)
}
s.PrepareRun().Run()
s.PrepareRun().Run(stopCh)
return nil
}
3 changes: 2 additions & 1 deletion examples/apiserver/server/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package main
import (
"k8s.io/kubernetes/examples/apiserver"
"k8s.io/kubernetes/pkg/util/flag"
"k8s.io/kubernetes/pkg/util/wait"

"github.com/golang/glog"
"github.com/spf13/pflag"
Expand All @@ -32,7 +33,7 @@ func main() {
serverRunOptions.AddEtcdStorageFlags(pflag.CommandLine)
flag.InitFlags()

if err := apiserver.Run(serverRunOptions); err != nil {
if err := apiserver.Run(serverRunOptions, wait.NeverStop); err != nil {
glog.Fatalf("Error in bringing up the server: %v", err)
}
}
2 changes: 1 addition & 1 deletion federation/cmd/federation-apiserver/app/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ func Run(s *options.ServerRunOptions) error {
installExtensionsAPIs(m, restOptionsFactory)

sharedInformers.Start(wait.NeverStop)
m.PrepareRun().Run()
m.PrepareRun().Run(wait.NeverStop)
return nil
}

Expand Down
1 change: 1 addition & 0 deletions hack/verify-flags/known-flags.txt
Original file line number Diff line number Diff line change
Expand Up @@ -555,6 +555,7 @@ tls-ca-file
tls-cert-file
tls-private-key-file
to-version
tls-sni-cert-key
token-auth-file
ttl-keys-prefix
ttl-secs
Expand Down
4 changes: 4 additions & 0 deletions pkg/genericapiserver/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ go_library(
"resource_config.go",
"resource_encoding_config.go",
"reststorage_interfaces.go",
"serve.go",
"storage_factory.go",
"tunneler.go",
],
Expand Down Expand Up @@ -66,6 +67,7 @@ go_library(
"//vendor:github.com/emicklei/go-restful",
"//vendor:github.com/go-openapi/spec",
"//vendor:github.com/golang/glog",
"//vendor:github.com/pkg/errors",
"//vendor:github.com/prometheus/client_golang/prometheus",
"//vendor:gopkg.in/natefinch/lumberjack.v2",
],
Expand All @@ -77,6 +79,7 @@ go_test(
"default_storage_factory_builder_test.go",
"genericapiserver_test.go",
"resource_config_test.go",
"serve_test.go",
"server_run_options_test.go",
"storage_factory_test.go",
"tunneler_test.go",
Expand All @@ -100,6 +103,7 @@ go_test(
"//pkg/registry/core/service/ipallocator:go_default_library",
"//pkg/storage/etcd/testing:go_default_library",
"//pkg/storage/storagebackend:go_default_library",
"//pkg/util/cert:go_default_library",
"//pkg/util/clock:go_default_library",
"//pkg/util/sets:go_default_library",
"//pkg/version:go_default_library",
Expand Down
62 changes: 52 additions & 10 deletions pkg/genericapiserver/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ type Config struct {
// same value for this field. (Numbers > 1 currently untested.)
MasterCount int

SecureServingInfo *ServingInfo
SecureServingInfo *SecureServingInfo
InsecureServingInfo *ServingInfo

// The port on PublicAddress where a read-write server will be installed.
Expand Down Expand Up @@ -177,17 +177,36 @@ type Config struct {
type ServingInfo struct {
// BindAddress is the ip:port to serve on
BindAddress string
}

type SecureServingInfo struct {
ServingInfo

// ServerCert is the TLS cert info for serving secure traffic
ServerCert CertInfo
ServerCert GeneratableKeyCert
// SNICerts are named CertKeys for serving secure traffic with SNI support.
SNICerts []NamedCertKey
// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
ClientCA string
}

type CertInfo struct {
type CertKey struct {
// CertFile is a file containing a PEM-encoded certificate
CertFile string
// KeyFile is a file containing a PEM-encoded private key for the certificate specified by CertFile
KeyFile string
}

type NamedCertKey struct {
CertKey

// Names is a list of domain patterns: fully qualified domain names, possibly prefixed with
// wildcard segments.
Names []string
}

type GeneratableKeyCert struct {
CertKey
// Generate indicates that the cert/key pair should be generated if its not present.
Generate bool
}
Expand Down Expand Up @@ -248,12 +267,17 @@ func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config {
}

if options.SecurePort > 0 {
secureServingInfo := &ServingInfo{
BindAddress: net.JoinHostPort(options.BindAddress.String(), strconv.Itoa(options.SecurePort)),
ServerCert: CertInfo{
CertFile: options.TLSCertFile,
KeyFile: options.TLSPrivateKeyFile,
secureServingInfo := &SecureServingInfo{
ServingInfo: ServingInfo{
BindAddress: net.JoinHostPort(options.BindAddress.String(), strconv.Itoa(options.SecurePort)),
},
ServerCert: GeneratableKeyCert{
CertKey: CertKey{
CertFile: options.TLSCertFile,
KeyFile: options.TLSPrivateKeyFile,
},
},
SNICerts: []NamedCertKey{},
ClientCA: options.ClientCAFile,
}
if options.TLSCertFile == "" && options.TLSPrivateKeyFile == "" {
Expand All @@ -262,6 +286,17 @@ func (c *Config) ApplyOptions(options *options.ServerRunOptions) *Config {
secureServingInfo.ServerCert.KeyFile = path.Join(options.CertDirectory, "apiserver.key")
}

secureServingInfo.SNICerts = nil
for _, nkc := range options.SNICertKeys {
secureServingInfo.SNICerts = append(secureServingInfo.SNICerts, NamedCertKey{
CertKey: CertKey{
KeyFile: nkc.KeyFile,
CertFile: nkc.CertFile,
},
Names: nkc.Names,
})
}

c.SecureServingInfo = secureServingInfo
c.ReadWritePort = options.SecurePort
}
Expand Down Expand Up @@ -434,9 +469,16 @@ func (c completedConfig) MaybeGenerateServingCerts() error {
alternateIPs := []net.IP{c.ServiceReadWriteIP}
alternateDNS := []string{"kubernetes.default.svc", "kubernetes.default", "kubernetes", "localhost"}

if err := certutil.GenerateSelfSignedCert(c.PublicAddress.String(), c.SecureServingInfo.ServerCert.CertFile, c.SecureServingInfo.ServerCert.KeyFile, alternateIPs, alternateDNS); err != nil {
return fmt.Errorf("Unable to generate self signed cert: %v", err)
if cert, key, err := certutil.GenerateSelfSignedCertKey(c.PublicAddress.String(), alternateIPs, alternateDNS); err != nil {
return fmt.Errorf("unable to generate self signed cert: %v", err)
} else {
if err := certutil.WriteCert(c.SecureServingInfo.ServerCert.CertFile, cert); err != nil {
return err
}

if err := certutil.WriteKey(c.SecureServingInfo.ServerCert.KeyFile, key); err != nil {
return err
}
glog.Infof("Generated self-signed cert (%s, %s)", c.SecureServingInfo.ServerCert.CertFile, c.SecureServingInfo.ServerCert.KeyFile)
}
}
Expand Down
107 changes: 12 additions & 95 deletions pkg/genericapiserver/genericapiserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ limitations under the License.
package genericapiserver

import (
"crypto/tls"
"fmt"
"mime"
"net"
Expand All @@ -44,9 +43,7 @@ import (
"k8s.io/kubernetes/pkg/genericapiserver/openapi/common"
"k8s.io/kubernetes/pkg/genericapiserver/routes"
"k8s.io/kubernetes/pkg/runtime"
certutil "k8s.io/kubernetes/pkg/util/cert"
utilnet "k8s.io/kubernetes/pkg/util/net"
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
"k8s.io/kubernetes/pkg/util/sets"
)

Expand Down Expand Up @@ -112,9 +109,12 @@ type GenericAPIServer struct {
// The registered APIs
HandlerContainer *genericmux.APIContainer

SecureServingInfo *ServingInfo
SecureServingInfo *SecureServingInfo
InsecureServingInfo *ServingInfo

// numerical ports, set after listening
effectiveSecurePort, effectiveInsecurePort int
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just reading through, this looks weird. I'm assuming I'll see why later, but a better comment may help.


// ExternalAddress is the address (hostname or IP and port) that should be used in
// external (public internet) URLs for this GenericAPIServer.
ExternalAddress string
Expand Down Expand Up @@ -180,7 +180,6 @@ type preparedGenericAPIServer struct {

// PrepareRun does post API installation setup steps.
func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
// install APIs which depend on other APIs to be installed
if s.enableSwaggerSupport {
routes.Swagger{ExternalAddress: s.ExternalAddress}.Install(s.HandlerContainer)
}
Expand All @@ -192,76 +191,18 @@ func (s *GenericAPIServer) PrepareRun() preparedGenericAPIServer {
return preparedGenericAPIServer{s}
}

func (s preparedGenericAPIServer) Run() {
// Run spawns the http servers (secure and insecure). It only returns if stopCh is closed
// or one of the ports cannot be listened on initially.
func (s preparedGenericAPIServer) Run(stopCh <-chan struct{}) {
if s.SecureServingInfo != nil && s.Handler != nil {
secureServer := &http.Server{
Addr: s.SecureServingInfo.BindAddress,
Handler: s.Handler,
MaxHeaderBytes: 1 << 20,
TLSConfig: &tls.Config{
// Can't use SSLv3 because of POODLE and BEAST
// Can't use TLSv1.0 because of POODLE and BEAST using CBC cipher
// Can't use TLSv1.1 because of RC4 cipher usage
MinVersion: tls.VersionTLS12,
},
if err := s.serveSecurely(stopCh); err != nil {
glog.Fatal(err)
}

if len(s.SecureServingInfo.ClientCA) > 0 {
clientCAs, err := certutil.NewPool(s.SecureServingInfo.ClientCA)
if err != nil {
glog.Fatalf("Unable to load client CA file: %v", err)
}
// Populate PeerCertificates in requests, but don't reject connections without certificates
// This allows certificates to be validated by authenticators, while still allowing other auth types
secureServer.TLSConfig.ClientAuth = tls.RequestClientCert
// Specify allowed CAs for client certificates
secureServer.TLSConfig.ClientCAs = clientCAs
// "h2" NextProtos is necessary for enabling HTTP2 for go's 1.7 HTTP Server
secureServer.TLSConfig.NextProtos = []string{"h2"}

}

glog.Infof("Serving securely on %s", s.SecureServingInfo.BindAddress)
go func() {
defer utilruntime.HandleCrash()

for {
if err := secureServer.ListenAndServeTLS(s.SecureServingInfo.ServerCert.CertFile, s.SecureServingInfo.ServerCert.KeyFile); err != nil {
glog.Errorf("Unable to listen for secure (%v); will try again.", err)
}
time.Sleep(15 * time.Second)
}
}()
}

if s.InsecureServingInfo != nil && s.InsecureHandler != nil {
insecureServer := &http.Server{
Addr: s.InsecureServingInfo.BindAddress,
Handler: s.InsecureHandler,
MaxHeaderBytes: 1 << 20,
}
glog.Infof("Serving insecurely on %s", s.InsecureServingInfo.BindAddress)
go func() {
defer utilruntime.HandleCrash()

for {
if err := insecureServer.ListenAndServe(); err != nil {
glog.Errorf("Unable to listen for insecure (%v); will try again.", err)
}
time.Sleep(15 * time.Second)
}
}()
}

// Attempt to verify the server came up for 20 seconds (100 tries * 100ms, 100ms timeout per try) per port
if s.SecureServingInfo != nil {
if err := waitForSuccessfulDial(true, "tcp", s.SecureServingInfo.BindAddress, 100*time.Millisecond, 100*time.Millisecond, 100); err != nil {
glog.Fatalf("Secure server never started: %v", err)
}
}
if s.InsecureServingInfo != nil {
if err := waitForSuccessfulDial(false, "tcp", s.InsecureServingInfo.BindAddress, 100*time.Millisecond, 100*time.Millisecond, 100); err != nil {
glog.Fatalf("Insecure server never started: %v", err)
if err := s.serveInsecurely(stopCh); err != nil {
glog.Fatal(err)
}
}

Expand All @@ -272,7 +213,7 @@ func (s preparedGenericAPIServer) Run() {
glog.Errorf("Unable to send systemd daemon successful start message: %v\n", err)
}

select {}
<-stopCh
}

// installAPIResources is a private method for installing the REST storage backing each api groupversionresource
Expand Down Expand Up @@ -471,27 +412,3 @@ func NewDefaultAPIGroupInfo(group string) APIGroupInfo {
NegotiatedSerializer: api.Codecs,
}
}

// waitForSuccessfulDial attempts to connect to the given address, closing and returning nil on the first successful connection.
func waitForSuccessfulDial(https bool, network, address string, timeout, interval time.Duration, retries int) error {
var (
conn net.Conn
err error
)
for i := 0; i <= retries; i++ {
dialer := net.Dialer{Timeout: timeout}
if https {
conn, err = tls.DialWithDialer(&dialer, network, address, &tls.Config{InsecureSkipVerify: true})
} else {
conn, err = dialer.Dial(network, address)
}
if err != nil {
glog.V(5).Infof("Got error %#v, trying again: %#v\n", err, address)
time.Sleep(interval)
continue
}
conn.Close()
return nil
}
return err
}