From 2a4a005b3cdb8ef970bb86f9ecd580feaef96bd9 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Mon, 15 Dec 2014 23:55:46 -0500 Subject: [PATCH 01/10] Can now whitelist users by github pubkeys. --- client.go | 8 +++++-- server.go | 68 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 69 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index f3d886b..da6223a 100644 --- a/client.go +++ b/client.go @@ -415,8 +415,12 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Missing $FINGERPRINT from: /whitelist $FINGERPRINT") } else { fingerprint := parts[1] - c.Server.Whitelist(fingerprint) - c.SysMsg("Added %s to the whitelist", fingerprint) + err = c.Server.Whitelist(fingerprint) + if err != nil { + c.SysMsg("Error adding to whitelist: %s", err) + } else { + c.SysMsg("Added %s to the whitelist", fingerprint) + } } default: diff --git a/server.go b/server.go index bd4230e..ea86f09 100644 --- a/server.go +++ b/server.go @@ -9,6 +9,10 @@ import ( "sync" "syscall" "time" + "net/http" + "io/ioutil" + "encoding/base64" + "errors" "golang.org/x/crypto/ssh" ) @@ -260,11 +264,65 @@ func (s *Server) Op(fingerprint string) { } // Whitelist adds the given fingerprint to the whitelist -func (s *Server) Whitelist(fingerprint string) { - logger.Infof("Adding whitelist: %s", fingerprint) - s.Lock() - s.whitelist[fingerprint] = struct{}{} - s.Unlock() +func (s *Server) Whitelist(fingerprint string) error { + if strings.HasPrefix(fingerprint, "github.com/") { + logger.Infof("Adding github account %s to whitelist", fingerprint) + + keys, err := getGithubKey(fingerprint) + if err != nil { + return err + } + if len(keys) == 0 { + return errors.New(fmt.Sprintf("No github user %s", fingerprint)) + } + for _, key := range keys { + fingerprint = Fingerprint(key) + logger.Infof("Adding whitelist: %s", fingerprint) + s.Lock() + s.whitelist[fingerprint] = struct{}{} + s.Unlock() + } + } else { + logger.Infof("Adding whitelist: %s", fingerprint) + s.Lock() + s.whitelist[fingerprint] = struct{}{} + s.Unlock() + } + return nil +} + +var r *regexp.Regexp = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +func getGithubKey(url string) ([]ssh.PublicKey, error) { + resp, err := http.Get("http://" + url + ".keys") + if err != nil { + return nil, err + } + defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + return nil, err + } + body_str := string(body) + keys := r.FindAllStringSubmatch(body_str, -1) + pubs := make([]ssh.PublicKey, 0, 3) + for _, key := range keys { + if(len(key) < 2) { + continue + } + + body_decoded, err := base64.StdEncoding.DecodeString(key[1]) + if err != nil { + return nil, err + } + + pub, err := ssh.ParsePublicKey(body_decoded) + if err != nil { + return nil, err + } + + pubs = append(pubs, pub) + } + return pubs, nil } // Uptime returns the time since the server was started From da7ee40d95fb5f989849be5f45e146a7d6ef9d0b Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 00:02:58 -0500 Subject: [PATCH 02/10] Fixed issues reported by golint. --- server.go | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/server.go b/server.go index ea86f09..95a5228 100644 --- a/server.go +++ b/server.go @@ -12,7 +12,6 @@ import ( "net/http" "io/ioutil" "encoding/base64" - "errors" "golang.org/x/crypto/ssh" ) @@ -273,7 +272,7 @@ func (s *Server) Whitelist(fingerprint string) error { return err } if len(keys) == 0 { - return errors.New(fmt.Sprintf("No github user %s", fingerprint)) + return fmt.Errorf("No github user %s", fingerprint) } for _, key := range keys { fingerprint = Fingerprint(key) @@ -291,7 +290,7 @@ func (s *Server) Whitelist(fingerprint string) error { return nil } -var r *regexp.Regexp = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +var r = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) func getGithubKey(url string) ([]ssh.PublicKey, error) { resp, err := http.Get("http://" + url + ".keys") if err != nil { @@ -302,20 +301,20 @@ func getGithubKey(url string) ([]ssh.PublicKey, error) { if err != nil { return nil, err } - body_str := string(body) - keys := r.FindAllStringSubmatch(body_str, -1) + bodyStr := string(body) + keys := r.FindAllStringSubmatch(bodyStr, -1) pubs := make([]ssh.PublicKey, 0, 3) for _, key := range keys { if(len(key) < 2) { continue } - body_decoded, err := base64.StdEncoding.DecodeString(key[1]) + bodyDecoded, err := base64.StdEncoding.DecodeString(key[1]) if err != nil { return nil, err } - pub, err := ssh.ParsePublicKey(body_decoded) + pub, err := ssh.ParsePublicKey(bodyDecoded) if err != nil { return nil, err } From 3d7ff07587713946479b33566d58d1ea1e1ccc74 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 00:04:38 -0500 Subject: [PATCH 03/10] Updated help text for /whitelist --- client.go | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/client.go b/client.go index da6223a..107d81d 100644 --- a/client.go +++ b/client.go @@ -32,13 +32,14 @@ const ( // OpHelpText is the additional text returned by /help if the client is an Op OpHelpText string = systemMessageFormat + `-> Available operator commands: - /ban $NAME - Banish a user from the chat - /kick $NAME - Kick em' out. - /op $NAME - Promote a user to server operator. - /silence $NAME - Revoke a user's ability to speak. - /shutdown $MESSAGE - Broadcast message and shutdown server. - /motd $MESSAGE - Set message shown whenever somebody joins. - /whitelist $FINGERPRINT - Add fingerprint to whitelist, prevent anyone else from joining.` + Reset + /ban $NAME - Banish a user from the chat + /kick $NAME - Kick em' out. + /op $NAME - Promote a user to server operator. + /silence $NAME - Revoke a user's ability to speak. + /shutdown $MESSAGE - Broadcast message and shutdown server. + /motd $MESSAGE - Set message shown whenever somebody joins. + /whitelist $FINGERPRINT - Add fingerprint to whitelist, prevent anyone else from joining. + /whitelist github.com/$USER - Add github user's pubkeys to whitelist.` + Reset // AboutText is the text returned by /about AboutText string = systemMessageFormat + `-> ssh-chat is made by @shazow. From 932410d0575c73888032ffd815dbe704bef49be4 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 00:07:16 -0500 Subject: [PATCH 04/10] Added comments and better variable names in getGithubPubKeys. --- server.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 95a5228..d22db99 100644 --- a/server.go +++ b/server.go @@ -267,7 +267,7 @@ func (s *Server) Whitelist(fingerprint string) error { if strings.HasPrefix(fingerprint, "github.com/") { logger.Infof("Adding github account %s to whitelist", fingerprint) - keys, err := getGithubKey(fingerprint) + keys, err := getGithubPubKeys(fingerprint) if err != nil { return err } @@ -290,8 +290,9 @@ func (s *Server) Whitelist(fingerprint string) error { return nil } -var r = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) -func getGithubKey(url string) ([]ssh.PublicKey, error) { +var pubKeyRegex = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +// Returns an array of public keys for the given github user URL +func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { resp, err := http.Get("http://" + url + ".keys") if err != nil { return nil, err @@ -302,7 +303,7 @@ func getGithubKey(url string) ([]ssh.PublicKey, error) { return nil, err } bodyStr := string(body) - keys := r.FindAllStringSubmatch(bodyStr, -1) + keys := pubKeyRegex.FindAllStringSubmatch(bodyStr, -1) pubs := make([]ssh.PublicKey, 0, 3) for _, key := range keys { if(len(key) < 2) { From 8329c8d7fd1447e09d494e859f7b5eeb790208f0 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 01:23:43 -0500 Subject: [PATCH 05/10] Made whitelisting user async. --- client.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/client.go b/client.go index 107d81d..b780aa9 100644 --- a/client.go +++ b/client.go @@ -416,12 +416,14 @@ func (c *Client) handleShell(channel ssh.Channel) { c.SysMsg("Missing $FINGERPRINT from: /whitelist $FINGERPRINT") } else { fingerprint := parts[1] - err = c.Server.Whitelist(fingerprint) - if err != nil { - c.SysMsg("Error adding to whitelist: %s", err) - } else { - c.SysMsg("Added %s to the whitelist", fingerprint) - } + go func() { + err = c.Server.Whitelist(fingerprint) + if err != nil { + c.SysMsg("Error adding to whitelist: %s", err) + } else { + c.SysMsg("Added %s to the whitelist", fingerprint) + } + }() } default: From 3c466dc88e40e598766631c80ece0acb57f29730 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 19:59:52 -0500 Subject: [PATCH 06/10] Added timeout to github pubkey request. --- server.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/server.go b/server.go index c425b80..702f7b5 100644 --- a/server.go +++ b/server.go @@ -296,7 +296,12 @@ func (s *Server) Whitelist(fingerprint string) error { var pubKeyRegex = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) // Returns an array of public keys for the given github user URL func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { - resp, err := http.Get("http://" + url + ".keys") + timeout := time.Duration(10 * time.Second) + client := http.Client{ + Timeout: timeout, + } + resp, err := client.Get("http://" + url + ".keys") + if err != nil { return nil, err } From a1455a8ebaec501e670c8ef2b019d2fcbfbe679c Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 20:47:39 -0500 Subject: [PATCH 07/10] Removed regex, added timeout. --- server.go | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/server.go b/server.go index 702f7b5..360c52e 100644 --- a/server.go +++ b/server.go @@ -293,32 +293,35 @@ func (s *Server) Whitelist(fingerprint string) error { return nil } -var pubKeyRegex = regexp.MustCompile(`ssh-rsa ([A-Za-z0-9\+=\/]+)\s*`) +// Client for getting github pub keys +var timeout = time.Duration(10 * time.Second) +var client = http.Client{ + Timeout: timeout, +} // Returns an array of public keys for the given github user URL func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { - timeout := time.Duration(10 * time.Second) - client := http.Client{ - Timeout: timeout, - } resp, err := client.Get("http://" + url + ".keys") - if err != nil { return nil, err } defer resp.Body.Close() + body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, err } + bodyStr := string(body) - keys := pubKeyRegex.FindAllStringSubmatch(bodyStr, -1) - pubs := make([]ssh.PublicKey, 0, 3) - for _, key := range keys { - if(len(key) < 2) { + pubs := []ssh.PublicKey{} + for _, key := range strings.SplitN(bodyStr, "\n", -1) { + splitKey := strings.SplitN(key, " ", -1) + + // In case of malformated key + if len(splitKey) < 2 { continue } - bodyDecoded, err := base64.StdEncoding.DecodeString(key[1]) + bodyDecoded, err := base64.StdEncoding.DecodeString(splitKey[1]) if err != nil { return nil, err } From 912175e65a0ec8c033eab47b719048d8980034fc Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 21:15:45 -0500 Subject: [PATCH 08/10] Split up whitelist func, made identity url get safer. --- server.go | 57 ++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 22 deletions(-) diff --git a/server.go b/server.go index 360c52e..7951fa3 100644 --- a/server.go +++ b/server.go @@ -268,28 +268,35 @@ func (s *Server) Op(fingerprint string) { // Whitelist adds the given fingerprint to the whitelist func (s *Server) Whitelist(fingerprint string) error { if strings.HasPrefix(fingerprint, "github.com/") { - logger.Infof("Adding github account %s to whitelist", fingerprint) - - keys, err := getGithubPubKeys(fingerprint) - if err != nil { - return err - } - if len(keys) == 0 { - return fmt.Errorf("No github user %s", fingerprint) - } - for _, key := range keys { - fingerprint = Fingerprint(key) - logger.Infof("Adding whitelist: %s", fingerprint) - s.Lock() - s.whitelist[fingerprint] = struct{}{} - s.Unlock() - } + return s.whitelistIdentityUrl(fingerprint) } else { - logger.Infof("Adding whitelist: %s", fingerprint) - s.Lock() - s.whitelist[fingerprint] = struct{}{} - s.Unlock() + return s.whitelistFingerprint(fingerprint) } +} + +func (s *Server) whitelistIdentityUrl(user string) error { + logger.Infof("Adding github account %s to whitelist", user) + + user = strings.Replace(user, "github.com/", "", -1) + keys, err := getGithubPubKeys(user) + if err != nil { + return err + } + if len(keys) == 0 { + return fmt.Errorf("No keys for github user %s", user) + } + for _, key := range keys { + fingerprint := Fingerprint(key) + s.whitelistFingerprint(fingerprint) + } + return nil +} + +func (s *Server) whitelistFingerprint(fingerprint string) error { + logger.Infof("Adding whitelist: %s", fingerprint) + s.Lock() + s.whitelist[fingerprint] = struct{}{} + s.Unlock() return nil } @@ -299,8 +306,8 @@ var client = http.Client{ Timeout: timeout, } // Returns an array of public keys for the given github user URL -func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { - resp, err := client.Get("http://" + url + ".keys") +func getGithubPubKeys(user string) ([]ssh.PublicKey, error) { + resp, err := client.Get("http://github.com/" + user + ".keys") if err != nil { return nil, err } @@ -312,6 +319,12 @@ func getGithubPubKeys(url string) ([]ssh.PublicKey, error) { } bodyStr := string(body) + + // More informative error than that from base64 DecodeString + if bodyStr == "Not Found" { + return nil, fmt.Errorf("No github user %s found", user) + } + pubs := []ssh.PublicKey{} for _, key := range strings.SplitN(bodyStr, "\n", -1) { splitKey := strings.SplitN(key, " ", -1) From 580ad79a22b818c45b1859d6f636e94c521f7548 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 21:18:10 -0500 Subject: [PATCH 09/10] Fixed issues found by golint. --- server.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 7951fa3..2ef62b8 100644 --- a/server.go +++ b/server.go @@ -268,13 +268,13 @@ func (s *Server) Op(fingerprint string) { // Whitelist adds the given fingerprint to the whitelist func (s *Server) Whitelist(fingerprint string) error { if strings.HasPrefix(fingerprint, "github.com/") { - return s.whitelistIdentityUrl(fingerprint) - } else { - return s.whitelistFingerprint(fingerprint) + return s.whitelistIdentityURL(fingerprint) } + + return s.whitelistFingerprint(fingerprint) } -func (s *Server) whitelistIdentityUrl(user string) error { +func (s *Server) whitelistIdentityURL(user string) error { logger.Infof("Adding github account %s to whitelist", user) user = strings.Replace(user, "github.com/", "", -1) From 68e82ff1155d54e6b752f3394a7e56567f4262d7 Mon Sep 17 00:00:00 2001 From: empathetic-alligator <willipovell@gmail.com> Date: Tue, 16 Dec 2014 21:27:55 -0500 Subject: [PATCH 10/10] Using bufio instead of ioutil. --- server.go | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/server.go b/server.go index 2ef62b8..74d3f3c 100644 --- a/server.go +++ b/server.go @@ -10,7 +10,7 @@ import ( "syscall" "time" "net/http" - "io/ioutil" + "bufio" "encoding/base64" "golang.org/x/crypto/ssh" @@ -313,21 +313,15 @@ func getGithubPubKeys(user string) ([]ssh.PublicKey, error) { } defer resp.Body.Close() - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, err - } - - bodyStr := string(body) - - // More informative error than that from base64 DecodeString - if bodyStr == "Not Found" { - return nil, fmt.Errorf("No github user %s found", user) - } - pubs := []ssh.PublicKey{} - for _, key := range strings.SplitN(bodyStr, "\n", -1) { - splitKey := strings.SplitN(key, " ", -1) + scanner := bufio.NewScanner(resp.Body) + for scanner.Scan() { + text := scanner.Text() + if text == "Not Found" { + continue + } + + splitKey := strings.SplitN(text, " ", -1) // In case of malformated key if len(splitKey) < 2 {