From c26878866860bcd810f096585055f792dbc2ee8c Mon Sep 17 00:00:00 2001 From: Todd Zullinger Date: Thu, 20 Aug 2009 12:52:47 -0400 Subject: [PATCH] Add script/cron job for checking git repo perms The git::check-perms class includes a script for checking that the permissions of git repositories are generally proper for shared repositories. It also runs this script each day via a cron job. This is included in the hosted class. --- manifests/servergroups/hosted.pp | 1 + modules/git/README | 4 + modules/git/files/check-perms.py | 148 ++++++++++++++++++++++++++++++++++++++ modules/git/manifests/init.pp | 21 ++++++ 4 files changed, 174 insertions(+), 0 deletions(-) create mode 100755 modules/git/files/check-perms.py diff --git a/manifests/servergroups/hosted.pp b/manifests/servergroups/hosted.pp index 55ef1f7..317ad1a 100644 --- a/manifests/servergroups/hosted.pp +++ b/manifests/servergroups/hosted.pp @@ -13,6 +13,7 @@ class hosted { include rsync::server include selinux include prelude::sensor::audisp + include git::check-perms include git::mail-hooks sysctl { 'ip_conntrack_max': diff --git a/modules/git/README b/modules/git/README index e9a5e99..100a560 100644 --- a/modules/git/README +++ b/modules/git/README @@ -14,6 +14,10 @@ The git rpm installs the core tools with minimal dependencies. To install all git packages, including tools for integrating with other SCMs, install the git-all meta-package. +The git::check-perms class includes a script for checking that the +permissions of git repositories are generally proper for shared +repositories. It also runs this script each day via a cron job. + The git::mail-hooks class installs some convenient tools for use as post-receive hooks, courtesy of the gnome.org sysadmins. diff --git a/modules/git/files/check-perms.py b/modules/git/files/check-perms.py new file mode 100755 index 0000000..88d7bff --- /dev/null +++ b/modules/git/files/check-perms.py @@ -0,0 +1,148 @@ +#!/usr/bin/python -tt +"""Check permissions of a tree of git repositories, optionally fixing any +problems found. +""" + +import os +import re +import sys +import optparse +from stat import * +from subprocess import call, PIPE, Popen + +usage = '%prog [options] [gitroot]' +parser = optparse.OptionParser(usage=usage) +parser.add_option('-f', '--fix', dest='fix', + action='store_true', default=False, + help='Correct any problems [%default]') +opts, args = parser.parse_args() + +if args: + gitroot = args[0] +else: + gitroot = '/git' + +object_re = re.compile('[0-9a-z]{40}') + +def is_object(path): + """Check if a path is a git object.""" + parts = path.split(os.path.sep) + if 'objects' in parts and len(parts) > 2 and \ + object_re.match(''.join(path.split(os.path.sep)[-2:])): + return True + return False + +def is_shared_repo(gitdir): + """Check if a git repository is shared.""" + cmd = ['git', '--git-dir', gitdir, 'config', 'core.sharedRepository'] + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + shared, error = p.communicate() + sharedmodes = ['1', 'group', 'true', '2', 'all', 'world', 'everybody'] + if shared.rstrip() not in sharedmodes or p.returncode: + return False + return True + +def set_shared_repo(gitdir, value='group'): + """Set core.sharedRepository for a git repository.""" + mode_re = re.compile('06[0-7]{2}') + if value in [0, 'false', 'umask']: + value = 'umask' + elif value in [1, 'true', 'group']: + value = 'group' + elif value in [2, 'all', 'world', 'everybody']: + value = 'all' + elif mode_re.match(value): + pass + else: + raise SystemExit('Bogus core.sharedRepository value "%s"' % value) + cmd = ['git', '--git-dir', gitdir, 'config', 'core.sharedRepository', + value] + ret = call(cmd) + if ret: + return False + return True + +def check_git_perms(path, fix=False): + """Check if permissions on a git repo are correct. + + If fix is true, problems found are corrected. + """ + object_mode = S_IRUSR | S_IRGRP | S_IROTH + oldmode = mode = S_IMODE(os.lstat(path)[ST_MODE]) + errors = [] + if os.path.isdir(path): + newmode = mode | S_ISGID + if mode != newmode: + msg = 'Not SETGID (should be "%s")' % oct(newmode) + errors.append(msg) + mode = newmode + elif is_object(path) and mode ^ object_mode: + msg = 'Wrong object mode "%s" (should be "%s")' % ( + oct(mode), oct(object_mode)) + errors.append(msg) + mode = object_mode + if mode & S_IWUSR and not is_object(path): + newmode = mode | S_IWGRP + if mode != newmode: + msg = 'Not group writable (should be "%s")' % oct(newmode) + errors.append(msg) + mode = newmode + if mode != oldmode and not os.path.islink(path): + print >> sys.stderr, '%s:' % path, + print >> sys.stderr, ', '.join(['%s' % e for e in errors]) + if not fix: + return False + try: + os.chmod(path, mode) + return True + except Exception, e: + error = hasattr(e, 'strerror') and e.strerror or e + mode = oct(mode) + print >> sys.stderr, 'Error setting "%s" mode on %s: %s' % ( + mode, path, error) + return False + return True + +def main(): + if not os.path.isdir(gitroot): + raise SystemExit('%s does not exist or is not a directory' % gitroot) + + gitdirs = [] + for path, dirs, files in os.walk(gitroot): + if path in gitdirs: + continue + if 'description' in os.listdir(path): + gitdirs.append(path) + + problems = [] + for gitdir in sorted(gitdirs): + if not is_shared_repo(gitdir): + print >> sys.stderr, '%s: core.sharedRepository not set' % gitdir + if not opts.fix or not set_shared_repo(gitdir): + problems.append(gitdir) + continue + paths = [] + for path, dirs, files in os.walk(gitdir): + for d in dirs: + d = os.path.join(path, d) + if d not in paths: + paths.append(d) + for f in files: + f = os.path.join(path, f) + if f not in paths: + paths.append(f) + for path in paths: + if not check_git_perms(path, fix=opts.fix): + if path not in problems: + problems.append(path) + + if problems: + raise SystemExit('%d paths remain unfixed' % len(problems)) + + raise SystemExit() + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + raise SystemExit('\nExiting on user cancel (Ctrl-C)') diff --git a/modules/git/manifests/init.pp b/modules/git/manifests/init.pp index 87282b5..2d0c3e0 100644 --- a/modules/git/manifests/init.pp +++ b/modules/git/manifests/init.pp @@ -35,3 +35,24 @@ class git::mail-hooks { require => [File["$mailhooks/git.py"], File["$mailhooks/util.py"]]; } } + +class git::check-perms { + include git::package + + file { "/usr/local/bin/git-check-perms": + owner => "root", + group => "root", + mode => 0755, + source => "puppet:///git/check-perms.py" + require => Package["git"], + } + + cron { "git-check-perms": + command => "/usr/local/bin/git-check-perms", + user => "nobody", + hour => 0, + minute => 10, + environment => "MAILTO=root", + require => File["/usr/local/bin/git-check-perms"], + } +} -- 1.6.4