![](http://www.gnu-darwin.org/hexley-gd-sm.png)
|
9. Security
This chapter discusses the security aspects of Pyro,
the features you can use to control security, and some
important warnings.
To guard against unwanted or unauthorized connections, Pyro uses so-called new connection validators.
These are objects that are called to check whether a Pyro server may or may not accept
a new connection that a client tries to make.
By default, there is only one built-in check; the number of connections was limited to a certain amount
specifed in the PYRO_MAXCONNECTIONS config item).
This check is done by the default connection validator 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.
Pyro 3.0 introduced 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:
- Unless you tell Pyro otherwise, no authentication check is done. Clients can connect anonymously,
unless you deny access based on max. connections or client host address.
- No passphrases are stored by Pyro, not on disk, not in memory. Pyro uses secure md5 hashes to compare passphrases.
- Server sends an authentication challenge to the client that must be used to generate the
actual auth ident (digest of passphrase+challenge). Because the challenge changes for each
connection attempt, and thus the required auth ident changes, this scheme is safe against
eavesdroppers (auth idents are not reusable).
- Once a client is accepted and connected, no further checks are done.
- The same passphrases are valid for all objects connected to a certain Pyro Daemon.
- Clients can use a different passphrase for each object.
- There is no config item to specify the passphrase (because this would be very insecure). Code your own passphrase requester/supplier.
The Name Server and the Event Server can both be instructed to require authentication too. See the chapters
for each server for more information on how to configure this.
Look at the "denyhosts" and "authenticate" examples to see how you can use the
connection validators.
How to use the default connection Validator
You can specify the maximum number of connections that Pyro accepts by setting the 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.
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.
The default SSL connection Validator
For SSL connections, the 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.
How to implement a custom validator that does even more
You can roll your own validator, or extend the default connection validator Pyro.protocol.DefaultConnValidator (recommended).
If you want to create your own, have a look at how the default validator works. The instructions below
are for a custom connection validator that extends the default validator.
- Subclass from
Pyro.protocol.DefaultConnValidator and call its __init__() in your own init (no arguments, required step!).
- Add an
acceptHost(self,daemon,connection) method that is called to check
the clients origin. The daemon is the current Pyro daemon, connection is a Pyro.protocol.TCPConnection object. The client's socket address is in connection.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.
- Add an
acceptIdentification(self, daemon, connection, hash, challenge) method that is called to check
if a client supplied a valid authentication passphrase. daemon and connection
are the same as above, hash is the binary md5 hash (digest) of the client's passphrase.
challenge is the challenge string that was generated by the server and must
be joined with the passphrase to check the actual auth id.
The default implementation of this method checks if the client-supplied hash is among the accepted
passphrases of the daemon (digest of passphrase+challenge) -- if any are specified, otherwise it is just accepted. Have a look at the code for exact details on this.
- If you subclass from
Pyro.protocol.BasicSSLValidator (for SSL connections),
you can override the checkCertificate(self, cert) method. cert is the
SSL certificate that the client provides. Note: this method is called from the
acceptHost method, so you must leave that one as-is or call the base class
implmentation of that method if you override it.
- All three check methods 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)
|
But it is probably easier just to call the base class's method and return that result. You keep the
default behavior this way, with minimal coding effort.
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.
- There is a third method,
setAllowedIdentifications(self, ids) that is used to
set the list of allowed authorization ids. It probably isn't interesting to override. It stores a
list of binary md5 hashes (digests) of the ids in self.allowedIDs .
- Tell your Pyro Daemon that it should use your own validator, by calling the daemon's
setNewConnectionValidator method. Supply an instance
of your validator class as an argument.
The Name Server supports security plugins, to facilitate access control to the Name Server.
Different options are available:
- A validator for incoming broadcast server requests (such as 'what is your location' and 'shutdown'). You can now decide what or when such commands are
accepted or denied, for instance, based on client IP address.
- A new connection validator for the NS server itself (which is a regular Pyro object, and this is the general validator mentioned in the Security chapter).
With this you can implement very coarse access control to the NS, for instance,
deny certain clients based on their IP address.
You'll have to write a Python module that contains the following:
BCGuard() function that returns a BC request validator object, or None .
NSGuard() function that returns a NS new conn validator object, or None .
- A class that implements a BC request validator. This class must inherit
from
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.
- A class that implements a NS new conn validator. See the documentation on
connection validators, above, to see how you must implement this.
When you start the NS using the '-s' switch, it will read your module and
call the two functions mentioned above to get your validator objects.
Make sure your module is in your Python import path.
The NS prints the names of the plugins to show that it's using them and then starts.
Have a look at the "NS_sec_plugins" example to see how things are done.
The mobile code support of Pyro is very powerful but also dangerous, because the server is
running code that comes in over the wire. Any code can enter over the wire, correct, buggy, but also evil code (Trojans).
It's obvious that loading and running arbitrary code is dangerous.
That's why you should set a codeValidator for each Pyro object that might load mobile code (mobile objects).
The default validator offers no protection: it accepts all code.
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.
Using a firewall to protect your network has nothing to do with security in Pyro,
but it may affect Pyro. Ofcourse the firewall can be used to fully protect your network
for systems outside the firewall; it can make it impossible for those systems
to connect to your Pyro servers. But if you want to access Pyro objects from outside
the firewall, you may have to take some additional steps. Because they have to
do with configuring Pyro, and not with security, they are described in
detail in the Freatures chapter (topic "DNS, IP addresses and firewalls").
Security warning: possible trojan attack
By default, Pyro uses the native Python 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 from Gnosis_Utils.
To enable this, set the PYRO_XML_PICKLE config item to 1. You need to have said Gnosis_Utils package installed
(at least version 1.0.2), 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 Gnosis_Utils package is installed on both ends of the communitcation.
If the server is configured to use PYRO_XML_PICKLE , it will only accept XML pickled requests!
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.
Pyro supports communication over secure sockets (SSL).
Because Python doesn't support server-side SSL out-of-the-box, you'll need
the following add-on libraries to enable SSL support:
Why M2Crypto? Because it works for both client and server applications, and is thread safe.
Note: M2Crypto needs support for RC5 and IDEA compiled into
OPENSSL. Since they're copyrighted these options are normally not included
in most major OpenSSL ditributions. You might have to recompile OpenSSL
from the sources yourself. Beware that if you use this to upgrade to a
new OpenSSL version you also have to recompile OpenSSH since it does a
version check on the OpenSSL library.
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.
See the "ssl" example for more info.
|