mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-06-10 04:12:07 +03:00
add /whitelist command with lots of open questions
This commit is contained in:
parent
1a533b023d
commit
7951c57ef2
@ -34,6 +34,7 @@ func TestAuthWhitelist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
auth.Whitelist(key, 0)
|
auth.Whitelist(key, 0)
|
||||||
|
auth.WhitelistMode = true
|
||||||
|
|
||||||
keyClone, err := ClonePublicKey(key)
|
keyClone, err := ClonePublicKey(key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
192
host.go
192
host.go
@ -9,8 +9,11 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/ssh"
|
||||||
|
|
||||||
"github.com/shazow/rateio"
|
"github.com/shazow/rateio"
|
||||||
"github.com/shazow/ssh-chat/chat"
|
"github.com/shazow/ssh-chat/chat"
|
||||||
|
"github.com/shazow/ssh-chat/set"
|
||||||
"github.com/shazow/ssh-chat/chat/message"
|
"github.com/shazow/ssh-chat/chat/message"
|
||||||
"github.com/shazow/ssh-chat/internal/humantime"
|
"github.com/shazow/ssh-chat/internal/humantime"
|
||||||
"github.com/shazow/ssh-chat/internal/sanitize"
|
"github.com/shazow/ssh-chat/internal/sanitize"
|
||||||
@ -695,4 +698,193 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
|||||||
return nil
|
return nil
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
c.Add(chat.Command{
|
||||||
|
// TODO: find a better name for reverify
|
||||||
|
// TODO: default for reload
|
||||||
|
// TODO: add keys for a specific duration?
|
||||||
|
// TODO: reverify: what about passphrases?
|
||||||
|
// - make this a different command (why? a passphrase can't change)
|
||||||
|
// - who cares, kick them? -- after all, they can just reconnect
|
||||||
|
// - store a flag in users that authenticated via passphrase and skip here (much more complicated)
|
||||||
|
// - in which cases does this situation actually happen?
|
||||||
|
// TODO: "panic" (?) command for (import + on + reverify)?
|
||||||
|
// TODO: "print" command with a format for saving to the whitelist file?
|
||||||
|
// -> hard because the whitelist set inly saved fingerprints
|
||||||
|
Op: true,
|
||||||
|
Prefix: "/whitelist",
|
||||||
|
PrefixHelp: "COMMAND [ARGS...]",
|
||||||
|
Help: "Manipulate the whitelist or whitelist state. See /whitelist help for subcommands",
|
||||||
|
Handler: func(room *chat.Room, msg message.CommandMsg) error {
|
||||||
|
if !room.IsOp(msg.From()){
|
||||||
|
return errors.New("must be op")
|
||||||
|
}
|
||||||
|
|
||||||
|
args := msg.Args()
|
||||||
|
if len(args) == 0 {
|
||||||
|
args = []string{"help"}
|
||||||
|
}
|
||||||
|
|
||||||
|
// send exactly one message to preserve order
|
||||||
|
replyLines := []string{}
|
||||||
|
sendMsg := func(content string, formatting ...interface{}){
|
||||||
|
replyLines = append(replyLines, fmt.Sprintf(content, formatting...))
|
||||||
|
}
|
||||||
|
|
||||||
|
forConnectedUsers := func(cmd func(*chat.Member, ssh.PublicKey) error)error{
|
||||||
|
return h.Members.Each(func(key string, item set.Item) error{
|
||||||
|
v := item.Value()
|
||||||
|
if v == nil { // expired between Each and here
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
user := v.(*chat.Member)
|
||||||
|
pk := user.Identifier.(*Identity).PublicKey()
|
||||||
|
return cmd(user, pk)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
forPubkeyUser := func(cmd func(ssh.PublicKey)) {
|
||||||
|
invalidUsers := []string{}
|
||||||
|
invalidKeys := []string{}
|
||||||
|
noKeyUsers := []string{}
|
||||||
|
var keyType string
|
||||||
|
for _, v := range args[1:]{
|
||||||
|
switch {
|
||||||
|
case keyType != "":
|
||||||
|
pk, _, _, _, err := ssh.ParseAuthorizedKey([]byte(keyType + " " + v))
|
||||||
|
if err == nil {
|
||||||
|
cmd(pk)
|
||||||
|
} else {
|
||||||
|
invalidKeys = append(invalidKeys, keyType + " " + v)
|
||||||
|
}
|
||||||
|
keyType = ""
|
||||||
|
case strings.HasPrefix(v, "ssh-"):
|
||||||
|
keyType = v
|
||||||
|
default:
|
||||||
|
user, ok := h.GetUser(v)
|
||||||
|
if ok {
|
||||||
|
pk := user.Identifier.(*Identity).PublicKey()
|
||||||
|
if pk == nil {
|
||||||
|
noKeyUsers = append(noKeyUsers, user.Identifier.Name())
|
||||||
|
} else {
|
||||||
|
cmd(pk)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
invalidUsers = append(invalidUsers, v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(noKeyUsers) != 0 {
|
||||||
|
sendMsg("users without a public key: %v", noKeyUsers)
|
||||||
|
}
|
||||||
|
if len(invalidUsers) != 0 {
|
||||||
|
sendMsg("invalid users: %v", invalidUsers)
|
||||||
|
}
|
||||||
|
if len(invalidKeys) != 0 {
|
||||||
|
sendMsg("invalid keys: %v", invalidKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch args[0] {
|
||||||
|
case "help":
|
||||||
|
sendMsg("Usage: /whitelist help | on | off | add {PUBKEY|USER}... | remove {PUBKEY|USER}... | import [AGE] | reload {keep|flush} | reverify | status")
|
||||||
|
sendMsg("help: this help message")
|
||||||
|
sendMsg("on, off: set whitelist mode (applies to new connections)")
|
||||||
|
sendMsg("add, remove: add or remove keys from the whitelist")
|
||||||
|
sendMsg("import: add all keys of users connected since AGE (default 0) ago to the whitelist")
|
||||||
|
sendMsg("reload: re-read the whitelist file and keep or discard entries in the current whitelist but not in the file")
|
||||||
|
sendMsg("reverify: kick all users not in the whitelist if whitelisting is enabled")
|
||||||
|
sendMsg("status: show status information")
|
||||||
|
case "on":
|
||||||
|
h.auth.WhitelistMode = true
|
||||||
|
case "off":
|
||||||
|
h.auth.WhitelistMode = false
|
||||||
|
case "add":
|
||||||
|
forPubkeyUser(func(pk ssh.PublicKey){h.auth.Whitelist(pk, 0)})
|
||||||
|
case "remove":
|
||||||
|
forPubkeyUser(func(pk ssh.PublicKey){h.auth.Whitelist(pk, 1)})
|
||||||
|
case "import":
|
||||||
|
var since time.Duration
|
||||||
|
var err error
|
||||||
|
if len(args) > 1 {
|
||||||
|
since, err = time.ParseDuration(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cutoff := time.Now().Add(-since)
|
||||||
|
noKeyUsers := []string{}
|
||||||
|
forConnectedUsers(func(user *chat.Member, pk ssh.PublicKey) error {
|
||||||
|
if user.Joined().Before(cutoff) {
|
||||||
|
if pk == nil {
|
||||||
|
noKeyUsers = append(noKeyUsers, user.Identifier.Name())
|
||||||
|
} else {
|
||||||
|
h.auth.Whitelist(pk, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if len(noKeyUsers) != 0 {
|
||||||
|
sendMsg("users without a public key: %v", noKeyUsers)
|
||||||
|
}
|
||||||
|
case "reload":
|
||||||
|
if !(len(args) > 1 && (args[1] == "keep" || args[1] == "flush")) {
|
||||||
|
return errors.New("must specify whether to keep or flush current entries")
|
||||||
|
}
|
||||||
|
if args[1] == "flush" {
|
||||||
|
h.auth.whitelist.Clear()
|
||||||
|
}
|
||||||
|
err := h.auth.LoadWhitelistFromFile(h.auth.whitelistFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
case "reverify":
|
||||||
|
if !h.auth.WhitelistMode {
|
||||||
|
sendMsg("whitelist is disabled, so nobody will be kicked")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
forConnectedUsers(func(user *chat.Member, pk ssh.PublicKey)error{
|
||||||
|
if !h.auth.IsOp(pk) && h.auth.CheckPublicKey(pk) != nil { // TODO: why doesn't CheckPublicKey do this?
|
||||||
|
user.Close() // TODO: some message anywhere?
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
case "status":
|
||||||
|
if h.auth.WhitelistMode {
|
||||||
|
sendMsg("The whitelist is currently enabled.")
|
||||||
|
} else {
|
||||||
|
sendMsg("The whitelist is currently disabled.")
|
||||||
|
}
|
||||||
|
whitelistedUsers := []string{}
|
||||||
|
whitelistedKeys := []string{}
|
||||||
|
// TODO: this can probably be optimized
|
||||||
|
h.auth.whitelist.Each(func(key string, item set.Item) error {
|
||||||
|
keyFP := item.Key()
|
||||||
|
if forConnectedUsers(func (user *chat.Member, pk ssh.PublicKey) error {
|
||||||
|
if pk != nil && sshd.Fingerprint(pk) == keyFP {
|
||||||
|
whitelistedUsers = append(whitelistedUsers, user.Name())
|
||||||
|
return errors.New("not an actual error, but exit early because we found the key")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}) == nil {
|
||||||
|
// if we land here, the key matches no users
|
||||||
|
whitelistedKeys = append(whitelistedKeys, keyFP)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if len(whitelistedUsers) != 0 {
|
||||||
|
sendMsg("The following connected users are on the whitelist: %v", whitelistedUsers)
|
||||||
|
}
|
||||||
|
if len(whitelistedKeys) != 0 {
|
||||||
|
sendMsg("The following keys of not connected users are on the whitelist: %v", whitelistedKeys)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return errors.New("invalid subcommand: " + args[0])
|
||||||
|
}
|
||||||
|
if len(replyLines) != 0 {
|
||||||
|
room.Send(message.NewSystemMsg(strings.Join(replyLines, "\n"), msg.From()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
@ -189,6 +189,7 @@ func TestHostWhitelist(t *testing.T) {
|
|||||||
|
|
||||||
clientpubkey, _ := ssh.NewPublicKey(clientkey.Public())
|
clientpubkey, _ := ssh.NewPublicKey(clientkey.Public())
|
||||||
auth.Whitelist(clientpubkey, 0)
|
auth.Whitelist(clientpubkey, 0)
|
||||||
|
auth.WhitelistMode = true
|
||||||
|
|
||||||
err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) error { return nil })
|
err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) error { return nil })
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user