mirror of
https://github.com/boringproxy/boringproxy.git
synced 2025-02-25 18:55:29 -06:00
Switch from TLS to HTTP proxying
Allows a few things: * Can terminate HTTP/2 without the upstream needing to implement it * Allows modification of headers in both directions * Allows logging at the proxy
This commit is contained in:
@@ -1,32 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
)
|
||||
|
||||
type AdminListener struct {
|
||||
connChan chan (net.Conn)
|
||||
}
|
||||
|
||||
func NewAdminListener() *AdminListener {
|
||||
connChan := make(chan (net.Conn))
|
||||
return &AdminListener{connChan}
|
||||
}
|
||||
|
||||
// implement net.Listener
|
||||
func (l *AdminListener) Accept() (net.Conn, error) {
|
||||
// TODO: error conditions?
|
||||
conn := <-l.connChan
|
||||
return conn, nil
|
||||
}
|
||||
func (l *AdminListener) Close() error {
|
||||
// TODO
|
||||
fmt.Println("AdminListener Close")
|
||||
return nil
|
||||
}
|
||||
func (l *AdminListener) Addr() net.Addr {
|
||||
// TODO
|
||||
fmt.Println("AdminListener Addr")
|
||||
return nil
|
||||
}
|
||||
133
boringproxy.go
133
boringproxy.go
@@ -8,9 +8,7 @@ import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type BoringProxyConfig struct {
|
||||
@@ -29,11 +27,9 @@ type BoringProxy struct {
|
||||
config *BoringProxyConfig
|
||||
auth *Auth
|
||||
tunMan *TunnelManager
|
||||
adminListener *AdminListener
|
||||
certConfig *certmagic.Config
|
||||
}
|
||||
|
||||
func NewBoringProxy() *BoringProxy {
|
||||
func Listen() {
|
||||
|
||||
config := &BoringProxyConfig{}
|
||||
|
||||
@@ -54,7 +50,6 @@ func NewBoringProxy() *BoringProxy {
|
||||
certConfig := certmagic.NewDefault()
|
||||
|
||||
tunMan := NewTunnelManager(certConfig)
|
||||
adminListener := NewAdminListener()
|
||||
|
||||
err = certConfig.ManageSync([]string{config.AdminDomain})
|
||||
if err != nil {
|
||||
@@ -64,106 +59,78 @@ func NewBoringProxy() *BoringProxy {
|
||||
|
||||
auth := NewAuth()
|
||||
|
||||
p := &BoringProxy{config, auth, tunMan, adminListener, certConfig}
|
||||
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
p.handleAdminRequest(w, r)
|
||||
})
|
||||
p := &BoringProxy{config, auth, tunMan}
|
||||
|
||||
api := NewApi(config, auth, tunMan)
|
||||
http.Handle("/api/", http.StripPrefix("/api", api))
|
||||
|
||||
go http.Serve(adminListener, nil)
|
||||
|
||||
log.Println("BoringProxy ready")
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
func (p *BoringProxy) Run() {
|
||||
|
||||
listener, err := net.Listen("tcp", ":443")
|
||||
tlsConfig := &tls.Config{
|
||||
GetCertificate: certConfig.GetCertificate,
|
||||
NextProtos: []string{"h2"},
|
||||
}
|
||||
tlsListener, err := tls.Listen("tcp", ":443", tlsConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
for {
|
||||
conn, err := listener.Accept()
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
continue
|
||||
}
|
||||
go p.handleConnection(conn)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *BoringProxy) handleConnection(clientConn net.Conn) {
|
||||
// TODO: does this need to be closed manually, or is it handled when decryptedConn is closed?
|
||||
//defer clientConn.Close()
|
||||
|
||||
log.Println("handleConnection")
|
||||
|
||||
var serverName string
|
||||
|
||||
decryptedConn := tls.Server(clientConn, &tls.Config{
|
||||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
|
||||
serverName = clientHello.ServerName
|
||||
|
||||
return p.certConfig.GetCertificate(clientHello)
|
||||
},
|
||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Host == config.AdminDomain {
|
||||
p.handleAdminRequest(w, r)
|
||||
} else {
|
||||
p.proxyRequest(w, r)
|
||||
}
|
||||
})
|
||||
//defer decryptedConn.Close()
|
||||
|
||||
// Need to manually do handshake to ensure serverName is populated by this point. Usually Handshake()
|
||||
// is automatically called on first read/write
|
||||
decryptedConn.Handshake()
|
||||
log.Println("BoringProxy ready")
|
||||
|
||||
if serverName == p.config.AdminDomain {
|
||||
p.handleAdminConnection(decryptedConn)
|
||||
} else {
|
||||
p.handleTunnelConnection(decryptedConn, serverName)
|
||||
}
|
||||
http.Serve(tlsListener, nil)
|
||||
}
|
||||
|
||||
func (p *BoringProxy) handleAdminConnection(decryptedConn net.Conn) {
|
||||
p.adminListener.connChan <- decryptedConn
|
||||
}
|
||||
func (p *BoringProxy) proxyRequest(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
func (p *BoringProxy) handleTunnelConnection(decryptedConn net.Conn, serverName string) {
|
||||
|
||||
defer decryptedConn.Close()
|
||||
|
||||
port, err := p.tunMan.GetPort(serverName)
|
||||
port, err := p.tunMan.GetPort(r.Host)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
errMessage := fmt.Sprintf("HTTP/1.1 500 Internal server error\n\nNo tunnel attached to %s", serverName)
|
||||
decryptedConn.Write([]byte(errMessage))
|
||||
errMessage := fmt.Sprintf("No tunnel attached to %s", r.Host)
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, errMessage)
|
||||
return
|
||||
}
|
||||
|
||||
upstreamAddr := fmt.Sprintf("127.0.0.1:%d", port)
|
||||
httpClient := &http.Client{}
|
||||
|
||||
upstreamConn, err := net.Dial("tcp", upstreamAddr)
|
||||
if err != nil {
|
||||
downstreamReqHeaders := r.Header.Clone()
|
||||
|
||||
upstreamAddr := fmt.Sprintf("localhost:%d", port)
|
||||
upstreamUrl := fmt.Sprintf("http://%s%s", upstreamAddr, r.URL.RequestURI())
|
||||
|
||||
upstreamReq, err := http.NewRequest(r.Method, upstreamUrl, r.Body)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
errMessage := fmt.Sprintf("%s", err)
|
||||
w.WriteHeader(500)
|
||||
io.WriteString(w, errMessage)
|
||||
return
|
||||
}
|
||||
defer upstreamConn.Close()
|
||||
}
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(2)
|
||||
upstreamReq.Header = downstreamReqHeaders
|
||||
|
||||
go func() {
|
||||
io.Copy(decryptedConn, upstreamConn)
|
||||
//decryptedConn.(*net.TCPConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
go func() {
|
||||
io.Copy(upstreamConn, decryptedConn)
|
||||
//upstreamConn.(*net.TCPConn).CloseWrite()
|
||||
wg.Done()
|
||||
}()
|
||||
upstreamRes, err := httpClient.Do(upstreamReq)
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
errMessage := fmt.Sprintf("%s", err)
|
||||
w.WriteHeader(502)
|
||||
io.WriteString(w, errMessage)
|
||||
return
|
||||
}
|
||||
defer upstreamRes.Body.Close()
|
||||
|
||||
wg.Wait()
|
||||
downstreamResHeaders := w.Header()
|
||||
|
||||
for k, v := range upstreamRes.Header {
|
||||
downstreamResHeaders[k] = v
|
||||
}
|
||||
|
||||
w.WriteHeader(upstreamRes.StatusCode)
|
||||
io.Copy(w, upstreamRes.Body)
|
||||
}
|
||||
|
||||
3
main.go
3
main.go
@@ -18,8 +18,7 @@ func main() {
|
||||
switch command {
|
||||
case "server":
|
||||
log.Println("Starting up")
|
||||
proxy := NewBoringProxy()
|
||||
proxy.Run()
|
||||
Listen()
|
||||
|
||||
case "client":
|
||||
client := NewBoringProxyClient()
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"golang.org/x/crypto/ssh"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
)
|
||||
|
||||
type SshServer struct {
|
||||
config *ssh.ServerConfig
|
||||
listener net.Listener
|
||||
}
|
||||
|
||||
func NewSshServer() *SshServer {
|
||||
config := &ssh.ServerConfig{}
|
||||
|
||||
privateBytes, err := ioutil.ReadFile("id_rsa_boringproxy")
|
||||
if err != nil {
|
||||
log.Fatal("Failed to load private key: ", err)
|
||||
}
|
||||
|
||||
private, err := ssh.ParsePrivateKey(privateBytes)
|
||||
if err != nil {
|
||||
log.Fatal("Failed to parse private key: ", err)
|
||||
}
|
||||
|
||||
config.AddHostKey(private)
|
||||
|
||||
listener, err := net.Listen("tcp", "0.0.0.0:2022")
|
||||
if err != nil {
|
||||
log.Fatal("failed to listen for connection: ", err)
|
||||
}
|
||||
|
||||
server := &SshServer{config, listener}
|
||||
|
||||
go server.acceptAll()
|
||||
|
||||
return server
|
||||
}
|
||||
|
||||
func (s *SshServer) acceptAll() {
|
||||
for {
|
||||
nConn, err := s.listener.Accept()
|
||||
if err != nil {
|
||||
log.Print("failed to accept incoming connection: ", err)
|
||||
continue
|
||||
}
|
||||
|
||||
go s.handleServerConn(nConn)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *SshServer) handleServerConn(nConn net.Conn) {
|
||||
|
||||
var password string
|
||||
|
||||
s.config.PasswordCallback = func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
|
||||
password = string(pass)
|
||||
if c.User() == "user" && string(pass) == "yolo" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, fmt.Errorf("password rejected for %q", c.User())
|
||||
}
|
||||
|
||||
conn, chans, reqs, err := ssh.NewServerConn(nConn, s.config)
|
||||
if err != nil {
|
||||
log.Print("failed to handshake: ", err)
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println(password)
|
||||
|
||||
go ssh.DiscardRequests(reqs)
|
||||
|
||||
go func() {
|
||||
for newChannel := range chans {
|
||||
newChannel.Reject(ssh.ResourceShortage, "too bad")
|
||||
}
|
||||
}()
|
||||
|
||||
ch, cReqs, err := conn.OpenChannel("boringproxy-tunnel", []byte{25, 25})
|
||||
if err != nil {
|
||||
log.Print(err)
|
||||
return
|
||||
}
|
||||
|
||||
go ssh.DiscardRequests(cReqs)
|
||||
|
||||
ch.Write([]byte("Hi there"))
|
||||
ch.Close()
|
||||
}
|
||||
Reference in New Issue
Block a user