p4-over-ssh #1

  • //
  • guest/
  • sandy_currier/
  • p4filter/
  • doc/
  • p4-over-ssh
  • View
  • Commits
  • Open Download .zip Download (14 KB)
-*- text -*-
Running Perforce Over Secure Shell Connections
David Maze <dmaze@akamai.com>
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 </dev/null
  gnome-session
  eval `ssh-agent -s -k`

This will start the agent and add the default RSA key; it will do so
before any GNOME services or the user's window manager starts,
however, providing a slightly ugly appearance.  A window will appear
prompting the user for their key's passphrase.

Alternatively, the program run as an X session may allow commands to
be run after it starts.  With GNOME, this can be done via the "Startup 
Programs" menu item; many window managers allow arbitrary commands to
be executed from their startup scripts.  In this case, the .Xclients
script could just be changed to

  #!/bin/sh
  exec ssh-agent gnome-session

(I use this technique elsewhere, but with fvwm2 as my window manager.
I run ssh-add and all of my X clients from within my .fvwm2rc file.)

As an alternative to providing all of ssh's options on the command
line, you can provide options in $HOME/.ssh/config.  This means that
you could set P4PORT to 'rsh:ssh perforce /bin/true', and put the
following in $HOME/.ssh/config:

  Host perforce
  # Don't prompt for passwords
  BatchMode yes
  # Never fall back to insecure rsh
  FallBackToRsh no
  # Don't forward the ssh agent or X
  ForwardAgent no
  ForwardX11 no
  # You could use ssh-keygen to create an alternate identity
  # IdentityFile /home/dmaze/.ssh/identity.p4
  # Only use RSA authentication
  PasswordAuthentication no
  RhostsAuthentication no
  RhostsRSAAuthentication no
  RSAAuthentication yes
  TISAuthentication no
  # Log in remotely as a different user
  User perforce

As suggested in the file, it is also possible to create an alternate
ssh RSA key pair if it is considered undesirable to use the normal key 
pair for Perforce.  'ssh-keygen -f $HOME/.ssh/identity.p4' will create 
the keypair referenced above.  Either specifying the identity file in
the ssh configuration file or using ssh's -i option will use this key
for further ssh connections.


5. Example Setup

This example setup is designed for a system running Red Hat Linux,
version 6.1.  It assumes that a GNOME environment is both available
and desirable, and that the user's shell is /bin/bash.

When Red Hat starts an X session, either through a display manager
like gdm or by running startx, it looks in $HOME/.Xclients for
programs to run.  A simple script that starts a GNOME session inside
an SSH agent would look like this:

  #!/bin/sh
  # .Xclients: things to start on login
  # Run GNOME inside an SSH agent.
  exec ssh-agent gnome-session

If other settings (environment variables, etc.) are needed, they
should be placed before the 'exec' line.

With this .Xclients script, log in and start X.  Start an X terminal,
and run ssh-keygen.  It will generate an RSA key pair, and ask you
where to save the key; the default location ($HOME/.ssh/identity) is
best if you have only a single key.  ssh-keygen will then prompt you
for a passphrase.  You should definitely enter a passphrase, and
preferably one that is noticably different from your normal login
password.  If you don't, your private key will be stored unencrypted
on the NFS server, and an attacker could steal your key just by
monitoring network traffic.

At this point, you'll need your RSA public key installed on the
Perforce server.  The public key is in $HOME/.ssh/identity.pub.
Your local administrator will have instructions on how to have the
installation performed.

Finally, set up GNOME to add your new ssh key to the agent at login
time.  Click on the GNOME menu.  On the Settings menu, choose
"Session", then "Startup Programs".  This will launch the GNOME
Control Center.  There is a box for "Non-session-managed Startup
Programs".  In this box, click the "Add..." button.  This pops up a
dialog box.  In the Startup Command field, enter "ssh-add", and set
the priority to 35.  Click "OK"; ssh-add should appear in the list
box.  Click "OK" at the bottom of the Control Center, and exit by
selecting "Exit" from the File menu.

Now log out and log in again.  A small dialog should appear, asking
you to type the passphrase for your RSA key.  Once you type the
passphrase, the key should be added to the SSH agent.  You can verify
this by typing 'ssh-add -l' from a shell prompt.

You'll also need to change your P4PORT variable to use the ssh
channel.  Find your definition of P4PORT; it'll probably be in your
.bashrc, .bash_profile, or .profile file in your home directory, or in 
a .perforce file at or above your workspace directory.  The setting
should look something like 'P4PORT=perforce:1669'; you'll want to
change it to 'P4PORT="rsh:ssh -q -a -x -l p4admin perforce
/bin/true"', which tells Perforce to use the ssh tunnel instead of an
unencrypted connection.

At this point, everything should work.  Log out and in again.  Try
running 'p4 info' to test that the Perforce connection does, in fact
work.  Other Perforce commands should work as well.  If you are
attempting to modify the P4USER environment variable, your connection
will fail.
# Change User Description Committed
#1 450 sandy_currier Initial import of p4filter code.
 This contains a solaris2.6 binary but
no others.