From c28cbcfcaac66e23dbeed62da94dea2f85ba3324 Mon Sep 17 00:00:00 2001 From: Toby Date: Mon, 24 Jan 2022 17:26:33 -0800 Subject: [PATCH] feat: multi-password & cmd auth --- cmd/auth/cmd.go | 30 +++++++++++++ {pkg => cmd}/auth/http.go | 26 ++++++++--- cmd/main.go | 2 +- cmd/server.go | 90 +++++++++++++++++++++++++++------------ 4 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 cmd/auth/cmd.go rename {pkg => cmd}/auth/http.go (57%) diff --git a/cmd/auth/cmd.go b/cmd/auth/cmd.go new file mode 100644 index 0000000..2f4276d --- /dev/null +++ b/cmd/auth/cmd.go @@ -0,0 +1,30 @@ +package auth + +import ( + "github.com/sirupsen/logrus" + "net" + "os/exec" + "strconv" + "strings" +) + +type CmdAuthProvider struct { + Cmd string +} + +func (p *CmdAuthProvider) Auth(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { + cmd := exec.Command(p.Cmd, addr.String(), string(auth), strconv.Itoa(int(sSend)), strconv.Itoa(int(sRecv))) + out, err := cmd.Output() + if err != nil { + if _, ok := err.(*exec.ExitError); ok { + return false, strings.TrimSpace(string(out)) + } else { + logrus.WithFields(logrus.Fields{ + "error": err, + }).Error("Failed to execute auth command") + return false, "internal error" + } + } else { + return true, strings.TrimSpace(string(out)) + } +} diff --git a/pkg/auth/http.go b/cmd/auth/http.go similarity index 57% rename from pkg/auth/http.go rename to cmd/auth/http.go index be55836..da4970f 100644 --- a/pkg/auth/http.go +++ b/cmd/auth/http.go @@ -3,6 +3,7 @@ package auth import ( "bytes" "encoding/json" + "github.com/sirupsen/logrus" "io/ioutil" "net" "net/http" @@ -33,24 +34,39 @@ func (p *HTTPAuthProvider) Auth(addr net.Addr, auth []byte, sSend uint64, sRecv Recv: sRecv, }) if err != nil { - return false, "Internal error" + logrus.WithFields(logrus.Fields{ + "error": err, + }).Error("Failed to marshal auth request") + return false, "internal error" } resp, err := p.Client.Post(p.URL, "application/json", bytes.NewBuffer(jbs)) if err != nil { - return false, "Internal error" + logrus.WithFields(logrus.Fields{ + "error": err, + }).Error("Failed to send auth request") + return false, "internal error" } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { - return false, "Auth endpoint error" + logrus.WithFields(logrus.Fields{ + "code": resp.StatusCode, + }).Error("Invalid status code from auth server") + return false, "internal error" } data, err := ioutil.ReadAll(resp.Body) if err != nil { - return false, "Auth endpoint error" + logrus.WithFields(logrus.Fields{ + "error": err, + }).Error("Failed to read auth response") + return false, "internal error" } var ar authResp err = json.Unmarshal(data, &ar) if err != nil { - return false, "Auth endpoint error" + logrus.WithFields(logrus.Fields{ + "error": err, + }).Error("Failed to unmarshal auth response") + return false, "internal error" } return ar.OK, ar.Msg } diff --git a/cmd/main.go b/cmd/main.go index 0d5a052..5439f8c 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -131,7 +131,7 @@ func initApp(c *cli.Context) error { "version", "url", "config", "file", "mode", "addr", "src", "dst", "session", "action", - "msg", "error", + "code", "msg", "error", }, TimestampFormat: c.String("log-timestamp"), }) diff --git a/cmd/server.go b/cmd/server.go index d38cb87..5d6eec6 100644 --- a/cmd/server.go +++ b/cmd/server.go @@ -2,14 +2,15 @@ package main import ( "crypto/tls" + "errors" "github.com/lucas-clemente/quic-go" "github.com/lucas-clemente/quic-go/congestion" "github.com/oschwald/geoip2-golang" "github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus/promhttp" "github.com/sirupsen/logrus" + "github.com/tobyxdd/hysteria/cmd/auth" "github.com/tobyxdd/hysteria/pkg/acl" - "github.com/tobyxdd/hysteria/pkg/auth" hyCongestion "github.com/tobyxdd/hysteria/pkg/congestion" "github.com/tobyxdd/hysteria/pkg/core" "github.com/tobyxdd/hysteria/pkg/obfs" @@ -84,7 +85,7 @@ func server(config *serverConfig) { quicConfig.MaxIncomingStreams = DefaultMaxIncomingStreams } // Auth - var authFunc func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) + var authFunc core.ConnectFunc var err error switch authMode := config.Auth.Mode; authMode { case "", "none": @@ -95,39 +96,24 @@ func server(config *serverConfig) { authFunc = func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { return true, "Welcome" } - case "password": - logrus.Info("Password authentication enabled") - var pwdConfig map[string]string - err = json5.Unmarshal(config.Auth.Config, &pwdConfig) - if err != nil || len(pwdConfig["password"]) == 0 { + case "password", "passwords": + authFunc, err = passwordAuthFunc(config.Auth.Config) + if err != nil { logrus.WithFields(logrus.Fields{ "error": err, - }).Fatal("Invalid password authentication config") - } - pwd := pwdConfig["password"] - authFunc = func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { - if string(auth) == pwd { - return true, "Welcome" - } else { - return false, "Wrong password" - } + }).Fatal("Failed to enable password authentication") + } else { + logrus.Info("Password authentication enabled") } case "external": - logrus.Info("External authentication enabled") - var extConfig map[string]string - err = json5.Unmarshal(config.Auth.Config, &extConfig) - if err != nil || len(extConfig["http"]) == 0 { + authFunc, err = externalAuthFunc(config.Auth.Config) + if err != nil { logrus.WithFields(logrus.Fields{ "error": err, - }).Fatal("Invalid external authentication config") + }).Fatal("Failed to enable external authentication") + } else { + logrus.Info("External authentication enabled") } - provider := &auth.HTTPAuthProvider{ - Client: &http.Client{ - Timeout: 10 * time.Second, - }, - URL: extConfig["http"], - } - authFunc = provider.Auth default: logrus.WithField("mode", config.Auth.Mode).Fatal("Unsupported authentication mode") } @@ -199,6 +185,54 @@ func server(config *serverConfig) { logrus.WithField("error", err).Fatal("Server shutdown") } +func passwordAuthFunc(rawMsg json5.RawMessage) (core.ConnectFunc, error) { + var pwds []string + err := json5.Unmarshal(rawMsg, &pwds) + if err != nil { + // not a string list, legacy format? + var pwdConfig map[string]string + err = json5.Unmarshal(rawMsg, &pwdConfig) + if err != nil || len(pwdConfig["password"]) == 0 { + // still no, invalid config + return nil, errors.New("invalid config") + } + // yes it is + pwds = []string{pwdConfig["password"]} + } + return func(addr net.Addr, auth []byte, sSend uint64, sRecv uint64) (bool, string) { + for _, pwd := range pwds { + if string(auth) == pwd { + return true, "Welcome" + } + } + return false, "Wrong password" + }, nil +} + +func externalAuthFunc(rawMsg json5.RawMessage) (core.ConnectFunc, error) { + var extConfig map[string]string + err := json5.Unmarshal(rawMsg, &extConfig) + if err != nil { + return nil, errors.New("invalid config") + } + if len(extConfig["http"]) != 0 { + hp := &auth.HTTPAuthProvider{ + Client: &http.Client{ + Timeout: 10 * time.Second, + }, + URL: extConfig["http"], + } + return hp.Auth, nil + } else if len(extConfig["cmd"]) != 0 { + cp := &auth.CmdAuthProvider{ + Cmd: extConfig["cmd"], + } + return cp.Auth, nil + } else { + return nil, errors.New("invalid config") + } +} + func disconnectFunc(addr net.Addr, auth []byte, err error) { logrus.WithFields(logrus.Fields{ "src": addr,