.. _parallelsecurity:

===========================
Security details of IPython
===========================

IPython's :mod:`IPython.kernel` package exposes the full power of the Python
interpreter over a TCP/IP network for the purposes of parallel computing. This
feature brings up the important question of IPython's security model. This
document gives details about this model and how it is implemented in IPython's
architecture.

Processs and network topology
=============================

To enable parallel computing, IPython has a number of different processes that
run. These processes are discussed at length in the IPython documentation and
are summarized here:

* The IPython *engine*.  This process is a full blown Python
  interpreter in which user code is executed.  Multiple
  engines are started to make parallel computing possible.
* The IPython *controller*.  This process manages a set of 
  engines, maintaining a queue for each and presenting
  an asynchronous interface to the set of engines.
* The IPython *client*.  This process is typically an
  interactive Python process that is used to coordinate the
  engines to get a parallel computation done.

Collectively, these three processes are called the IPython *kernel*.

These three processes communicate over TCP/IP connections with a well defined
topology. The IPython controller is the only process that listens on TCP/IP
sockets. Upon starting, an engine connects to a controller and registers
itself with the controller. These engine/controller TCP/IP connections persist
for the lifetime of each engine.

The IPython client also connects to the controller using one or more TCP/IP
connections. These connections persist for the lifetime of the client only.

A given IPython controller and set of engines typically has a relatively short
lifetime. Typically this lifetime corresponds to the duration of a single
parallel simulation performed by a single user. Finally, the controller,
engines and client processes typically execute with the permissions of that
same user. More specifically, the controller and engines are *not* executed as
root or with any other superuser permissions.

Application logic
=================

When running the IPython kernel to perform a parallel computation, a user
utilizes the IPython client to send Python commands and data through the
IPython controller to the IPython engines, where those commands are executed
and the data processed. The design of IPython ensures that the client is the
only access point for the capabilities of the engines.  That is, the only way of addressing the engines is through a client.

A user can utilize the client to instruct the IPython engines to execute
arbitrary Python commands. These Python commands can include calls to the
system shell, access the filesystem, etc., as required by the user's
application code. From this perspective, when a user runs an IPython engine on
a host, that engine has the same capabilities and permissions as the user
themselves (as if they were logged onto the engine's host with a terminal).

Secure network connections
==========================

Overview
--------

All TCP/IP connections between the client and controller as well as the
engines and controller are fully encrypted and authenticated. This section
describes the details of the encryption and authentication approached used
within IPython.

IPython uses the Foolscap network protocol [Foolscap]_ for all communications
between processes. Thus, the details of IPython's security model are directly
related to those of Foolscap. Thus, much of the following discussion is
actually just a discussion of the security that is built in to Foolscap.

Encryption
----------

For encryption purposes, IPython and Foolscap use the well known Secure Socket
Layer (SSL) protocol [RFC5246]_. We use the implementation of this protocol
provided by the OpenSSL project through the pyOpenSSL [pyOpenSSL]_ Python
bindings to OpenSSL.

Authentication
--------------

IPython clients and engines must also authenticate themselves with the
controller. This is handled in a capabilities based security model
[Capability]_. In this model, the controller creates a strong cryptographic
key or token that represents each set of capability that the controller
offers. Any party who has this key and presents it to the controller has full
access to the corresponding capabilities of the controller. This model is
analogous to using a physical key to gain access to physical items
(capabilities) behind a locked door.

For a capabilities based authentication system to prevent unauthorized access,
two things must be ensured:

* The keys must be cryptographically strong.  Otherwise attackers could gain 
  access by a simple brute force key guessing attack.
* The actual keys must be distributed only to authorized parties.

The keys in Foolscap are called Foolscap URL's or FURLs. The following section
gives details about how these FURLs are created in Foolscap. The IPython
controller creates a number of FURLs for different purposes:

* One FURL that grants IPython engines access to the controller.  Also
  implicit in this access is permission to execute code sent by an
  authenticated IPython client.
* Two or more FURLs that grant IPython clients access to the controller.
  Implicit in this access is permission to give the controller's engine code 
  to execute.

Upon starting, the controller creates these different FURLS and writes them
files in the user-read-only directory :file:`$HOME/.ipython/security`. Thus, only the
user who starts the controller has access to the FURLs.

For an IPython client or engine to authenticate with a controller, it must
present the appropriate FURL to the controller upon connecting. If the
FURL matches what the controller expects for a given capability, access is
granted. If not, access is denied. The exchange of FURLs is done after
encrypted communications channels have been established to prevent attackers
from capturing them.

.. note::

    The FURL is similar to an unsigned private key in SSH.

Details of the Foolscap handshake
---------------------------------

In this section we detail the precise security handshake that takes place at
the beginning of any network connection in IPython. For the purposes of this
discussion, the SERVER is the IPython controller process and the CLIENT is the
IPython engine or client process.

Upon starting, all IPython processes do the following:

1. Create a public key x509 certificate (ISO/IEC 9594).
2. Create a hash of the contents of the certificate using the SHA-1 algorithm.
   The base-32 encoded version of this hash is saved by the process as its 
   process id (actually in Foolscap, this is the Tub id, but here refer to
   it as the process id).

Upon starting, the IPython controller also does the following:

1. Save the x509 certificate to disk in a secure location. The CLIENT
   certificate is never saved to disk.
2. Create a FURL for each capability that the controller has.  There are
   separate capabilities the controller offers for clients and engines.  The
   FURL is created using:  a) the process id of the SERVER, b) the IP 
   address and port the SERVER is listening on and c) a 160 bit,    
   cryptographically secure string that represents the capability (the
   "capability id").
3. The FURLs are saved to disk in a secure location on the SERVER's host.

For a CLIENT to be able to connect to the SERVER and access a capability of
that SERVER, the CLIENT must have knowledge of the FURL for that SERVER's
capability. This typically requires that the file containing the FURL be
moved from the SERVER's host to the CLIENT's host. This is done by the end
user who started the SERVER and wishes to have a CLIENT connect to the SERVER.

When a CLIENT connects to the SERVER, the following handshake protocol takes
place:

1. The CLIENT tells the SERVER what process (or Tub) id it expects the SERVER
   to have.
2. If the SERVER has that process id, it notifies the CLIENT that it will now 
   enter encrypted mode.  If the SERVER has a different id, the SERVER aborts.
3. Both CLIENT and SERVER initiate the SSL handshake protocol.
4. Both CLIENT and SERVER request the certificate of their peer and verify
   that certificate.  If this succeeds, all further communications are 
   encrypted.
5. Both CLIENT and SERVER send a hello block containing connection parameters
   and their process id.
6. The CLIENT and SERVER check that their peer's stated process id matches the
   hash of the x509 certificate the peer presented.  If not, the connection is
   aborted.
7. The CLIENT verifies that the SERVER's stated id matches the id of the
   SERVER the CLIENT is intending to connect to.  If not, the connection is 
   aborted.
8. The CLIENT and SERVER elect a master who decides on the final connection
   parameters.

The public/private key pair associated with each process's x509 certificate
are completely hidden from this handshake protocol. There are however, used
internally by OpenSSL as part of the SSL handshake protocol. Each process
keeps their own private key hidden and sends its peer only the public key
(embedded in the certificate).

Finally, when the CLIENT requests access to a particular SERVER capability,
the following happens:

1. The CLIENT asks the SERVER for access to a capability by presenting that 
   capabilities id.
2. If the SERVER has a capability with that id, access is granted. If not,
   access is not granted.
3. Once access has been gained, the CLIENT can use the capability.

Specific security vulnerabilities
=================================

There are a number of potential security vulnerabilities present in IPython's
architecture. In this section we discuss those vulnerabilities and detail how
the security architecture described above prevents them from being exploited.

Unauthorized clients
--------------------

The IPython client can instruct the IPython engines to execute arbitrary
Python code with the permissions of the user who started the engines. If an
attacker were able to connect their own hostile IPython client to the IPython
controller, they could instruct the engines to execute code.

This attack is prevented by the capabilities based client authentication
performed after the encrypted channel has been established. The relevant
authentication information is encoded into the FURL that clients must
present to gain access to the IPython controller. By limiting the distribution
of those FURLs, a user can grant access to only authorized persons.

It is highly unlikely that a client FURL could be guessed by an attacker
in a brute force guessing attack. A given instance of the IPython controller
only runs for a relatively short amount of time (on the order of hours). Thus
an attacker would have only a limited amount of time to test a search space of
size 2**320. Furthermore, even if a controller were to run for a longer amount
of time, this search space is quite large (larger for instance than that of
typical username/password pair).

Unauthorized engines
--------------------

If an attacker were able to connect a hostile engine to a user's controller,
the user might unknowingly send sensitive code or data to the hostile engine.
This attacker's engine would then have full access to that code and data.

This type of attack is prevented in the same way as the unauthorized client
attack, through the usage of the capabilities based authentication scheme.

Unauthorized controllers
------------------------

It is also possible that an attacker could try to convince a user's IPython
client or engine to connect to a hostile IPython controller. That controller
would then have full access to the code and data sent between the IPython
client and the IPython engines.

Again, this attack is prevented through the FURLs, which ensure that a
client or engine connects to the correct controller. It is also important to
note that the FURLs also encode the IP address and port that the
controller is listening on, so there is little chance of mistakenly connecting
to a controller running on a different IP address and port.

When starting an engine or client, a user must specify which FURL to use
for that connection. Thus, in order to introduce a hostile controller, the
attacker must convince the user to use the FURLs associated with the
hostile controller. As long as a user is diligent in only using FURLs from
trusted sources, this attack is not possible.

Other security measures
=======================

A number of other measures are taken to further limit the security risks
involved in running the IPython kernel.

First, by default, the IPython controller listens on random port numbers.
While this can be overridden by the user, in the default configuration, an
attacker would have to do a port scan to even find a controller to attack.
When coupled with the relatively short running time of a typical controller
(on the order of hours), an attacker would have to work extremely hard and
extremely *fast* to even find a running controller to attack.

Second, much of the time, especially when run on supercomputers or clusters,
the controller is running behind a firewall. Thus, for engines or client to
connect to the controller:

* The different processes have to all be behind the firewall.

or:

* The user has to use SSH port forwarding to tunnel the
  connections through the firewall.
   
In either case, an attacker is presented with addition barriers that prevent
attacking or even probing the system.

Summary
=======

IPython's architecture has been carefully designed with security in mind. The
capabilities based authentication model, in conjunction with the encrypted
TCP/IP channels, address the core potential vulnerabilities in the system,
while still enabling user's to use the system in open networks.

Other questions
===============

About keys
----------

Can you clarify the roles of the certificate and its keys versus the FURL,
which is also called a key?

The certificate created by IPython processes is a standard public key x509
certificate, that is used by the SSL handshake protocol to setup encrypted
channel between the controller and the IPython engine or client. This public
and private key associated with this certificate are used only by the SSL
handshake protocol in setting up this encrypted channel.

The FURL serves a completely different and independent purpose from the
key pair associated with the certificate. When we refer to a FURL as a
key, we are using the word "key" in the capabilities based security model
sense. This has nothing to do with "key" in the public/private key sense used
in the SSL protocol.

With that said the FURL is used as an cryptographic key, to grant
IPython engines and clients access to particular capabilities that the
controller offers.

Self signed certificates
------------------------

Is the controller creating a self-signed certificate? Is this created for per
instance/session, one-time-setup or each-time the controller is started?

The Foolscap network protocol, which handles the SSL protocol details, creates
a self-signed x509 certificate using OpenSSL for each IPython process. The
lifetime of the certificate is handled differently for the IPython controller
and the engines/client.

For the IPython engines and client, the certificate is only held in memory for
the lifetime of its process. It is never written to disk.

For the controller, the certificate can be created anew each time the
controller starts or it can be created once and reused each time the
controller starts. If at any point, the certificate is deleted, a new one is
created the next time the controller starts.

SSL private key
---------------

How the private key (associated with the certificate) is distributed?

In the usual implementation of the SSL protocol, the private key is never
distributed. We follow this standard always.

SSL versus Foolscap authentication
----------------------------------

Many SSL connections only perform one sided authentication (the server to the
client). How is the client authentication in IPython's system related to SSL
authentication?

We perform a two way SSL handshake in which both parties request and verify
the certificate of their peer. This mutual authentication is handled by the
SSL handshake and is separate and independent from the additional
authentication steps that the CLIENT and SERVER perform after an encrypted
channel is established.

.. [RFC5246] <http://tools.ietf.org/html/rfc5246>
