s/whitelist/allowlist/

This commit is contained in:
mik2k2 2021-12-23 18:58:03 +01:00
parent dbc0bdbeac
commit 110aca2e8b
5 changed files with 74 additions and 74 deletions

64
auth.go
View File

@ -18,9 +18,9 @@ import (
"golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh"
) )
// ErrNotWhitelisted Is the error returned when a key is checked that is not whitelisted, // ErrNotAllowlisted Is the error returned when a key is checked that is not allowlisted,
// when whitelisting is enabled. // when allowlisting is enabled.
var ErrNotWhitelisted = errors.New("not whitelisted") var ErrNotAllowlisted = errors.New("not allowlisted")
// ErrBanned is the error returned when a client is banned. // ErrBanned is the error returned when a client is banned.
var ErrBanned = errors.New("banned") var ErrBanned = errors.New("banned")
@ -50,19 +50,19 @@ func newAuthAddr(addr net.Addr) string {
return host return host
} }
// Auth stores lookups for bans, whitelists, and ops. It implements the sshd.Auth interface. // Auth stores lookups for bans, allowlists, and ops. It implements the sshd.Auth interface.
// If the contained passphrase is not empty, it complements a whitelist. // If the contained passphrase is not empty, it complements a allowlist.
type Auth struct { type Auth struct {
passphraseHash []byte passphraseHash []byte
whitelistModeMu sync.RWMutex allowlistModeMu sync.RWMutex
whitelistMode bool allowlistMode bool
bannedAddr *set.Set bannedAddr *set.Set
bannedClient *set.Set bannedClient *set.Set
banned *set.Set banned *set.Set
whitelist *set.Set allowlist *set.Set
ops *set.Set ops *set.Set
opFile string opFile string
whitelistFile string allowlistFile string
} }
// NewAuth creates a new empty Auth. // NewAuth creates a new empty Auth.
@ -71,21 +71,21 @@ func NewAuth() *Auth {
bannedAddr: set.New(), bannedAddr: set.New(),
bannedClient: set.New(), bannedClient: set.New(),
banned: set.New(), banned: set.New(),
whitelist: set.New(), allowlist: set.New(),
ops: set.New(), ops: set.New(),
} }
} }
func (a *Auth) WhitelistMode() bool { func (a *Auth) AllowlistMode() bool {
a.whitelistModeMu.RLock() a.allowlistModeMu.RLock()
defer a.whitelistModeMu.RUnlock() defer a.allowlistModeMu.RUnlock()
return a.whitelistMode return a.allowlistMode
} }
func (a *Auth) SetWhitelistMode(value bool) { func (a *Auth) SetAllowlistMode(value bool) {
a.whitelistModeMu.Lock() a.allowlistModeMu.Lock()
defer a.whitelistModeMu.Unlock() defer a.allowlistModeMu.Unlock()
a.whitelistMode = value a.allowlistMode = value
} }
// SetPassphrase enables passphrase authentication with the given passphrase. // SetPassphrase enables passphrase authentication with the given passphrase.
@ -101,7 +101,7 @@ func (a *Auth) SetPassphrase(passphrase string) {
// AllowAnonymous determines if anonymous users are permitted. // AllowAnonymous determines if anonymous users are permitted.
func (a *Auth) AllowAnonymous() bool { func (a *Auth) AllowAnonymous() bool {
return !a.WhitelistMode() && a.passphraseHash == nil return !a.AllowlistMode() && a.passphraseHash == nil
} }
// AcceptPassphrase determines if passphrase authentication is accepted. // AcceptPassphrase determines if passphrase authentication is accepted.
@ -134,11 +134,11 @@ func (a *Auth) CheckBans(addr net.Addr, key ssh.PublicKey, clientVersion string)
// CheckPubkey determines if a pubkey fingerprint is permitted. // CheckPubkey determines if a pubkey fingerprint is permitted.
func (a *Auth) CheckPublicKey(key ssh.PublicKey) error { func (a *Auth) CheckPublicKey(key ssh.PublicKey) error {
authkey := newAuthKey(key) authkey := newAuthKey(key)
whitelisted := a.whitelist.In(authkey) allowlisted := a.allowlist.In(authkey)
if a.AllowAnonymous() || whitelisted || a.IsOp(key) { if a.AllowAnonymous() || allowlisted || a.IsOp(key) {
return nil return nil
} else { } else {
return ErrNotWhitelisted return ErrNotAllowlisted
} }
} }
@ -180,29 +180,29 @@ func (a *Auth) LoadOpsFromFile(path string) error {
return fromFile(path, func(key ssh.PublicKey) { a.Op(key, 0) }) return fromFile(path, func(key ssh.PublicKey) { a.Op(key, 0) })
} }
// Whitelist will set a public key as a whitelisted user. // Allowlist will set a public key as a allowlisted user.
func (a *Auth) Whitelist(key ssh.PublicKey, d time.Duration) { func (a *Auth) Allowlist(key ssh.PublicKey, d time.Duration) {
if key == nil { if key == nil {
return return
} }
var err error var err error
authItem := newAuthItem(key) authItem := newAuthItem(key)
if d != 0 { if d != 0 {
err = a.whitelist.Set(set.Expire(authItem, d)) err = a.allowlist.Set(set.Expire(authItem, d))
} else { } else {
err = a.whitelist.Set(authItem) err = a.allowlist.Set(authItem)
} }
if err == nil { if err == nil {
logger.Debugf("Added to whitelist: %q (for %s)", authItem.Key(), d) logger.Debugf("Added to allowlist: %q (for %s)", authItem.Key(), d)
} else { } else {
logger.Errorf("Error adding %q to whitelist for %s: %s", authItem.Key(), d, err) logger.Errorf("Error adding %q to allowlist for %s: %s", authItem.Key(), d, err)
} }
} }
// LoadWhitelistFromFile reads a file in authorized_keys format and whitelists public keys // LoadAllowlistFromFile reads a file in authorized_keys format and allowlists public keys
func (a *Auth) LoadWhitelistFromFile(path string) error { func (a *Auth) LoadAllowlistFromFile(path string) error {
a.whitelistFile = path a.allowlistFile = path
return fromFile(path, func(key ssh.PublicKey) { a.Whitelist(key, 0) }) return fromFile(path, func(key ssh.PublicKey) { a.Allowlist(key, 0) })
} }
// Ban will set a public key as banned. // Ban will set a public key as banned.

View File

@ -21,7 +21,7 @@ func ClonePublicKey(key ssh.PublicKey) (ssh.PublicKey, error) {
return ssh.ParsePublicKey(key.Marshal()) return ssh.ParsePublicKey(key.Marshal())
} }
func TestAuthWhitelist(t *testing.T) { func TestAuthAllowlist(t *testing.T) {
key, err := NewRandomPublicKey(512) key, err := NewRandomPublicKey(512)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
@ -33,8 +33,8 @@ func TestAuthWhitelist(t *testing.T) {
t.Error("Failed to permit in default state:", err) t.Error("Failed to permit in default state:", err)
} }
auth.Whitelist(key, 0) auth.Allowlist(key, 0)
auth.SetWhitelistMode(true) auth.SetAllowlistMode(true)
keyClone, err := ClonePublicKey(key) keyClone, err := ClonePublicKey(key)
if err != nil { if err != nil {
@ -47,7 +47,7 @@ func TestAuthWhitelist(t *testing.T) {
err = auth.CheckPublicKey(keyClone) err = auth.CheckPublicKey(keyClone)
if err != nil { if err != nil {
t.Error("Failed to permit whitelisted:", err) t.Error("Failed to permit allowlisted:", err)
} }
key2, err := NewRandomPublicKey(512) key2, err := NewRandomPublicKey(512)
@ -57,7 +57,7 @@ func TestAuthWhitelist(t *testing.T) {
err = auth.CheckPublicKey(key2) err = auth.CheckPublicKey(key2)
if err == nil { if err == nil {
t.Error("Failed to restrict not whitelisted:", err) t.Error("Failed to restrict not allowlisted:", err)
} }
} }

View File

@ -34,8 +34,8 @@ type Options struct {
Pprof int `long:"pprof" description:"Enable pprof http server for profiling."` Pprof int `long:"pprof" description:"Enable pprof http server for profiling."`
Verbose []bool `short:"v" long:"verbose" description:"Show verbose logging."` Verbose []bool `short:"v" long:"verbose" description:"Show verbose logging."`
Version bool `long:"version" description:"Print version and exit."` Version bool `long:"version" description:"Print version and exit."`
Whitelist string `long:"whitelist" description:"Optional file of public keys who are allowed to connect."` Allowlist string `long:"allowlist" description:"Optional file of public keys who are allowed to connect."`
Passphrase string `long:"unsafe-passphrase" description:"Require an interactive passphrase to connect. Whitelist feature is more secure."` Passphrase string `long:"unsafe-passphrase" description:"Require an interactive passphrase to connect. Allowlist feature is more secure."`
} }
const extraHelp = `There are hidden options and easter eggs in ssh-chat. The source code is a good const extraHelp = `There are hidden options and easter eggs in ssh-chat. The source code is a good
@ -144,11 +144,11 @@ func main() {
fail(5, "Failed to load admins: %v\n", err) fail(5, "Failed to load admins: %v\n", err)
} }
err = auth.LoadWhitelistFromFile(options.Whitelist) err = auth.LoadAllowlistFromFile(options.Allowlist)
if err != nil { if err != nil {
fail(6, "Failed to load whitelist: %v\n", err) fail(6, "Failed to load allowlist: %v\n", err)
} }
auth.SetWhitelistMode(options.Whitelist != "") auth.SetAllowlistMode(options.Allowlist != "")
if options.Motd != "" { if options.Motd != "" {
host.GetMOTD = func() (string, error) { host.GetMOTD = func() (string, error) {

36
host.go
View File

@ -780,7 +780,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
if pk == nil { if pk == nil {
noKeyUsers = append(noKeyUsers, user.Identifier.Name()) noKeyUsers = append(noKeyUsers, user.Identifier.Name())
} else { } else {
h.auth.Whitelist(pk, 0) h.auth.Allowlist(pk, 0)
} }
} }
return nil return nil
@ -796,13 +796,13 @@ func (h *Host) InitCommands(c *chat.Commands) {
return errors.New("must specify whether to keep or flush current entries") return errors.New("must specify whether to keep or flush current entries")
} }
if args[0] == "flush" { if args[0] == "flush" {
h.auth.whitelist.Clear() h.auth.allowlist.Clear()
} }
return h.auth.LoadWhitelistFromFile(h.auth.whitelistFile) return h.auth.LoadAllowlistFromFile(h.auth.allowlistFile)
} }
allowlistReverify := func(room *chat.Room) []string { allowlistReverify := func(room *chat.Room) []string {
if !h.auth.WhitelistMode() { if !h.auth.AllowlistMode() {
return []string{"allowlist is disabled, so nobody will be kicked"} return []string{"allowlist is disabled, so nobody will be kicked"}
} }
var kicked []string var kicked []string
@ -820,32 +820,32 @@ func (h *Host) InitCommands(c *chat.Commands) {
} }
allowlistStatus := func() (msgs []string) { allowlistStatus := func() (msgs []string) {
if h.auth.WhitelistMode() { if h.auth.AllowlistMode() {
msgs = []string{"The allowlist is currently enabled."} msgs = []string{"The allowlist is currently enabled."}
} else { } else {
msgs = []string{"The allowlist is currently disabled."} msgs = []string{"The allowlist is currently disabled."}
} }
whitelistedUsers := []string{} allowlistedUsers := []string{}
whitelistedKeys := []string{} allowlistedKeys := []string{}
h.auth.whitelist.Each(func(key string, item set.Item) error { h.auth.allowlist.Each(func(key string, item set.Item) error {
keyFP := item.Key() keyFP := item.Key()
if forConnectedUsers(func(user *chat.Member, pk ssh.PublicKey) error { if forConnectedUsers(func(user *chat.Member, pk ssh.PublicKey) error {
if pk != nil && sshd.Fingerprint(pk) == keyFP { if pk != nil && sshd.Fingerprint(pk) == keyFP {
whitelistedUsers = append(whitelistedUsers, user.Name()) allowlistedUsers = append(allowlistedUsers, user.Name())
return errors.New("not an actual error, but exit early because we found the key") return errors.New("not an actual error, but exit early because we found the key")
} }
return nil return nil
}) == nil { }) == nil {
// if we land here, the key matches no users // if we land here, the key matches no users
whitelistedKeys = append(whitelistedKeys, keyFP) allowlistedKeys = append(allowlistedKeys, keyFP)
} }
return nil return nil
}) })
if len(whitelistedUsers) != 0 { if len(allowlistedUsers) != 0 {
msgs = append(msgs, fmt.Sprintf("The following connected users are on the allowlist: %v", whitelistedUsers)) msgs = append(msgs, fmt.Sprintf("The following connected users are on the allowlist: %v", allowlistedUsers))
} }
if len(whitelistedKeys) != 0 { if len(allowlistedKeys) != 0 {
msgs = append(msgs, fmt.Sprintf("The following keys of not connected users are on the allowlist: %v", whitelistedKeys)) msgs = append(msgs, fmt.Sprintf("The following keys of not connected users are on the allowlist: %v", allowlistedKeys))
} }
return return
} }
@ -872,13 +872,13 @@ func (h *Host) InitCommands(c *chat.Commands) {
case "help": case "help":
replyLines = allowlistHelptext replyLines = allowlistHelptext
case "on": case "on":
h.auth.SetWhitelistMode(true) h.auth.SetAllowlistMode(true)
case "off": case "off":
h.auth.SetWhitelistMode(false) h.auth.SetAllowlistMode(false)
case "add": case "add":
replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Whitelist(pk, 0) }) replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, 0) })
case "remove": case "remove":
replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Whitelist(pk, 1) }) replyLines = forPubkeyUser(args[1:], func(pk ssh.PublicKey) { h.auth.Allowlist(pk, 1) })
case "import": case "import":
replyLines, err = allowlistImport(args[1:]) replyLines, err = allowlistImport(args[1:])
case "reload": case "reload":

View File

@ -173,7 +173,7 @@ func TestHostNameCollision(t *testing.T) {
} }
} }
func TestHostWhitelist(t *testing.T) { func TestHostAllowlist(t *testing.T) {
auth := NewAuth() auth := NewAuth()
s, host := getHost(t, auth) s, host := getHost(t, auth)
defer s.Close() defer s.Close()
@ -192,12 +192,12 @@ func TestHostWhitelist(t *testing.T) {
} }
clientpubkey, _ := ssh.NewPublicKey(clientkey.Public()) clientpubkey, _ := ssh.NewPublicKey(clientkey.Public())
auth.Whitelist(clientpubkey, 0) auth.Allowlist(clientpubkey, 0)
auth.SetWhitelistMode(true) auth.SetAllowlistMode(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 {
t.Error("Failed to block unwhitelisted connection.") t.Error("Failed to block unallowlisted connection.")
} }
} }
@ -268,52 +268,52 @@ func TestHostAllowlistCommand(t *testing.T) {
} }
sendCmd("/allowlist on") sendCmd("/allowlist on")
if !host.auth.WhitelistMode() { if !host.auth.AllowlistMode() {
t.Error("allowlist not enabled after /allowlist on") t.Error("allowlist not enabled after /allowlist on")
} }
sendCmd("/allowlist off") sendCmd("/allowlist off")
if host.auth.WhitelistMode() { if host.auth.AllowlistMode() {
t.Error("allowlist not disabled after /allowlist off") t.Error("allowlist not disabled after /allowlist off")
} }
testKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPUiNw0nQku4pcUCbZcJlIEAIf5bXJYTy/DKI1vh5b+P" testKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPUiNw0nQku4pcUCbZcJlIEAIf5bXJYTy/DKI1vh5b+P"
testKeyFP := "SHA256:GJNSl9NUcOS2pZYALn0C5Qgfh5deT+R+FfqNIUvpM9s=" testKeyFP := "SHA256:GJNSl9NUcOS2pZYALn0C5Qgfh5deT+R+FfqNIUvpM9s="
if host.auth.whitelist.Len() != 0 { if host.auth.allowlist.Len() != 0 {
t.Error("allowlist not empty before adding anyone") t.Error("allowlist not empty before adding anyone")
} }
sendCmd("/allowlist add ssh-invalid blah ssh-rsa wrongAsWell invalid foo bar %s", testKey) sendCmd("/allowlist add ssh-invalid blah ssh-rsa wrongAsWell invalid foo bar %s", testKey)
assertLineEq("users without a public key: [foo]\r") assertLineEq("users without a public key: [foo]\r")
assertLineEq("invalid users: [invalid]\r") assertLineEq("invalid users: [invalid]\r")
assertLineEq("invalid keys: [ssh-invalid blah ssh-rsa wrongAsWell]\r") assertLineEq("invalid keys: [ssh-invalid blah ssh-rsa wrongAsWell]\r")
if !host.auth.whitelist.In(testKeyFP) || !host.auth.whitelist.In(clientKeyFP) { if !host.auth.allowlist.In(testKeyFP) || !host.auth.allowlist.In(clientKeyFP) {
t.Error("failed to add keys to allowlist") t.Error("failed to add keys to allowlist")
} }
sendCmd("/allowlist remove invalid bar") sendCmd("/allowlist remove invalid bar")
assertLineEq("invalid users: [invalid]\r") assertLineEq("invalid users: [invalid]\r")
if host.auth.whitelist.In(clientKeyFP) { if host.auth.allowlist.In(clientKeyFP) {
t.Error("failed to remove key from allowlist") t.Error("failed to remove key from allowlist")
} }
if !host.auth.whitelist.In(testKeyFP) { if !host.auth.allowlist.In(testKeyFP) {
t.Error("removed wrong key") t.Error("removed wrong key")
} }
sendCmd("/allowlist import 5h") sendCmd("/allowlist import 5h")
if host.auth.whitelist.In(clientKeyFP) { if host.auth.allowlist.In(clientKeyFP) {
t.Error("imporrted key not seen long enough") t.Error("imporrted key not seen long enough")
} }
sendCmd("/allowlist import") sendCmd("/allowlist import")
assertLineEq("users without a public key: [foo]\r") assertLineEq("users without a public key: [foo]\r")
if !host.auth.whitelist.In(clientKeyFP) { if !host.auth.allowlist.In(clientKeyFP) {
t.Error("failed to import key") t.Error("failed to import key")
} }
sendCmd("/allowlist reload keep") sendCmd("/allowlist reload keep")
if !host.auth.whitelist.In(testKeyFP) { if !host.auth.allowlist.In(testKeyFP) {
t.Error("cleared allowlist to be kept") t.Error("cleared allowlist to be kept")
} }
sendCmd("/allowlist reload flush") sendCmd("/allowlist reload flush")
if host.auth.whitelist.In(testKeyFP) { if host.auth.allowlist.In(testKeyFP) {
t.Error("kept allowlist to be cleared") t.Error("kept allowlist to be cleared")
} }
sendCmd("/allowlist reload thisIsWrong") sendCmd("/allowlist reload thisIsWrong")