def setUp(self): self.checker = SSHPublicKeyDatabase() self.sshDir = FilePath(self.mktemp()) self.sshDir.makedirs() self.key1 = base64.encodestring("foobar") self.key2 = base64.encodestring("eggspam") self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2) self.mockos = MockOS() self.mockos.path = self.sshDir.path self.patch(os.path, "expanduser", self.mockos.expanduser) self.patch(pwd, "getpwnam", self.mockos.getpwnam) self.patch(os, "seteuid", self.mockos.seteuid) self.patch(os, "setegid", self.mockos.setegid)
def setupManhole(application, config): """Setup an SSH manhole for the API service. The manhole port is taken from the C{manhole-port} option in the config file. If this option is not provided the api port plus 100 is used. @param application: The fluidinfo API L{Application} object. @param config: The configuration object. """ servicePort = config.getint('service', 'port') if config.has_option('service', 'manhole-port'): manholePort = config.getint('service', 'manhole-port') else: manholePort = servicePort + 100 def getManhole(_): manhole = Manhole(globals()) ps1 = 'fluidinfo-api [%d] > ' % servicePort ps2 = '... '.rjust(len(ps1)) manhole.ps = (ps1, ps2) return manhole realm = TerminalRealm() realm.chainedProtocolFactory.protocolFactory = getManhole portal = Portal(realm) portal.registerChecker(SSHPublicKeyDatabase()) factory = ConchFactory(portal) manholeService = TCPServer(manholePort, factory, interface='127.0.0.1') manholeService.setServiceParent(application)
def _cbRequestAvatarId(self, validKey, credentials): returnMe = SSHPublicKeyDatabase._cbRequestAvatarId( self, validKey[0], credentials) if returnMe == validKey[1].username: return validKey[1] else: return returnMe
def generateChecker(self, argstring=''): """ This checker factory ignores the argument string. Everything needed to authenticate users is pulled out of the public keys listed in user .ssh/ directories. """ return SSHPublicKeyDatabase()
def checkKey(self, credentials): """Override the default behavior so that we only allow the owner user.""" if not credentials.username in self.allowed_users: return False else: return SSHPublicKeyDatabase.checkKey(self, credentials)
def getShellFactory(interpreterType, **namespace): realm = ShellTerminalRealm(namespace, CommandAPI) sshPortal = portal.Portal(realm) factory = manhole_ssh.ConchFactory(sshPortal) factory.privateKeys = {'ssh-rsa': dreamssh_util.getPrivKey()} factory.publicKeys = {'ssh-rsa': dreamssh_util.getPubKey()} factory.portal.registerChecker(SSHPublicKeyDatabase()) return factory
def getAuthorizedKeysFiles(self, credentials): if config.ssh.usesystemkeys: return SSHPublicKeyDatabase.getAuthorizedKeysFiles( self, credentials) return [ FilePath( config.ssh.userauthkeys.replace("{{USER}}", credentials.username)) ]
def getGameShellFactory(**namespace): game = None sshRealm = gameshell.TerminalRealm(namespace, game) sshPortal = portal.Portal(sshRealm) factory = manhole_ssh.ConchFactory(sshPortal) factory.privateKeys = {'ssh-rsa': util.getPrivKey()} factory.publicKeys = {'ssh-rsa': util.getPubKey()} factory.portal.registerChecker(SSHPublicKeyDatabase()) return factory
def setUp(self): self.checker = SSHPublicKeyDatabase() self.key1 = base64.encodestring("foobar") self.key2 = base64.encodestring("eggspam") self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2) self.mockos = MockOS() self.mockos.path = FilePath(self.mktemp()) self.mockos.path.makedirs() self.sshDir = self.mockos.path.child('.ssh') self.sshDir.makedirs() userdb = UserDatabase() userdb.addUser('user', 'password', 1, 2, 'first last', self.mockos.path.path, '/bin/shell') self.patch(pwd, "getpwnam", userdb.getpwnam) self.patch(os, "seteuid", self.mockos.seteuid) self.patch(os, "setegid", self.mockos.setegid)
def test_registerChecker(self): """ L{SSHProcotolChecker.registerChecker} should add the given checker to the list of registered checkers. """ checker = SSHProtocolChecker() self.assertEquals(checker.credentialInterfaces, []) checker.registerChecker(SSHPublicKeyDatabase(), ) self.assertEquals(checker.credentialInterfaces, [ISSHPrivateKey]) self.assertIsInstance(checker.checkers[ISSHPrivateKey], SSHPublicKeyDatabase)
def test_registerCheckerWithInterface(self): """ If a apecific interface is passed into L{SSHProtocolChecker.registerChecker}, that interface should be registered instead of what the checker specifies in credentialIntefaces. """ checker = SSHProtocolChecker() self.assertEquals(checker.credentialInterfaces, []) checker.registerChecker(SSHPublicKeyDatabase(), IUsernamePassword) self.assertEquals(checker.credentialInterfaces, [IUsernamePassword]) self.assertIsInstance(checker.checkers[IUsernamePassword], SSHPublicKeyDatabase)
def checkKey(self, credentials): if config.ssh.usesystemkeys: return SSHPublicKeyDatabase.checkKey(self, credentials) for filePath in self.getAuthorizedKeysFiles(credentials): if not filePath.exists(): continue lines = filePath.open() for line in lines: lineData = line.split() if len(lineData) < 2: continue try: if base64.decodestring(lineData[1]) == credentials.blob: return True except binascii.Error: continue return False
def checkKey(self, credentials): if config.ssh.usesystemkeys: return SSHPublicKeyDatabase.checkKey( self, credentials) for filePath in self.getAuthorizedKeysFiles(credentials): if not filePath.exists(): continue lines = filePath.open() for line in lines: lineData = line.split() if len(lineData) < 2: continue try: if base64.decodestring(lineData[1]) == credentials.blob: return True except binascii.Error: continue return False
class SSHPublicKeyDatabaseTests(TestCase): """ Tests for L{SSHPublicKeyDatabase}. """ if pwd is None: skip = "Cannot run without pwd module" elif SSHPublicKeyDatabase is None: skip = "Cannot run without PyCrypto" def setUp(self): self.checker = SSHPublicKeyDatabase() self.sshDir = FilePath(self.mktemp()) self.sshDir.makedirs() self.key1 = base64.encodestring("foobar") self.key2 = base64.encodestring("eggspam") self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2) self.mockos = MockOS() self.mockos.path = self.sshDir.path self.patch(os.path, "expanduser", self.mockos.expanduser) self.patch(pwd, "getpwnam", self.mockos.getpwnam) self.patch(os, "seteuid", self.mockos.seteuid) self.patch(os, "setegid", self.mockos.setegid) def _testCheckKey(self, filename): self.sshDir.child(filename).setContent(self.content) user = UsernamePassword("user", "password") user.blob = "foobar" self.assertTrue(self.checker.checkKey(user)) user.blob = "eggspam" self.assertTrue(self.checker.checkKey(user)) user.blob = "notallowed" self.assertFalse(self.checker.checkKey(user)) def test_checkKey(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys file and check the keys against that file. """ self._testCheckKey("authorized_keys") self.assertEquals(self.mockos.seteuidCalls, []) self.assertEquals(self.mockos.setegidCalls, []) def test_checkKey2(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys2 file and check the keys against that file. """ self._testCheckKey("authorized_keys2") self.assertEquals(self.mockos.seteuidCalls, []) self.assertEquals(self.mockos.setegidCalls, []) def test_checkKeyAsRoot(self): """ If the key file is readable, L{SSHPublicKeyDatabase.checkKey} should switch its uid/gid to the ones of the authenticated user. """ keyFile = self.sshDir.child("authorized_keys") keyFile.setContent(self.content) # Fake permission error by changing the mode keyFile.chmod(0000) self.addCleanup(keyFile.chmod, 0777) # And restore the right mode when seteuid is called savedSeteuid = os.seteuid def seteuid(euid): keyFile.chmod(0777) return savedSeteuid(euid) self.patch(os, "seteuid", seteuid) user = UsernamePassword("user", "password") user.blob = "foobar" self.assertTrue(self.checker.checkKey(user)) self.assertEquals(self.mockos.seteuidCalls, [0, 1, 0, os.getuid()]) self.assertEquals(self.mockos.setegidCalls, [2, os.getgid()])
"""Authentication/authorization backend using the 'login' PAM service""" credentialInterfaces = IUsernamePassword, implements(ICredentialsChecker) def requestAvatarId(self, credentials): if pam.authenticate(credentials.username, credentials.password): return defer.succeed(credentials.username) return defer.fail(UnauthorizedLogin("invalid password")) class UnixSSHdFactory(factory.SSHFactory): publicKeys = {'ssh-rsa': keys.Key.fromString(data=publicKey)} privateKeys = {'ssh-rsa': keys.Key.fromString(data=privateKey)} services = { 'ssh-userauth': userauth.SSHUserAuthServer, 'ssh-connection': connection.SSHConnection } # Components have already been registered in twisted.conch.unix portal = portal.Portal(UnixSSHRealm()) portal.registerChecker(PamPasswordDatabase()) # Supports PAM portal.registerChecker(SSHPublicKeyDatabase()) # Supports PKI UnixSSHdFactory.portal = portal if __name__ == '__main__': reactor.listenTCP(5022, UnixSSHdFactory()) reactor.run()
class SSHPublicKeyDatabaseTestCase(TestCase): """ Tests for L{SSHPublicKeyDatabase}. """ if pwd is None: skip = "Cannot run without pwd module" elif SSHPublicKeyDatabase is None: skip = "Cannot run without PyCrypto or PyASN1" def setUp(self): self.checker = SSHPublicKeyDatabase() self.key1 = base64.encodestring("foobar") self.key2 = base64.encodestring("eggspam") self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2) self.mockos = MockOS() self.mockos.path = FilePath(self.mktemp()) self.mockos.path.makedirs() self.sshDir = self.mockos.path.child('.ssh') self.sshDir.makedirs() userdb = UserDatabase() userdb.addUser('user', 'password', 1, 2, 'first last', self.mockos.path.path, '/bin/shell') self.patch(pwd, "getpwnam", userdb.getpwnam) self.patch(os, "seteuid", self.mockos.seteuid) self.patch(os, "setegid", self.mockos.setegid) def _testCheckKey(self, filename): self.sshDir.child(filename).setContent(self.content) user = UsernamePassword("user", "password") user.blob = "foobar" self.assertTrue(self.checker.checkKey(user)) user.blob = "eggspam" self.assertTrue(self.checker.checkKey(user)) user.blob = "notallowed" self.assertFalse(self.checker.checkKey(user)) def test_checkKey(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys file and check the keys against that file. """ self._testCheckKey("authorized_keys") self.assertEquals(self.mockos.seteuidCalls, []) self.assertEquals(self.mockos.setegidCalls, []) def test_checkKey2(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys2 file and check the keys against that file. """ self._testCheckKey("authorized_keys2") self.assertEquals(self.mockos.seteuidCalls, []) self.assertEquals(self.mockos.setegidCalls, []) def test_checkKeyAsRoot(self): """ If the key file is readable, L{SSHPublicKeyDatabase.checkKey} should switch its uid/gid to the ones of the authenticated user. """ keyFile = self.sshDir.child("authorized_keys") keyFile.setContent(self.content) # Fake permission error by changing the mode keyFile.chmod(0000) self.addCleanup(keyFile.chmod, 0777) # And restore the right mode when seteuid is called savedSeteuid = os.seteuid def seteuid(euid): keyFile.chmod(0777) return savedSeteuid(euid) self.patch(os, "seteuid", seteuid) user = UsernamePassword("user", "password") user.blob = "foobar" self.assertTrue(self.checker.checkKey(user)) self.assertEquals(self.mockos.seteuidCalls, [0, 1, 0, os.getuid()]) self.assertEquals(self.mockos.setegidCalls, [2, os.getgid()]) def test_requestAvatarId(self): """ L{SSHPublicKeyDatabase.requestAvatarId} should return the avatar id passed in if its C{_checkKey} method returns True. """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh, 'foo', keys.Key.fromString(keydata.privateRSA_openssh).sign('foo')) d = self.checker.requestAvatarId(credentials) def _verify(avatarId): self.assertEquals(avatarId, 'test') return d.addCallback(_verify) def test_requestAvatarIdWithoutSignature(self): """ L{SSHPublicKeyDatabase.requestAvatarId} should raise L{ValidPublicKey} if the credentials represent a valid key without a signature. This tells the user that the key is valid for login, but does not actually allow that user to do so without a signature. """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh, None, None) d = self.checker.requestAvatarId(credentials) return self.assertFailure(d, ValidPublicKey) def test_requestAvatarIdInvalidKey(self): """ If L{SSHPublicKeyDatabase.checkKey} returns False, C{_cbRequestAvatarId} should raise L{UnauthorizedLogin}. """ def _checkKey(ignored): return False self.patch(self.checker, 'checkKey', _checkKey) d = self.checker.requestAvatarId(None); return self.assertFailure(d, UnauthorizedLogin) def test_requestAvatarIdInvalidSignature(self): """ Valid keys with invalid signatures should cause L{SSHPublicKeyDatabase.requestAvatarId} to return a {UnauthorizedLogin} failure """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh, 'foo', keys.Key.fromString(keydata.privateDSA_openssh).sign('foo')) d = self.checker.requestAvatarId(credentials) return self.assertFailure(d, UnauthorizedLogin) def test_requestAvatarIdNormalizeException(self): """ Exceptions raised while verifying the key should be normalized into an C{UnauthorizedLogin} failure. """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey('test', None, 'blob', 'sigData', 'sig') d = self.checker.requestAvatarId(credentials) def _verifyLoggedException(failure): errors = self.flushLoggedErrors(keys.BadKeyError) self.assertEqual(len(errors), 1) return failure d.addErrback(_verifyLoggedException) return self.assertFailure(d, UnauthorizedLogin)
def getAuthorizedKeysFiles(self, credentials): if self.authorized_keys is not None: return [FilePath(ak) for ak in self.authorized_keys] return SSHPublicKeyDatabase.getAuthorizedKeysFiles(self, credentials)
def _cbRequestAvatarId(self, validKey, credentials): returnMe = SSHPublicKeyDatabase._cbRequestAvatarId(self, validKey[0], credentials) if returnMe == validKey[1].username: return validKey[1] else: return returnMe
class SSHPublicKeyDatabaseTestCase(TestCase): """ Tests for L{SSHPublicKeyDatabase}. """ if pwd is None: skip = "Cannot run without pwd module" elif SSHPublicKeyDatabase is None: skip = "Cannot run without PyCrypto or PyASN1" def setUp(self): self.checker = SSHPublicKeyDatabase() self.sshDir = FilePath(self.mktemp()) self.sshDir.makedirs() self.key1 = base64.encodestring("foobar") self.key2 = base64.encodestring("eggspam") self.content = "t1 %s foo\nt2 %s egg\n" % (self.key1, self.key2) self.mockos = MockOS() self.mockos.path = self.sshDir.path self.patch(os.path, "expanduser", self.mockos.expanduser) self.patch(pwd, "getpwnam", self.mockos.getpwnam) self.patch(os, "seteuid", self.mockos.seteuid) self.patch(os, "setegid", self.mockos.setegid) def _testCheckKey(self, filename): self.sshDir.child(filename).setContent(self.content) user = UsernamePassword("user", "password") user.blob = "foobar" self.assertTrue(self.checker.checkKey(user)) user.blob = "eggspam" self.assertTrue(self.checker.checkKey(user)) user.blob = "notallowed" self.assertFalse(self.checker.checkKey(user)) def test_checkKey(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys file and check the keys against that file. """ self._testCheckKey("authorized_keys") self.assertEquals(self.mockos.seteuidCalls, []) self.assertEquals(self.mockos.setegidCalls, []) def test_checkKey2(self): """ L{SSHPublicKeyDatabase.checkKey} should retrieve the content of the authorized_keys2 file and check the keys against that file. """ self._testCheckKey("authorized_keys2") self.assertEquals(self.mockos.seteuidCalls, []) self.assertEquals(self.mockos.setegidCalls, []) def test_checkKeyAsRoot(self): """ If the key file is readable, L{SSHPublicKeyDatabase.checkKey} should switch its uid/gid to the ones of the authenticated user. """ keyFile = self.sshDir.child("authorized_keys") keyFile.setContent(self.content) # Fake permission error by changing the mode keyFile.chmod(0000) self.addCleanup(keyFile.chmod, 0777) # And restore the right mode when seteuid is called savedSeteuid = os.seteuid def seteuid(euid): keyFile.chmod(0777) return savedSeteuid(euid) self.patch(os, "seteuid", seteuid) user = UsernamePassword("user", "password") user.blob = "foobar" self.assertTrue(self.checker.checkKey(user)) self.assertEquals(self.mockos.seteuidCalls, [0, 1, 0, os.getuid()]) self.assertEquals(self.mockos.setegidCalls, [2, os.getgid()]) def test_requestAvatarId(self): """ L{SSHPublicKeyDatabase.requestAvatarId} should return the avatar id passed in if its C{_checkKey} method returns True. """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey( 'test', 'ssh-rsa', keydata.publicRSA_openssh, 'foo', keys.Key.fromString(keydata.privateRSA_openssh).sign('foo')) d = self.checker.requestAvatarId(credentials) def _verify(avatarId): self.assertEquals(avatarId, 'test') return d.addCallback(_verify) def test_requestAvatarIdWithoutSignature(self): """ L{SSHPublicKeyDatabase.requestAvatarId} should raise L{ValidPublicKey} if the credentials represent a valid key without a signature. This tells the user that the key is valid for login, but does not actually allow that user to do so without a signature. """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey('test', 'ssh-rsa', keydata.publicRSA_openssh, None, None) d = self.checker.requestAvatarId(credentials) return self.assertFailure(d, ValidPublicKey) def test_requestAvatarIdInvalidKey(self): """ If L{SSHPublicKeyDatabase.checkKey} returns False, C{_cbRequestAvatarId} should raise L{UnauthorizedLogin}. """ def _checkKey(ignored): return False self.patch(self.checker, 'checkKey', _checkKey) d = self.checker.requestAvatarId(None) return self.assertFailure(d, UnauthorizedLogin) def test_requestAvatarIdInvalidSignature(self): """ Valid keys with invalid signatures should cause L{SSHPublicKeyDatabase.requestAvatarId} to return a {UnauthorizedLogin} failure """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey( 'test', 'ssh-rsa', keydata.publicRSA_openssh, 'foo', keys.Key.fromString(keydata.privateDSA_openssh).sign('foo')) d = self.checker.requestAvatarId(credentials) return self.assertFailure(d, UnauthorizedLogin) def test_requestAvatarIdNormalizeException(self): """ Exceptions raised while verifying the key should be normalized into an C{UnauthorizedLogin} failure. """ def _checkKey(ignored): return True self.patch(self.checker, 'checkKey', _checkKey) credentials = SSHPrivateKey('test', None, 'blob', 'sigData', 'sig') d = self.checker.requestAvatarId(credentials) def _verifyLoggedException(failure): errors = self.flushLoggedErrors(keys.BadKeyError) self.assertEqual(len(errors), 1) return failure d.addErrback(_verifyLoggedException) return self.assertFailure(d, UnauthorizedLogin)
def getAuthorizedKeysFiles(self, credentials): if config.ssh.usesystemkeys: return SSHPublicKeyDatabase.getAuthorizedKeysFiles( self, credentials) return [FilePath( config.ssh.userauthkeys.replace("{{USER}}", credentials.username))]