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"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -37,10 +39,11 @@ type SmtpConfig struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Server struct {
|
type Server struct {
|
||||||
db *Database
|
db *Database
|
||||||
tunMan *TunnelManager
|
tunMan *TunnelManager
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
httpListener *PassthroughListener
|
httpListener *PassthroughListener
|
||||||
|
waygateServer *waygate.Server
|
||||||
}
|
}
|
||||||
|
|
||||||
func Listen() {
|
func Listen() {
|
||||||
@@ -93,7 +96,17 @@ func Listen() {
|
|||||||
fmt.Printf("WARNING: Failed to access %s:%d from the internet\n", ip, *httpsPort)
|
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 := 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
|
autoCerts := true
|
||||||
if *httpPort != 80 || *httpsPort != 443 {
|
if *httpPort != 80 || *httpsPort != 443 {
|
||||||
@@ -148,7 +161,8 @@ func Listen() {
|
|||||||
users := db.GetUsers()
|
users := db.GetUsers()
|
||||||
if len(users) == 0 {
|
if len(users) == 0 {
|
||||||
db.AddUser("admin", true)
|
db.AddUser("admin", true)
|
||||||
_, err := db.AddToken("admin", "")
|
clientId := ""
|
||||||
|
_, err := db.AddToken("admin", clientId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("Failed to initialize admin user")
|
log.Fatal("Failed to initialize admin user")
|
||||||
}
|
}
|
||||||
@@ -190,7 +204,7 @@ func Listen() {
|
|||||||
|
|
||||||
httpListener := NewPassthroughListener()
|
httpListener := NewPassthroughListener()
|
||||||
|
|
||||||
p := &Server{db, tunMan, httpClient, httpListener}
|
p := &Server{db, tunMan, httpClient, httpListener, waygateServer}
|
||||||
|
|
||||||
tlsConfig := &tls.Config{
|
tlsConfig := &tls.Config{
|
||||||
GetCertificate: certConfig.GetCertificate,
|
GetCertificate: certConfig.GetCertificate,
|
||||||
@@ -199,6 +213,13 @@ func Listen() {
|
|||||||
tlsListener := tls.NewListener(httpListener, tlsConfig)
|
tlsListener := tls.NewListener(httpListener, tlsConfig)
|
||||||
|
|
||||||
http.Handle("/waygate/", http.StripPrefix("/waygate", waygateServer))
|
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) {
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
timestamp := time.Now().Format(time.RFC3339)
|
timestamp := time.Now().Format(time.RFC3339)
|
||||||
@@ -357,6 +378,18 @@ func (p *Server) handleConnection(clientConn net.Conn, certConfig *certmagic.Con
|
|||||||
return
|
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)
|
passConn := NewProxyConn(clientConn, clientReader)
|
||||||
|
|
||||||
tunnel, exists := p.db.GetTunnel(clientHello.ServerName)
|
tunnel, exists := p.db.GetTunnel(clientHello.ServerName)
|
||||||
|
|||||||
103
database.go
103
database.go
@@ -8,17 +8,20 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/takingnames/namedrop-go"
|
"github.com/takingnames/namedrop-go"
|
||||||
|
"github.com/takingnames/waygate-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
var DBFolderPath string
|
var DBFolderPath string
|
||||||
|
|
||||||
type Database struct {
|
type Database struct {
|
||||||
AdminDomain string `json:"admin_domain"`
|
AdminDomain string `json:"admin_domain"`
|
||||||
Tokens map[string]TokenData `json:"tokens"`
|
Tokens map[string]TokenData `json:"tokens"`
|
||||||
Tunnels map[string]Tunnel `json:"tunnels"`
|
Tunnels map[string]Tunnel `json:"tunnels"`
|
||||||
Users map[string]User `json:"users"`
|
Users map[string]User `json:"users"`
|
||||||
dnsRequests map[string]namedrop.DNSRequest `json:"dns_requests"`
|
dnsRequests map[string]namedrop.DNSRequest `json:"dns_requests"`
|
||||||
mutex *sync.Mutex
|
WaygateTunnels map[string]waygate.WaygateTunnel `json:"waygate_tunnels"`
|
||||||
|
WaygateTalismans map[string]waygate.WaygateTalisman `json:"waygate_talismans"`
|
||||||
|
mutex *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
type TokenData struct {
|
type TokenData struct {
|
||||||
@@ -96,6 +99,13 @@ func NewDatabase(path string) (*Database, error) {
|
|||||||
db.dnsRequests = make(map[string]namedrop.DNSRequest)
|
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 = &sync.Mutex{}
|
||||||
|
|
||||||
db.mutex.Lock()
|
db.mutex.Lock()
|
||||||
@@ -322,6 +332,87 @@ func (d *Database) DeleteUser(username string) {
|
|||||||
d.persist()
|
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() {
|
func (d *Database) persist() {
|
||||||
saveJson(d, DBFolderPath+"boringproxy_db.json")
|
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"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/takingnames/waygate-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed logo.png templates
|
//go:embed logo.png templates
|
||||||
@@ -104,6 +106,105 @@ func (h *WebUiHandler) handleWebUiRequest(w http.ResponseWriter, r *http.Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
switch r.URL.Path {
|
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":
|
case "/login":
|
||||||
h.handleLogin(w, r)
|
h.handleLogin(w, r)
|
||||||
case "/users":
|
case "/users":
|
||||||
|
|||||||
Reference in New Issue
Block a user