diff --git a/boringproxy.go b/boringproxy.go index ae9a2ab..f10d1b6 100644 --- a/boringproxy.go +++ b/boringproxy.go @@ -11,6 +11,8 @@ import ( "net" "net/http" "os" + "os/user" + "path/filepath" "strings" "sync" "time" @@ -37,10 +39,11 @@ type SmtpConfig struct { } type Server struct { - db *Database - tunMan *TunnelManager - httpClient *http.Client - httpListener *PassthroughListener + db *Database + tunMan *TunnelManager + httpClient *http.Client + httpListener *PassthroughListener + waygateServer *waygate.Server } func Listen() { @@ -93,7 +96,17 @@ func Listen() { fmt.Printf("WARNING: Failed to access %s:%d from the internet\n", ip, *httpsPort) } + user, err := user.Current() + if err != nil { + log.Fatalf("Unable to get current user: %v", err) + } waygateServer := waygate.NewServer(db) + waygateServer.SshConfig = &waygate.SshConfig{ + ServerAddress: db.GetAdminDomain(), + ServerPort: *sshServerPort, + Username: user.Username, + AuthorizedKeysPath: filepath.Join(user.HomeDir, ".ssh", "authorized_keys"), + } autoCerts := true if *httpPort != 80 || *httpsPort != 443 { @@ -148,7 +161,8 @@ func Listen() { users := db.GetUsers() if len(users) == 0 { db.AddUser("admin", true) - _, err := db.AddToken("admin", "") + clientId := "" + _, err := db.AddToken("admin", clientId) if err != nil { log.Fatal("Failed to initialize admin user") } @@ -190,7 +204,7 @@ func Listen() { httpListener := NewPassthroughListener() - p := &Server{db, tunMan, httpClient, httpListener} + p := &Server{db, tunMan, httpClient, httpListener, waygateServer} tlsConfig := &tls.Config{ GetCertificate: certConfig.GetCertificate, @@ -199,6 +213,13 @@ func Listen() { tlsListener := tls.NewListener(httpListener, tlsConfig) http.Handle("/waygate/", http.StripPrefix("/waygate", waygateServer)) + // TODO: This feels like a bit of a hack. + http.HandleFunc("/waygate/authorize", func(w http.ResponseWriter, r *http.Request) { + webUiHandler.handleWebUiRequest(w, r) + }) + http.HandleFunc("/waygate/authorized", func(w http.ResponseWriter, r *http.Request) { + webUiHandler.handleWebUiRequest(w, r) + }) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { timestamp := time.Now().Format(time.RFC3339) @@ -357,6 +378,18 @@ func (p *Server) handleConnection(clientConn net.Conn, certConfig *certmagic.Con return } + port, err := p.waygateServer.GetSSHPortForDomain(clientHello.ServerName) + if err == nil { + fmt.Println("yolo", port) + useTls := true + err := ProxyTcp(clientConn, "127.0.0.1", port, useTls, certConfig) + if err != nil { + log.Println(err.Error()) + return + } + return + } + passConn := NewProxyConn(clientConn, clientReader) tunnel, exists := p.db.GetTunnel(clientHello.ServerName) diff --git a/database.go b/database.go index 5bdf5e9..801ba38 100644 --- a/database.go +++ b/database.go @@ -8,17 +8,20 @@ import ( "sync" "github.com/takingnames/namedrop-go" + "github.com/takingnames/waygate-go" ) var DBFolderPath string type Database struct { - AdminDomain string `json:"admin_domain"` - Tokens map[string]TokenData `json:"tokens"` - Tunnels map[string]Tunnel `json:"tunnels"` - Users map[string]User `json:"users"` - dnsRequests map[string]namedrop.DNSRequest `json:"dns_requests"` - mutex *sync.Mutex + AdminDomain string `json:"admin_domain"` + Tokens map[string]TokenData `json:"tokens"` + Tunnels map[string]Tunnel `json:"tunnels"` + Users map[string]User `json:"users"` + dnsRequests map[string]namedrop.DNSRequest `json:"dns_requests"` + WaygateTunnels map[string]waygate.WaygateTunnel `json:"waygate_tunnels"` + WaygateTalismans map[string]waygate.WaygateTalisman `json:"waygate_talismans"` + mutex *sync.Mutex } type TokenData struct { @@ -96,6 +99,13 @@ func NewDatabase(path string) (*Database, error) { db.dnsRequests = make(map[string]namedrop.DNSRequest) } + if db.WaygateTunnels == nil { + db.WaygateTunnels = make(map[string]waygate.WaygateTunnel) + } + if db.WaygateTalismans == nil { + db.WaygateTalismans = make(map[string]waygate.WaygateTalisman) + } + db.mutex = &sync.Mutex{} db.mutex.Lock() @@ -322,6 +332,87 @@ func (d *Database) DeleteUser(username string) { d.persist() } +func (d *Database) AddWaygateTunnel(domains []string) (string, error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + id, err := genRandomCode(32) + if err != nil { + return "", errors.New("Could not generate waygate id") + } + + // TODO: verify none of the domains are already in use. + + waygate := waygate.WaygateTunnel{ + Domains: domains, + } + + d.WaygateTunnels[id] = waygate + + d.persist() + + return id, nil +} +func (d *Database) GetWaygateTunnel(id string) (waygate.WaygateTunnel, error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + tun, exists := d.WaygateTunnels[id] + if !exists { + return waygate.WaygateTunnel{}, errors.New("No such waygate") + } + + return tun, nil +} +func (d *Database) GetWaygates() map[string]waygate.WaygateTunnel { + d.mutex.Lock() + defer d.mutex.Unlock() + + wgs := make(map[string]waygate.WaygateTunnel) + + for id, wg := range d.WaygateTunnels { + wgs[id] = wg + } + + return wgs +} + +func (d *Database) AddWaygateTalisman(waygateId string) (string, error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + talisman, err := genRandomCode(32) + if err != nil { + return "", errors.New("Could not generate waygate talisman") + } + + _, exists := d.WaygateTunnels[waygateId] + if !exists { + return "", errors.New("No such waygate") + } + + talismanData := waygate.WaygateTalisman{ + WaygateId: waygateId, + } + + d.WaygateTalismans[talisman] = talismanData + + d.persist() + + return talisman, nil +} +func (d *Database) GetWaygateTalisman(id string) (waygate.WaygateTalisman, error) { + d.mutex.Lock() + defer d.mutex.Unlock() + + talisman, exists := d.WaygateTalismans[id] + if !exists { + return waygate.WaygateTalisman{}, errors.New("No such talisman") + } + + return talisman, nil +} + func (d *Database) persist() { saveJson(d, DBFolderPath+"boringproxy_db.json") } diff --git a/templates/authorize.tmpl b/templates/authorize.tmpl new file mode 100644 index 0000000..9768374 --- /dev/null +++ b/templates/authorize.tmpl @@ -0,0 +1,28 @@ +

+ A service is requesting to create a tunnel. If you want to approve this action, select a domain below. +

+ +

Select Domain

+ +
+ + + + + +
+ + . + +
+ +
+ + +
+
diff --git a/ui_handler.go b/ui_handler.go index 5230c9c..98f66ed 100644 --- a/ui_handler.go +++ b/ui_handler.go @@ -14,6 +14,8 @@ import ( "strings" "sync" "time" + + "github.com/takingnames/waygate-go" ) //go:embed logo.png templates @@ -104,6 +106,105 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request } switch r.URL.Path { + case "/waygate/authorized": + if r.Method != "POST" { + w.WriteHeader(405) + io.WriteString(w, "Invalid method") + return + } + + r.ParseForm() + + authReq, err := waygate.ExtractAuthRequest(r) + if err != nil { + w.WriteHeader(400) + io.WriteString(w, err.Error()) + return + } + + domain := r.Form.Get("domain") + host := r.Form.Get("host") + + fqdn := fmt.Sprintf("%s.%s", host, domain) + + fmt.Println(fqdn) + + waygateId, err := h.db.AddWaygateTunnel([]string{fqdn}) + if err != nil { + w.WriteHeader(500) + h.alertDialog(w, r, err.Error(), "/") + return + } + + talisman, err := h.db.AddWaygateTalisman(waygateId) + if err != nil { + w.WriteHeader(500) + h.alertDialog(w, r, err.Error(), "/") + return + } + + if authReq.RedirectUri == "urn:ietf:wg:oauth:2.0:oob" { + fmt.Fprintf(w, talisman) + } else { + w.WriteHeader(500) + h.alertDialog(w, r, "Unsupported auth", "/") + return + } + + case "/waygate/authorize": + if r.Method != "GET" { + w.WriteHeader(405) + h.alertDialog(w, r, err.Error(), "/") + return + } + + r.ParseForm() + + authReq, err := waygate.ExtractAuthRequest(r) + if err != nil { + w.WriteHeader(400) + h.alertDialog(w, r, err.Error(), "/") + return + } + + //user, exists := db.GetUser(tokenData.Owner) + //if !exists { + // sendLoginPage(w, r) + // return + //} + + //domains := db.GetDomainsForUser(tokenData.Owner) + + //displayClientId := authReq.ClientId + + //if strings.HasSuffix(authReq.ClientId, ".ip.takingnames.live") { + // domainParts := strings.Split(authReq.ClientId, ".ip.takingnames.live") + // ip := strings.Replace(domainParts[0], "-", ".", -1) + // displayClientId = ip + //} + + data := struct { + // LoggedIn bool + Domains []string + // FreeDomains []string + // User User + AuthRequest *waygate.AuthRequest + // DisplayClientId string + }{ + // LoggedIn: loggedIn, + Domains: []string{}, + // FreeDomains: FreeDomains(), + // User: user, + AuthRequest: authReq, + // DisplayClientId: displayClientId, + } + + err = h.tmpl.ExecuteTemplate(w, "authorize.tmpl", data) + if err != nil { + w.WriteHeader(500) + h.alertDialog(w, r, err.Error(), "/") + return + } case "/login": h.handleLogin(w, r) case "/users":