mirror of
https://github.com/shazow/ssh-chat.git
synced 2025-04-22 19:50:33 +03:00
s/whitelist/allowlist/
This commit is contained in:
parent
dbc0bdbeac
commit
110aca2e8b
64
auth.go
64
auth.go
@ -18,9 +18,9 @@ import (
|
||||
"golang.org/x/crypto/ssh"
|
||||
)
|
||||
|
||||
// ErrNotWhitelisted Is the error returned when a key is checked that is not whitelisted,
|
||||
// when whitelisting is enabled.
|
||||
var ErrNotWhitelisted = errors.New("not whitelisted")
|
||||
// ErrNotAllowlisted Is the error returned when a key is checked that is not allowlisted,
|
||||
// when allowlisting is enabled.
|
||||
var ErrNotAllowlisted = errors.New("not allowlisted")
|
||||
|
||||
// ErrBanned is the error returned when a client is banned.
|
||||
var ErrBanned = errors.New("banned")
|
||||
@ -50,19 +50,19 @@ func newAuthAddr(addr net.Addr) string {
|
||||
return host
|
||||
}
|
||||
|
||||
// Auth stores lookups for bans, whitelists, and ops. It implements the sshd.Auth interface.
|
||||
// If the contained passphrase is not empty, it complements a whitelist.
|
||||
// Auth stores lookups for bans, allowlists, and ops. It implements the sshd.Auth interface.
|
||||
// If the contained passphrase is not empty, it complements a allowlist.
|
||||
type Auth struct {
|
||||
passphraseHash []byte
|
||||
whitelistModeMu sync.RWMutex
|
||||
whitelistMode bool
|
||||
allowlistModeMu sync.RWMutex
|
||||
allowlistMode bool
|
||||
bannedAddr *set.Set
|
||||
bannedClient *set.Set
|
||||
banned *set.Set
|
||||
whitelist *set.Set
|
||||
allowlist *set.Set
|
||||
ops *set.Set
|
||||
opFile string
|
||||
whitelistFile string
|
||||
allowlistFile string
|
||||
}
|
||||
|
||||
// NewAuth creates a new empty Auth.
|
||||
@ -71,21 +71,21 @@ func NewAuth() *Auth {
|
||||
bannedAddr: set.New(),
|
||||
bannedClient: set.New(),
|
||||
banned: set.New(),
|
||||
whitelist: set.New(),
|
||||
allowlist: set.New(),
|
||||
ops: set.New(),
|
||||
}
|
||||
}
|
||||
|
||||
func (a *Auth) WhitelistMode() bool {
|
||||
a.whitelistModeMu.RLock()
|
||||
defer a.whitelistModeMu.RUnlock()
|
||||
return a.whitelistMode
|
||||
func (a *Auth) AllowlistMode() bool {
|
||||
a.allowlistModeMu.RLock()
|
||||
defer a.allowlistModeMu.RUnlock()
|
||||
return a.allowlistMode
|
||||
}
|
||||
|
||||
func (a *Auth) SetWhitelistMode(value bool) {
|
||||
a.whitelistModeMu.Lock()
|
||||
defer a.whitelistModeMu.Unlock()
|
||||
a.whitelistMode = value
|
||||
func (a *Auth) SetAllowlistMode(value bool) {
|
||||
a.allowlistModeMu.Lock()
|
||||
defer a.allowlistModeMu.Unlock()
|
||||
a.allowlistMode = value
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (a *Auth) AllowAnonymous() bool {
|
||||
return !a.WhitelistMode() && a.passphraseHash == nil
|
||||
return !a.AllowlistMode() && a.passphraseHash == nil
|
||||
}
|
||||
|
||||
// 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.
|
||||
func (a *Auth) CheckPublicKey(key ssh.PublicKey) error {
|
||||
authkey := newAuthKey(key)
|
||||
whitelisted := a.whitelist.In(authkey)
|
||||
if a.AllowAnonymous() || whitelisted || a.IsOp(key) {
|
||||
allowlisted := a.allowlist.In(authkey)
|
||||
if a.AllowAnonymous() || allowlisted || a.IsOp(key) {
|
||||
return nil
|
||||
} 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) })
|
||||
}
|
||||
|
||||
// Whitelist will set a public key as a whitelisted user.
|
||||
func (a *Auth) Whitelist(key ssh.PublicKey, d time.Duration) {
|
||||
// Allowlist will set a public key as a allowlisted user.
|
||||
func (a *Auth) Allowlist(key ssh.PublicKey, d time.Duration) {
|
||||
if key == nil {
|
||||
return
|
||||
}
|
||||
var err error
|
||||
authItem := newAuthItem(key)
|
||||
if d != 0 {
|
||||
err = a.whitelist.Set(set.Expire(authItem, d))
|
||||
err = a.allowlist.Set(set.Expire(authItem, d))
|
||||
} else {
|
||||
err = a.whitelist.Set(authItem)
|
||||
err = a.allowlist.Set(authItem)
|
||||
}
|
||||
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 {
|
||||
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
|
||||
func (a *Auth) LoadWhitelistFromFile(path string) error {
|
||||
a.whitelistFile = path
|
||||
return fromFile(path, func(key ssh.PublicKey) { a.Whitelist(key, 0) })
|
||||
// LoadAllowlistFromFile reads a file in authorized_keys format and allowlists public keys
|
||||
func (a *Auth) LoadAllowlistFromFile(path string) error {
|
||||
a.allowlistFile = path
|
||||
return fromFile(path, func(key ssh.PublicKey) { a.Allowlist(key, 0) })
|
||||
}
|
||||
|
||||
// Ban will set a public key as banned.
|
||||
|
10
auth_test.go
10
auth_test.go
@ -21,7 +21,7 @@ func ClonePublicKey(key ssh.PublicKey) (ssh.PublicKey, error) {
|
||||
return ssh.ParsePublicKey(key.Marshal())
|
||||
}
|
||||
|
||||
func TestAuthWhitelist(t *testing.T) {
|
||||
func TestAuthAllowlist(t *testing.T) {
|
||||
key, err := NewRandomPublicKey(512)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
@ -33,8 +33,8 @@ func TestAuthWhitelist(t *testing.T) {
|
||||
t.Error("Failed to permit in default state:", err)
|
||||
}
|
||||
|
||||
auth.Whitelist(key, 0)
|
||||
auth.SetWhitelistMode(true)
|
||||
auth.Allowlist(key, 0)
|
||||
auth.SetAllowlistMode(true)
|
||||
|
||||
keyClone, err := ClonePublicKey(key)
|
||||
if err != nil {
|
||||
@ -47,7 +47,7 @@ func TestAuthWhitelist(t *testing.T) {
|
||||
|
||||
err = auth.CheckPublicKey(keyClone)
|
||||
if err != nil {
|
||||
t.Error("Failed to permit whitelisted:", err)
|
||||
t.Error("Failed to permit allowlisted:", err)
|
||||
}
|
||||
|
||||
key2, err := NewRandomPublicKey(512)
|
||||
@ -57,7 +57,7 @@ func TestAuthWhitelist(t *testing.T) {
|
||||
|
||||
err = auth.CheckPublicKey(key2)
|
||||
if err == nil {
|
||||
t.Error("Failed to restrict not whitelisted:", err)
|
||||
t.Error("Failed to restrict not allowlisted:", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -34,8 +34,8 @@ type Options struct {
|
||||
Pprof int `long:"pprof" description:"Enable pprof http server for profiling."`
|
||||
Verbose []bool `short:"v" long:"verbose" description:"Show verbose logging."`
|
||||
Version bool `long:"version" description:"Print version and exit."`
|
||||
Whitelist string `long:"whitelist" 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."`
|
||||
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. Allowlist feature is more secure."`
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
err = auth.LoadWhitelistFromFile(options.Whitelist)
|
||||
err = auth.LoadAllowlistFromFile(options.Allowlist)
|
||||
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 != "" {
|
||||
host.GetMOTD = func() (string, error) {
|
||||
|
36
host.go
36
host.go
@ -780,7 +780,7 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
if pk == nil {
|
||||
noKeyUsers = append(noKeyUsers, user.Identifier.Name())
|
||||
} else {
|
||||
h.auth.Whitelist(pk, 0)
|
||||
h.auth.Allowlist(pk, 0)
|
||||
}
|
||||
}
|
||||
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")
|
||||
}
|
||||
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 {
|
||||
if !h.auth.WhitelistMode() {
|
||||
if !h.auth.AllowlistMode() {
|
||||
return []string{"allowlist is disabled, so nobody will be kicked"}
|
||||
}
|
||||
var kicked []string
|
||||
@ -820,32 +820,32 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
}
|
||||
|
||||
allowlistStatus := func() (msgs []string) {
|
||||
if h.auth.WhitelistMode() {
|
||||
if h.auth.AllowlistMode() {
|
||||
msgs = []string{"The allowlist is currently enabled."}
|
||||
} else {
|
||||
msgs = []string{"The allowlist is currently disabled."}
|
||||
}
|
||||
whitelistedUsers := []string{}
|
||||
whitelistedKeys := []string{}
|
||||
h.auth.whitelist.Each(func(key string, item set.Item) error {
|
||||
allowlistedUsers := []string{}
|
||||
allowlistedKeys := []string{}
|
||||
h.auth.allowlist.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())
|
||||
allowlistedUsers = append(allowlistedUsers, 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)
|
||||
allowlistedKeys = append(allowlistedKeys, keyFP)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if len(whitelistedUsers) != 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("The following connected users are on the allowlist: %v", whitelistedUsers))
|
||||
if len(allowlistedUsers) != 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("The following connected users are on the allowlist: %v", allowlistedUsers))
|
||||
}
|
||||
if len(whitelistedKeys) != 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("The following keys of not connected users are on the allowlist: %v", whitelistedKeys))
|
||||
if len(allowlistedKeys) != 0 {
|
||||
msgs = append(msgs, fmt.Sprintf("The following keys of not connected users are on the allowlist: %v", allowlistedKeys))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -872,13 +872,13 @@ func (h *Host) InitCommands(c *chat.Commands) {
|
||||
case "help":
|
||||
replyLines = allowlistHelptext
|
||||
case "on":
|
||||
h.auth.SetWhitelistMode(true)
|
||||
h.auth.SetAllowlistMode(true)
|
||||
case "off":
|
||||
h.auth.SetWhitelistMode(false)
|
||||
h.auth.SetAllowlistMode(false)
|
||||
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":
|
||||
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":
|
||||
replyLines, err = allowlistImport(args[1:])
|
||||
case "reload":
|
||||
|
28
host_test.go
28
host_test.go
@ -173,7 +173,7 @@ func TestHostNameCollision(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestHostWhitelist(t *testing.T) {
|
||||
func TestHostAllowlist(t *testing.T) {
|
||||
auth := NewAuth()
|
||||
s, host := getHost(t, auth)
|
||||
defer s.Close()
|
||||
@ -192,12 +192,12 @@ func TestHostWhitelist(t *testing.T) {
|
||||
}
|
||||
|
||||
clientpubkey, _ := ssh.NewPublicKey(clientkey.Public())
|
||||
auth.Whitelist(clientpubkey, 0)
|
||||
auth.SetWhitelistMode(true)
|
||||
auth.Allowlist(clientpubkey, 0)
|
||||
auth.SetAllowlistMode(true)
|
||||
|
||||
err = sshd.ConnectShell(target, "foo", func(r io.Reader, w io.WriteCloser) error { return 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")
|
||||
if !host.auth.WhitelistMode() {
|
||||
if !host.auth.AllowlistMode() {
|
||||
t.Error("allowlist not enabled after /allowlist on")
|
||||
}
|
||||
sendCmd("/allowlist off")
|
||||
if host.auth.WhitelistMode() {
|
||||
if host.auth.AllowlistMode() {
|
||||
t.Error("allowlist not disabled after /allowlist off")
|
||||
}
|
||||
|
||||
testKey := "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIPUiNw0nQku4pcUCbZcJlIEAIf5bXJYTy/DKI1vh5b+P"
|
||||
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")
|
||||
}
|
||||
sendCmd("/allowlist add ssh-invalid blah ssh-rsa wrongAsWell invalid foo bar %s", testKey)
|
||||
assertLineEq("users without a public key: [foo]\r")
|
||||
assertLineEq("invalid users: [invalid]\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")
|
||||
}
|
||||
sendCmd("/allowlist remove invalid bar")
|
||||
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")
|
||||
}
|
||||
if !host.auth.whitelist.In(testKeyFP) {
|
||||
if !host.auth.allowlist.In(testKeyFP) {
|
||||
t.Error("removed wrong key")
|
||||
}
|
||||
|
||||
sendCmd("/allowlist import 5h")
|
||||
if host.auth.whitelist.In(clientKeyFP) {
|
||||
if host.auth.allowlist.In(clientKeyFP) {
|
||||
t.Error("imporrted key not seen long enough")
|
||||
}
|
||||
sendCmd("/allowlist import")
|
||||
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")
|
||||
}
|
||||
|
||||
sendCmd("/allowlist reload keep")
|
||||
if !host.auth.whitelist.In(testKeyFP) {
|
||||
if !host.auth.allowlist.In(testKeyFP) {
|
||||
t.Error("cleared allowlist to be kept")
|
||||
}
|
||||
sendCmd("/allowlist reload flush")
|
||||
if host.auth.whitelist.In(testKeyFP) {
|
||||
if host.auth.allowlist.In(testKeyFP) {
|
||||
t.Error("kept allowlist to be cleared")
|
||||
}
|
||||
sendCmd("/allowlist reload thisIsWrong")
|
||||
|
Loading…
x
Reference in New Issue
Block a user