dynamiclistener/server/server.go
2020-02-07 14:20:45 -07:00

221 lines
5.4 KiB
Go

package server
import (
"context"
"crypto"
"crypto/tls"
"crypto/x509"
"fmt"
"log"
"net"
"net/http"
"github.com/rancher/dynamiclistener"
"github.com/rancher/dynamiclistener/factory"
"github.com/rancher/dynamiclistener/storage/file"
"github.com/rancher/dynamiclistener/storage/kubernetes"
"github.com/rancher/dynamiclistener/storage/memory"
v1 "github.com/rancher/wrangler-api/pkg/generated/controllers/core/v1"
"github.com/sirupsen/logrus"
"golang.org/x/crypto/acme/autocert"
)
type ListenOpts struct {
CA *x509.Certificate
CAKey crypto.Signer
Storage dynamiclistener.TLSStorage
Secrets v1.SecretController
CertNamespace string
CertName string
CANamespace string
CAName string
CertBackup string
AcmeDomains []string
BindHost string
NoRedirect bool
TLSListenerConfig dynamiclistener.Config
}
func ListenAndServe(ctx context.Context, httpsPort, httpPort int, handler http.Handler, opts *ListenOpts) error {
if opts == nil {
opts = &ListenOpts{}
}
if opts.TLSListenerConfig.TLSConfig == nil {
opts.TLSListenerConfig.TLSConfig = &tls.Config{}
}
logger := logrus.StandardLogger()
errorLog := log.New(logger.WriterLevel(logrus.DebugLevel), "", log.LstdFlags)
if httpsPort > 0 {
tlsTCPListener, err := dynamiclistener.NewTCPListener(opts.BindHost, httpsPort)
if err != nil {
return err
}
tlsTCPListener, handler, err = getTLSListener(ctx, tlsTCPListener, handler, *opts)
if err != nil {
return err
}
if !opts.NoRedirect {
handler = dynamiclistener.HTTPRedirect(handler)
}
tlsServer := http.Server{
Handler: handler,
ErrorLog: errorLog,
}
go func() {
logrus.Infof("Listening on %s:%d", opts.BindHost, httpsPort)
err := tlsServer.Serve(tlsTCPListener)
if err != http.ErrServerClosed && err != nil {
logrus.Fatalf("https server failed: %v", err)
}
}()
go func() {
<-ctx.Done()
tlsServer.Shutdown(context.Background())
}()
}
if httpPort > 0 {
httpServer := http.Server{
Addr: fmt.Sprintf("%s:%d", opts.BindHost, httpPort),
Handler: handler,
ErrorLog: errorLog,
}
go func() {
logrus.Infof("Listening on %s:%d", opts.BindHost, httpPort)
err := httpServer.ListenAndServe()
if err != http.ErrServerClosed && err != nil {
logrus.Fatalf("http server failed: %v", err)
}
}()
go func() {
<-ctx.Done()
httpServer.Shutdown(context.Background())
}()
}
return nil
}
func getTLSListener(ctx context.Context, tcp net.Listener, handler http.Handler, opts ListenOpts) (net.Listener, http.Handler, error) {
if len(opts.TLSListenerConfig.TLSConfig.NextProtos) == 0 {
opts.TLSListenerConfig.TLSConfig.NextProtos = []string{"h2", "http/1.1"}
}
if len(opts.TLSListenerConfig.TLSConfig.Certificates) > 0 {
return tls.NewListener(tcp, opts.TLSListenerConfig.TLSConfig), handler, nil
}
if len(opts.AcmeDomains) > 0 {
return acmeListener(tcp, handler, opts)
}
storage := opts.Storage
if storage == nil {
storage = newStorage(ctx, opts)
}
caCert, caKey, err := getCA(opts)
if err != nil {
return nil, nil, err
}
listener, dynHandler, err := dynamiclistener.NewListener(tcp, storage, caCert, caKey, opts.TLSListenerConfig)
if err != nil {
return nil, nil, err
}
return listener, wrapHandler(dynHandler, handler), nil
}
func getCA(opts ListenOpts) (*x509.Certificate, crypto.Signer, error) {
if opts.CA != nil && opts.CAKey != nil {
return opts.CA, opts.CAKey, nil
}
if opts.Secrets == nil {
return factory.LoadOrGenCA()
}
if opts.CAName == "" {
opts.CAName = "serving-ca"
}
if opts.CANamespace == "" {
opts.CANamespace = opts.CertNamespace
}
if opts.CANamespace == "" {
opts.CANamespace = "kube-system"
}
return kubernetes.LoadOrGenCA(opts.Secrets, opts.CANamespace, opts.CAName)
}
func newStorage(ctx context.Context, opts ListenOpts) dynamiclistener.TLSStorage {
var result dynamiclistener.TLSStorage
if opts.CertBackup == "" {
result = memory.New()
} else {
result = memory.NewBacked(file.New(opts.CertBackup))
}
if opts.Secrets == nil {
return result
}
if opts.CertName == "" {
opts.CertName = "serving-cert"
}
if opts.CertNamespace == "" {
opts.CertNamespace = "kube-system"
}
return kubernetes.Load(ctx, opts.Secrets, opts.CertNamespace, opts.CertName, result)
}
func wrapHandler(handler http.Handler, next http.Handler) http.Handler {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
handler.ServeHTTP(rw, req)
next.ServeHTTP(rw, req)
})
}
func acmeListener(tcp net.Listener, handler http.Handler, opts ListenOpts) (net.Listener, http.Handler, error) {
hosts := map[string]bool{}
for _, domain := range opts.AcmeDomains {
hosts[domain] = true
}
manager := autocert.Manager{
Cache: autocert.DirCache("certs-cache"),
Prompt: func(tosURL string) bool {
return true
},
HostPolicy: func(ctx context.Context, host string) error {
if !hosts[host] {
return fmt.Errorf("host %s is not configured", host)
}
return nil
},
}
opts.TLSListenerConfig.TLSConfig.GetCertificate = func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if hello.ServerName == "localhost" || hello.ServerName == "" {
newHello := *hello
newHello.ServerName = opts.AcmeDomains[0]
return manager.GetCertificate(&newHello)
}
return manager.GetCertificate(hello)
}
return tls.NewListener(tcp, opts.TLSListenerConfig.TLSConfig), manager.HTTPHandler(handler), nil
}