From 6ffc5ccb68f970127ad932fae32e87dcb9d1303b Mon Sep 17 00:00:00 2001 From: nato Date: Mon, 23 Jan 2023 17:41:22 -0800 Subject: [PATCH 1/2] Auth: store ssh key comments when loading keys from allowlist or ops files. --- auth.go | 31 ++++++++++++++++++++++++------- cmd/ssh-chat/cmd.go | 12 +++++++----- host.go | 8 ++++---- 3 files changed, 35 insertions(+), 16 deletions(-) diff --git a/auth.go b/auth.go index 0cd5e0d..ff0ecf7 100644 --- a/auth.go +++ b/auth.go @@ -18,7 +18,7 @@ import ( // KeyLoader loads public keys, e.g. from an authorized_keys file. // It must return a nil slice on error. -type KeyLoader func() ([]ssh.PublicKey, error) +type KeyLoader func() ([]ssh.PublicKey, []string, error) // ErrNotAllowed Is the error returned when a key is checked that is not allowlisted, // when allowlisting is enabled. @@ -61,6 +61,7 @@ type Auth struct { banned *set.Set allowlist *set.Set ops *set.Set + comments map[string]string settingsMu sync.RWMutex allowlistMode bool @@ -76,6 +77,7 @@ func NewAuth() *Auth { banned: set.New(), allowlist: set.New(), ops: set.New(), + comments: make(map[string]string), } } @@ -158,7 +160,7 @@ func (a *Auth) CheckPassphrase(passphrase string) error { } // Op sets a public key as a known operator. -func (a *Auth) Op(key ssh.PublicKey, d time.Duration) { +func (a *Auth) Op(key ssh.PublicKey, comment string, d time.Duration) { if key == nil { return } @@ -168,6 +170,11 @@ func (a *Auth) Op(key ssh.PublicKey, d time.Duration) { } else { a.ops.Set(authItem) } + + if len(comment) >0 { + a.comments[ authItem.Key() ] = comment + } + logger.Debugf("Added to ops: %q (for %s)", authItem.Key(), d) } @@ -193,7 +200,7 @@ func (a *Auth) ReloadOps() error { } // Allowlist will set a public key as a allowlisted user. -func (a *Auth) Allowlist(key ssh.PublicKey, d time.Duration) { +func (a *Auth) Allowlist(key ssh.PublicKey, comment string, d time.Duration) { if key == nil { return } @@ -204,6 +211,11 @@ func (a *Auth) Allowlist(key ssh.PublicKey, d time.Duration) { } else { err = a.allowlist.Set(authItem) } + + if len(comment) >0 { + a.comments[ authItem.Key() ] = comment + } + if err == nil { logger.Debugf("Added to allowlist: %q (for %s)", authItem.Key(), d) } else { @@ -226,13 +238,18 @@ func (a *Auth) ReloadAllowlist() error { return addFromLoader(a.allowlistLoader, a.Allowlist) } -func addFromLoader(loader KeyLoader, adder func(ssh.PublicKey, time.Duration)) error { +func addFromLoader(loader KeyLoader, adder func(ssh.PublicKey, string, time.Duration)) error { if loader == nil { return nil } - keys, err := loader() - for _, key := range keys { - adder(key, 0) + keys, comments, err := loader() + var comment string + for index, key := range keys { + comment = "" + if len(comments) > index { + comment = comments[index] + } + adder(key, comment, 0) } return err } diff --git a/cmd/ssh-chat/cmd.go b/cmd/ssh-chat/cmd.go index 96e5a5a..1566a16 100644 --- a/cmd/ssh-chat/cmd.go +++ b/cmd/ssh-chat/cmd.go @@ -200,28 +200,30 @@ func loaderFromFile(path string, logger *golog.Logger) sshchat.KeyLoader { if path == "" { return nil } - return func() ([]ssh.PublicKey, error) { + return func() ([]ssh.PublicKey, []string, error) { file, err := os.Open(path) if err != nil { - return nil, err + return nil, nil, err } defer file.Close() var keys []ssh.PublicKey + var comments []string scanner := bufio.NewScanner(file) for scanner.Scan() { - key, _, _, _, err := ssh.ParseAuthorizedKey(scanner.Bytes()) + key, comment, _, _, err := ssh.ParseAuthorizedKey(scanner.Bytes()) if err != nil { if err.Error() == "ssh: no key found" { continue // Skip line } - return nil, err + return nil, nil, err } keys = append(keys, key) + comments = append(comments, comment) } if keys == nil { logger.Warning("file", path, "contained no keys") } - return keys, nil + return keys, comments, nil } } diff --git a/host.go b/host.go index be5eeab..62276ab 100644 --- a/host.go +++ b/host.go @@ -626,7 +626,7 @@ func (h *Host) InitCommands(c *chat.Commands) { member.IsOp = opValue id := member.Identifier.(*Identity) - h.auth.Op(id.PublicKey(), until) + h.auth.Op(id.PublicKey(), "", until) var body string if opValue { @@ -780,7 +780,7 @@ func (h *Host) InitCommands(c *chat.Commands) { if pk == nil { noKeyUsers = append(noKeyUsers, user.Identifier.Name()) } else { - h.auth.Allowlist(pk, 0) + h.auth.Allowlist(pk, "", 0) } } return nil @@ -876,9 +876,9 @@ func (h *Host) InitCommands(c *chat.Commands) { case "off": h.auth.SetAllowlistMode(false) case "add": - replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, 0) }) + replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, "", 0) }) case "remove": - replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, 1) }) + replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, "", 1) }) case "import": replyLines, err = allowlistImport(args[1:]) case "reload": From 3e6d7e08a3a07e82a893cbcbca76087b1ebf13a4 Mon Sep 17 00:00:00 2001 From: nato Date: Mon, 23 Jan 2023 17:43:36 -0800 Subject: [PATCH 2/2] Host.Connect(): upon connection, initialize the user's symbol prefix to their key's comment. --- host.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/host.go b/host.go index 62276ab..3b281d3 100644 --- a/host.go +++ b/host.go @@ -101,6 +101,9 @@ func (h *Host) isOp(conn sshd.Connection) bool { // Connect a specific Terminal to this host and its room. func (h *Host) Connect(term *sshd.Terminal) { id := NewIdentity(term.Conn) + + id.SetSymbol(h.auth.comments[sshd.Fingerprint(id.PublicKey())]) + user := message.NewUserScreen(id, term) user.OnChange = func() { term.SetPrompt(GetPrompt(user))