Differences between revisions 2 and 27 (spanning 25 versions)
Revision 2 as of 2005-12-22 21:09:13
Size: 6460
Comment:
Revision 27 as of 2008-04-02 22:11:19
Size: 8386
Editor: abuehl
Comment: mercurial -> Mercurial
Deletions are marked like this. Additions are marked like this.
Line 2: Line 2:
is setting up a central repository every user pushes his changes to and pulls is setting up a central [:Repository:repository] every user pushes his changes to and pulls
Line 4: Line 4:
accessible via a shared ssh account. accessible via a shared ssh account without needing to give full shell access
to other people.

[[TableOfContents]]

== How this works ==

When accessing a remote repository via Mercurial's ssh repository type, ''hg''
basically does a

{{{
$ ssh hg.example.com hg -R /path/to/repos serve --stdio
}}}

and relies on ssh for authentication and tunneling. When using public key
authentication, ssh allows limiting the user to one specific command, which
can do all the sanity checks we want and then calls ''hg'' just like ssh would
in the example above. Note that every user gets his own private key and
his own entry in authorized_keys, which allows the scripts to distinguish
between different users and thus enforce e.g. access permissions.


There are two alternative implementations of scripts which provide access only to
explicitly allowed repositories:

=== hg-ssh ===

  A python script available in
  [http://www.selenic.com/repo/hg-stable/raw-file/tip/contrib/hg-ssh contrib/hg-ssh].
  Allowed repositories are managed directly in the authorized_keys file.

  Look at the start of the script for usage instructions.

=== hg-login ===
Line 8: Line 41:
    described here a) works for mercurial out of the box and b) solves some
    problems from [http://www.kitenet.net/~joey/sshcvs/]: In particular, it
    described here a) works for Mercurial out of the box and b) solves some
    problems from [(Link broken) http://www.kitenet.net/~joey/sshcvs/]: In particular, it
Line 15: Line 48:
    '''Note 2:''' A python script similar to the hg-login script described here is available in
    current tip in [http://www.selenic.com/hg/?fl=0;file=contrib/hg-ssh contrib/hg-ssh]
    --- ThomasArendsenHein

== How this works ==

When accessing a remote repository via mercurial's ssh repository type, ''hg''
basically does a

{{{
$ ssh remote.server hg -R /path/to/repos serve --stdio
}}}

and relies on ssh for authentication and tunneling. When using public key
authentication, ssh allows limiting the user to one specific command, which
can do all the sanity checks we want and then execs ''hg'' just like ssh would
in the example above. Note that every user gets his own private key and
his own entry in authorized_keys, which allows the script to distinguish
between different users and thus enforce e.g. access permissions.

== Setting up the shared SSH account ==
== Setting up the shared SSH account for hg-login ==
Line 39: Line 52:
password, so set the password field in the /etc/passwd to *. It needs a valid password, so set the password field in the /etc/passwd (or /etc/shadow) to *. It needs a valid
Line 52: Line 65:
for how to create one). Append it to ''~mercurial/.ssh/authorized_keys'' for how to create one). Append the generated public key to ''~mercurial/.ssh/authorized_keys''
Line 57: Line 70:
command="/home/mercurial/hg-login [user]",no-port-forwarding,no-X11-forwarding,no-pty ssh-[type] [key] command="/home/mercurial/hg-login [user]",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-[type] [key]
Line 104: Line 117:
my $r_file = qr#[a-zA-Z0-9][a-zA-Z0-9-:+.]*#; my $r_file = qr#[a-zA-Z0-9][a-zA-Z0-9-:+._]*#;
Line 120: Line 133:
    or die "Invalid username `$user_in'.}n";     or die "Invalid username `$user_in'.\n";
Line 139: Line 152:
# Only the toplevel-directory of every mercurial repository contains # Only the toplevel-directory of every Mercurial repository contains
Line 162: Line 175:

== Extending above to allow read only access for some ==

Using a hook and slightly extending above, you can implement read only access for some users. Change

{{{
$user eq $_ and $allowed = 1 for @allowed_in;
}}}

to

{{{
my $allowed = '';
for (@allowed_in) {
  if ($user eq $_) {
    $allowed = 1;
    $ENV{READ_WRITE} = 1;
    last;
  } elsif ("${user}.ro" eq $_) {
    $allowed = 1;
    $ENV{READ_ONLY} = 1;
    last;
  }
}
}}}

Then add a hook via {{{.hgrc}}} like so...

{{{
[hooks]
pretxnchangegroup.deny.lock = /path/to/lock_script
}}}

Where {{{lock_script}}} is a simple shell script like

{{{
#!/bin/ksh
if [[ "x${READ_ONLY}" != "x" ]]; then
  print "You do not have write access." 1>&2
  exit 1
fi
exit 0
}}}

Now any users in the {{{allow}}} file above specified like
{{{
someuser.ro
}}}
will have read only access while others will have read write access.

== Further enhancement to allow even finer grained control ==

The above can be further extended to add

{{{
# allow more control via hooks
$ENV{SSH_HG_USER} = $user;
}}}

just before

{{{
exec $hg, '-R', "$repositories/$path", 'serve', '--stdio';
}}}

By doing so, the hooks can make use of the {{{SSH_HG_USER}}} env. variable and make even more fine grained access control decisions e.g.

{{{
#!/bin/ksh
if [[ "x${SSH_HG_USER}" == "xmpm" ]]; then
   print "Allowing access to mpm" 1>&2
else
   print "You are not mpm. Access denied." 1>&2
   exit 1
fi
exit 0
}}}
----
CategoryWeb CategoryHowTo

As described on MultipleCommitters, one way of collaboration (the CVS-like model) is setting up a central [:Repository:repository] every user pushes his changes to and pulls the others' changes from. This page describes how to create such repositories accessible via a shared ssh account without needing to give full shell access to other people.

TableOfContents

How this works

When accessing a remote repository via Mercurial's ssh repository type, hg basically does a

$ ssh hg.example.com hg -R /path/to/repos serve --stdio

and relies on ssh for authentication and tunneling. When using public key authentication, ssh allows limiting the user to one specific command, which can do all the sanity checks we want and then calls hg just like ssh would in the example above. Note that every user gets his own private key and his own entry in authorized_keys, which allows the scripts to distinguish between different users and thus enforce e.g. access permissions.

There are two alternative implementations of scripts which provide access only to explicitly allowed repositories:

hg-ssh

hg-login

  • Note: The following instructions describe the very personal setup we use on our system. I decided to add this page because the configuration described here a) works for Mercurial out of the box and b) solves some problems from [(Link broken) http://www.kitenet.net/~joey/sshcvs/]: In particular, it allows distinguishing multiple committers and a (crude) form of permissions. It is most probably neither the best nor the most elegant way and I don't promise anything more than that it works for me. --- MarcSchaefer

Setting up the shared SSH account for hg-login

The first step is creating a dedicated user on the server side -- let's call it mercurial. Nobody should be able to log into this account with a password, so set the password field in the /etc/passwd (or /etc/shadow) to *. It needs a valid shell though, since sshd always calls scripts through the shell. Then, copy the hg-login script at the end of this page into the home directory and create a directory repositories, which will contain (wait for it) the repositories (duh).

Note that everybody with read/write permissions to the repository directory can read/write to the repositories directly, so you might want to prevent that.

Allowing connections from a user

Every user needs his own public/private key (see the manpage of ssh-keygen for how to create one). Append the generated public key to ~mercurial/.ssh/authorized_keys on the server side, prefixed with some options to grant access to mercurial only. More precisely, every line has to look like this:

command="/home/mercurial/hg-login [user]",no-port-forwarding,no-X11-forwarding,no-agent-forwarding ssh-[type] [key]

Here [user] is an identifier which will later be used for granting access to a repository, [type] is dsa or rsa depending on the key type and [key] is the key itself, followed by an optional comment.

On every connect, the user must be able to present the corresponding private key, for example by adding it to his ssh-agent.

Creating repositories and setting permissions

Creating a shared repository is simple: Just initialise it in repositories like every other repository. However, nobody will be able to access it unless you grant them permission. To allow a user to access the repository ~mercurial/repositories/<repos>, create a file ~mercurial/repositories/<repos>.allow which contains his username (the one from authorized_keys) alone on a line.

Note that it is not possible to only grant read rights -- it's full access or nothing.

The hg-login script

The following is a (Perl) script (sorry ;) ) to mediate the access to the shared repositories. It first of all checks the supplied username and the command that is to be executed for sanity (usernames must be alphanumeric, starting with a letter), then normalises and checks the repository path (creating subdirectories in repositories is allowed, but file names must match ^[a-zA-Z0-9][a-zA-Z0-9-:+.]$). Only if these checks pass and the desired repository exists and allows access by the user, the server process is started.

use strict;

$ENV{PATH} = '/usr/local/bin:/usr/bin:/bin';

my $hg = '/usr/local/bin/hg';
my $repositories = '/home/mercurial/repositories';

# The following character classes describe the allowed user-
# and repository names. Note that we forbid all path constituents
# which begin with a dot -- look ma, no directory traversal.

my $r_user = qr#[a-zA-Z][a-zA-Z0-9]*#;
my $r_file = qr#[a-zA-Z0-9][a-zA-Z0-9-:+._]*#;

# The username is given as the first argument (from command=
# in authorized_keys), sshd is kind enough to pass the requested
# command as an environment variable.

my $user_in = $ARGV[0];
my $cmd_in  = $ENV{SSH_ORIGINAL_COMMAND} || '';

# First, basic sanity checking on the username. The assignment
# is necessary to convince Perl that the username is no longer
# tainted.

defined $user_in
    or die "No username given.\n";
my ($user) = $user_in =~ /^($r_user)$/
    or die "Invalid username `$user_in'.\n";

# The command passed by hg has a very specific structure: Check that.

my ($repos) = $cmd_in =~ m#^hg -R (\S+) serve --stdio$#
    or die "Invalid command `$cmd_in' requested.\n";

# Now for the repository path: We assume that it consists of $r_files
# separated by slashes. Leading and trailing ones are ignored.

s#^/+##, s#/+$##, s#/+#/#g for $repos;

my $path = '';
foreach my $file_in (split m#/#, $repos) {
    my ($file) = $file_in =~ /^($r_file)$/
        or die "Invalid repository path `$repos'";
    $path .= "/$file";
}

# Only the toplevel-directory of every Mercurial repository contains
# a subdir `.hg'.

-d "$repositories/$path/.hg" or die "No such repository `$path'.\n";

# Now for permissions ...

open my $perms, '<', "$repositories/$path.allow"
    or die "No such repositoriy `$path'.\n";

chomp( my @allowed_in = <$perms> );

close $perms;

my $allowed = '';
$user eq $_ and $allowed = 1 for @allowed_in;
$allowed or die "No such repository `$path'.\n";

# Ok, everything is in order: go for it.

exec $hg, '-R', "$repositories/$path", 'serve', '--stdio';
die "Unable to exec `hg' on repository `$path' ($!)\n";

Extending above to allow read only access for some

Using a hook and slightly extending above, you can implement read only access for some users. Change

$user eq $_ and $allowed = 1 for @allowed_in;

to

my $allowed = '';
for (@allowed_in) {
  if ($user eq $_) {
    $allowed = 1;
    $ENV{READ_WRITE} = 1;
    last;
  } elsif ("${user}.ro" eq $_) {
    $allowed = 1;
    $ENV{READ_ONLY} = 1;
    last;
  }
}

Then add a hook via .hgrc like so...

[hooks]
pretxnchangegroup.deny.lock = /path/to/lock_script

Where lock_script is a simple shell script like

if [[ "x${READ_ONLY}" != "x" ]]; then
  print "You do not have write access." 1>&2
  exit 1
fi
exit 0

Now any users in the allow file above specified like

someuser.ro

will have read only access while others will have read write access.

Further enhancement to allow even finer grained control

The above can be further extended to add

# allow more control via hooks
$ENV{SSH_HG_USER} = $user;

just before

exec $hg, '-R', "$repositories/$path", 'serve', '--stdio';

By doing so, the hooks can make use of the SSH_HG_USER env. variable and make even more fine grained access control decisions e.g.

if [[ "x${SSH_HG_USER}" == "xmpm" ]]; then
   print "Allowing access to mpm" 1>&2
else
   print "You are not mpm. Access denied." 1>&2
   exit 1
fi
exit 0


CategoryWeb CategoryHowTo

SharedSSH (last edited 2021-03-19 07:37:31 by RobinMunn)