[ZODB-Dev] SecureServerStorage and SecureClientStorage
Johan Dahlin
jdahlin@telia.com
03 Oct 2002 15:52:47 -0300
--=-jVffnJ4Qa0jsF1qhwbLp
Content-Type: text/plain
Content-Transfer-Encoding: 7bit
Hi folks
For the past week i have played around a bit with ServerStorage and
ClientStorage and added basic authentication.
Usernames and passwords are sent in clear text to not give a false sense
of security, in other words you have to trust your local network.
No changes to ZODB/ZEO directly, instead everything is subclassed.
It seems to work quite well, however, there are a few problems.
1. When authentication fails everything just locks up. Should this be
considered as a feature?
2. It's authenticated per connection id (storage_id). I don't know if
this is optimal. Isn't it possible that an attacker can send bogus
connection ids and access the storage unauthenticated (this is only
possible under a few microsecond on a relatively fast machine, since
it's dropped just after it's checked).
3. Thread safe? I have no experience with threads, am i doing something
completely wrong?
4. I am modifying the ServerStub class directly at the moment, could
this be solved in a different way?
Attaching secure.py (in which the classes are) and myserver.py/myclient.py which is a small set of
tests.
(cc: me eventual replies, since i'm not on the list (yet)(
--
Johan Dahlin
--=-jVffnJ4Qa0jsF1qhwbLp
Content-Disposition: attachment; filename=myclient.py
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-python; name=myclient.py; charset=ISO-8859-1
import os
import signal
import ZODB
from ZODB import DB
from secure import SecureClientStorage
from ZEO.ClientStorage import ClientStorage
def open_db (pwd=3D'p4ssw0rd'):
storage =3D SecureClientStorage(('nachocano', 4000),
username=3D'mrjoe',
password=3Dpwd)
try:
database =3D DB (storage)
except ClientStorage.ClientDisconnected:
print 'Could not connect to database, is it running?'
os.kill (os.getpid(), signal.SIGABRT)
=20
return database
if len(sys.argv) > 1:
database =3D open_db (sys.argv[1])
else:
database =3D open_db ()
conn =3D database.open ()
root =3D conn.root ()
print root
#get_transaction().commit()
--=-jVffnJ4Qa0jsF1qhwbLp
Content-Disposition: attachment; filename=myserver.py
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-python; name=myserver.py; charset=ISO-8859-1
import asyncore
from signal import signal, SIGTERM, SIGINT, SIGHUP
import ZODB.FileStorage
def shutdown (storages):
import asyncore
# Do this twice, in case we got some more connections
# while going through the loop. This is really sort of
# unnecessary, since we now use so_reuseaddr.
for ignored in 1,2:
for socket in asyncore.socket_map.values():
try: socket.close()
except: pass
for storage in storages.values():
try: storage.close()
finally: pass
raise SystemExit
=20
storages =3D {}
storages['1'] =3D ZODB.FileStorage.FileStorage('db/Data.fs')
signal (SIGINT,
lambda g,f,s=3Dstorages: shutdown(s))
# hostname to listen to, port
unix =3D ('nachocano', 4000)
from secure import SecureStorageServer
serv =3D SecureStorageServer (unix, storages)
serv.add_user ('mrjoe', 'p4ssw0rd')
print serv
asyncore.loop ()
--=-jVffnJ4Qa0jsF1qhwbLp
Content-Disposition: attachment; filename=secure.py
Content-Transfer-Encoding: quoted-printable
Content-Type: text/x-python; name=secure.py; charset=ISO-8859-1
import os
from zLOG import INFO, LOG
from ZEO import ServerStub
from ZEO.zrpc.connection import ManagedServerConnection
from ZEO.ClientStorage import ClientStorage
from ZEO.StorageServer import StorageServer, ZEOStorage
from ZODB import POSException
=20
def authUser(self, storage_id, username, password):
return self.rpc.call('authUser', storage_id, username, password)
ServerStub.StorageServer.authUser =3D authUser
class AuthError(Exception): pass
class SecureZEOStorage(ZEOStorage):
__super_register =3D ZEOStorage.register
auth =3D {}
def authUser(self, storage_id, username, password):
users =3D self.server.users
if not users.has_key(username):
return 0
elif users[username] !=3D password:
return 0
=20
self.auth[storage_id] =3D None=20
return 1
def register(self, storage_id, read_only):
auth =3D self.auth
if not auth.has_key(storage_id):
raise AuthError, 'must authenticate'
del auth[storage_id]
self.__super_register(storage_id, read_only)
=20
class SecureStorageServer(StorageServer):
users =3D {}
def add_user(self, username, password=3D''):
self.users[username] =3D password
=20
def new_connection(self, sock, addr):
c =3D ManagedServerConnection(sock, addr,
SecureZEOStorage(self), self)
LOG('SecureServerStorage', INFO,
"new connection %s: %s" % (addr, `c`))
return c
class SecureClientStorage(ClientStorage):
__super_init =3D ClientStorage.__init__
def __init__(self, *args, **kwargs):
self._username =3D kwargs.get('username', '')
self._password =3D kwargs.get('password', '')
try:
del kwargs['username']
del kwargs['password']=20
except KeyError: pass
self.__super_init(*args, **kwargs)
=20
def testConnection(self, conn):
LOG("ClientStorage", INFO, "Testing connection %r" % conn)
stub =3D ServerStub.StorageServer(conn)
storage_id =3D str(self._storage)
retval =3D stub.authUser(storage_id,
self._username,
self._password)
if not retval:
raise AuthError
=20
try:
stub.register(storage_id, self._is_read_only)
return 1
except POSException.ReadOnlyError:
if not self._read_only_fallback:
raise
LOG("ClientStorage", INFO,
"Got ReadOnlyError; trying again with read_only=3D1")
stub.register(storage_id, read_only=3D1)
return 0
--=-jVffnJ4Qa0jsF1qhwbLp--