--- a/access.py Thu Jun 05 16:53:57 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,55 +0,0 @@
-# Copyright 2008 LShift Ltd
-# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# Authors:
-# Paul Crowley <paul@lshift.net>
-# Vadim Gelfer <vadim.gelfer@gmail.com>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-from mercurial.i18n import _
-from mercurial.node import *
-from mercurial import util
-
-import os
-import ruleset
-
-class Checker(object):
- '''acl checker.'''
-
- def __init__(self, ui, repo):
- self.ui = ui
- self.repo = repo
- self.rules = ruleset.Ruleset.readfile(os.environ['HG_ACCESS_RULES_FILE'])
- self.rules.set(user = os.environ['REMOTE_USER'])
- self.rules.set(repo = os.environ['HG_REPO_PATH'])
-
- def allow(self, node):
- '''return if access allowed, raise exception if not.'''
- ctx = self.repo.changectx(node)
- branch = ctx.branch()
- if not self.rules.allow("write", branch=branch, file=None):
- return False
- for f in ctx.files():
- if not self.rules.allow("write", branch=branch, file=f):
- return False
- self.ui.debug(_('%s: allowing changeset %s\n') % (__name__, short(node)))
- return True
-
- def check(self, node):
- if not self.allow(node):
- raise util.Abort(_('%s: access denied for changeset %s') %
- (__name__, short(node)))
-
-
-def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
- if hooktype != 'pretxnchangegroup':
- raise util.Abort(_('config error - hook type "%s" cannot stop '
- 'incoming changesets') % hooktype)
- c = Checker(ui, repo)
- start = repo.changelog.rev(bin(node))
- end = repo.changelog.count()
- for rev in xrange(start, end):
- c.check(repo.changelog.node(rev))
-
--- a/hg-ssh Thu Jun 05 16:53:57 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,77 +0,0 @@
-#!/usr/bin/env python
-#
-# Copyright 2008 LShift Ltd
-# Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
-# Authors:
-# Paul Crowley <paul@lshift.net>
-# Thomas Arendsen Hein <thomas@intevation.de>
-# with ideas from Mathieu PASQUET <kiorky@cryptelium.net>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-"""
-hg-ssh - limit access to hg repositories reached via ssh. Part of
-hg-admin-tools.
-
-This script is called by hg-ssh-wrapper with no arguments - everything
-should be in enviroment variables:
-
-HG_ACCESS_RULES_FILE identifies the path to the rules file
-REMOTE_USER the remote user (which is the key used by ssh)
-SSH_ORIGINAL_COMMAND the command the user was trying to run
-
-It uses SSH_ORIGINAL_COMMAND 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
-from mercurial import demandimport; demandimport.enable()
-
-from mercurial import dispatch
-
-import sys, os
-import ruleset
-
-def fail(message):
- #logfile.write("Fail: %s\n" % message)
- sys.stderr.write(message + "\n")
- sys.exit(-1)
-
-def getpath(path):
- if path.endswith("/"):
- path = path[:-1]
- if not ruleset.goodpath(path):
- fail("Disallowing path: %s" % path)
- return path
-
-def get_cmd(rules, cmd):
- if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'):
- repo = getpath(cmd[6:-14])
- if rules.allow("read", repo=repo):
- os.environ["HG_REPO_PATH"] = repo
- return ['-R', repo, 'serve', '--stdio']
- elif cmd.startswith('hg init '):
- repo = getpath(cmd[8:])
- if rules.allow("init", repo=repo):
- os.environ["HG_REPO_PATH"] = repo
- return ['init', repo]
- 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) != 1:
- fail("hg-ssh must have no arguments (%s)"
- % sys.argv)
-
-rules = ruleset.Ruleset.readfile(os.environ['HG_ACCESS_RULES_FILE'])
-rules.set(user = getpath(os.environ['REMOTE_USER']))
-rules.set(branch = None, file = None)
-todispatch = get_cmd(rules,
- os.environ.get('SSH_ORIGINAL_COMMAND', '?'))
-dispatch.dispatch(todispatch)
-
--- a/hginit Thu Jun 05 16:53:57 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,10 +0,0 @@
-#!/bin/sh
-
-set -e
-
-cd ~hg
-mkdir -p repos/hgadmin .ssh
-cd repos/hgadmin
-hg init .
-cp /usr/local/lib/hg-admin-tools/hgadmin-hgrc .hg/hgrc
-
--- a/install Thu Jun 05 16:53:57 2008 +0100
+++ b/install Mon Jun 16 17:12:20 2008 +0100
@@ -4,7 +4,21 @@
install -o root -g root -d /usr/local/lib/hg-admin-tools
install -o root -g root -t /usr/local/lib/hg-admin-tools \
- access.py hg-ssh refresh-auth ruleset.py hgadmin-hgrc create-breakin-repository ssh-replacement as-if-by-ssh break-in
+ src/access.py \
+ src/hg-ssh \
+ src/refresh-auth \
+ src/ruleset.py
+install -o root -g root -d /usr/local/lib/hg-admin-tools/init
+install -o root -g root -t /usr/local/lib/hg-admin-tools/init \
+ src/init/hgadmin-hgrc
+install -o root -g root -d /usr/local/lib/hg-admin-tools/init/break-in
+install -o root -g root -t /usr/local/lib/hg-admin-tools/init/break-in \
+ src/init/break-in/create-breakin-repository \
+ src/init/break-in/ssh-replacement
+ src/init/break-in/as-if-by-ssh
+ src/init/break-in/break-in
install -o root -g root -d /etc/hg-admin-tools
-install -o root -g root -t /etc/hg-admin-tools hg-ssh-wrapper remote-hgrc
+install -o root -g root -t /etc/hg-admin-tools \
+ src/init/conf/hg-ssh-wrapper \
+ src/init/conf/remote-hgrc
--- a/refresh-auth Thu Jun 05 16:53:57 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,61 +0,0 @@
-#!/usr/bin/env python
-
-# WARNING
-# This script completely destroys your ~/.ssh/authorized_keys
-# file every time it is run
-# WARNING
-
-import sys
-import os
-import os.path
-import ruleset
-import subprocess
-
-if len(sys.argv) != 3:
- sys.stderr.write("refresh-auth: wrong number of arguments (%s)\n" % sys.argv)
- sys.exit(-1)
-
-akeyfile = sys.argv[1]
-wrappercommand = sys.argv[2]
-prefix='no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command='
-
-if os.path.exists(akeyfile):
- f = open(akeyfile)
- try:
- for l in f:
- if not l.startswith(prefix):
- raise Exception("Safety check failed, delete %s to continue" % akeyfile)
- finally:
- f.close()
-
-akeys = open(akeyfile + "_new", "w")
-for root, dirs, files in os.walk("keys"):
- for fn in files:
- ffn = os.path.join(root, fn)
- if not ruleset.goodpath(ffn):
- # ignore any path that contains dodgy characters
- continue
- keyname = ffn[5:]
- if keyname == "root":
- # No key can claim root privileges
- continue
- p = subprocess.Popen(("ssh-keygen", "-i", "-f", ffn),
- stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- newkey = p.communicate()[0]
- if p.wait() == 0:
- klines = [l.strip() for l in newkey.split("\n")]
- else:
- # Conversion failed, read it directly.
- kf = open(ffn)
- try:
- klines = [l.strip() for l in kf]
- finally:
- kf.close()
- for l in klines:
- if len(l):
- akeys.write('%s"%s %s" %s\n' % (prefix, wrappercommand, keyname, l))
-
-akeys.close()
-
-os.rename(akeyfile + "_new", akeyfile)
-
--- a/remote-hgrc Thu Jun 05 16:53:57 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,7 +0,0 @@
-# hgrc to use for all remote users
-
-[extensions]
-access = /usr/local/lib/hg-admin-tools/access.py
-
-[hooks]
-pretxnchangegroup.access = python:access.hook
--- a/ruleset.py Thu Jun 05 16:53:57 2008 +0100
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,95 +0,0 @@
-# Copyright 2008 LShift Ltd
-# Author(s):
-# Paul Crowley <paul@lshift.net>
-#
-# This software may be used and distributed according to the terms
-# of the GNU General Public License, incorporated herein by reference.
-
-import sys
-import re
-
-allowedchars = "A-Za-z0-9_-"
-
-goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))
-def goodpath(path):
- return goodpathre.match(path) is not None
-
-goodglobre = re.compile("[*/%s]+$" % allowedchars)
-
-def goodglob(pattern):
- return goodglobre.match(pattern) is not None
-
-# Don't put anything except *A-Za-z0-9_- in rule globs or
-# it will match nothing. No regexp metachars, not even .
-# We may fix this later.
-def globmatcher(pattern):
- if not goodglob(pattern):
- #fail("Bad glob pattern in auth config: %s" % pattern)
- # FIXME: report it somehow
- return lambda x: False
- # Substitution cunning so ** can be different from *
- pattern = pattern.replace("*", "[]")
- pattern = pattern.replace("[][]", "[/%s]*" % allowedchars)
- pattern = pattern.replace("[]", "[%s]*" % allowedchars)
- rex = re.compile(pattern + "$")
- # None matches everything
- return lambda x: x is None or rex.match(x) is not None
-
-def rule(pairs):
- matchers = [(k, globmatcher(v)) for k, v in pairs]
- def c(**kw):
- for k, m in matchers:
- if k not in kw or not m(kw[k]):
- return False
- return True
- return c
-
-class Ruleset(object):
- '''Class representing the rules in a rule file'''
-
- levels = ["init", "write", "read", "deny"]
-
- def __init__(self):
- # The user called "root" automatically has the highest
- # privilege
- self.rules = [(self.levels[0], rule([('user', 'root')]))]
- self.preset = {}
-
- def add(self, action, conditions):
- self.rules.append((action, conditions))
-
-
- def set(self, **kw):
- self.preset.update(kw)
-
- def matchrule(self, **kw):
- d = self.preset.copy()
- d.update(**kw)
- for a, c in self.rules:
- if c(**d):
- return a
- return None
-
- def allow(self, level, **kw):
- a = self.matchrule(**kw)
- return a in self.levels and self.levels.index(a) <= self.levels.index(level)
-
- @classmethod
- def readfile(cls, fn):
- res = cls()
- try:
- f = open(fn)
- try:
- for l in f:
- l = l.strip()
- if len(l) == 0 or l.startswith("#"):
- continue
- l = l.split()
- res.add(l[0], rule([c.split("=", 1) for c in l[1:]]))
- finally:
- f.close()
- except Exception, e:
- print >> sys.stderr, "Failure reading rules file:", e
- return cls()
- return res
-
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/access.py Mon Jun 16 17:12:20 2008 +0100
@@ -0,0 +1,55 @@
+# Copyright 2008 LShift Ltd
+# Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# Authors:
+# Paul Crowley <paul@lshift.net>
+# Vadim Gelfer <vadim.gelfer@gmail.com>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+from mercurial.i18n import _
+from mercurial.node import *
+from mercurial import util
+
+import os
+import ruleset
+
+class Checker(object):
+ '''acl checker.'''
+
+ def __init__(self, ui, repo):
+ self.ui = ui
+ self.repo = repo
+ self.rules = ruleset.Ruleset.readfile(os.environ['HG_ACCESS_RULES_FILE'])
+ self.rules.set(user = os.environ['REMOTE_USER'])
+ self.rules.set(repo = os.environ['HG_REPO_PATH'])
+
+ def allow(self, node):
+ '''return if access allowed, raise exception if not.'''
+ ctx = self.repo.changectx(node)
+ branch = ctx.branch()
+ if not self.rules.allow("write", branch=branch, file=None):
+ return False
+ for f in ctx.files():
+ if not self.rules.allow("write", branch=branch, file=f):
+ return False
+ self.ui.debug(_('%s: allowing changeset %s\n') % (__name__, short(node)))
+ return True
+
+ def check(self, node):
+ if not self.allow(node):
+ raise util.Abort(_('%s: access denied for changeset %s') %
+ (__name__, short(node)))
+
+
+def hook(ui, repo, hooktype, node=None, source=None, **kwargs):
+ if hooktype != 'pretxnchangegroup':
+ raise util.Abort(_('config error - hook type "%s" cannot stop '
+ 'incoming changesets') % hooktype)
+ c = Checker(ui, repo)
+ start = repo.changelog.rev(bin(node))
+ end = repo.changelog.count()
+ for rev in xrange(start, end):
+ c.check(repo.changelog.node(rev))
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/hg-ssh Mon Jun 16 17:12:20 2008 +0100
@@ -0,0 +1,77 @@
+#!/usr/bin/env python
+#
+# Copyright 2008 LShift Ltd
+# Copyright 2005-2007 by Intevation GmbH <intevation@intevation.de>
+# Authors:
+# Paul Crowley <paul@lshift.net>
+# Thomas Arendsen Hein <thomas@intevation.de>
+# with ideas from Mathieu PASQUET <kiorky@cryptelium.net>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+"""
+hg-ssh - limit access to hg repositories reached via ssh. Part of
+hg-admin-tools.
+
+This script is called by hg-ssh-wrapper with no arguments - everything
+should be in enviroment variables:
+
+HG_ACCESS_RULES_FILE identifies the path to the rules file
+REMOTE_USER the remote user (which is the key used by ssh)
+SSH_ORIGINAL_COMMAND the command the user was trying to run
+
+It uses SSH_ORIGINAL_COMMAND 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
+from mercurial import demandimport; demandimport.enable()
+
+from mercurial import dispatch
+
+import sys, os
+import ruleset
+
+def fail(message):
+ #logfile.write("Fail: %s\n" % message)
+ sys.stderr.write(message + "\n")
+ sys.exit(-1)
+
+def getpath(path):
+ if path.endswith("/"):
+ path = path[:-1]
+ if not ruleset.goodpath(path):
+ fail("Disallowing path: %s" % path)
+ return path
+
+def get_cmd(rules, cmd):
+ if cmd.startswith('hg -R ') and cmd.endswith(' serve --stdio'):
+ repo = getpath(cmd[6:-14])
+ if rules.allow("read", repo=repo):
+ os.environ["HG_REPO_PATH"] = repo
+ return ['-R', repo, 'serve', '--stdio']
+ elif cmd.startswith('hg init '):
+ repo = getpath(cmd[8:])
+ if rules.allow("init", repo=repo):
+ os.environ["HG_REPO_PATH"] = repo
+ return ['init', repo]
+ 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) != 1:
+ fail("hg-ssh must have no arguments (%s)"
+ % sys.argv)
+
+rules = ruleset.Ruleset.readfile(os.environ['HG_ACCESS_RULES_FILE'])
+rules.set(user = getpath(os.environ['REMOTE_USER']))
+rules.set(branch = None, file = None)
+todispatch = get_cmd(rules,
+ os.environ.get('SSH_ORIGINAL_COMMAND', '?'))
+dispatch.dispatch(todispatch)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/conf/remote-hgrc Mon Jun 16 17:12:20 2008 +0100
@@ -0,0 +1,7 @@
+# hgrc to use for all remote users
+
+[extensions]
+access = /usr/local/lib/hg-admin-tools/access.py
+
+[hooks]
+pretxnchangegroup.access = python:access.hook
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/init/hginit Mon Jun 16 17:12:20 2008 +0100
@@ -0,0 +1,10 @@
+#!/bin/sh
+
+set -e
+
+cd ~hg
+mkdir -p repos/hgadmin .ssh
+cd repos/hgadmin
+hg init .
+cp /usr/local/lib/hg-admin-tools/hgadmin-hgrc .hg/hgrc
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/refresh-auth Mon Jun 16 17:12:20 2008 +0100
@@ -0,0 +1,61 @@
+#!/usr/bin/env python
+
+# WARNING
+# This script completely destroys your ~/.ssh/authorized_keys
+# file every time it is run
+# WARNING
+
+import sys
+import os
+import os.path
+import ruleset
+import subprocess
+
+if len(sys.argv) != 3:
+ sys.stderr.write("refresh-auth: wrong number of arguments (%s)\n" % sys.argv)
+ sys.exit(-1)
+
+akeyfile = sys.argv[1]
+wrappercommand = sys.argv[2]
+prefix='no-pty,no-port-forwarding,no-X11-forwarding,no-agent-forwarding,command='
+
+if os.path.exists(akeyfile):
+ f = open(akeyfile)
+ try:
+ for l in f:
+ if not l.startswith(prefix):
+ raise Exception("Safety check failed, delete %s to continue" % akeyfile)
+ finally:
+ f.close()
+
+akeys = open(akeyfile + "_new", "w")
+for root, dirs, files in os.walk("keys"):
+ for fn in files:
+ ffn = os.path.join(root, fn)
+ if not ruleset.goodpath(ffn):
+ # ignore any path that contains dodgy characters
+ continue
+ keyname = ffn[5:]
+ if keyname == "root":
+ # No key can claim root privileges
+ continue
+ p = subprocess.Popen(("ssh-keygen", "-i", "-f", ffn),
+ stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+ newkey = p.communicate()[0]
+ if p.wait() == 0:
+ klines = [l.strip() for l in newkey.split("\n")]
+ else:
+ # Conversion failed, read it directly.
+ kf = open(ffn)
+ try:
+ klines = [l.strip() for l in kf]
+ finally:
+ kf.close()
+ for l in klines:
+ if len(l):
+ akeys.write('%s"%s %s" %s\n' % (prefix, wrappercommand, keyname, l))
+
+akeys.close()
+
+os.rename(akeyfile + "_new", akeyfile)
+
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/src/ruleset.py Mon Jun 16 17:12:20 2008 +0100
@@ -0,0 +1,95 @@
+# Copyright 2008 LShift Ltd
+# Author(s):
+# Paul Crowley <paul@lshift.net>
+#
+# This software may be used and distributed according to the terms
+# of the GNU General Public License, incorporated herein by reference.
+
+import sys
+import re
+
+allowedchars = "A-Za-z0-9_-"
+
+goodpathre = re.compile("([%s]+/)*[%s]+$" % (allowedchars, allowedchars))
+def goodpath(path):
+ return goodpathre.match(path) is not None
+
+goodglobre = re.compile("[*/%s]+$" % allowedchars)
+
+def goodglob(pattern):
+ return goodglobre.match(pattern) is not None
+
+# Don't put anything except *A-Za-z0-9_- in rule globs or
+# it will match nothing. No regexp metachars, not even .
+# We may fix this later.
+def globmatcher(pattern):
+ if not goodglob(pattern):
+ #fail("Bad glob pattern in auth config: %s" % pattern)
+ # FIXME: report it somehow
+ return lambda x: False
+ # Substitution cunning so ** can be different from *
+ pattern = pattern.replace("*", "[]")
+ pattern = pattern.replace("[][]", "[/%s]*" % allowedchars)
+ pattern = pattern.replace("[]", "[%s]*" % allowedchars)
+ rex = re.compile(pattern + "$")
+ # None matches everything
+ return lambda x: x is None or rex.match(x) is not None
+
+def rule(pairs):
+ matchers = [(k, globmatcher(v)) for k, v in pairs]
+ def c(**kw):
+ for k, m in matchers:
+ if k not in kw or not m(kw[k]):
+ return False
+ return True
+ return c
+
+class Ruleset(object):
+ '''Class representing the rules in a rule file'''
+
+ levels = ["init", "write", "read", "deny"]
+
+ def __init__(self):
+ # The user called "root" automatically has the highest
+ # privilege
+ self.rules = [(self.levels[0], rule([('user', 'root')]))]
+ self.preset = {}
+
+ def add(self, action, conditions):
+ self.rules.append((action, conditions))
+
+
+ def set(self, **kw):
+ self.preset.update(kw)
+
+ def matchrule(self, **kw):
+ d = self.preset.copy()
+ d.update(**kw)
+ for a, c in self.rules:
+ if c(**d):
+ return a
+ return None
+
+ def allow(self, level, **kw):
+ a = self.matchrule(**kw)
+ return a in self.levels and self.levels.index(a) <= self.levels.index(level)
+
+ @classmethod
+ def readfile(cls, fn):
+ res = cls()
+ try:
+ f = open(fn)
+ try:
+ for l in f:
+ l = l.strip()
+ if len(l) == 0 or l.startswith("#"):
+ continue
+ l = l.split()
+ res.add(l[0], rule([c.split("=", 1) for c in l[1:]]))
+ finally:
+ f.close()
+ except Exception, e:
+ print >> sys.stderr, "Failure reading rules file:", e
+ return cls()
+ return res
+