diff --git a/gitosis/run_hook.py b/gitosis/run_hook.py index e535e6a..a1e1d21 100644 --- a/gitosis/run_hook.py +++ b/gitosis/run_hook.py @@ -10,6 +10,7 @@ import shutil from gitosis import repository from gitosis import ssh +from gitosis import ssh_principals from gitosis import gitweb from gitosis import gitdaemon from gitosis import app @@ -47,6 +48,11 @@ def post_update(cfg, git_dir): path=authorized_keys, keydir=os.path.join(export, 'keydir'), ) + principals = util.getSSHPrincipalsPath(config=cfg) + ssh.writePrincipals( + path=principals, + principals=os.path.join(export, 'principals'), + ) class Main(app.App): def create_parser(self): diff --git a/gitosis/ssh_principals.py b/gitosis/ssh_principals.py new file mode 100644 index 0000000..448ee8b --- /dev/null +++ b/gitosis/ssh_principals.py @@ -0,0 +1,81 @@ +import os, errno, re +import logging + +log = logging.getLogger('gitosis.ssh') + +_ACCEPTABLE_USER_RE = re.compile(r'^[a-zA-Z][a-zA-Z0-9_.-]*(@[a-zA-Z][a-zA-Z0-9.-]*)?$') + +def isSafeUsername(user): + match = _ACCEPTABLE_USER_RE.match(user) + return (match is not None) + +def readPrincipals(principals): + """ + Read SSH principals from ``principals`` + """ + f = file(principals) + for line in f: + if not isSafeUsername(line): + log.warn('Unsafe SSH username in principalfile: %r', line) + continue + line = line.rstrip('\n') + yield (line) + f.close() + +COMMENT = '### autogenerated by gitosis, DO NOT EDIT' + +def generatePrincipals(keys): + TEMPLATE=('command="gitosis-serve %(user)s",no-port-forwarding,' + +'no-X11-forwarding,no-agent-forwarding,no-pty %(user)s') + + yield COMMENT + for (user) in keys: + yield TEMPLATE % dict(user=user) + +_COMMAND_RE = re.compile('^command="(/[^ "]+/)?gitosis-serve [^"]+",no-port-forw' + +'arding,no-X11-forwarding,no-agent-forwardi' + +'ng,no-pty .*') + +def filterPrincipals(fp): + """ + Read lines from ``fp``, filter out autogenerated ones. + + Note removes newlines. + """ + + for line in fp: + line = line.rstrip('\n') + if line == COMMENT: + continue + if _COMMAND_RE.match(line): + continue + yield line + +def writePrincipals(path, principals): + tmp = '%s.%d.tmp' % (path, os.getpid()) + try: + in_ = file(path) + except IOError, e: + if e.errno == errno.ENOENT: + in_ = None + else: + raise + + try: + out = file(tmp, 'w') + try: + if in_ is not None: + for line in filterPrincipals(in_): + print >>out, line + + keygen = readPrincipals(principals) + for line in generatePrincipals(keygen): + print >>out, line + + os.fsync(out) + finally: + out.close() + finally: + if in_ is not None: + in_.close() + os.rename(tmp, path) diff --git a/gitosis/util.py b/gitosis/util.py index 479b2e9..4ec6bde 100644 --- a/gitosis/util.py +++ b/gitosis/util.py @@ -34,3 +34,10 @@ def getSSHAuthorizedKeysPath(config): except (NoSectionError, NoOptionError): path = os.path.expanduser('~/.ssh/authorized_keys') return path + +def getSSHPrincipalsPath(config): + try: + path = config.get('gitosis', 'ssh-principals-path') + except (NoSectionError, NoOptionError): + path = os.path.expanduser('~/.ssh/principals') + return path