# HG changeset patch # User Paul Crowley # Date 1208346201 -3600 # Node ID dcd195f3e52c8ef6dd63638067001a689529526f # Parent 7e659a6870de9458c3cfdf85f92e302f1659e2e9 move config out of Python files; don't make hg-ssh-wrapper a dotfile; update documentation. diff -r 7e659a6870de -r dcd195f3e52c README --- a/README Tue Apr 15 18:30:43 2008 +0100 +++ b/README Wed Apr 16 12:43:21 2008 +0100 @@ -40,7 +40,7 @@ hg clone ~/hg/hg-admin-tools admin/hg-admin-tools hg clone ~/hg/hgadmin repos/hgadmin cp admin/hg-admin-tools/hgadmin-hgrc repos/hgadmin/.hg/hgrc - cp admin/hg-admin-tools/hg-ssh-wrapper .hg-ssh-wrapper + cp admin/hg-admin-tools/hg-ssh-wrapper hg-ssh-wrapper cd repos/hgadmin ../../admin/hg-admin-tools/refresh-auth exit diff -r 7e659a6870de -r dcd195f3e52c hg-ssh --- a/hg-ssh Tue Apr 15 18:30:43 2008 +0100 +++ b/hg-ssh Wed Apr 16 12:43:21 2008 +0100 @@ -1,34 +1,24 @@ #!/usr/bin/env python # # Copyright 2008 LShift Ltd -# Modified by Paul Crowley -# with ideas from Mathieu PASQUET # Copyright 2005-2007 by Intevation GmbH # Author(s): +# Paul Crowley # Thomas Arendsen Hein +# with ideas from Mathieu PASQUET # # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. """ -hg-ssh - a wrapper for ssh access to a limited set of mercurial repos +hg-ssh - limit access to hg repositories reached via ssh. Part of hg-admin-tools. -DO NOT USE WITHOUT READING THE SOURCE! In particular note that we HARDWIRE -the path to hgadmin/hg-ssh-access.conf. +This script is called by hg-ssh-wrapper with two arguments: -To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8): -command="hg-ssh keyid" ssh-dss ... -(probably together with these other useful options: - no-port-forwarding,no-X11-forwarding,no-agent-forwarding) - -This allows pull/push over ssh to to the repositories given as arguments. +hg-ssh -If all your repositories are subdirectories of a common directory, you can -allow shorter paths with: -command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2" - -You can use pattern matching of your normal shell, e.g.: -command="cd repos && hg-ssh user/thomas/* projects/{mercurial,foo}" +It expects to find the command the SSH user was trying to run in the environment variable +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. """ # enable importing on demand to reduce startup time @@ -49,7 +39,6 @@ if goodpathre.match(path) is None: fail("Disallowing path: %s" % path) - # Don't put anything except *A-Za-z0-9_- in rule globs or # you'll probably break security. No regexp metachars, not even . # We may fix this later. @@ -61,10 +50,10 @@ pattern = pattern.replace("*", "[%s]*" % allowedchars) return re.compile(pattern + "$").match(match) is not None -def testrule(keyname, path, applicable): +def testrule(rulefile keyname, path, applicable): goodpath(keyname) goodpath(path) - f = open("hgadmin/hg-ssh-access.conf") + f = open(rulefile) try: for l in f: l = l.strip() @@ -78,26 +67,26 @@ finally: f.close() -def get_cmd(keyname, cmd): +def get_cmd(rulefile, keyname, cmd): if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'): path = cmd[6:-14] - if testrule(keyname, path, set(["allow", "init"])): + if testrule(rulefile, keyname, path, set(["allow", "init"])): return ['-R', path, 'serve', '--stdio'] elif cmd.startswith('hg init '): path = cmd[8:] - if testrule(keyname, path, set(["init"])): + if testrule(rulefile, keyname, path, set(["init"])): return ['init', path] fail("Illegal command %r" % cmd) #logfile = open("/tmp/hg-ssh.%d.txt" % os.getpid(), "w") #logfile.write("Started: %s\n" % sys.argv) -if len(sys.argv) != 2: - fail("Error in hg-ssh configuration in .ssh/authorized_keys: too many arguments (%s)" +if len(sys.argv) != 3: + fail("hg-ssh must have exactly two arguments (%s)" % sys.argv) -keyname = sys.argv[1] -todispatch = get_cmd(keyname, os.environ.get('SSH_ORIGINAL_COMMAND', '?')) -os.environ["HG_ACL_USER"] = keyname +rulefile = sys.argv[1] +keyname = sys.argv[2] +todispatch = get_cmd(rulefile, keyname, os.environ.get('SSH_ORIGINAL_COMMAND', '?')) dispatch.dispatch(todispatch) diff -r 7e659a6870de -r dcd195f3e52c hg-ssh-wrapper --- a/hg-ssh-wrapper Tue Apr 15 18:30:43 2008 +0100 +++ b/hg-ssh-wrapper Wed Apr 16 12:43:21 2008 +0100 @@ -1,5 +1,17 @@ #!/bin/sh +# This file needs to be saved as ~/hg-ssh-wrapper for the user whose ~/.ssh/authorized_keys file +# is rewritten by refresh-auth. It expects to be specified as the target of the "command" section +# in the prefix of a key in the authorized_keys file, and be passed a name associated with an +# ssh key as its only argument. It does some setting up before calling hg-ssh, which does the real +# work of deciding whether to allow the users action based on the type of the action, the key name, +# and the contents of the specified rules file. + +# If your repository is laid out differently you may need to modify this file. + set -e cd repos -../admin/hg-admin-tools/hg-ssh "$@" +# Set up this environment variable - useful for hg hooks to check. +HG_ACL_USER=$1 +export HG_ACL_USER +../admin/hg-admin-tools/hg-ssh hgadmin/hg-ssh-access.conf "$@" diff -r 7e659a6870de -r dcd195f3e52c hgadmin-hgrc --- a/hgadmin-hgrc Tue Apr 15 18:30:43 2008 +0100 +++ b/hgadmin-hgrc Wed Apr 16 12:43:21 2008 +0100 @@ -1,4 +1,7 @@ +# WARNING: when these hooks run they will entirely destroy and rewrite +# ~/.ssh/authorized_keys + [hooks] changegroup.aaaaa_update = hg update -C default > /dev/null -changegroup.refreshauth = ../../admin/hg-admin-tools/refresh-auth +changegroup.refreshauth = ../../admin/hg-admin-tools/refresh-auth ./hg-ssh-wrapper diff -r 7e659a6870de -r dcd195f3e52c refresh-auth --- a/refresh-auth Tue Apr 15 18:30:43 2008 +0100 +++ b/refresh-auth Wed Apr 16 12:43:21 2008 +0100 @@ -1,14 +1,20 @@ #!/usr/bin/python # WARNING -# This script completely destroys your .ssh/authorized_keys +# This script completely destroys your ~/.ssh/authorized_keys # file every time it is run # WARNING +import sys import os import os.path import re +if len(sys.argv) != 2: + sys.stderr.write("refresh-auth: wrong number of arguments (%s)" % sys.argv) + sys.exit(-1) + +wrappercommand = sys.argv[1] akeyfile = os.path.expanduser("~/.ssh/authorized_keys") allowedchars = "A-Za-z0-9_.-" @@ -22,11 +28,10 @@ continue keyname = ffn[5:] prefix=('command="%s",no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding' - % ('./.hg-ssh-wrapper %s' % keyname)) + % ('%s %s' % (wrappercommand, keyname)) kf = open(ffn) try: for l in kf: - #prefix='no-port-forwarding,no-X11-forwarding,no-agent-forwarding' akeys.write("%s %s\n" % (prefix, l.strip())) finally: kf.close()