Pārlūkot izejas kodu

organized and clean the codebase

Mohamed Al Ashaal 7 gadi atpakaļ
vecāks
revīzija
d59c040612
7 mainītis faili ar 95 papildinājumiem un 296 dzēšanām
  1. 0 51
      README.md.v1
  2. 18 0
      flags.go
  3. 23 0
      hosts.go
  4. 0 141
      httpsify.go
  5. 0 104
      httpsify.go.v1
  6. 6 0
      main.go
  7. 48 0
      server.go

+ 0 - 51
README.md.v1

@@ -1,51 +0,0 @@
-# Intro
-A transparent HTTPS proxy with automatic certificate renewal
-using https://letsencrypt.org/
-
-# How it works ?
-httpsify is a https reverse proxy ...
-[https request] --> httpsify --> [apache/nginx/nodejs/... etc]
-but this isn't the point because there are many https offloaders,
-but httpsify uses letsencrypt (https://letsencrypt.org/)
-for automatically generating free and valid ssl certificates, as well as auto renewal of certs,
-this web server by default uses HTTP/2 .
-you can say that httpsify is just a http/2 & letsencrypt wrapper for any http web server with no hassle, it just works .
-
-# Features
-* SSL Offloader.
-* HTTP/2 support.
-* Multi-Core support.
-* Auto-Renewal for generated certificates.
-* Blazing fast.
-* Very light.
-* Portable and small `~ 2 MB`
-* No system requirements.
-* No configurations required, just `httpsify --domains="domain.com,www.domain.com,sub.domain.com"`
-* Passes `X-Forwarded-*` headers, `X-Real-IP` header and `X-Remote-IP`/`X-Remote-Port` to the backend server.
-
-# Installation
-> Currently the only available binaries are built for `linux` `386/amd64` and you can download them from [here](https://github.com/alash3al/httpsify/releases) .  
-
-# Building from source :
-* Make sure you have `Golang` installed .
-* `go get github.com/alash3al/httpsify`.
-* `go install github.com/alash3al/httpsify`.
-*  make sure that `$GOPATH/bin` in your `$PATH` .
-
-# Quick Usage
-> lets say that you have extracted/built httpsify in the current working directory .
-```bash
-# this is the simplest way to run httpsify
-# this will run a httpsify instance listening on port 443 and passing the incoming requests to http://localhost
-# and building valid signed cerificates for the specified domains [they must be valid domain names]
-./httpsify --domains="domain.tld,www.domain.tld,another.domain.tld"
-```
-
-# Author
-I'm [Mohammed Al Ashaal](https://www.alash3al.xyz)
-
-# Thanks
-I must thank the following awesome libraries
-
-* [github.com/xenolf/lego](https://github.com/xenolf/lego)
-* [github.com/dkumor/acmewrapper](https://github.com/dkumor/acmewrapper)

+ 18 - 0
flags.go

@@ -0,0 +1,18 @@
+package main
+
+import (
+	"flag"
+	"github.com/mitchellh/go-homedir"
+	"path"
+)
+
+var (
+	HOME_DIR, _ = homedir.Dir()
+	HTTPS_ADDR  = flag.String("https", ":443", "the https address to listen on")
+	STORAGE     = flag.String("storage", path.Join(HOME_DIR, "httpsify/certs"), "the ssl certs storage directory")
+	HOSTS_FILE  = flag.String("hosts", path.Join(HOME_DIR, "httpsify/hosts.json"), "the sites configurations filename")
+)
+
+func InitFlags() {
+	flag.Parse()
+}

+ 23 - 0
hosts.go

@@ -0,0 +1,23 @@
+package main
+
+import (
+	"encoding/json"
+	"os"
+	"sync"
+)
+
+var (
+	HOSTS  = make(map[string][]string)
+	LOCKER = new(sync.Mutex)
+)
+
+func InitSites() error {
+	LOCKER.Lock()
+	defer LOCKER.Unlock()
+	f, e := os.Open(*HOSTS_FILE)
+	if e != nil {
+		return e
+	}
+	defer f.Close()
+	return json.NewDecoder(f).Decode(&HOSTS)
+}

+ 0 - 141
httpsify.go

@@ -1,141 +0,0 @@
-package main
-
-import (
-	"crypto/tls"
-	"flag"
-	"fmt"
-	"io"
-	"log"
-	"net"
-	"net/http"
-	"net/http/httputil"
-	"net/url"
-	"strings"
-
-	"golang.org/x/crypto/acme/autocert"
-)
-
-var (
-	// CMD options
-	listen      = flag.String("listen", ":443", "the local listen address")
-	domains     = flag.String("domains", "", "a comma separated strings of domain[->[ip]:port]")
-	backend     = flag.String("backend", ":80", "the default backend to be used")
-	sslCacheDir = flag.String("ssl-cache-dir", "./httpsify-ssl-cache", "the cache directory to cache generated ssl certs")
-
-	// internal vars
-	domain_backend = map[string]string{}
-	whitelisted    = []string{}
-)
-
-func main() {
-	flag.Parse()
-
-	if *domains == "" {
-		flag.Usage()
-		fmt.Println(`Example(1): httpsify -domains "example.org,api.example.org->localhost:366, api2.example.org->:367"`)
-		fmt.Println(`Example(2): httpsify -domains "www.site.com,apiv1.site.com->:8080,apiv2.site.com->:8081"`)
-		return
-	}
-
-	for _, zone := range strings.Split(*domains, ",") {
-		parts := strings.SplitN(zone, "->", 2)
-		if len(parts) < 2 {
-			parts = append(parts, *backend)
-		}
-		parts[1] = fixUrl(parts[1])
-		domain_backend[parts[0]] = parts[1]
-		whitelisted = append(whitelisted, parts[0])
-	}
-
-	m := autocert.Manager{
-		Prompt:     autocert.AcceptTOS,
-		HostPolicy: autocert.HostWhitelist(whitelisted...),
-		Cache:      autocert.DirCache(*sslCacheDir),
-	}
-
-	h := handler()
-
-	s := &http.Server{
-		Addr:      *listen,
-		Handler:   h,
-		TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
-	}
-
-	log.Fatal(s.ListenAndServeTLS("", ""))
-}
-
-// fix the specified url
-// this function will make sure that "http://" already exists,
-// also it will make sure that it has a hostname .
-func fixUrl(u string) string {
-	u = strings.TrimPrefix(strings.TrimSpace(u), "https://")
-	if strings.Index(u, ":") == 0 {
-		u = "localhost" + u
-	}
-	if !strings.HasPrefix(u, "ws://") && !strings.HasPrefix(u, "http://") {
-		u = "http://" + u
-	}
-	u = strings.TrimRight(u, "/")
-	return u
-}
-
-// the proxy handler
-func handler() http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		r.Host = strings.SplitN(r.Host, ":", 2)[0]
-		if _, found := domain_backend[r.Host]; !found {
-			http.Error(w, r.Host+": not found", http.StatusNotImplemented)
-			return
-		}
-		r.Header["X-Forwarded-Proto"] = []string{"https"}
-		r.Header["X-Forwarded-For"] = append(r.Header["X-Forwarded-For"], strings.SplitN(r.RemoteAddr, ":", 2)[0])
-		u, _ := url.Parse(domain_backend[r.Host] + "/" + strings.TrimLeft(r.URL.RequestURI(), "/"))
-		if strings.ToLower(r.Header.Get("Upgrade")) == "websocket" {
-			NewWebsocketReverseProxy(u).ServeHTTP(w, r)
-			return
-		} else {
-			proxy := httputil.NewSingleHostReverseProxy(u)
-			defaultDirector := proxy.Director
-			proxy.Director = func(req *http.Request) {
-				defaultDirector(req)
-				req.Host = r.Host
-				req.URL = u
-			}
-			proxy.ServeHTTP(w, r)
-			return
-		}
-	})
-}
-
-// the websocket proxy handler
-func NewWebsocketReverseProxy(u *url.URL) http.Handler {
-	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		backConn, err := net.Dial("tcp", u.Host)
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		defer backConn.Close()
-		hj, ok := w.(http.Hijacker)
-		if !ok {
-			http.Error(w, "webserver doesn't support hijacking", http.StatusInternalServerError)
-			return
-		}
-		clientConn, _, err := hj.Hijack()
-		if err != nil {
-			http.Error(w, err.Error(), http.StatusInternalServerError)
-			return
-		}
-		defer clientConn.Close()
-		message := r.Method + " " + r.URL.RequestURI() + " " + r.Proto + "\n"
-		message += "Host: " + r.Host + "\n"
-		for k, vals := range r.Header {
-			for _, v := range vals {
-				message += k + ": " + v + "\n"
-			}
-		}
-		message += "\n"
-		go io.Copy(backConn, io.MultiReader(strings.NewReader(message), r.Body, clientConn))
-		io.Copy(clientConn, backConn)
-	})
-}

+ 0 - 104
httpsify.go.v1

@@ -1,104 +0,0 @@
-// httpsify is a transparent blazing fast https offloader with auto certificates renewal .
-// this software is published under MIT License .
-// by Mohammed Al ashaal <alash3al.xyz> with the help of those opensource libraries [github.com/xenolf/lego, github.com/dkumor/acmewrapper] .
-package main
-
-import (
-	"crypto/tls"
-	"flag"
-	"github.com/dkumor/acmewrapper"
-	"io"
-	"log"
-	"net"
-	"net/http"
-	"path/filepath"
-	"strings"
-)
-
-// --------------
-
-const version = "httpsify/v1"
-
-var (
-	port    = flag.String("port", "443", "the port that will serve the https requests")
-	cert    = flag.String("cert", "./cert.pem", "the cert.pem save-path")
-	key     = flag.String("key", "./key.pem", "the key.pem save-path")
-	domains = flag.String("domains", "", "a comma separated list of your site(s) domain(s)")
-	backend = flag.String("backend", "http://127.0.0.1:80", "the backend http server that will serve the terminated requests")
-	info    = flag.String("info", "yes", "whether to send information about httpsify or not ^_^")
-)
-
-// --------------
-
-func init() {
-	flag.Parse()
-	if *domains == "" {
-		log.Fatal("err> Please enter your site(s) domain(s)")
-	}
-}
-
-// --------------
-
-func main() {
-	acme, err := acmewrapper.New(acmewrapper.Config{
-		Domains:          strings.Split(*domains, ","),
-		Address:          ":" + *port,
-		TLSCertFile:      *cert,
-		TLSKeyFile:       *key,
-		RegistrationFile: filepath.Dir(*cert) + "/lets-encrypt-user.reg",
-		PrivateKeyFile:   filepath.Dir(*cert) + "/lets-encrypt-user.pem",
-		TOSCallback:      acmewrapper.TOSAgree,
-	})
-	if err != nil {
-		log.Fatal("err> " + err.Error())
-	}
-	listener, err := tls.Listen("tcp", ":"+*port, acme.TLSConfig())
-	if err != nil {
-		log.Fatal("err> " + err.Error())
-	}
-	log.Fatal(http.Serve(listener, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		defer r.Body.Close()
-		client := &http.Client{
-			CheckRedirect: func(req *http.Request, via []*http.Request) error {
-				return http.ErrUseLastResponse
-		    	},
-		}
-		//client := &http.Client{Transport: tr}
-		req, err := http.NewRequest(r.Method, *backend + r.URL.RequestURI(), r.Body)
-		if err != nil {
-			http.Error(w, http.StatusText(504), 504)
-			return
-		}
-		for k, vs := range r.Header {
-			for _, v := range vs {
-				req.Header.Add(k, v)
-			}
-		}
-		uip, uport, _ := net.SplitHostPort(r.RemoteAddr)
-		req.Host = r.Host
-		req.Header.Set("Host", r.Host)
-		req.Header.Set("X-Real-IP", uip)
-		req.Header.Set("X-Remote-IP", uip)
-		req.Header.Set("X-Remote-Port", uport)
-		req.Header.Set("X-Forwarded-For", uip)
-		req.Header.Set("X-Forwarded-Proto", "https")
-		req.Header.Set("X-Forwarded-Host", r.Host)
-		req.Header.Set("X-Forwarded-Port", *port)
-		res, err := client.Do(req)
-		if err != nil {
-			http.Error(w, http.StatusText(504), 504)
-			return
-		}
-		defer res.Body.Close()
-		for k, vs := range res.Header {
-			for _, v := range vs {
-				w.Header().Add(k, v)
-			}
-		}
-		if *info == "yes" {
-			w.Header().Set("Server", version)
-		}
-		w.WriteHeader(res.StatusCode)
-		io.Copy(w, res.Body)
-	})))
-}

+ 6 - 0
main.go

@@ -0,0 +1,6 @@
+package main
+
+func main() {
+	InitFlags()
+	InitSites()
+}

+ 48 - 0
server.go

@@ -0,0 +1,48 @@
+package main
+
+import (
+	"context"
+	"crypto/tls"
+	"errors"
+	"net/http"
+)
+
+import (
+	"github.com/vulcand/oxy/forward"
+	"github.com/vulcand/oxy/roundrobin"
+	"github.com/vulcand/oxy/testutils"
+	"golang.org/x/crypto/acme/autocert"
+)
+
+func InitServer() error {
+	m := autocert.Manager{
+		Cache:  autocert.DirCache(*STORAGE),
+		Prompt: autocert.AcceptTOS,
+		HostPolicy: func(ctx context.Context, host string) error {
+			if _, ok := HOSTS[host]; ok {
+				return nil
+			}
+			return errors.New("Unkown host(" + host + ")")
+		},
+	}
+	s := &http.Server{
+		Addr:      ":https",
+		TLSConfig: &tls.Config{GetCertificate: m.GetCertificate},
+		Handler:   ServeHTTP(),
+	}
+	return s.ListenAndServeTLS("", "")
+}
+
+func ServeHTTP() http.Handler {
+	return http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) {
+		if upstreams, ok := HOSTS[req.Host]; ok {
+			forwarder, _ := forward.New()
+			loadbalancer, _ := roundrobin.New(forwarder)
+			for _, upstream := range upstreams {
+				loadbalancer.UpsertServer(testutils.ParseURI(upstream))
+			}
+			loadbalancer.ServeHTTP(res, req)
+		}
+		http.Error(res, "The request domain couldn't be found here", http.StatusNotImplemented)
+	})
+}