Pyro.protocol.DefaultConnValidator
. The fun is that you can supply your own validator
object, and that you can therefore implement much more complex access checks. For instance, you might want to check
if the client's site is authorized to connect. Or perhaps you require a password to connect.
The default validator already supports passphrase protection as authentication validation. This means that a client that wants to connect to your Pyro server needs to supply a valid authentication passphrase, or the connection is denied. The check takes place automatically (it is performed by the default connection validator), at connect time. The following items are important:
Look at the "denyhosts", "authenticate" and "user_passwd_auth" examples to see how you can use the connection validators.
PYRO_MAXCONNECTIONS
configuration item. This limit is always checked
when a new client connects.
To enable passphrase authentication, you must tell the Pyro Daemon a list of accepted passphrases. Do this by
calling the setAllowedIdentifications(ids)
method of the daemon, where ids
is a list of
passphrases (strings). If you use None
for this, the authentication is disabled again. Note that the
ID list is a shared resource and that you will have to use thread locking if you change it from different
threads. To specify for your client what passphrase to use for a specific object, call the
proxy._setIdentification(id)
method of the Pyro proxy, where id
is your passphrase
(string). Use Null
to disable authentication again. Call the method right after you obtained the proxy
using getProxyForURI
or whatever.
If a connection is denied, Pyro will raise a ConnectionDeniedError
, otherwise the connection is
granted and your client proxy can invoke any methods it likes, untill disconnected.
Pyro.protocol.BasicSSLValidator
is
used by default. This is an extension to the normal validator, it also checks if the client has supplied
a SSL certificate. See the "ssl" example for details.
DefaultConnValidator
, you can control all logic that Pyro uses on the client-side and server-side for
authenicating new connections. You are required to make a subclass (specialization) of the default connection
validator Pyro.protocol.DefaultConnValidator
. There are two methods that you can use to set your own
validator object:
_setNewConnectionValidator(validator)
setNewConnectionValidator(validator)
_setIdentification(ident)
setAllowedIdentifications(idents)
Below you see the meaning of the different methods that are used in the connection validator class (and that you can override in your custom validator):
class MyCustomValidator(Pyro.protocol.DefaultConnValidator):
The three check methods
def __init__(self):
Pyro.protocol.DefaultConnValidator.__init__(self)
...required...def acceptHost(self,daemon,connection):
- ...called first, to check the client's origin. Arguments are the current Pyro Daemon, and the connection object (
Pyro.protocol.TCPConnection
object). The client's socket address is inconnection.addr
. You can check the client's IP address for instance, to see if it is in a trusted range. The default implementation of this method checks if the number of active connections has not reached the limit. (Pyro.config.PYRO_MAXCONNECTIONS
) See table below for return codesdef acceptIdentification(self, daemon, connection, token, challenge):
- ...called to verify the client's identification token (check if the client supplied a correct authentication passphrase). The arguments are: daemon and connection same as above, client's token object (that was created by
createAuthToken
below), server challenge object that was sent to the client. The default implementation usescreateAuthToken
to create a secure hash of the auth id plus the challenge to compare that to the client's token. Effectively, it checks if the client-supplied hash is among the accepted passphrases of the daemon (hash of passphrase+challenge) -- if any are specified, otherwise it is just accepted. See table below for return codes. NOTE: you can use theconnection
argument to store the authentication token (which might be a username). A Pyro object may access this again by getting the connection objectself.getLocalStorage().caller
and getting the authentication token from there.def createAuthToken(self, authid, challenge, peeraddr, URI, daemon):
- ...called from both client (proxy) and server (daemon) to create a token that is used to validate the connection. The arguments are: identification string (comes from
mungeIdent
below), challenge from server, socket address of the other party, Pyro URI of the object that is to be accessed, current Pyro Daemon. When in the client (proxy), daemon is always None. When in the server (daemon), URI is always None. The default implementation returns a secure hmac-md5 hash of the ident string and the challenge.def createAuthChallenge(self, tcpserver, conn):
- ...called in the server (daemon) when a new connection comes in. It must return a challenge string that is to be sent to the client, to be used in creating the authentication token. By default it returns a secure hash of server IP, process ID, timestamp and a random value. Currently it is required that the challenge string is exactly 16 bytes long! (a md5 hash is 16 bytes).
def mungeIdent(self, ident):
- utility method to change a clear-text ident string into something that isn't easily recognised. By default it returns the secure hash of the ident string. This is used to store the authentication strings more securely (
setAllowedIdentifications
). The ident object that is passed is actually free to be what you want, for instance you could useobj._setIdentification( ("login", "password") )
to use a login/password tuple. You have to use a custom connection validator to handle this, of course.def setAllowedIdentifications(self, ids):
- To tell the Daemon what identification strings are valid (the allowed secure passphrases).
(the following only if you subclass fromPyro.protocol.BasicSSLValidator
(for SSL connections):
def checkCertificate(self, cert):
- ...checks the SSL certificate. The client's SLL certificate is passed as an argument. Note: this method is called from the
acceptHost
method, so you must leave that one as-is or call theBasicSSLValidator
base class implementation of that method if you override it. See table below for return codes
acceptHost
, acceptIdentification
and
checkCertificate
must return (1,0)
if the connection is accepted, or (0,code)
when the connection is refused, where code
is one of the following:
Deny Reason Code | Description |
---|---|
Pyro.constants.DENIED_UNSPECIFIED |
unspecified |
Pyro.constants.DENIED_SERVERTOOBUSY |
server too busy (too many connections) |
Pyro.constants.DENIED_HOSTBLOCKED |
host blocked |
Pyro.constants.DENIED_SECURITY |
security reasons (general) |
Pyro will raise the appropriate ConnectionDeniedError
on the client when you deny a new connection.
On the server, you'll have to log the reason in the Pyro logfile yourself, if desired. When you accept a connection,
the daemon will log an entry for you.
BCGuard()
function that returns a BC request validator object, or None
.NSGuard()
function that returns a NS new conn validator object, or None
.Pyro.naming.BCReqValidator
. You must override the two methods that check for each command if it is
allowed or if it is refused. These are acceptLocationCmd(self)
and
acceptShutdownCmd(self)
, and they return 0 or 1 (accept or deny). You can access
self.addr
to have the client's address (ip,port). You can call self.reply('message')
to
send a message back to the client. This may be polite, to let it know why you refused the command.This codeValidator is a function (or callable object) that takes three arguments: the name of the module, the code
itself, and the address of the client (usually a (IP,port) tuple). It should return 0 or 1, for 'deny' and 'accept'.
Pyro.core.ObjBase
, the base class of all Pyro objects, has a setCodeValidator(v)
method
that you must call with your custom validator function (or callable object). You can set a different validator for
each Pyro object that your server has.
The codeValidator is used for both directions; it checks if code is allowed from clients into the server, but also if code is allowed to be sent from the server to clients. In the first case, all three parameters have a value as mentioned above. In the second case (code from server to client), only the name has a value, the other two are None. For example, the code validator shown below is taken from the "agent2" example. It checks if incoming code is from the "agent.ShoppingAgent" module, and outgoing code is from the "objects" package:
def codeValidator(name,module,address): if module and address: return name=='agent.ShoppingAgent' # client uploads to us else: return name.startswith("objects.") # client downloads from us . . . mall.setCodeValidator(codeValidator)
Notice that a client doesn't have a code validator. If you're using 2-way mobile code (you've enabled
PYRO_MOBILE_CODE
on the client), you will silently receive everything you need from the server. This is
because the clients usually trust the server... otherwise they wouldn't be calling it, would they?
pickle
protocol
to pass calls to remote objects. There is a security problem with pickle
: it is possible to execute
arbitrary code on the server by passing an artificially constructed pickled string message. The standard Python
Cookie
module also suffers from this problem. At the moment of writing, the Python documentation is not
clear on this subject. The problem is known to various people. Using Pyro over the internet could expose your
server to this vulnerability!!!!
Using the (safe) marshal
module is no option for Pyro because we lose the ability to
serialize user defined objects. But, if you accept a performance penalty of an order of a magnitude,
and more required bandwith (2-4 times more), you can choose to use the safe XML pickling.
To enable this, set the PYRO_XML_PICKLE
config item to the appropriate value.
You need to have the appropriate library installed otherwise Pyro won't start. The server
will answer in XML pickled messages also, regardless of the server's PYRO_XML_PICKLE
setting.
So make sure that the correct XML packages are installed on both ends of the communication. If the
server is configured to use PYRO_XML_PICKLE
, it will
only accept XML pickled requests! This means that if you set this option, your server is safe
against pickling attacks.
Please note that at least since Python 2.2 a few pickle security flaws appear to have been removed, and the
obvious trojan exploit with pickle no longer works on Python 2.2+. But still, do you trust pickle? ;-) Use
PYRO_XML_PICKLE
if you want to be safe.
If you decide to use the 'gnosis' XML Pickler, there is an additional config item to think about: PYRO_GNOSIS_PARANOIA
.
It sets the 'paranoia' level that will be used for the Gnosis XML pickler. Higher=more secure.
The default setting (0) prevents automatic imports of modules during unpickling, because
this is potentially unsafe. However, it creates problems when you are sending arbitrary
user-defined types across the wire. The receiving side may not be able to fully
reconstruct the data types that were sent. You could explicitly import the needed
modules on the receiving side, or you could consider to set this config item to -1,
which enables automatic imports of user defined modules in the Gnosis pickler.
Note that setting it to a higer value than 0 breaks Pyro altogether because the pickler
will operate in a too strict way. The only sensible values at this time are 0 and -1.
When you want to use mobile code with Gnosis XML pickler, you need to set this to -1 as well.
Note that you have to use the same Gnosis XML library version everywhere. You can't mix
older versions with newer versions.
To start using SSL, you need to tell your Pyro daemon that it must use SSL instead of regular sockets. Do that by
passing a prtcol
parameter when you create a daemon, as follows:
daemon = Pyro.core.Daemon(prtcol='PYROSSL')(the
prtcol
defaults to 'PYRO' ofcourse). All Pyro objects connected to this daemon will get
registered in the Name Server using the special PYROSSL protocol, that tells Pyro to use SSL instead of regular
sockets. You may also want to add a special SSL connection validator on your daemon that checks the client certificate.
The client programs don't need any changes because Pyro knows automatically how to deal with the PYROSSL protocol.
There are a few configuration items that deal with the SSL configuration, look for PYROSSL_CERTDIR
and the
other items starting with PYROSSL
. See the M2Crypto
homepage or OpenSSL documentation for instructions on how to create your own
Certificate Authority- and server/client certificates. There's also a nice guide here.
There are a few config items to tweak how Pyro uses SSL, where it can find your cert files, key files, and so on. Have a look in the Configuration chapter to see what they are (they all have 'SSL' in their name)
Note: it is very likely that you have to set PYRO_DNS_URI to True, because Pyro uses IP addresses by default while SSL certificates will usually contain hostnames. If you don't set this config item the SSL certificate will be rejected because the name won't match.
Please note that there is a known bug in M2Crypto that makes it impossible to use socket timeouts (socket.setdefaulttimeout) when using SSL.