#!/usr/bin/env python # Copyright (C) 2013, 2014 Linaro Ltd. # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . import os import sys sys.path.append(os.path.dirname(os.path.abspath(os.path.dirname(__file__)))) import logging import logging.config import subprocess import linaro_ldap logging.basicConfig(stream=sys.stdout, level=logging.WARN) log = logging.getLogger() GITOLITE_HOME = "/home/git" GITOLITE_BIN = "/home/git/bin/gitolite" GITOLITE_TRIGGER = "SSH_AUTHKEYS" KEYDIR = os.path.join(GITOLITE_HOME, '.gitolite', 'keydir') def run_cmd(cmd_args, shell=False, cwd=None): """Runs the provided cmd arguments.""" try: p = subprocess.Popen(cmd_args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=shell, bufsize=4096, cwd=cwd) _, err = p.communicate() if p.returncode != 0: log.error("Error executing the following command: " "{0}".format(" ".join(cmd_args))) log.error("Command stderr:\n{0}".format(err)) except OSError, e: log.error(str(e)) def get_local_users(): """Builds a list of locally defined users, removes invalid keys""" users = [] removed_keys = False if not os.path.exists(KEYDIR): return (users, removed_keys) for listing in os.listdir(KEYDIR): path = os.path.join(KEYDIR, listing) if os.path.isfile(path): with open(path) as f: key = f.read() if linaro_ldap.validate_key(key): users.append((listing, key)) else: log.info("Key %s is invalid" % listing) gitolite_remove_key(listing) removed_keys = True return (users, removed_keys) def gitolite_remove_key(key_name): """Removes the provided user name from the gitolite-admin repo.""" pubkey_file = os.path.join(KEYDIR, key_name) if os.path.exists(pubkey_file): log.info("Removing public key {0}...".format(key_name)) try: os.unlink(pubkey_file) except OSError, e: log.error(str(e)) else: log.info("Public key {0} does not exists.".format(key_name)) def gitolite_add_key(key_name, ssh_key): """Adds the provided key_name and their SSH key to the gitolite keydir.""" if not os.path.exists(KEYDIR): os.makedirs(KEYDIR, 0700) pubkey_file = os.path.join(KEYDIR, key_name) if os.path.exists(pubkey_file): log.info("Key file {0} already in the repository. " "Updating...".format(key_name)) else: log.info("Adding public key {0}...".format(key_name)) try: with open(pubkey_file, "w") as write_file: write_file.write(ssh_key) except OSError: log.error("Error writing file {0}.".format(pubkey_file)) else: os.chmod(pubkey_file, 0600) def gitolite_trigger_auth_keys(): """Executes the trigger for the SSH keys.""" cmd_args = [] cmd_args.append(GITOLITE_BIN) cmd_args.append("trigger") cmd_args.append(GITOLITE_TRIGGER) log.info("Calling gitolite SSH keys trigger.") run_cmd(cmd_args, cwd=GITOLITE_HOME) if __name__ == '__main__': trigger = False remote = [] users_and_keys = linaro_ldap.get_users_and_keys(only_validated=True) for keysets in users_and_keys.values(): remote.extend(keysets) (local, trigger) = get_local_users() # Are there users to remove? keys_to_remove = set(local) - set(remote) if keys_to_remove: for (key_name, _) in keys_to_remove: gitolite_remove_key(key_name) trigger = True # Are there new users? # This will handle also key updates, since a tuple (key_name, key) will # be different from the value we store on file. keys_to_add = set(remote) - set(local) if keys_to_add: for (key_name, ssh_key) in keys_to_add: gitolite_add_key(key_name, ssh_key) trigger = True # If something changed, update the SSH keys. if trigger: gitolite_trigger_auth_keys() else: log.debug("No need to trigger gitolite.")