commit
31f01c98ca
3 changed files with 49 additions and 34 deletions
18
caddyfile.go
18
caddyfile.go
|
|
@ -1,6 +1,7 @@
|
|||
package forwardproxy
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
|
@ -21,6 +22,14 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
|||
return &fp, err
|
||||
}
|
||||
|
||||
// EncodeAuthCredentials base64-encode credentials
|
||||
func EncodeAuthCredentials(user, pass string) (result []byte) {
|
||||
raw := []byte(user + ":" + pass)
|
||||
result = make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
|
||||
base64.StdEncoding.Encode(result, raw)
|
||||
return
|
||||
}
|
||||
|
||||
// UnmarshalCaddyfile unmarshals Caddyfile tokens into h.
|
||||
func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
||||
if !d.Next() {
|
||||
|
|
@ -45,13 +54,10 @@ func (h *Handler) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
if strings.Contains(args[0], ":") {
|
||||
return d.Err("character ':' in usernames is not allowed")
|
||||
}
|
||||
// TODO: Support multiple basicauths.
|
||||
// TODO: Actually, just try to use Caddy 2's existing basicauth module.
|
||||
if h.BasicauthUser != "" || h.BasicauthPass != "" {
|
||||
return d.Err("Multi-user basicauth is not supported")
|
||||
if h.AuthCredentials == nil {
|
||||
h.AuthCredentials = [][]byte{}
|
||||
}
|
||||
h.BasicauthUser = args[0]
|
||||
h.BasicauthPass = args[1]
|
||||
h.AuthCredentials = append(h.AuthCredentials, EncodeAuthCredentials(args[0], args[1]))
|
||||
case "hosts":
|
||||
if len(args) == 0 {
|
||||
return d.ArgErr()
|
||||
|
|
|
|||
|
|
@ -201,10 +201,9 @@ func TestMain(m *testing.M) {
|
|||
root: "./test/forwardproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
BasicauthUser: "test",
|
||||
BasicauthPass: "pass",
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
AuthCredentials: [][]byte{EncodeAuthCredentials("test", "pass")},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -212,10 +211,9 @@ func TestMain(m *testing.M) {
|
|||
addr: "127.0.69.73:6973",
|
||||
root: "./test/forwardproxy",
|
||||
proxyHandler: &Handler{
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
BasicauthUser: "test",
|
||||
BasicauthPass: "pass",
|
||||
PACPath: defaultPACPath,
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
AuthCredentials: [][]byte{EncodeAuthCredentials("test", "pass")},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -227,8 +225,7 @@ func TestMain(m *testing.M) {
|
|||
PACPath: "/superhiddenfile.pac",
|
||||
ACL: []ACLRule{{Subjects: []string{"all"}, Allow: true}},
|
||||
ProbeResistance: &ProbeResistance{Domain: "test.localhost"},
|
||||
BasicauthUser: "test",
|
||||
BasicauthPass: "pass",
|
||||
AuthCredentials: [][]byte{EncodeAuthCredentials("test", "pass")},
|
||||
},
|
||||
httpRedirPort: "8880",
|
||||
}
|
||||
|
|
@ -255,9 +252,8 @@ func TestMain(m *testing.M) {
|
|||
root: "./test/upstreamingproxy",
|
||||
tls: true,
|
||||
proxyHandler: &Handler{
|
||||
Upstream: "https://test:pass@127.0.0.1:4891",
|
||||
BasicauthUser: "upstreamtest",
|
||||
BasicauthPass: "upstreampass",
|
||||
Upstream: "https://test:pass@127.0.0.1:4891",
|
||||
AuthCredentials: [][]byte{EncodeAuthCredentials("upstreamtest", "upstreampass")},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
caddy "github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/caddyconfig/caddyfile"
|
||||
|
|
@ -91,10 +92,7 @@ type Handler struct {
|
|||
aclRules []aclRule
|
||||
|
||||
// TODO: temporary/deprecated - we should try to reuse existing authentication modules instead!
|
||||
BasicauthUser string `json:"auth_user_deprecated,omitempty"`
|
||||
BasicauthPass string `json:"auth_pass_deprecated,omitempty"`
|
||||
authRequired bool
|
||||
authCredentials [][]byte // slice with base64-encoded credentials
|
||||
AuthCredentials [][]byte `json:"auth_credentials,omitempty"` // slice with base64-encoded credentials
|
||||
}
|
||||
|
||||
// CaddyModule returns the Caddy module information.
|
||||
|
|
@ -120,14 +118,6 @@ func (h *Handler) Provision(ctx caddy.Context) error {
|
|||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
// TODO: temporary, in an effort to get the tests to pass
|
||||
if h.BasicauthUser != "" && h.BasicauthPass != "" {
|
||||
basicAuthBuf := make([]byte, base64.StdEncoding.EncodedLen(len(h.BasicauthUser)+1+len(h.BasicauthPass)))
|
||||
base64.StdEncoding.Encode(basicAuthBuf, []byte(h.BasicauthUser+":"+h.BasicauthPass))
|
||||
h.authRequired = true
|
||||
h.authCredentials = [][]byte{basicAuthBuf}
|
||||
}
|
||||
|
||||
// access control lists
|
||||
for _, rule := range h.ACL {
|
||||
for _, subj := range rule.Subjects {
|
||||
|
|
@ -155,7 +145,7 @@ func (h *Handler) Provision(ctx caddy.Context) error {
|
|||
h.aclRules = append(h.aclRules, &aclAllRule{allow: true})
|
||||
|
||||
if h.ProbeResistance != nil {
|
||||
if !h.authRequired {
|
||||
if h.AuthCredentials == nil {
|
||||
return fmt.Errorf("probe resistance requires authentication")
|
||||
}
|
||||
if len(h.ProbeResistance.Domain) > 0 {
|
||||
|
|
@ -237,7 +227,7 @@ func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request, next caddyht
|
|||
}
|
||||
|
||||
var authErr error
|
||||
if h.authRequired {
|
||||
if h.AuthCredentials != nil {
|
||||
authErr = h.checkCredentials(r)
|
||||
}
|
||||
if h.ProbeResistance != nil && len(h.ProbeResistance.Domain) > 0 && reqHost == h.ProbeResistance.Domain {
|
||||
|
|
@ -421,14 +411,37 @@ func (h Handler) checkCredentials(r *http.Request) error {
|
|||
if strings.ToLower(pa[0]) != "basic" {
|
||||
return errors.New("auth type is not supported")
|
||||
}
|
||||
for _, creds := range h.authCredentials {
|
||||
for _, creds := range h.AuthCredentials {
|
||||
if subtle.ConstantTimeCompare(creds, []byte(pa[1])) == 1 {
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
buf := make([]byte, base64.StdEncoding.DecodedLen(len(creds)))
|
||||
_, _ = base64.StdEncoding.Decode(buf, creds) // should not err ever since we are decoding a known good input
|
||||
cred := string(buf)
|
||||
repl.Set("http.auth.user.id", cred[:strings.IndexByte(cred, ':')])
|
||||
// Please do not consider this to be timing-attack-safe code. Simple equality is almost
|
||||
// mindlessly substituted with constant time algo and there ARE known issues with this code,
|
||||
// e.g. size of smallest credentials is guessable. TODO: protect from all the attacks! Hash?
|
||||
return nil
|
||||
}
|
||||
}
|
||||
repl := r.Context().Value(caddy.ReplacerCtxKey).(*caddy.Replacer)
|
||||
buf := make([]byte, base64.StdEncoding.DecodedLen(len([]byte(pa[1]))))
|
||||
n, err := base64.StdEncoding.Decode(buf, []byte(pa[1]))
|
||||
if err != nil {
|
||||
repl.Set("http.auth.user.id", "invalidbase64:"+err.Error())
|
||||
return err
|
||||
}
|
||||
if utf8.Valid(buf[:n]) {
|
||||
cred := string(buf[:n])
|
||||
i := strings.IndexByte(cred, ':')
|
||||
if i >= 0 {
|
||||
repl.Set("http.auth.user.id", "invalid:"+cred[:i])
|
||||
} else {
|
||||
repl.Set("http.auth.user.id", "invalidformat:"+cred)
|
||||
}
|
||||
} else {
|
||||
repl.Set("http.auth.user.id", "invalid::")
|
||||
}
|
||||
return errors.New("invalid credentials")
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue