Skip to content

Instantly share code, notes, and snippets.

@x893675
Created October 18, 2020 00:32
Show Gist options
  • Save x893675/1fbf72acc4630453fdab02df824c4337 to your computer and use it in GitHub Desktop.
Save x893675/1fbf72acc4630453fdab02df824c4337 to your computer and use it in GitHub Desktop.
http2 server
package main
import (
"context"
"crypto/tls"
"fmt"
"golang.org/x/net/http2"
"golang.org/x/sys/unix"
"io"
"io/ioutil"
"log"
"net"
"net/http"
"os"
"strconv"
"strings"
"syscall"
"time"
)
func main() {
h := &http.ServeMux{}
h.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
for k, v := range r.Header {
fmt.Printf("key: %s, value: %s\n", k, v)
}
w.Write([]byte(fmt.Sprintf("Protocol: %s", r.Proto)))
})
opt := NewSecureServingOptions()
_ = opt.Apply()
s := SecureServingInfo{
Listener: opt.Listener,
MinTLSVersion: 0,
CipherSuites: nil,
HTTP2MaxStreamsPerConnection: opt.HTTP2MaxStreamsPerConnection,
DisableHTTP2: false,
}
tlsConfig, err := s.tlsConfig()
if err != nil {
panic(err)
}
secureServer := &http.Server{
Addr: s.Listener.Addr().String(),
Handler: h,
MaxHeaderBytes: 1 << 20,
TLSConfig: tlsConfig,
}
// At least 99% of serialized resources in surveyed clusters were smaller than 256kb.
// This should be big enough to accommodate most API POST requests in a single frame,
// and small enough to allow a per connection buffer of this size multiplied by `MaxConcurrentStreams`.
const resourceBody99Percentile = 256 * 1024
http2Options := &http2.Server{}
// shrink the per-stream buffer and max framesize from the 1MB default while still accommodating most API POST requests in a single frame
http2Options.MaxUploadBufferPerStream = resourceBody99Percentile
http2Options.MaxReadFrameSize = resourceBody99Percentile
// use the overridden concurrent streams setting or make the default of 250 explicit so we can size MaxUploadBufferPerConnection appropriately
if s.HTTP2MaxStreamsPerConnection > 0 {
http2Options.MaxConcurrentStreams = uint32(s.HTTP2MaxStreamsPerConnection)
} else {
http2Options.MaxConcurrentStreams = 250
}
// increase the connection buffer size from the 1MB default to handle the specified number of concurrent streams
http2Options.MaxUploadBufferPerConnection = http2Options.MaxUploadBufferPerStream * int32(http2Options.MaxConcurrentStreams)
if !s.DisableHTTP2 {
// apply settings to the server
if err := http2.ConfigureServer(secureServer, http2Options); err != nil {
panic(fmt.Errorf("error configuring http2: %v", err))
}
}
// use tlsHandshakeErrorWriter to handle messages of tls handshake error
tlsErrorWriter := &tlsHandshakeErrorWriter{os.Stderr}
tlsErrorLogger := log.New(tlsErrorWriter, "", 0)
secureServer.ErrorLog = tlsErrorLogger
fmt.Printf("Serving securely on %s\n", secureServer.Addr)
var listener net.Listener
listener = tcpKeepAliveListener{s.Listener}
if secureServer.TLSConfig != nil {
listener = tls.NewListener(listener, secureServer.TLSConfig)
}
err = secureServer.ServeTLS(s.Listener, "", "")
if err != nil {
log.Fatal(err)
}
}
// tcpKeepAliveListener sets TCP keep-alive timeouts on accepted
// connections. It's used by ListenAndServe and ListenAndServeTLS so
// dead TCP connections (e.g. closing laptop mid-download) eventually
// go away.
//
// Copied from Go 1.7.2 net/http/server.go
type tcpKeepAliveListener struct {
net.Listener
}
const (
defaultKeepAlivePeriod = 3 * time.Minute
)
func (ln tcpKeepAliveListener) Accept() (net.Conn, error) {
c, err := ln.Listener.Accept()
if err != nil {
return nil, err
}
if tc, ok := c.(*net.TCPConn); ok {
tc.SetKeepAlive(true)
tc.SetKeepAlivePeriod(defaultKeepAlivePeriod)
}
return c, nil
}
type SecureServingInfo struct {
// Listener is the secure server network listener.
Listener net.Listener
// Cert is the main server cert which is used if SNI does not match. Cert must be non-nil and is
// allowed to be in SNICerts.
//Cert dynamiccertificates.CertKeyContentProvider
//
//// SNICerts are the TLS certificates used for SNI.
//SNICerts []dynamiccertificates.SNICertKeyContentProvider
//
//// ClientCA is the certificate bundle for all the signers that you'll recognize for incoming client certificates
//ClientCA dynamiccertificates.CAContentProvider
// MinTLSVersion optionally overrides the minimum TLS version supported.
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
MinTLSVersion uint16
// CipherSuites optionally overrides the list of allowed cipher suites for the server.
// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
CipherSuites []uint16
// HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client.
// A value of zero means to use the default provided by golang's HTTP/2 support.
HTTP2MaxStreamsPerConnection int
// DisableHTTP2 indicates that http2 should not be enabled.
DisableHTTP2 bool
}
type SecureServingOptions struct {
BindAddress net.IP
// BindPort is ignored when Listener is set, will serve https even with 0.
BindPort int
// BindNetwork is the type of network to bind to - defaults to "tcp", accepts "tcp",
// "tcp4", and "tcp6".
BindNetwork string
// Required set to true means that BindPort cannot be zero.
Required bool
// ExternalAddress is the address advertised, even if BindAddress is a loopback. By default this
// is set to BindAddress if the later no loopback, or to the first host interface address.
ExternalAddress net.IP
// Listener is the secure server network listener.
// either Listener or BindAddress/BindPort/BindNetwork is set,
// if Listener is set, use it and omit BindAddress/BindPort/BindNetwork.
Listener net.Listener
//// ServerCert is the TLS cert info for serving secure traffic
//ServerCert GeneratableKeyCert
//// SNICertKeys are named CertKeys for serving secure traffic with SNI support.
//SNICertKeys []cliflag.NamedCertKey
//// CipherSuites is the list of allowed cipher suites for the server.
//// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
//CipherSuites []string
//// MinTLSVersion is the minimum TLS version supported.
//// Values are from tls package constants (https://golang.org/pkg/crypto/tls/#pkg-constants).
//MinTLSVersion string
// HTTP2MaxStreamsPerConnection is the limit that the api server imposes on each client.
// A value of zero means to use the default provided by golang's HTTP/2 support.
HTTP2MaxStreamsPerConnection int
// PermitPortSharing controls if SO_REUSEPORT is used when binding the port, which allows
// more than one instance to bind on the same address and port.
PermitPortSharing bool
}
func NewSecureServingOptions() *SecureServingOptions {
return &SecureServingOptions{
BindAddress: net.ParseIP("0.0.0.0"),
BindPort: 6443,
BindNetwork: "",
Required: false,
ExternalAddress: nil,
Listener: nil,
HTTP2MaxStreamsPerConnection: 0,
PermitPortSharing: false,
}
}
func (s *SecureServingOptions) Apply() error {
if s.Listener == nil {
var err error
addr := net.JoinHostPort(s.BindAddress.String(), strconv.Itoa(s.BindPort))
c := net.ListenConfig{}
if s.PermitPortSharing {
c.Control = permitPortReuse
}
s.Listener, s.BindPort, err = CreateListener(s.BindNetwork, addr, c)
if err != nil {
return fmt.Errorf("failed to create listener: %v", err)
}
} else {
if _, ok := s.Listener.Addr().(*net.TCPAddr); !ok {
return fmt.Errorf("failed to parse ip and port from listener")
}
s.BindPort = s.Listener.Addr().(*net.TCPAddr).Port
s.BindAddress = s.Listener.Addr().(*net.TCPAddr).IP
}
return nil
}
func CreateListener(network, addr string, config net.ListenConfig) (net.Listener, int, error) {
if len(network) == 0 {
network = "tcp"
}
ln, err := config.Listen(context.TODO(), network, addr)
if err != nil {
return nil, 0, fmt.Errorf("failed to listen on %v: %v", addr, err)
}
// get port
tcpAddr, ok := ln.Addr().(*net.TCPAddr)
if !ok {
ln.Close()
return nil, 0, fmt.Errorf("invalid listen address: %q", ln.Addr().String())
}
return ln, tcpAddr.Port, nil
}
func permitPortReuse(network, addr string, conn syscall.RawConn) error {
return conn.Control(func(fd uintptr) {
syscall.SetsockoptInt(int(fd), syscall.SOL_SOCKET, unix.SO_REUSEPORT, 1)
})
}
func (s *SecureServingInfo) tlsConfig() (*tls.Config, error) {
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,
// enable HTTP2 for go's 1.7 HTTP Server
NextProtos: []string{"h2", "http/1.1"},
}
// these are static aspects of the tls.Config
if s.DisableHTTP2 {
tlsConfig.NextProtos = []string{"http/1.1"}
}
if s.MinTLSVersion > 0 {
tlsConfig.MinVersion = s.MinTLSVersion
}
if len(s.CipherSuites) > 0 {
tlsConfig.CipherSuites = s.CipherSuites
insecureCiphers := InsecureTLSCiphers()
for i := 0; i < len(s.CipherSuites); i++ {
for cipherName, cipherID := range insecureCiphers {
if s.CipherSuites[i] == cipherID {
fmt.Printf("Use of insecure cipher '%s' detected.", cipherName)
}
}
}
}
// openssl genrsa -out private.key 4096
// openssl req -new -x509 -sha256 -days 1825 -key private.key -out public.crt
crt, err := ioutil.ReadFile("./public.crt")
if err != nil {
log.Fatal(err)
}
key, err := ioutil.ReadFile("./private.key")
if err != nil {
log.Fatal(err)
}
cert, err := tls.X509KeyPair(crt, key)
if err != nil {
log.Fatal(err)
}
tlsConfig.Certificates = []tls.Certificate{cert}
tlsConfig.ServerName = "localhost"
//if s.ClientCA != nil {
// // 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
// tlsConfig.ClientAuth = tls.RequestClientCert
//}
return tlsConfig, nil
}
var insecureCiphers = map[string]uint16{
"TLS_RSA_WITH_RC4_128_SHA": tls.TLS_RSA_WITH_RC4_128_SHA,
"TLS_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_RSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_ECDSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_ECDSA_WITH_RC4_128_SHA,
"TLS_ECDHE_RSA_WITH_RC4_128_SHA": tls.TLS_ECDHE_RSA_WITH_RC4_128_SHA,
"TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256,
"TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256": tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256,
}
// InsecureTLSCiphers returns the cipher suites implemented by crypto/tls which have
// security issues.
func InsecureTLSCiphers() map[string]uint16 {
cipherKeys := make(map[string]uint16, len(insecureCiphers))
for k, v := range insecureCiphers {
cipherKeys[k] = v
}
return cipherKeys
}
// tlsHandshakeErrorWriter writes TLS handshake errors to klog with
// trace level - V(5), to avoid flooding of tls handshake errors.
type tlsHandshakeErrorWriter struct {
out io.Writer
}
const tlsHandshakeErrorPrefix = "http: TLS handshake error"
func (w *tlsHandshakeErrorWriter) Write(p []byte) (int, error) {
if strings.Contains(string(p), tlsHandshakeErrorPrefix) {
fmt.Println(string(p))
//metrics.TLSHandshakeErrors.Inc()
return len(p), nil
}
// for non tls handshake error, log it as usual
return w.out.Write(p)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment