-*- text -*- Running Perforce Over Secure Shell Connections David Maze 14 June 2000 $Header: //guest/sandy_currier/p4filter/doc/p4-over-ssh#1 $ 1. Introduction 2. Requirements 3. Implementation 4. Usage 5. Example Setup 1. Introduction Perforce is a commercial Source Control Management (SCM) tool. It performs many of the same functions as other popular tools such as the GNU Revision Control System (RCS) and the GNU Concurrent Version System (CVS). Perforce stores version history on a centralized server; other systems run a client program to access information on the server. The Perforce protocol is relatively simple, but is also completely insecure. A user account may be protected with a password, but this is inconvenient; the only way to provide the password to the standard client is to store it, in plain text, in an environment variable. Aside from the password, none of the Perforce data stream is encrypted. Thus, an attacker can easily grab the contents of a file they would not normally have access to. Furthermore, if a user does not use a Perforce password, an attacker can trivially masquerade as them; even if they do, on many systems it is nearly trivial to find the environment variable containing the password, even without root access. Secure Shell (ssh) is a popular package for providing encrypted logins between machines over insecure networks. Among other features, ssh provides authentication using RSA public/private key pairs, and allows arbitrary ports to be securely forwarded through an ssh tunnel. Perforce offers the suggestion of running Perforce through an ssh tunnel, but this either requires users to have a login on the Perforce server or for data to pass through the network unencrypted at some point. This technique may be useful if the local network is "trusted" but clients are accessing the server over an "untrusted" network, but it does not prevent internal attacks. This document describes a technique for sending Perforce traffic over an ssh pipe without using port forwarding. A local account is created on the Perforce server machine. Users are allowed to use the account by adding their RSA public keys to the account's authorized_keys file, but this login is restricted to only allow data to be sent to and from the Perforce server. ssh provides an encrypted channel, and the Perforce stream only travels in the clear locally on the Perforce server. ssh is also used to authenticate users via their RSA keys; some additional code can be used to check authorization to Perforce. 2. Requirements This technique requires the following programs: -- Perforce; see http://www.perforce.com/ for details. -- Secure Shell; see http://www.ssh.org/ for details. ssh may already be installed on your local machine or globally at your site. -- A way for data to get from ssh to the Perforce server. Netcat (ftp://ftp.avian.org/src/hacks/nc110.tgz) is a useful diagnostic tool, and it may be used here to connect ssh to p4d. This will provide user authentication, but not Perforce user authorization. A program called p4filter, which should be distributed with this file, can monitor a Perforce data stream and reject connections not from a specified user. The remainder of this document will assume that you have built and installed p4filter. 3. Implementation Begin by creating a local account on the Perforce server. This document will assume that the account is named "perforce", and has a home directory of /home/perforce. The account should contain the p4filter binary; we will assume it is installed as /home/perforce/p4filter. Create a .ssh directory under the account's home directory, and change its permissions to 0700 and its owner to perforce. Each user will need to create a public/private RSA key pair using ssh-keygen(1); see its manual page for more information. Users' public keys will manually need to be added to the local perforce account's authorized_keys file. The public key can be found in $HOME/.ssh/identity.pub; it is safe to distribute this file. In contrast, the private key should never be distributed. If it must be stored on a public network and/or transmitted over insecure channels (for example, if it is stored on an NFS or AFS server), it should be encrypted with a passphrase; ssh-keygen(1) will prompt you for a passphrase for the newly generated key. The public key file should contain a single (long) line that looks something like this: 1024 37 1359...0707 dmaze@black-magic.akamai.com Add this line to the /home/perforce/.ssh/authorized_keys on the Perforce server. If the file does not exist, create it with just that line. The ssh daemon supports having options set for each RSA key in the authorized_keys file by adding them to the front of the line; multiple options are separated by commas. A complete list of options is in sshd(8). Among the supported options is a 'command' option; providing this option with a value means that, on every ssh attempt, the specified command is run, regardless of the local user's shell or another command the remote user may have specified. Other options disable port forwarding and ssh agent forwarding. Options should be added to the front of the public key line in the authorized_keys file. If the server is running locally on port 1666 and the key should allow access only to the user 'dmaze', a set of options might look like this (lines broken for clarity, but this whole thing is one line in the authorized_keys file): command="/home/perforce/p4filter 1666 dmaze",no-port-forwarding, no-X11-forwarding,no-agent-forwarding,no-pty 1024 37 1359...0707 dmaze@black-magic.akamai.com This restricts the specified key to only run the Perforce filter, which will connect to port 1666 on the local machine and only allow access to 'dmaze'. It disables ssh port forwarding, X forwarding, and ssh agent forwarding. It also disables pty allocation. The remainder of the line contains the same RSA public key as before. 4. Usage As noted before, the user will need to create an RSA key pair and provide the public key to the Perforce administrator. Various other changes need to be made for this scheme to work properly. Firstly, Perforce's P4PORT environment variable needs to be set appropriately to tell Perforce to use ssh instead of directly connecting to the Perforce server. A valid setting would look something like this (using Bourne shell syntax): P4PORT='rsh:ssh -q -a -x -l perforce perforce /bin/true' This tells Perforce to open an ssh connection, suppressing error messages (-q), and without forwarding the ssh agent (-a) or X11 data (-x). The connection is made to the user 'perforce' (-l perforce) on the machine named 'perforce'. The local ssh client will ask the remote host to run the command '/bin/true'; while this command is ignored by the server, specifying it avoids a warning message. With this setup, Perforce will now invoke ssh instead of opening a connection directly. However, if the user specified a passphrase for their ssh key, ssh will prompt them for that passphrase every time a p4 command is run. ssh's workaround to this inconvenience is to provide an ssh authentication agent, ssh-agent(1). There are two ways to invoke the ssh agent. You can start the ssh agent by typing 'eval `ssh-agent`', and stop it by typing 'eval `ssh-agent -k'`. Unfortunately, it is altogether too easy to forget to stop the agent when you log out, potentially leaving your ssh keys available for any local user of the machine. It is easier to start up a shell or other subprogram by typing 'ssh-agent $SHELL'; the shell will run as a child of the agent and have the agent's services available, and the agent will exit when the shell exits. After the agent is started, ssh-add(1) needs to be run inside of it to allow the agent to use a particular RSA key. One might write a shell script to start a subshell that uses the ssh agent like the following: #!/bin/sh if test -n "$1"; then P4CLIENT=$USER_$1 export P4CLIENT cd $HOME/workspaces/$P4CLIENT fi eval `ssh-agent -s` ssh-add $SHELL eval `ssh-agent -s -k` The beginning 'if' command assumes that your Perforce clients are all named user_client, and are stored under $HOME/workspaces. The script starts an ssh agent, adds the default RSA key to it, and starts a shell. When the shell exits, the script stops the agent. Thus, if the script were named 'p4shell', running 'p4shell foo' would set my client to dmaze_foo and change my directory to $HOME/workspaces/dmaze_foo. I would then be ready to do Perforce work on this client. A similar technique can be used to make an ssh agent always be available inside an X Window System session. On Red Hat Linux systems, the commands to be run when a user logs in are stored in a file called .Xclients in the user's home directory. To start the GNOME desktop environment, for example, $HOME/.Xclients might contain #!/bin/sh exec gnome-session This can be modified in two ways. One is to use a technique similar to the above, and change the script to #!/bin/sh eval `ssh-agent -s` ssh-add