commit fc0962b16ca191158218de0c29e340e6b3c5f4af Author: Cadey Dodrill Date: Wed Jan 18 01:57:18 2017 -0800 Initial commit diff --git a/cmd/api/main.go b/cmd/api/main.go new file mode 100644 index 0000000..e7b3684 --- /dev/null +++ b/cmd/api/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "log" + "net/rpc" + + "git.xeserv.us/xena/route/routerpc" +) + +func main() { + client, err := rpc.DialHTTP("tcp", "127.0.0.1:9234") + if err != nil { + log.Fatal(err) + } + + req := routerpc.AddHostRequest{ + APIKey: "hunter2", + Hostname: "", + PrivKey: nil, + } + resp := &routerpc.AddHostResponse{} + + err = client.Call("Urls.AddHost", req, resp) + if err != nil { + log.Fatal(err) + } + + log.Printf("Created host %s with token %s", resp.Hostname, resp.Token) +} diff --git a/cmd/httpagent/main.go b/cmd/httpagent/main.go new file mode 100644 index 0000000..4ee7596 --- /dev/null +++ b/cmd/httpagent/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "flag" + "log" + + "github.com/koding/tunnel" +) + +var ( + token = flag.String("token", "", "Service identifier token") + backend = flag.String("backend", "127.0.0.1:9090", "backend TCP/HTTP server") +) + +func main() { + flag.Parse() + + cfg := &tunnel.ClientConfig{ + Identifier: *token, + LocalAddr: *backend, + ServerAddr: "127.0.0.1:9234", + } + + client, err := tunnel.NewClient(cfg) + if err != nil { + log.Fatal(err) + } + + client.Start() +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..9619d7b --- /dev/null +++ b/main.go @@ -0,0 +1,253 @@ +package main + +import ( + "crypto/x509" + "encoding/pem" + "errors" + "flag" + "fmt" + "io/ioutil" + "log" + "math/rand" + "net" + "net/http" + "net/rpc" + "os" + "path/filepath" + "strconv" + "time" + + "git.xeserv.us/xena/route/routerpc" + + r "github.com/GoRethink/gorethink" + "github.com/Xe/uuid" + "github.com/Yawning/bulb" + "github.com/facebookgo/flagenv" + _ "github.com/joho/godotenv/autoload" + "github.com/koding/tunnel" + "github.com/sycamoreone/orc/tor" +) + +var ( + rethinkDBHost = flag.String("rethink-host", "", "RethinkDB host") + rethinkDBDatabase = flag.String("rethink-database", "", "RethinkDB database") + controlKeyFile = flag.String("control-key-file", "", "Control host keyfile") + controlHost = flag.String("control-host", "", "Control host onion hash") + torDataDir = flag.String("tor-data-dir", "./var", "Tor data directory") + torHashedPassword = flag.String("tor-hashed-password", "", "Tor hashed password") + torPassword = flag.String("tor-password", "hunter2", "Tor clear password") + webPort = flag.String("web-port", "9234", "HTTP ingress port for backends and users") +) + +// RPC constants +const ( + RPCPort uint16 = 39453 +) + +func main() { + flag.Parse() + flagenv.Parse() + rand.Seed(time.Now().Unix()) + + s, err := create(&ServerConfig{ + ControlHost: *controlHost, + ControlKeyFile: *controlKeyFile, + RethinkDBHost: *rethinkDBHost, + RethinkDBDatabase: *rethinkDBDatabase, + TorDataDir: *torDataDir, + TorHashedPassword: *torHashedPassword, + TorPassword: *torPassword, + }) + if err != nil { + log.Fatal(err) + } + + l, err := net.Listen("tcp", "127.0.0.1:"+*webPort) + if err != nil { + log.Fatal(err) + } + defer l.Close() + + hs := &http.Server{ + Handler: s, + Addr: "127.0.0.1:" + *webPort, + } + + hs.Serve(l) +} + +// Server is the main server type +type Server struct { + cfg *ServerConfig + + sess *r.Session + + torCon *bulb.Conn + torCmd *tor.Cmd + torControlPath string + + rpcS *rpc.Server + rpcAddr string + + ts *tunnel.Server +} + +// ServerConfig configures Server +type ServerConfig struct { + ControlHost, ControlKeyFile string + RethinkDBHost, RethinkDBDatabase string + TorDataDir, TorHashedPassword, TorPassword string +} + +func create(cfg *ServerConfig) (*Server, error) { + session, err := r.Connect(r.ConnectOpts{ + Address: cfg.RethinkDBHost, + Database: cfg.RethinkDBDatabase, + }) + if err != nil { + return nil, err + } + + l, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + return nil, err + } + + torControlPath := filepath.Join(cfg.TorDataDir, fmt.Sprintf("%d.sock", rand.Int63())) + + tc := tor.NewConfig() + tc.Set("ControlSocket", torControlPath) + tc.Set("DataDirectory", cfg.TorDataDir) + tc.Set("HashedControlPassword", cfg.TorHashedPassword) + tc.Set("SocksPort", "0") + cp := rand.Intn(64512) // 64k - 1k + tc.Set("ControlPort", cp) + + tc.Timeout = 30 * time.Second + log.Println(tc.ToCmdLineFormat()) + + tcmd, err := tor.NewCmd(tc) + if err != nil { + return nil, err + } + + err = tcmd.Start() + if err != nil { + return nil, err + } + + time.Sleep(5 * time.Second) + + bc, err := bulb.Dial("tcp", "127.0.0.1:"+strconv.Itoa(cp)) + if err != nil { + return nil, err + } + + err = bc.Authenticate(cfg.TorPassword) + if err != nil { + return nil, err + } + + fin, err := os.Open(cfg.ControlKeyFile) + if err != nil { + return nil, err + } + defer fin.Close() + + data, err := ioutil.ReadAll(fin) + if err != nil { + return nil, err + } + + var block *pem.Block + + block, _ = pem.Decode([]byte(data)) + pKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + + _, err = bc.AddOnion([]bulb.OnionPortSpec{ + bulb.OnionPortSpec{ + VirtPort: RPCPort, + Target: l.Addr().String(), + }, + }, pKey, false) + if err != nil { + return nil, err + } + + rpcs := rpc.NewServer() + + ts, err := tunnel.NewServer(&tunnel.ServerConfig{}) + if err != nil { + return nil, err + } + + s := &Server{ + cfg: cfg, + + sess: session, + + torCon: bc, + torCmd: tcmd, + torControlPath: torControlPath, + + rpcS: rpcs, + rpcAddr: l.Addr().String(), + + ts: ts, + } + + rpcs.RegisterName("Urls", &RPCServer{Server: s}) + go rpcs.Accept(l) + + return s, nil +} + +func (s *Server) onionPath(name string) string { + return filepath.Join(s.cfg.TorDataDir, name) +} + +func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.RequestURI == rpc.DefaultRPCPath { + s.rpcS.ServeHTTP(w, r) + return + } + s.ts.ServeHTTP(w, r) +} + +type RPCServer struct { + *Server +} + +func (rs *RPCServer) AddHost(req routerpc.AddHostRequest, resp *routerpc.AddHostResponse) error { + if req.APIKey != "hunter2" { + return errors.New("invalid api key") + } + + token := uuid.New() + + oi, err := rs.torCon.AddOnion([]bulb.OnionPortSpec{ + bulb.OnionPortSpec{ + VirtPort: 80, + Target: "127.0.0.1:" + *webPort, + }, + }, req.PrivKey, false) + if err != nil { + return err + } + + resp.Hostname = oi.OnionID + ".onion" + resp.Token = token + + rs.Server.ts.AddHost(resp.Hostname, token) + + if oi.PrivateKey != nil { + resp.PrivKey = oi.PrivateKey + } else { + resp.PrivKey = req.PrivKey + } + + return nil +} diff --git a/routerpc/rpc.go b/routerpc/rpc.go new file mode 100644 index 0000000..a4e58c2 --- /dev/null +++ b/routerpc/rpc.go @@ -0,0 +1,27 @@ +package routerpc + +import ( + "crypto" + "crypto/rsa" + "encoding/gob" +) + +func init() { + gob.Register(&AddHostRequest{}) + gob.Register(&AddHostResponse{}) + gob.Register(&rsa.PrivateKey{}) +} + +// AddHostRequest is for adding a host to the routing mesh +type AddHostRequest struct { + APIKey string + Hostname string + PrivKey crypto.PrivateKey +} + +// AddHostResponse ... +type AddHostResponse struct { + Token string + Hostname string + PrivKey crypto.PrivateKey +}