package main import ( "crypto/tls" "errors" "net" "net/http" "net/http/httputil" "time" "git.xeserv.us/xena/route/internal/database" "git.xeserv.us/xena/route/internal/middleware" "git.xeserv.us/xena/route/internal/tun2" proto "git.xeserv.us/xena/route/proto" "github.com/Xe/ln" "github.com/lucas-clemente/quic-go/h2quic" "github.com/mtneug/pkg/ulid" kcp "github.com/xtaci/kcp-go" "golang.org/x/crypto/acme/autocert" "golang.org/x/net/context" ) // RPC constants const ( RPCPort uint16 = 39453 ) // Server is the main server type. type Server struct { cfg *Config db database.Storage ts *tun2.Server QuicServer *h2quic.Server *autocert.Manager } // Config configures Server type Config struct { BoltDBPath string `env:"BOLTDB_PATH,required"` WebAddr string `env:"WEB_ADDR,required"` SSLAddr string `env:"SSL_ADDR,required"` QuicAddr string `env:"QUIC_ADDR,required"` BackendTCPAddr string `env:"BACKEND_TCP_ADDR,required"` BackendKCPAddr string `env:"BACKEND_KCP_ADDR,required"` GRPCAddr string `env:"GRPC_ADDR,required"` DomainSuffix string `env:"DOMAIN_SUFFIX,required"` ACMEEmail string `env:"ACME_EMAIL,required"` CertKey *[32]byte } // listenTCP configures a listener for TCP+TLS agent connections. func (s *Server) listenTCP(ctx context.Context, addr string, tcfg *tls.Config) { l, err := tls.Listen("tcp", addr, tcfg) if err != nil { panic(err) } ln.Log(ctx, ln.Action("tcp+tls listening"), ln.F{"addr": l.Addr()}) for { conn, err := l.Accept() if err != nil { ln.Error(ctx, err, ln.Action("accept backend client socket")) } ln.Log(ctx, ln.F{ "action": "new backend client", "addr": conn.RemoteAddr(), "network": conn.RemoteAddr().Network(), }) go s.ts.HandleConn(conn, false) } } // listenKCP configures a listener for KCP+TLS agent connections. func (s *Server) listenKCP(ctx context.Context, addr string, tcfg *tls.Config) { l, err := kcp.Listen(addr) if err != nil { panic(err) } ln.Log(ctx, ln.Action("kcp+tls listening"), ln.F{"addr": l.Addr()}) for { conn, err := l.Accept() if err != nil { ln.Error(ctx, err, ln.F{"kind": "kcp", "addr": l.Addr().String()}) } ln.Log(ctx, ln.F{ "action": "new_client", "network": conn.RemoteAddr().Network(), "addr": conn.RemoteAddr(), }) tc := tls.Server(conn, tcfg) go s.ts.HandleConn(tc, true) } } // New creates a new Server func New(cfg Config) (*Server, error) { if cfg.CertKey == nil { return nil, errors.New("no cert decryption key, can't do anything") } db, err := database.NewBoltStorage(cfg.BoltDBPath, cfg.CertKey) if err != nil { return nil, err } m := &autocert.Manager{ Prompt: autocert.AcceptTOS, Cache: database.Cache(db), HostPolicy: nil, Email: cfg.ACMEEmail, } tcfg := &tun2.ServerConfig{ Storage: &storageWrapper{ Storage: db, }, } ts, err := tun2.NewServer(tcfg) if err != nil { return nil, err } s := &Server{ cfg: &cfg, db: db, ts: ts, Manager: m, } tc := &tls.Config{ GetCertificate: m.GetCertificate, } go s.listenKCP(context.Background(), cfg.BackendKCPAddr, tc) go s.listenTCP(context.Background(), cfg.BackendTCPAddr, tc) withHandler := func(disc string, h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { h.ServeHTTP(w, r.WithContext(ln.WithF(r.Context(), ln.F{"disc": disc}))) }) } bhdr := proto.NewBackendsServer(&Backend{Server: s}, s.makeTwirpHooks()) rhdr := proto.NewRoutesServer(&Route{Server: s}, s.makeTwirpHooks()) thdr := proto.NewTokensServer(&Token{Server: s}, s.makeTwirpHooks()) mux := http.NewServeMux() mux.Handle(proto.BackendsPathPrefix, withHandler("backends", bhdr)) mux.Handle(proto.RoutesPathPrefix, withHandler("routes", rhdr)) mux.Handle(proto.TokensPathPrefix, withHandler("tokens", thdr)) hs := &http.Server{ TLSConfig: tc, Addr: cfg.GRPCAddr, Handler: middleware.SaveHeaders(middleware.Trace("twirp-https")(mux)), } ln.Log(context.Background(), ln.F{ "kind": "api", "backends": proto.BackendsPathPrefix, "routes": proto.RoutesPathPrefix, "tokens": proto.TokensPathPrefix, "addr": cfg.GRPCAddr, }) go hs.ListenAndServeTLS("", "") return s, nil } // Director removes headers that are typically stripped off at request ingress. func (s *Server) Director(r *http.Request) { r.Header.Del("X-Forwarded-For") r.Header.Del("X-Client-Ip") } // ServeHTTP proxies traffic to a remote backend based on the request meta-information. func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { if r.Header.Get("X-Tor2web") != "" { http.Error(w, "tor2web proxy use is not allowed", 400) return } host, _, err := net.SplitHostPort(r.RemoteAddr) if err != nil { http.Error(w, err.Error(), 500) return } r.Header.Set("X-Remote-Ip", host) r.Header.Set("X-Request-Ingress", time.Now().Format(time.RFC3339)) rid := ulid.New().String() r.Header.Set("X-Request-Id", rid) w.Header().Set("X-Request-Id", rid) s.QuicServer.SetQuicHeaders(w.Header()) // http://www.gnuterrypratchett.com/ w.Header().Set("X-Clacks-Overhead", "GNU Ashlynn") rp := &httputil.ReverseProxy{ Director: s.Director, Transport: s.ts, FlushInterval: 1 * time.Second, } rp.ServeHTTP(w, r) } // insecureRedirect redirects a client to https if they connect over plain HTTP. func insecureRedirect(w http.ResponseWriter, r *http.Request) { switch r.Method { case http.MethodPatch, http.MethodPut, http.MethodPost: http.Error(w, "use https", http.StatusNotAcceptable) ln.Log(r.Context(), ln.Action("cannot redirect (wrong method)"), ln.F{"remote": r.RemoteAddr, "host": r.Host, "path": r.URL.Path}) return } r.URL.Host = r.Host r.URL.Scheme = "https" ln.Log(r.Context(), ln.Action("redirecting insecure HTTP to HTTPS"), ln.F{"remote": r.RemoteAddr, "host": r.Host, "path": r.URL.Path}) http.Redirect(w, r, r.URL.String(), http.StatusPermanentRedirect) }