Add optional HTTP BASIC authorization

Fixes #18.
This commit is contained in:
Andrew Gaul 2017-07-05 18:20:26 -07:00
parent 8ac256351f
commit 69b89413bd
4 changed files with 82 additions and 0 deletions

View file

@ -76,6 +76,12 @@ Probably the best option is to run it with both TLS and MitM support, combining
compy -cert cert.crt -key cert.key -ca ca.crt -cakey ca.key compy -cert cert.crt -key cert.key -ca ca.crt -cakey ca.key
``` ```
You can limit access to your proxy via HTTP BASIC authentication:
```
compy -cert cert.crt -key cert.key -user myuser -pass mypass
```
You can also specify the listen port (defaults to 9999): You can also specify the listen port (defaults to 9999):
``` ```
compy -host :9999 compy -host :9999

View file

@ -17,6 +17,8 @@ var (
key = flag.String("key", "", "proxy cert key path") key = flag.String("key", "", "proxy cert key path")
ca = flag.String("ca", "", "CA path") ca = flag.String("ca", "", "CA path")
caKey = flag.String("cakey", "", "CA key path") caKey = flag.String("cakey", "", "CA key path")
user = flag.String("user", "", "proxy user name")
pass = flag.String("pass", "", "proxy password")
brotli = flag.Int("brotli", -1, "Brotli compression level (0-11, default 6)") brotli = flag.Int("brotli", -1, "Brotli compression level (0-11, default 6)")
jpeg = flag.Int("jpeg", 50, "jpeg quality (1-100, 0 to disable)") jpeg = flag.Int("jpeg", 50, "jpeg quality (1-100, 0 to disable)")
@ -45,6 +47,13 @@ func main() {
} }
} }
// TODO: require cert and key?
if (*user == "") != (*pass == "") {
log.Fatalln("must specify both user and pass")
} else {
p.SetAuthentication(*user, *pass)
}
if *jpeg != 0 { if *jpeg != 0 {
p.AddTranscoder("image/jpeg", tc.NewJpeg(*jpeg)) p.AddTranscoder("image/jpeg", tc.NewJpeg(*jpeg))
} }

View file

@ -5,6 +5,7 @@ import (
"bytes" "bytes"
gzipp "compress/gzip" gzipp "compress/gzip"
"encoding/base64"
gifp "image/gif" gifp "image/gif"
jpegp "image/jpeg" jpegp "image/jpeg"
pngp "image/png" pngp "image/png"
@ -183,3 +184,34 @@ func (s *CompyTest) TestPngToWebP(c *C) {
_, err = webp.Decode(resp.Body) _, err = webp.Decode(resp.Body)
c.Assert(err, IsNil) c.Assert(err, IsNil)
} }
func (s *CompyTest) TestAuthentication(c *C) {
s.proxy.SetAuthentication("user", "pass")
defer s.proxy.SetAuthentication("", "")
// no password
resp, err := s.client.Get(s.server.URL + "/status/200")
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 407)
// incorrect password
req, err := http.NewRequest("GET", s.server.URL+"/status/200", nil)
c.Assert(err, IsNil)
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("wrong:bad")))
resp, err = s.client.Do(req)
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 407)
// correct password
req, err = http.NewRequest("GET", s.server.URL+"/status/200", nil)
c.Assert(err, IsNil)
req.Header.Add("Proxy-Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte("user:pass")))
resp, err = s.client.Do(req)
c.Assert(err, IsNil)
defer resp.Body.Close()
c.Assert(resp.StatusCode, Equals, 200)
}

View file

@ -1,10 +1,12 @@
package proxy package proxy
import ( import (
"encoding/base64"
"fmt" "fmt"
"log" "log"
"net" "net"
"net/http" "net/http"
"strings"
"sync/atomic" "sync/atomic"
) )
@ -13,6 +15,8 @@ type Proxy struct {
ml *mitmListener ml *mitmListener
ReadCount uint64 ReadCount uint64
WriteCount uint64 WriteCount uint64
user string
pass string
} }
type Transcoder interface { type Transcoder interface {
@ -37,6 +41,11 @@ func (p *Proxy) EnableMitm(ca, key string) error {
return nil return nil
} }
func (p *Proxy) SetAuthentication(user, pass string) {
p.user = user
p.pass = pass
}
func (p *Proxy) AddTranscoder(contentType string, transcoder Transcoder) { func (p *Proxy) AddTranscoder(contentType string, transcoder Transcoder) {
p.transcoders[contentType] = transcoder p.transcoders[contentType] = transcoder
} }
@ -56,10 +65,36 @@ func (p *Proxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
} }
} }
func (p *Proxy) checkHttpBasicAuth(auth string) bool {
prefix := "Basic "
if !strings.HasPrefix(auth, prefix) {
return false
}
decoded, err := base64.StdEncoding.DecodeString(auth[len(prefix):])
if err != nil {
return false
}
values := strings.SplitN(string(decoded), ":", 2)
if len(values) != 2 || values[0] != p.user || values[1] != p.pass {
return false
}
return true
}
func (p *Proxy) handle(w http.ResponseWriter, r *http.Request) error { func (p *Proxy) handle(w http.ResponseWriter, r *http.Request) error {
if r.Method == "CONNECT" { if r.Method == "CONNECT" {
return p.handleConnect(w, r) return p.handleConnect(w, r)
} }
// TODO: only HTTPS?
if p.user != "" {
if !p.checkHttpBasicAuth(r.Header.Get("Proxy-Authorization")) {
w.Header().Set("WWW-Authenticate", "Basic realm=\"Compy\"")
w.WriteHeader(http.StatusProxyAuthRequired)
return nil
}
}
resp, err := forward(r) resp, err := forward(r)
if err != nil { if err != nil {
w.WriteHeader(http.StatusInternalServerError) w.WriteHeader(http.StatusInternalServerError)