mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
Start implementing remote-controlled clients
This commit is contained in:
parent
27a487a032
commit
4cd19cb90f
16
api.go
16
api.go
@ -13,7 +13,6 @@ type Api struct {
|
||||
mux *http.ServeMux
|
||||
}
|
||||
|
||||
|
||||
func NewApi(config *BoringProxyConfig, auth *Auth, tunMan *TunnelManager) *Api {
|
||||
|
||||
api := &Api{config, auth, tunMan, nil}
|
||||
@ -34,7 +33,20 @@ func (a *Api) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
func (a *Api) handleTunnels(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
body, err := json.Marshal(a.tunMan.GetTunnels())
|
||||
query := r.URL.Query()
|
||||
|
||||
tunnels := a.tunMan.GetTunnels()
|
||||
|
||||
if len(query["client-name"]) == 1 {
|
||||
clientName := query["client-name"][0]
|
||||
for k, tun := range tunnels {
|
||||
if tun.ClientName != clientName {
|
||||
delete(tunnels, k)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
body, err := json.Marshal(tunnels)
|
||||
if err != nil {
|
||||
w.WriteHeader(500)
|
||||
w.Write([]byte("Error encoding tunnels"))
|
||||
|
@ -190,27 +190,35 @@ func (p *BoringProxy) handleCreateTunnel(w http.ResponseWriter, r *http.Request)
|
||||
|
||||
r.ParseForm()
|
||||
|
||||
if len(r.Form["host"]) != 1 {
|
||||
if len(r.Form["domain"]) != 1 {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid host parameter"))
|
||||
w.Write([]byte("Invalid domain parameter"))
|
||||
return
|
||||
}
|
||||
host := r.Form["host"][0]
|
||||
domain := r.Form["domain"][0]
|
||||
|
||||
if len(r.Form["port"]) != 1 {
|
||||
if len(r.Form["client-name"]) != 1 {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid port parameter"))
|
||||
w.Write([]byte("Invalid client-name parameter"))
|
||||
return
|
||||
}
|
||||
clientName := r.Form["client-name"][0]
|
||||
|
||||
if len(r.Form["client-port"]) != 1 {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid client-port parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
port, err := strconv.Atoi(r.Form["port"][0])
|
||||
clientPort, err := strconv.Atoi(r.Form["client-port"][0])
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
w.Write([]byte("Invalid port parameter"))
|
||||
w.Write([]byte("Invalid client-port parameter"))
|
||||
return
|
||||
}
|
||||
|
||||
err = p.tunMan.SetTunnel(host, port)
|
||||
fmt.Println(domain, clientName, clientPort)
|
||||
_, err = p.tunMan.CreateTunnelForClient(domain, clientName, clientPort)
|
||||
if err != nil {
|
||||
w.WriteHeader(400)
|
||||
io.WriteString(w, "Failed to get cert. Ensure your domain is valid")
|
||||
|
@ -90,12 +90,12 @@ func Listen() {
|
||||
}
|
||||
})
|
||||
|
||||
// taken from: https://stackoverflow.com/a/37537134/943814
|
||||
go func() {
|
||||
if err := http.ListenAndServe(":80", http.HandlerFunc(redirectTLS)); err != nil {
|
||||
log.Fatalf("ListenAndServe error: %v", err)
|
||||
}
|
||||
}()
|
||||
// taken from: https://stackoverflow.com/a/37537134/943814
|
||||
go func() {
|
||||
if err := http.ListenAndServe(":80", http.HandlerFunc(redirectTLS)); err != nil {
|
||||
log.Fatalf("ListenAndServe error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
log.Println("BoringProxy ready")
|
||||
|
||||
@ -154,7 +154,7 @@ func (p *BoringProxy) proxyRequest(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func redirectTLS(w http.ResponseWriter, r *http.Request) {
|
||||
url := fmt.Sprintf("https://%s:443%s", r.Host, r.RequestURI)
|
||||
log.Println("redir", url)
|
||||
http.Redirect(w, r, url, http.StatusMovedPermanently)
|
||||
url := fmt.Sprintf("https://%s:443%s", r.Host, r.RequestURI)
|
||||
log.Println("redir", url)
|
||||
http.Redirect(w, r, url, http.StatusMovedPermanently)
|
||||
}
|
||||
|
80
client.go
80
client.go
@ -11,6 +11,8 @@ import (
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@ -21,6 +23,84 @@ func NewBoringProxyClient() *BoringProxyClient {
|
||||
return &BoringProxyClient{}
|
||||
}
|
||||
|
||||
func (c *BoringProxyClient) RunPuppetClient() {
|
||||
flagSet := flag.NewFlagSet(os.Args[0], flag.ExitOnError)
|
||||
server := flagSet.String("server", "", "boringproxy server")
|
||||
token := flagSet.String("token", "", "Access token")
|
||||
name := flagSet.String("client-name", "", "Client name")
|
||||
flagSet.Parse(os.Args[2:])
|
||||
|
||||
httpClient := &http.Client{}
|
||||
|
||||
url := fmt.Sprintf("https://%s/api/tunnels?client-name=%s", *server, *name)
|
||||
|
||||
listenReq, err := http.NewRequest("GET", url, nil)
|
||||
if err != nil {
|
||||
log.Fatal("Failed making request", err)
|
||||
}
|
||||
|
||||
if len(*token) > 0 {
|
||||
listenReq.Header.Add("Authorization", "bearer "+*token)
|
||||
}
|
||||
|
||||
resp, err := httpClient.Do(listenReq)
|
||||
if err != nil {
|
||||
log.Fatal("Failed make tunnel request", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
||||
if resp.StatusCode != 200 {
|
||||
log.Fatal("Failed to create tunnel: " + string(body))
|
||||
}
|
||||
|
||||
tunnels := make(map[string]Tunnel)
|
||||
|
||||
err = json.Unmarshal(body, &tunnels)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse response", err)
|
||||
}
|
||||
|
||||
for _, tun := range tunnels {
|
||||
go c.BoreTunnel(tun)
|
||||
}
|
||||
|
||||
//go c.BoreTunnel(tunnels["apitman.com"])
|
||||
|
||||
sigChan := make(chan os.Signal, 1)
|
||||
signal.Notify(sigChan, os.Interrupt)
|
||||
for range sigChan {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BoringProxyClient) BoreTunnel(tun Tunnel) {
|
||||
privKeyFile, err := ioutil.TempFile("", "")
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
defer os.Remove(privKeyFile.Name())
|
||||
|
||||
if _, err := privKeyFile.Write([]byte(tun.TunnelPrivateKey)); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
if err := privKeyFile.Close(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
tunnelSpec := fmt.Sprintf("127.0.0.1:%d:127.0.0.1:%d", tun.TunnelPort, tun.ClientPort)
|
||||
sshLogin := fmt.Sprintf("%s@%s", tun.Username, tun.ServerAddress)
|
||||
serverPortStr := fmt.Sprintf("%d", tun.ServerPort)
|
||||
fmt.Println(tunnelSpec, sshLogin, serverPortStr)
|
||||
cmd := exec.Command("ssh", "-i", privKeyFile.Name(), "-NR", tunnelSpec, sshLogin, "-p", serverPortStr)
|
||||
err = cmd.Run()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *BoringProxyClient) Run() {
|
||||
log.Println("Run client")
|
||||
|
||||
|
@ -24,9 +24,10 @@ type Tunnel struct {
|
||||
Username string `json:"username"`
|
||||
TunnelPort int `json:"tunnel_port"`
|
||||
TunnelPrivateKey string `json:"tunnel_private_key"`
|
||||
ClientName string `json:"client_name"`
|
||||
ClientPort int `json:"client_port"`
|
||||
}
|
||||
|
||||
|
||||
func NewDatabase() (*Database, error) {
|
||||
|
||||
dbJson, err := ioutil.ReadFile("boringproxy_db.json")
|
||||
|
2
main.go
2
main.go
@ -22,7 +22,7 @@ func main() {
|
||||
|
||||
case "client":
|
||||
client := NewBoringProxyClient()
|
||||
client.Run()
|
||||
client.RunPuppetClient()
|
||||
default:
|
||||
fmt.Println("Invalid command " + command)
|
||||
os.Exit(1)
|
||||
|
3
todo.md
3
todo.md
@ -1,8 +1,7 @@
|
||||
* I don't think it's properly closing connections. Browser are hanging on
|
||||
some requests, possibly because it's HTTP/1.1 and hitting the max concurrent
|
||||
requests.
|
||||
* Might want to proxy requests at the HTTP level since it lets us do things
|
||||
like terminating HTTP/2.
|
||||
* Implement a custom SSH server in Go and connect the sockets directly?
|
||||
* Use HTML redirects for showing errors then refreshing. Maybe for polling
|
||||
after login and submitting a new tunnel too.
|
||||
* Save next port in db
|
||||
|
@ -32,4 +32,4 @@ ssh -i $keyFile \
|
||||
echo "Cleaning up"
|
||||
|
||||
rm $keyFile
|
||||
curl -s -H "Authorization: bearer $token" -X DELETE "$api/tunnels?domain=$domain"
|
||||
#curl -s -H "Authorization: bearer $token" -X DELETE "$api/tunnels?domain=$domain"
|
||||
|
@ -17,7 +17,7 @@ import (
|
||||
)
|
||||
|
||||
type TunnelManager struct {
|
||||
config *BoringProxyConfig
|
||||
config *BoringProxyConfig
|
||||
db *Database
|
||||
nextPort int
|
||||
mutex *sync.Mutex
|
||||
@ -50,18 +50,20 @@ func (m *TunnelManager) GetTunnels() map[string]Tunnel {
|
||||
return m.db.GetTunnels()
|
||||
}
|
||||
|
||||
// TODO: Update this
|
||||
func (m *TunnelManager) SetTunnel(host string, port int) error {
|
||||
err := m.certConfig.ManageSync([]string{host})
|
||||
func (m *TunnelManager) CreateTunnelForClient(domain string, clientName string, clientPort int) (Tunnel, error) {
|
||||
tun, err := m.CreateTunnel(domain)
|
||||
if err != nil {
|
||||
log.Println(err)
|
||||
return errors.New("Failed to get cert")
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
tunnel := Tunnel{TunnelPort: port}
|
||||
m.db.SetTunnel(host, tunnel)
|
||||
tun.ClientName = clientName
|
||||
tun.ClientPort = clientPort
|
||||
|
||||
return nil
|
||||
// TODO: It's a bit hacky that we call db.SetTunnel in CreateTunnel and
|
||||
// then again here
|
||||
m.db.SetTunnel(domain, tun)
|
||||
|
||||
return tun, nil
|
||||
}
|
||||
|
||||
func (m *TunnelManager) CreateTunnel(domain string) (Tunnel, error) {
|
||||
@ -87,7 +89,7 @@ func (m *TunnelManager) CreateTunnel(domain string) (Tunnel, error) {
|
||||
return Tunnel{}, err
|
||||
}
|
||||
|
||||
tunnel := Tunnel{
|
||||
tunnel := Tunnel{
|
||||
ServerAddress: m.config.AdminDomain,
|
||||
ServerPort: 22,
|
||||
ServerPublicKey: "",
|
||||
|
@ -48,7 +48,7 @@
|
||||
{{range $domain, $tunnel:= .}}
|
||||
<div class='tunnel'>
|
||||
<div>
|
||||
<a href="https://{{$domain}}">{{$domain}}</a> -> {{$tunnel.TunnelPort}}
|
||||
<a href="https://{{$domain}}">{{$domain}}</a>:{{$tunnel.TunnelPort}} -> {{$tunnel.ClientName}}:{{$tunnel.ClientPort}}
|
||||
</div>
|
||||
<a href="/delete-tunnel?host={{$domain}}">Delete</a>
|
||||
</div>
|
||||
@ -57,9 +57,11 @@
|
||||
<div class='tunnel-adder'>
|
||||
<form action="/tunnels" method="POST">
|
||||
<label for="domain">Domain:</label>
|
||||
<input type="text" id="domain" name="host">
|
||||
<label for="port">Port:</label>
|
||||
<input type="text" id="port" name="port">
|
||||
<input type="text" id="domain" name="domain">
|
||||
<label for="client-name">Client Name:</label>
|
||||
<input type="text" id="client-name" name="client-name">
|
||||
<label for="client-port">Client Port:</label>
|
||||
<input type="text" id="client-port" name="client-port">
|
||||
<button type="submit">Add/Update Tunnel</button>
|
||||
</form>
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user