hg-ssh
changeset 4 dcd195f3e52c
parent 0 41ecb5a3172c
child 5 6fc5eab8ae58
equal deleted inserted replaced
3:7e659a6870de 4:dcd195f3e52c
     1 #!/usr/bin/env python
     1 #!/usr/bin/env python
     2 #
     2 #
     3 # Copyright 2008 LShift Ltd
     3 # Copyright 2008 LShift Ltd
     4 # Modified by Paul Crowley <paul@lshift.net>
       
     5 # with ideas from  Mathieu PASQUET <kiorky@cryptelium.net>
       
     6 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
     4 # Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
     7 # Author(s):
     5 # Author(s):
       
     6 # Paul Crowley <paul@lshift.net>
     8 # Thomas Arendsen Hein <thomas@intevation.de>
     7 # Thomas Arendsen Hein <thomas@intevation.de>
       
     8 # with ideas from  Mathieu PASQUET <kiorky@cryptelium.net>
     9 #
     9 #
    10 # This software may be used and distributed according to the terms
    10 # This software may be used and distributed according to the terms
    11 # of the GNU General Public License, incorporated herein by reference.
    11 # of the GNU General Public License, incorporated herein by reference.
    12 
    12 
    13 """
    13 """
    14 hg-ssh - a wrapper for ssh access to a limited set of mercurial repos
    14 hg-ssh - limit access to hg repositories reached via ssh.  Part of hg-admin-tools.
    15 
    15 
    16 DO NOT USE WITHOUT READING THE SOURCE!  In particular note that we HARDWIRE
    16 This script is called by hg-ssh-wrapper with two arguments:
    17 the path to hgadmin/hg-ssh-access.conf.
       
    18 
    17 
    19 To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8):
    18 hg-ssh <rulefile> <keyname>
    20 command="hg-ssh keyid" ssh-dss ...
       
    21 (probably together with these other useful options:
       
    22  no-port-forwarding,no-X11-forwarding,no-agent-forwarding)
       
    23 
    19 
    24 This allows pull/push over ssh to to the repositories given as arguments.
    20 It expects to find the command the SSH user was trying to run in the environment variable
    25 
    21 SSH_ORIGINAL_COMMAND, and uses it to determine what the user was trying to do and to what repository, and then checks each rule in the rule file in turn for a matching rule which decides what to do, defaulting to disallowing the action.
    26 If all your repositories are subdirectories of a common directory, you can
       
    27 allow shorter paths with:
       
    28 command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2"
       
    29 
       
    30 You can use pattern matching of your normal shell, e.g.:
       
    31 command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}"
       
    32 """
    22 """
    33 
    23 
    34 # enable importing on demand to reduce startup time
    24 # enable importing on demand to reduce startup time
    35 from mercurial import demandimport; demandimport.enable()
    25 from mercurial import demandimport; demandimport.enable()
    36 
    26 
    47 goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))
    37 goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))
    48 def goodpath(path):
    38 def goodpath(path):
    49     if goodpathre.match(path) is None:
    39     if goodpathre.match(path) is None:
    50         fail("Disallowing path: %s" % path)
    40         fail("Disallowing path: %s" % path)
    51 
    41 
    52 
       
    53 # Don't put anything except *A-Za-z0-9_- in rule globs or
    42 # Don't put anything except *A-Za-z0-9_- in rule globs or
    54 # you'll probably break security.  No regexp metachars, not even .
    43 # you'll probably break security.  No regexp metachars, not even .
    55 # We may fix this later.
    44 # We may fix this later.
    56 goodglobre = re.compile("[*/%s]+$" % allowedchars)
    45 goodglobre = re.compile("[*/%s]+$" % allowedchars)
    57 def globmatch(pattern, match):
    46 def globmatch(pattern, match):
    59         fail("Bad glob pattern in auth config: %s" % pattern)
    48         fail("Bad glob pattern in auth config: %s" % pattern)
    60     pattern = pattern.replace(".", r'\.')
    49     pattern = pattern.replace(".", r'\.')
    61     pattern = pattern.replace("*", "[%s]*" % allowedchars)
    50     pattern = pattern.replace("*", "[%s]*" % allowedchars)
    62     return re.compile(pattern + "$").match(match) is not None
    51     return re.compile(pattern + "$").match(match) is not None
    63 
    52 
    64 def testrule(keyname, path, applicable):
    53 def testrule(rulefile keyname, path, applicable):
    65     goodpath(keyname)
    54     goodpath(keyname)
    66     goodpath(path)
    55     goodpath(path)
    67     f = open("hgadmin/hg-ssh-access.conf")
    56     f = open(rulefile)
    68     try:
    57     try:
    69         for l in f:
    58         for l in f:
    70             l = l.strip()
    59             l = l.strip()
    71             if l == "" or l.startswith("#"):
    60             if l == "" or l.startswith("#"):
    72                 continue
    61                 continue
    76                 return rule in applicable
    65                 return rule in applicable
    77         return False
    66         return False
    78     finally:
    67     finally:
    79         f.close()
    68         f.close()
    80 
    69 
    81 def get_cmd(keyname, cmd):
    70 def get_cmd(rulefile, keyname, cmd):
    82     if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'):
    71     if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'):
    83         path = cmd[6:-14]
    72         path = cmd[6:-14]
    84         if testrule(keyname, path, set(["allow", "init"])):
    73         if testrule(rulefile, keyname, path, set(["allow", "init"])):
    85             return ['-R', path, 'serve', '--stdio']
    74             return ['-R', path, 'serve', '--stdio']
    86     elif cmd.startswith('hg init '):
    75     elif cmd.startswith('hg init '):
    87         path = cmd[8:]
    76         path = cmd[8:]
    88         if testrule(keyname, path, set(["init"])):
    77         if testrule(rulefile, keyname, path, set(["init"])):
    89             return ['init', path]
    78             return ['init', path]
    90     fail("Illegal command %r" % cmd)
    79     fail("Illegal command %r" % cmd)
    91 
    80 
    92 #logfile = open("/tmp/hg-ssh.%d.txt" % os.getpid(), "w")
    81 #logfile = open("/tmp/hg-ssh.%d.txt" % os.getpid(), "w")
    93 #logfile.write("Started: %s\n" % sys.argv)
    82 #logfile.write("Started: %s\n" % sys.argv)
    94 
    83 
    95 if len(sys.argv) != 2:
    84 if len(sys.argv) != 3:
    96     fail("Error in hg-ssh configuration in .ssh/authorized_keys: too many arguments (%s)" 
    85     fail("hg-ssh must have exactly two arguments (%s)" 
    97         % sys.argv)
    86         % sys.argv)
    98 
    87 
    99 keyname = sys.argv[1]
    88 rulefile = sys.argv[1]
   100 todispatch = get_cmd(keyname, os.environ.get('SSH_ORIGINAL_COMMAND', '?'))
    89 keyname = sys.argv[2]
   101 os.environ["HG_ACL_USER"] = keyname
    90 todispatch = get_cmd(rulefile, keyname, os.environ.get('SSH_ORIGINAL_COMMAND', '?'))
   102 dispatch.dispatch(todispatch)
    91 dispatch.dispatch(todispatch)
   103 
    92