mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
First draft of Waygate implementation
This commit is contained in:
@@ -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)
|
||||
|
||||
103
database.go
103
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")
|
||||
}
|
||||
|
||||
28
templates/authorize.tmpl
Normal file
28
templates/authorize.tmpl
Normal file
@@ -0,0 +1,28 @@
|
||||
<p>
|
||||
A service is requesting to create a tunnel. If you want to approve this action, select a domain below.
|
||||
</p>
|
||||
|
||||
<h1>Select Domain</h1>
|
||||
|
||||
<form action="/waygate/authorized" method="POST">
|
||||
<input type="hidden" name="client_id" value="{{.AuthRequest.ClientId}}" required>
|
||||
<input type="hidden" name="redirect_uri" value="{{.AuthRequest.RedirectUri}}" required>
|
||||
<input type="hidden" name="scope" value="{{.AuthRequest.Scope}}" required>
|
||||
<input type="hidden" name="state" value="{{.AuthRequest.State}}" required>
|
||||
|
||||
<div>
|
||||
<input type="text" name="host" placeholder="Subdomain optional">
|
||||
<span>.</span>
|
||||
<select id="domain-input" name="domain">
|
||||
{{ range .Domains}}
|
||||
<option>{{.DomainName}}</option>
|
||||
{{ end }}
|
||||
<option>takingnames.co</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class='tn-button-row'>
|
||||
<button class='button'>Approve</button>
|
||||
<button class='button' formaction="/deny">Deny</button>
|
||||
</div>
|
||||
</form>
|
||||
101
ui_handler.go
101
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":
|
||||
|
||||
Reference in New Issue
Block a user