package goxy import ( "crypto/tls" "crypto/x509" "fmt" "net/http" "net/http/httputil" "net/url" ) type Server struct { DataFile string Routes Certs wwwServer http.Server tlsServer http.Server rpcServer http.Server } // Certs holds certificates type Certs map[string]*tls.Certificate // GetCertificate returns certificate for SNI negotiation func (c Certs) getCertificate(h *tls.ClientHelloInfo) (*tls.Certificate, error) { if v, ok := c[h.ServerName]; ok { return v, nil } return nil, fmt.Errorf("no cert for %q", h.ServerName) } func (c Certs) ServeHTTP(w http.ResponseWriter, _ *http.Request) { for k, v := range c { fmt.Fprintf(w, "%v: valid untill %v\n", k, v.Leaf.NotAfter) } } func (c Certs) addCertificate(cert, key []byte) error { crt, err := tls.X509KeyPair(cert, key) if err != nil { return err } crt.Leaf, err = x509.ParseCertificate(crt.Certificate[0]) if err != nil { return err } if cn := crt.Leaf.Subject.CommonName; cn != "" { c[cn] = &crt } for _, name := range crt.Leaf.DNSNames { c[name] = &crt } for _, ip := range crt.Leaf.IPAddresses { c[ip.String()] = &crt } return nil } func NewServer(dataFile, listenWWW, listenTLS, listenRPC string) (*Server, error) { if listenRPC == "" { listenRPC = RPCPort } server := &Server{ DataFile: dataFile, Routes: make(Routes), Certs: make(Certs), wwwServer: http.Server{Addr: listenWWW}, tlsServer: http.Server{Addr: listenTLS}, rpcServer: http.Server{Addr: listenRPC}, } server.tlsServer.TLSConfig = &tls.Config{ GetCertificate: server.getCertificate, } if dataFile != "" { server.Load(dataFile) } registerRPC(server) http.Handle("/debug/routes", server.Routes) http.Handle("/debug/certs", server.Certs) return server, server.UpdateMux() } func NewRedirect(host string) http.Handler { return http.RedirectHandler(host, http.StatusMovedPermanently) } func NewReverseProxy(target *url.URL) *httputil.ReverseProxy { return httputil.NewSingleHostReverseProxy(target) } // Update routes from in-memory state func (s *Server) UpdateMux() error { wwwMux := http.NewServeMux() tlsMux := http.NewServeMux() for host, route := range s.Routes { serverName, err := url.Parse(route.Host) if err != nil { return err } upstream, err := url.Parse(route.Upstream) if err != nil { return err } switch serverName.Scheme { case "http", "": wwwMux.Handle(host, NewReverseProxy(upstream)) case "https": err := s.Certs.addCertificate(route.Cert, route.Key) if err != nil { return err } tlsMux.Handle(host, NewReverseProxy(upstream)) wwwMux.Handle(host, NewRedirect("https://"+host)) case "ws": wwwMux.Handle(host, NewWebSocketProxy(upstream)) case "wss": err := s.Certs.addCertificate(route.Cert, route.Key) if err != nil { return err } tlsMux.Handle(host, NewWebSocketProxy(upstream)) wwwMux.Handle(host, NewRedirect("wss://"+host)) } } s.wwwServer.Handler = wwwMux s.tlsServer.Handler = tlsMux return nil } func (s *Server) Start() error { errc := make(chan error) go func() { errc <- s.wwwServer.ListenAndServe() }() go func() { errc <- s.tlsServer.ListenAndServeTLS("", "") }() go func() { errc <- s.rpcServer.ListenAndServe() }() return <-errc }