Skip to content

Commit e0ffde2

Browse files
author
Dariusz Suchojad
committed
SESPRINGPYTHONPY-155: Done documentation.
1 parent 04b3b07 commit e0ffde2

File tree

2 files changed

+259
-8
lines changed

2 files changed

+259
-8
lines changed

docs/sphinx/source/remoting.rst

Lines changed: 249 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -677,11 +677,258 @@ password, it's the same one for each private key.
677677
Configuration
678678
+++++++++++++
679679

680-
**ZzzzzZzz** All the config options go here..
680+
The two main classes to use in secure XML-RPC communications are
681+
:ref:`springpython.remoting.xmlrpc.SSLServer <remoting-secure-xml-config-sslserver>`
682+
and
683+
:ref:`springpython.remoting.xmlrpc.SSLClient <remoting-secure-xml-config-sslclient>`
684+
both of which support a number of options discussed below.
685+
Keep in mind that those classes are thin wrappers around the base objects found
686+
in Python's standard library and as such they always accept all the default arguments
687+
of their super-classes along with those specific to Spring Python's secure XML-RPC
688+
implementation.
689+
690+
.. _remoting-secure-xml-config-sslserver:
691+
692+
SSLServer
693+
>>>>>>>>>
694+
695+
SSLServer is a subclass of Python's
696+
`SimpleXMLRPCServer.SimpleXMLRPCServer <http://docs.python.org/library/simplexmlrpcserver.html#module-SimpleXMLRPCServer>`_
697+
which accepts arguments related to SSL in addition to those inherited from
698+
the base class. You expose XML-RPC services by extending SSLServer in your
699+
own subclass which is required to override one method, *register_functions*.
700+
*register_functions* may in turn use *self.register_function* for exposing those
701+
methods that should be accessible via XML-RPC, see
702+
`Python's documentation <http://docs.python.org/library/simplexmlrpcserver.html#SimpleXMLRPCServer.SimpleXMLRPCServer.register_function>`_
703+
for details of using *self.register_function*.
704+
705+
SSLServer.__init__'s default arguments::
706+
707+
class SSLServer(object, SimpleXMLRPCServer):
708+
def __init__(self, host=None, port=None, keyfile=None, certfile=None,
709+
ca_certs=None, cert_reqs=ssl.CERT_NONE, ssl_version=ssl.PROTOCOL_TLSv1,
710+
do_handshake_on_connect=True, suppress_ragged_eofs=True, ciphers=None, **kwargs):
711+
712+
* *host* - interface to listen on, e.g. "localhost",
713+
* *port* - port to listen on, e.g. 8000,
714+
* *keyfile* - path to a PEM-encoded private key of the server, e.g. "./server-key.pem",
715+
* *certfile* - path to a PEM-encoded certificate of the server, e.g. "./server-cert.pem",
716+
* *ca_certs* - path to a PEM-encoded list (possibly one element long) of certificates
717+
of Certificate Authorities signing the certificates of clients you deal with,
718+
e.g. "./ca-chain.pem",
719+
* *cert_reqs* - whether the client is required to authenticate itself with a certificate,
720+
see `Python's documentation <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_
721+
for supported values,
722+
* *ssl_version* - the SSL/TLS version to use, see
723+
`Python's documentation <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_
724+
for supported values, note that the same value **must** be used by the client
725+
application,
726+
* *do_handshake_on_connect* - `same as in Python <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_,
727+
* *suppress_ragged_eofs* - `same as in Python <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_,
728+
* *ciphers* - `same as in Python <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_,
729+
the value will be silently ignored if not running Python 2.7 or newer,
730+
* *\**kwargs* - an open-ended list of keyword arguments, currently the only
731+
argument being recognized is *verify_fields* which must be a dictionary
732+
containing fields and values of the client certificate that must exist when the client's
733+
connecting. Fields names should be in the format given in `Appendix A of RFC 3280 <http://tools.ietf.org/html/rfc3280>`_,
734+
which means using long names instead of short ones (commonName not CN, organizationName not O, etc.),
735+
for instance, setting verify_fields to:
736+
737+
::
738+
739+
{"commonName":"My Client", "localityName":"My Town"}
740+
741+
will make sure the client certificate's subject has both commonName and localityName
742+
set and will also validate their respective values. The connection will not
743+
be accepted unless the fields and values match.
744+
745+
.. _remoting-secure-xml-config-sslserver-sample:
746+
747+
Sample SSL XML-RPC server which expects the client to use a certificate whose
748+
fields must match the configuration. The server exposes one method, *listdir*::
749+
750+
# -*- coding: utf-8 -*-
751+
752+
# stdlib
753+
import logging
754+
import os
755+
import ssl
756+
757+
# Spring Python
758+
from springpython.remoting.xmlrpc import SSLServer
759+
760+
class MySSLServer(SSLServer):
761+
def __init__(self, *args, **kwargs):
762+
super(MySSLServer, self).__init__(*args, **kwargs)
763+
764+
def _listdir(self, path):
765+
return os.listdir(path)
766+
767+
def register_functions(self):
768+
self.register_function(self._listdir, "listdir")
769+
770+
host = "localhost"
771+
port = 8000
772+
keyfile = "./server-key.pem"
773+
certfile = "./server-cert.pem"
774+
ca_certs = "./ca-chain.pem"
775+
verify_fields = {"commonName": "My Client", "organizationName":"My Company",
776+
"stateOrProvinceName":"My State"}
777+
778+
logging.basicConfig(level=logging.ERROR)
779+
780+
server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED,
781+
verify_fields=verify_fields)
782+
server.serve_forever()
783+
784+
.. _remoting-secure-xml-config-sslclient:
785+
786+
SSLClient
787+
>>>>>>>>>
788+
789+
SSLClient extends Python's built-in
790+
`xmlrpclib.ServerProxy <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_
791+
class and, unlike :ref:`SSLServer <remoting-secure-xml-config-sslserver>`,
792+
can be used directly without the need for subclassing. You can simply create
793+
an instance and start invoking server's methods.
794+
795+
SSLClient.__init__’s default arguments::
796+
797+
class SSLClient(ServerProxy):
798+
def __init__(self, uri=None, ca_certs=None, keyfile=None, certfile=None,
799+
cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1,
800+
transport=None, encoding=None, verbose=0, allow_none=0, use_datetime=0,
801+
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, strict=None):
802+
803+
* *uri* - address of the XML-RPC server, e.g. "https://localhost:8000/RPC2",
804+
* *ca_certs* - path to a PEM-encoded list (possibly one element long) containing certificates
805+
of Certificate Authorities the client is to trust; client will be establishing
806+
authenticity of the server's certificate against certificates from that file;
807+
e.g. "./ca-chain.pem",
808+
* *keyfile* - path to a PAM-encoded private key of the client, e.g. "./client-key.pem",
809+
* *certfile* - path to a PAM-encoded certificate of the client, e.g. "./client-key.pem",
810+
* *cert_reqs* - whether a server is required to have a certificate,
811+
see `Python's documentation <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_
812+
for supported values,
813+
* *ssl_version* - the SSL/TLS version to use, see
814+
`Python's documentation <http://docs.python.org/library/ssl.html#ssl.wrap_socket>`_
815+
for supported values, note that the same value **must** be used by the server,
816+
* *transport* - `same as in Python <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_,
817+
* *encoding* - `same as in Python <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_,
818+
* *verbose* - `same as in Python <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_,
819+
* *allow_none* - `same as in Python <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_,
820+
* *use_datetime* - `same as in Python <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_,
821+
822+
Sample SSL XML-RPC client which uses a private key and a certificate, can be
823+
used for invoking the :ref:`server <remoting-secure-xml-config-sslserver-sample>`
824+
shown in previous chapter::
825+
826+
# -*- coding: utf-8 -*-
827+
828+
# Spring Python
829+
from springpython.remoting.xmlrpc import SSLClient
830+
831+
server_location = "https://localhost:8000/RPC2"
832+
keyfile = "./client-key.pem"
833+
certfile = "./client-cert.pem"
834+
ca_certs = "./ca-chain.pem"
835+
836+
client = SSLClient(server_location, ca_certs, keyfile, certfile)
837+
838+
print client.listdir("/home")
681839

682840
.. _remoting-secure-xml-rpc-logging:
683841

684842
Logging
685843
+++++++
686844

687-
**ZzzzzZzz** Describe loggers used..
845+
.. _remoting-secure-xml-logging-sslserver:
846+
847+
SSLServer
848+
>>>>>>>>>
849+
850+
Your subclass of SSLServer can be configured to use Python's
851+
standard `logging <http://docs.python.org/library/logging.html>`_ module.
852+
Currently, logging events are emitted at *DEBUG* and *ERROR* levels.
853+
854+
At ERROR level all failed attempts at validating of client certificates will
855+
be logged giving the exact reason for the failure. Interal errors (should they ever happen)
856+
are also logged at the ERROR level.
857+
858+
When told to run at DEBUG level, in addition to information logged at the ERROR level,
859+
the server will also log details of each client's certificate received along with
860+
the IP address of a client application connecting.
861+
862+
A sample SSL XML-RPC running with full verbosity turned on::
863+
864+
# -*- coding: utf-8 -*-
865+
866+
# stdlib
867+
import logging
868+
import os
869+
import ssl
870+
871+
# Spring Python
872+
from springpython.remoting.xmlrpc import SSLServer
873+
874+
class MySSLServer(SSLServer):
875+
def __init__(self, *args, **kwargs):
876+
super(MySSLServer, self).__init__(*args, **kwargs)
877+
878+
def _listdir(self, path):
879+
return os.listdir(path)
880+
881+
def register_functions(self):
882+
self.register_function(self._listdir, "listdir")
883+
884+
host = "localhost"
885+
port = 8000
886+
keyfile = "./server-key.pem"
887+
certfile = "./server-cert.pem"
888+
ca_certs = "./ca-chain.pem"
889+
verify_fields = {"commonName": "My Client", "organizationName":"My Company",
890+
"stateOrProvinceName":"My State"}
891+
892+
log_format = "%(asctime)s - %(levelname)s - %(process)d - %(threadName)s - %(name)s - %(message)s"
893+
formatter = logging.Formatter(log_format)
894+
895+
handler = logging.StreamHandler()
896+
handler.setFormatter(formatter)
897+
898+
logger = logging.getLogger("MySSLServer")
899+
900+
logger.setLevel(level=logging.DEBUG)
901+
logger.addHandler(handler)
902+
903+
904+
server = MySSLServer(host, port, keyfile, certfile, ca_certs, cert_reqs=ssl.CERT_REQUIRED,
905+
verify_fields=verify_fields)
906+
server.serve_forever()
907+
908+
.. _remoting-secure-xml-logging-sslclient:
909+
910+
SSLClient
911+
>>>>>>>>>
912+
913+
Although SSLClient does define a self.logger object it isn't currently used
914+
internally in any situation (subject to change without notice so you shouldn't
915+
rely on the current status). On the other hand, as a subclass of
916+
`xmlrpclib.ServerProxy <http://docs.python.org/library/xmlrpclib.html#xmlrpclib.ServerProxy>`_,
917+
the client may be configured to run in a *verbose* mode which means all HTTP traffic
918+
will be printed onto *standard output*.
919+
920+
A sample SSL XML-RPC client configured to use the verbose mode::
921+
922+
# -*- coding: utf-8 -*-
923+
924+
# Spring Python
925+
from springpython.remoting.xmlrpc import SSLClient
926+
927+
server_location = "https://localhost:8000/RPC2"
928+
keyfile = "./client-key.pem"
929+
certfile = "./client-cert.pem"
930+
ca_certs = "./ca-chain.pem"
931+
932+
client = SSLClient(server_location, ca_certs, keyfile, certfile, verbose=1)
933+
934+
print client.listdir("/home")

src/springpython/remoting/xmlrpc.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,10 @@ def get_request(self):
7373
kwargs["ciphers"] = self.ciphers
7474

7575
sock = ssl.wrap_socket(sock, **kwargs)
76+
77+
if self.logger.isEnabledFor(logging.DEBUG):
78+
self.logger.debug("get_request cert='%s', from_addr='%s'" % (sock.getpeercert(), from_addr))
79+
7680
return sock, from_addr
7781

7882
def verify_request(self, sock, from_addr):
@@ -87,7 +91,7 @@ def verify_request(self, sock, from_addr):
8791
msg = "Couldn't verify fields, peer didn't send the certificate, from_addr='%s'" % (from_addr,)
8892
raise VerificationException(msg)
8993

90-
allow_peer, reason = self.verify_peer(cert)
94+
allow_peer, reason = self.verify_peer(cert, from_addr)
9195
if not allow_peer:
9296
self.logger.error(reason)
9397
sock.close()
@@ -110,14 +114,11 @@ def verify_request(self, sock, from_addr):
110114

111115
return True
112116

113-
def verify_peer(self, cert):
117+
def verify_peer(self, cert, from_addr):
114118
""" Verifies the other side's certificate. May be overridden in subclasses
115119
if the verification process needs to be customized.
116120
"""
117121

118-
if self.logger.isEnabledFor(logging.DEBUG):
119-
self.logger.debug("verify_peer cert='%s'" % (cert))
120-
121122
subject = cert.get("subject")
122123
if not subject:
123124
msg = "Peer certificate doesn't have the 'subject' field, cert='%s'" % cert
@@ -152,6 +153,9 @@ def register_functions(self):
152153
class SSLClientTransport(Transport):
153154
""" Handles an HTTPS transaction to an XML-RPC server.
154155
"""
156+
157+
user_agent = "SSL XML-RPC Client (by http://springpython.webfactional.com)"
158+
155159
def __init__(self, keyfile=None, certfile=None, ca_certs=None, cert_reqs=None,
156160
ssl_version=None, timeout=None, strict=None):
157161
self.keyfile = keyfile
@@ -171,7 +175,7 @@ def make_connection(self, host):
171175

172176
class SSLClient(ServerProxy):
173177
def __init__(self, uri=None, ca_certs=None, keyfile=None, certfile=None,
174-
cert_reqs=ssl.CERT_OPTIONAL, ssl_version=ssl.PROTOCOL_TLSv1,
178+
cert_reqs=ssl.CERT_REQUIRED, ssl_version=ssl.PROTOCOL_TLSv1,
175179
transport=None, encoding=None, verbose=0, allow_none=0, use_datetime=0,
176180
timeout=socket._GLOBAL_DEFAULT_TIMEOUT, strict=None):
177181

0 commit comments

Comments
 (0)