From 3cc94a3cf0f123c436df95bde77c247501cfbf9b Mon Sep 17 00:00:00 2001 From: Brice Colucci Date: Fri, 7 Jul 2017 09:34:49 +0200 Subject: [PATCH 1/6] removing gin --- beeping.go | 106 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 69 insertions(+), 37 deletions(-) diff --git a/beeping.go b/beeping.go index fae4085..5bd9379 100644 --- a/beeping.go +++ b/beeping.go @@ -2,6 +2,7 @@ package main import ( "crypto/tls" + "encoding/json" "flag" "fmt" "io/ioutil" @@ -14,7 +15,6 @@ import ( "strings" "time" - "github.com/gin-gonic/gin" "github.com/oschwald/geoip2-golang" "github.com/tcnksm/go-httpstat" "github.com/yanc0/beeping/sslcheck" @@ -31,6 +31,10 @@ var port *string var tlsmode *bool var validatetarget *bool +type ErrorMessage struct { + Message string `json:"message"` +} + type Beeping struct { Version string `json:"version"` Message string `json:"message"` @@ -79,6 +83,16 @@ type Response struct { SSL *sslcheck.CheckSSL `json:"ssl,omitempty"` } +func NewErrorMessage(message string) *ErrorMessage { + var response = ErrorMessage{} + response.Message = message + return &response +} + +func InvalidJSONResponse() *ErrorMessage { + return NewErrorMessage("Invalid JSON sent") +} + func NewResponse() *Response { var response = Response{} response.Timeline = &Timeline{} @@ -143,55 +157,73 @@ func main() { validatetarget = flag.Bool("validatetarget", true, "Perform some security checks on the target provided") flag.Parse() - gin.SetMode("release") - - router := gin.New() - router.POST("/check", handlerCheck) - router.GET("/", handlerDefault) + http.HandleFunc("/check", handlerCheck) + http.HandleFunc("/", handlerDefault) log.Println("[INFO] Listening on", *listen, *port) - router.Run(*listen + ":" + *port) + http.ListenAndServe(*listen+":"+*port, nil) } -func handlerDefault(c *gin.Context) { +func handlerDefault(w http.ResponseWriter, r *http.Request) { var beeping Beeping beeping.Version = VERSION beeping.Message = MESSAGE log.Println("[INFO] Beeping version", beeping.Version) - c.JSON(http.StatusOK, beeping) + jsonRes, err := json.Marshal(beeping) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + w.Header().Set("Content-Type", "application/json") + w.Write(jsonRes) } -func handlerCheck(c *gin.Context) { +func handlerCheck(w http.ResponseWriter, r *http.Request) { var check = NewCheck() - if c.BindJSON(&check) == nil { - if *validatetarget { - if err := check.validateTarget(); err != nil { - log.Println("[WARN] Invalid target:", err.Error()) - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - } else { - response, err := CheckHTTP(check) - if err != nil { - log.Println("[WARN] Check failed:", err.Error()) - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - } else { - log.Println("[INFO] Successful check:", check.URL, "-", response.HTTPRequestTime, "ms") - c.JSON(http.StatusOK, response) - } - } - } else { - response, err := CheckHTTP(check) - if err != nil { - log.Println("[WARN] Check failed:", err.Error()) - c.JSON(http.StatusInternalServerError, gin.H{"message": err.Error()}) - } else { - log.Println("[INFO] Successful check:", check.URL, "-", response.HTTPRequestTime, "ms") - c.JSON(http.StatusOK, response) - } - } - } else { + + w.Header().Set("Content-Type", "application/json") + + decoder := json.NewDecoder(r.Body) + err := decoder.Decode(&check) + if err != nil { log.Println("[WARN] Invalid JSON sent") - c.JSON(http.StatusBadRequest, gin.H{"message": "invalid json sent"}) + jsonRes, _ := json.Marshal(InvalidJSONResponse()) + w.Write(jsonRes) + return + } + + if !*validatetarget { + response, err := CheckHTTP(check) + if err != nil { + log.Println("[WARN] Check failed:", err.Error()) + jsonRes, _ := json.Marshal(NewErrorMessage(err.Error())) + w.Write(jsonRes) + return + } + log.Println("[INFO] Successful check:", check.URL, "-", response.HTTPRequestTime, "ms") + jsonRes, _ := json.Marshal(response) + w.Write(jsonRes) + return } + + if err := check.validateTarget(); err != nil { + log.Println("[WARN] Invalid target:", err.Error()) + jsonRes, _ := json.Marshal(NewErrorMessage(err.Error())) + w.Write(jsonRes) + return + } + + response, err := CheckHTTP(check) + if err != nil { + log.Println("[WARN] Check failed:", err.Error()) + jsonRes, _ := json.Marshal(NewErrorMessage(err.Error())) + w.Write(jsonRes) + return + } + + log.Println("[INFO] Successful check:", check.URL, "-", response.HTTPRequestTime, "ms") + jsonRes, _ := json.Marshal(response) + w.Write(jsonRes) } // CheckHTTP do HTTP check and return a beeping response From 7b6461e2909bdd316d558b57655e8a83124e83da Mon Sep 17 00:00:00 2001 From: Brice Colucci Date: Fri, 7 Jul 2017 09:36:32 +0200 Subject: [PATCH 2/6] remove usless error check when unmarshall basic struct Beeping --- beeping.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/beeping.go b/beeping.go index 5bd9379..0367685 100644 --- a/beeping.go +++ b/beeping.go @@ -169,11 +169,7 @@ func handlerDefault(w http.ResponseWriter, r *http.Request) { beeping.Version = VERSION beeping.Message = MESSAGE log.Println("[INFO] Beeping version", beeping.Version) - jsonRes, err := json.Marshal(beeping) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } + jsonRes, _ := json.Marshal(beeping) w.Header().Set("Content-Type", "application/json") w.Write(jsonRes) } From a6f57172cd48888c2d0cd991faed864dea351f88 Mon Sep 17 00:00:00 2001 From: Brice Colucci Date: Sat, 8 Jul 2017 22:34:26 +0200 Subject: [PATCH 3/6] - Add a http basic auth (digest) mechanism to secure the server - Update the readme --- README.md | 14 +++++++++++++- beeping.go | 33 +++++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b011c8d..7cbe9de 100644 --- a/README.md +++ b/README.md @@ -152,7 +152,19 @@ beeping returns HTTP 500 when check fail. The body contains the reason of the fa ## HTTP Basic Auth -Just add the 'auth' option in your JSON. +### Protect your BeePing server + +Add the 'auth' argument : + +``` +$ go run beeping.go -auth "john:8c7d3c4a9107c7a929f82210d9241d4e" +``` + +**For the moment, only digest method is supported**: The password depends also on the realm. You can generate one from here (choose digest) : [http://www.askapache.com/online-tools/htpasswd-generator/](http://www.askapache.com/online-tools/htpasswd-generator/) + +### Authenticated request to the client + +Add the 'auth' field in your JSON. ``` $ curl -XPOST http://localhost:8080/check -d '{"url":"http://127.0.0.1:3000","auth":"john:secret"}' diff --git a/beeping.go b/beeping.go index cab78e2..027f1c2 100644 --- a/beeping.go +++ b/beeping.go @@ -4,6 +4,7 @@ import ( "crypto/tls" "encoding/base64" "encoding/json" + "errors" "flag" "fmt" "io/ioutil" @@ -19,6 +20,8 @@ import ( "github.com/oschwald/geoip2-golang" "github.com/tcnksm/go-httpstat" "github.com/yanc0/beeping/sslcheck" + + auth "github.com/abbot/go-http-auth" ) var VERSION = "0.5.0" @@ -31,6 +34,7 @@ var listen *string var port *string var tlsmode *bool var validatetarget *bool +var basicAuth *string type ErrorMessage struct { Message string `json:"message"` @@ -157,15 +161,40 @@ func main() { port = flag.String("port", "8080", "The port to bind the server to") tlsmode = flag.Bool("tlsmode", false, "Activate SSL/TLS versions and Cipher support checks (slow)") validatetarget = flag.Bool("validatetarget", true, "Perform some security checks on the target provided") + basicAuth = flag.String("auth", "", "HTTP Basic Auth e.g. john:secret") flag.Parse() - http.HandleFunc("/check", handlerCheck) - http.HandleFunc("/", handlerDefault) + if basicAuth == nil { + PlugBasicHandlers() + } else { + PlugAuthenticatedHandlers() + } log.Println("[INFO] Listening on", *listen, *port) http.ListenAndServe(*listen+":"+*port, nil) } +func PlugBasicHandlers() { + http.HandleFunc("/check", handlerCheck) + http.HandleFunc("/", handlerDefault) +} + +func PlugAuthenticatedHandlers() { + splitAuth := strings.Split(*basicAuth, ":") + if len(splitAuth) < 2 { + panic(errors.New("Invalid HTTP Basic Auth format")) + } + authenticator := auth.NewDigestAuthenticator(*instance, func(user, realm string) string { + if user == splitAuth[0] { + return splitAuth[1] + } + return "" + }) + log.Println("[INFO] HTTP Basic Auth enabled") + http.HandleFunc("/check", auth.JustCheck(authenticator, handlerCheck)) + http.HandleFunc("/", auth.JustCheck(authenticator, handlerDefault)) +} + func handlerDefault(w http.ResponseWriter, r *http.Request) { var beeping Beeping beeping.Version = VERSION From 526141f842326a7301ff5524a0269cf19cff8e57 Mon Sep 17 00:00:00 2001 From: Brice Colucci Date: Sat, 8 Jul 2017 22:36:35 +0200 Subject: [PATCH 4/6] fix bug when checking the auth option value --- beeping.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/beeping.go b/beeping.go index 027f1c2..d6b13bd 100644 --- a/beeping.go +++ b/beeping.go @@ -164,7 +164,7 @@ func main() { basicAuth = flag.String("auth", "", "HTTP Basic Auth e.g. john:secret") flag.Parse() - if basicAuth == nil { + if *basicAuth == "" { PlugBasicHandlers() } else { PlugAuthenticatedHandlers() From d16290dfe2601ccd7d20f834f06f6b835ed09a34 Mon Sep 17 00:00:00 2001 From: Brice Colucci Date: Mon, 10 Jul 2017 12:43:15 +0200 Subject: [PATCH 5/6] update deps lock --- Gopkg.lock | 54 ++++++++++++++++++++++-------------------------------- 1 file changed, 22 insertions(+), 32 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 65c9b2a..c1d4bf1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,28 +1,11 @@ -memo = "85a9af901e8e5abbab7be176652cf5619eae1955e8e11be0db6e4dbc449c6b57" +# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. -[[projects]] - name = "github.com/gin-gonic/gin" - packages = [".","binding","render"] - revision = "e2212d40c62a98b388a5eb48ecbdcf88534688ba" - version = "v1.1.4" [[projects]] - branch = "master" - name = "github.com/golang/protobuf" - packages = ["proto"] - revision = "2bba0603135d7d7f5cb73b2125beeda19c09f4ef" - -[[projects]] - branch = "master" - name = "github.com/manucorporat/sse" + name = "github.com/abbot/go-http-auth" packages = ["."] - revision = "ee05b128a739a0fb76c7ebd3ae4810c1de808d6d" - -[[projects]] - name = "github.com/mattn/go-isatty" - packages = ["."] - revision = "fc9e8d8ef48496124e79ae0df75490096eccf6fe" - version = "v0.0.2" + revision = "0ddd408d5d60ea76e320503cc7dd091992dee608" + version = "v0.4.0" [[projects]] name = "github.com/oschwald/geoip2-golang" @@ -41,6 +24,18 @@ memo = "85a9af901e8e5abbab7be176652cf5619eae1955e8e11be0db6e4dbc449c6b57" packages = ["."] revision = "fae40520f4ba0a112874d1f0deb9498ebb2198cb" +[[projects]] + name = "github.com/yanc0/beeping" + packages = ["sslcheck"] + revision = "964570feba06594fc570ec4e08bfffe20f5ca805" + version = "v0.5.0" + +[[projects]] + branch = "master" + name = "golang.org/x/crypto" + packages = ["bcrypt","blowfish"] + revision = "08a7dbd3d99261d9ae86ef1b3b8bdb0382fb82cd" + [[projects]] branch = "master" name = "golang.org/x/net" @@ -53,14 +48,9 @@ memo = "85a9af901e8e5abbab7be176652cf5619eae1955e8e11be0db6e4dbc449c6b57" packages = ["unix","windows"] revision = "f3918c30c5c2cb527c0b071a27c35120a6c0719a" -[[projects]] - name = "gopkg.in/go-playground/validator.v8" - packages = ["."] - revision = "5f57d2222ad794d0dffb07e664ea05e2ee07d60c" - version = "v8.18.1" - -[[projects]] - branch = "v2" - name = "gopkg.in/yaml.v2" - packages = ["."] - revision = "cd8b52f8269e0feb286dfeef29f8fe4d5b397e0b" +[solve-meta] + analyzer-name = "dep" + analyzer-version = 1 + inputs-digest = "abb07f1de2e2ba4f16fc2cc3712a43529a18f6f7dd67310af5120af5c4d1246d" + solver-name = "gps-cdcl" + solver-version = 1 From d27e068555309bc00e93fb5e1f0cdf0d6a0fd80f Mon Sep 17 00:00:00 2001 From: Brice Colucci Date: Mon, 10 Jul 2017 17:31:26 +0200 Subject: [PATCH 6/6] - Split arguments for server auth mechanism - The default digest method is now 'clear' (no hashing) - Remove the go-http-auth dependency --- Gopkg.lock | 20 +------------- README.md | 14 +++++++--- beeping.go | 77 ++++++++++++++++++++++++++++++++++++------------------ 3 files changed, 63 insertions(+), 48 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index c1d4bf1..b8c07e1 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -1,12 +1,6 @@ # This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'. -[[projects]] - name = "github.com/abbot/go-http-auth" - packages = ["."] - revision = "0ddd408d5d60ea76e320503cc7dd091992dee608" - version = "v0.4.0" - [[projects]] name = "github.com/oschwald/geoip2-golang" packages = ["."] @@ -30,18 +24,6 @@ revision = "964570feba06594fc570ec4e08bfffe20f5ca805" version = "v0.5.0" -[[projects]] - branch = "master" - name = "golang.org/x/crypto" - packages = ["bcrypt","blowfish"] - revision = "08a7dbd3d99261d9ae86ef1b3b8bdb0382fb82cd" - -[[projects]] - branch = "master" - name = "golang.org/x/net" - packages = ["context"] - revision = "5602c733f70afc6dcec6766be0d5034d4c4f14de" - [[projects]] branch = "master" name = "golang.org/x/sys" @@ -51,6 +33,6 @@ [solve-meta] analyzer-name = "dep" analyzer-version = 1 - inputs-digest = "abb07f1de2e2ba4f16fc2cc3712a43529a18f6f7dd67310af5120af5c4d1246d" + inputs-digest = "c2420b8e195d681f876e58304a699cc47c4ff446fbae426f42272180f4b8d933" solver-name = "gps-cdcl" solver-version = 1 diff --git a/README.md b/README.md index 7cbe9de..d8e1c99 100644 --- a/README.md +++ b/README.md @@ -154,14 +154,20 @@ beeping returns HTTP 500 when check fail. The body contains the reason of the fa ### Protect your BeePing server -Add the 'auth' argument : +Add the auth-* arguments : + +- **auth-user** The auth username +- **auth-secret** The auth user secret +- **auth-method** The auth secret digest mechanism + +**For now, only the 'clear' method is supported.** + +For example : ``` -$ go run beeping.go -auth "john:8c7d3c4a9107c7a929f82210d9241d4e" +$ go run beeping.go -auth-user "john" -auth-password "passw0rd" ``` -**For the moment, only digest method is supported**: The password depends also on the realm. You can generate one from here (choose digest) : [http://www.askapache.com/online-tools/htpasswd-generator/](http://www.askapache.com/online-tools/htpasswd-generator/) - ### Authenticated request to the client Add the 'auth' field in your JSON. diff --git a/beeping.go b/beeping.go index d6b13bd..5525bb4 100644 --- a/beeping.go +++ b/beeping.go @@ -20,8 +20,6 @@ import ( "github.com/oschwald/geoip2-golang" "github.com/tcnksm/go-httpstat" "github.com/yanc0/beeping/sslcheck" - - auth "github.com/abbot/go-http-auth" ) var VERSION = "0.5.0" @@ -34,7 +32,16 @@ var listen *string var port *string var tlsmode *bool var validatetarget *bool -var basicAuth *string + +var authUser *string +var authSecret *string +var authMethod *string + +type HTTPAuth struct { + User, Secret, Method string +} + +var auth *HTTPAuth type ErrorMessage struct { Message string `json:"message"` @@ -161,41 +168,57 @@ func main() { port = flag.String("port", "8080", "The port to bind the server to") tlsmode = flag.Bool("tlsmode", false, "Activate SSL/TLS versions and Cipher support checks (slow)") validatetarget = flag.Bool("validatetarget", true, "Perform some security checks on the target provided") - basicAuth = flag.String("auth", "", "HTTP Basic Auth e.g. john:secret") + authUser = flag.String("auth-user", "", "HTTP Auth User e.g. 'admin'") + authSecret = flag.String("auth-secret", "", "HTTP Auth Secret e.g. 'passw0rd'") + authMethod = flag.String("auth-method", "clear", "HTTP Auth Method (only 'clear' for now)") flag.Parse() - if *basicAuth == "" { - PlugBasicHandlers() - } else { - PlugAuthenticatedHandlers() - } + instantiateAuthMechanism() + + http.HandleFunc("/check", handlerCheck) + http.HandleFunc("/", handlerDefault) log.Println("[INFO] Listening on", *listen, *port) http.ListenAndServe(*listen+":"+*port, nil) } -func PlugBasicHandlers() { - http.HandleFunc("/check", handlerCheck) - http.HandleFunc("/", handlerDefault) +func instantiateAuthMechanism() { + if *authUser == "" { + return + } + if *authSecret == "" { + panic(errors.New("Auth secret can not be empty.")) + } + switch strings.ToLower(*authMethod) { + case "clear": + break + default: + panic(errors.New("Unsupported auth method.")) + } + auth = &HTTPAuth{*authUser, *authSecret, *authMethod} + log.Printf("[INFO] HTTP Auth enabled: %v\n", auth) } -func PlugAuthenticatedHandlers() { - splitAuth := strings.Split(*basicAuth, ":") - if len(splitAuth) < 2 { - panic(errors.New("Invalid HTTP Basic Auth format")) +func checkAuth(w http.ResponseWriter, r *http.Request) bool { + if auth == nil { + return true } - authenticator := auth.NewDigestAuthenticator(*instance, func(user, realm string) string { - if user == splitAuth[0] { - return splitAuth[1] - } - return "" - }) - log.Println("[INFO] HTTP Basic Auth enabled") - http.HandleFunc("/check", auth.JustCheck(authenticator, handlerCheck)) - http.HandleFunc("/", auth.JustCheck(authenticator, handlerDefault)) + user, pass, _ := r.BasicAuth() + fmt.Println(user + " " + pass) + //TODO depending the auth method, transform the pass + if user == auth.User && pass == auth.Secret { + return true + } + log.Println("[INFO] Unauthorized (", user, ")") + w.Header().Add("WWW-Authenticate", "Basic realm=\"Access Denied\"") + http.Error(w, "401, Unauthorized", 401) + return false } func handlerDefault(w http.ResponseWriter, r *http.Request) { + if !checkAuth(w, r) { + return + } var beeping Beeping beeping.Version = VERSION beeping.Message = MESSAGE @@ -206,6 +229,10 @@ func handlerDefault(w http.ResponseWriter, r *http.Request) { } func handlerCheck(w http.ResponseWriter, r *http.Request) { + if !checkAuth(w, r) { + return + } + var check = NewCheck() w.Header().Set("Content-Type", "application/json")