Exemple #1
0
    def main(self, cycle):
        """
        cycle is the duration in seconds of one cycle

        Corner cases:
        * cycle = None : fetch value from config_bases
        * cycle = 0 : run just once (for debug mostly)
        """

        if cycle is None:
            cycle = Config().value('accounts', 'cycle')
        cycle = int(cycle)
        policy = Config().value('accounts', 'access_policy')
        if policy not in ('open', 'leased', 'closed'):
            logger.error("Unknown policy {} - using 'closed'"
                         .format(policy))
            policy = 'closed'
        # trick is
        if cycle != 0:
            self.run_forever(cycle, policy)
        else:
            logger.info("---------- rhubarbe accounts manager oneshot "
                        "policy = {}"
                        .format(policy))
            self.manage_accounts(policy)
Exemple #2
0
 def run_forever(self, period):
     while True:
         beg = time.time()
         logger.info("---------- rhubarbe accounts manager (period {})"
                     .format(period))
         self.manage_accounts()
         now = time.time()
         duration = now - beg
         towait = period - duration
         logger.info("---------- rhubarbe accounts manager - sleeping for {}"
                     .format(towait))
         time.sleep(period - duration)
Exemple #3
0
def replace_file_with_string(destination_path, new_contents,
                             owner=None, chmod=None,
                             remove_if_empty=False):
    """
    Replace a file with new contents
    checks for changes
      does not do anything if previous state was already right
    can handle chmod/chown if requested
    can also remove resulting file if contents are void, if requested

    returns
      * True if a change occurred, or the file is deleted
      * False if the file has no change
      * None if the file could not be created (e.g. directory missing)
    """
    try:
        with destination_path.open() as previous:
            current = previous.read()
    except IOError:
        current = ""
    if current == new_contents:
        # if turns out to be an empty string, and remove_if_empty is set,
        # then make sure to trash the file if it exists
        if remove_if_empty and not new_contents and destination_path.is_file():
            logger.info(
                "replace_file_with_string: removing file {}"
                .format(destination_path))
            try:
                destination_path.unlink()
            finally:
                return True                             # pylint: disable=w0150
        # we're done and have nothing to do
        return False
    # overwrite file: create a temp in the same directory
    try:
        with destination_path.open('w') as new:
            new.write(new_contents)
        if chmod:
            destination_path.chmod(chmod)
        if owner:
            os.system("chown {} {}".format(owner, destination_path))
        return True
    except IOError as exc:
        logger.error("Cannot create {}".format(destination_path))
        return None
Exemple #4
0
 def create_account(self, slicename):
     """
     Does useradd with the right options
     Plus, creates an empty .ssh dir with proper permissions
     NOTE that this addresses ubuntu for now, fedora 'useradd'
     being slightly different as far as I remember
     (at least wrt homedir creation, IIRC again)
     """
     commands = [
         "useradd --create-home --user-group {x} --shell /bin/bash",
         "mkdir /home/{x}/.ssh",
         "chmod 700 /home/{x}/.ssh",
         "chown -R {x}:{x} /home/{x}",
     ]
     for cmd in commands:
         command = cmd.format(x=slicename)
         logger.info("Running {}".format(command))
         retcod = os.system(command)
         if retcod != 0:
             logger.error("{} -> {}".format(command, retcod))
Exemple #5
0
 def create_account(slicename):
     """
     Does useradd with the right options
     Plus, creates an empty .ssh dir with proper permissions
     NOTE that this addresses ubuntu for now, fedora 'useradd'
     being slightly different as far as I remember
     (at least wrt homedir creation, IIRC again)
     """
     commands = [
         "useradd --create-home --user-group {x} --shell /bin/bash",
         "mkdir /home/{x}/.ssh",
         "chmod 700 /home/{x}/.ssh",
         "chown -R {x}:{x} /home/{x}",
     ]
     for cmd in commands:
         command = cmd.format(x=slicename)
         logger.info("Running {}".format(command))
         retcod = os.system(command)
         if retcod != 0:
             logger.error("{} -> {}".format(command, retcod))
Exemple #6
0
def replace_file_with_string(filename, new_contents,
                             owner=None, chmod=None, remove_if_empty=False):
    """
    Replace a file with new contents
    checks for changes
      does not do anything if previous state was already right
    can handle chmod/chown if requested
    can also remove resulting file if contents are void, if requested

    returns True if a change occurred, or the file is deleted
    """
    try:
        with open(filename) as f:
            current = f.read()
    except:
        current = ""
    if current == new_contents:
        # if turns out to be an empty string, and remove_if_empty is set,
        # then make sure to trash the file if it exists
        if remove_if_empty and not new_contents and os.path.isfile(filename):
            logger.info(
                "replace_file_with_string: removing file {}".format(filename))
            try:
                os.unlink(filename)
            finally:
                return True
        # we're done and have nothing to do
        return False
    # overwrite filename file: create a temp in the same directory
    path = os.path.dirname(filename) or '.'
    with open(filename, 'w') as f:
        f.write(new_contents)
    if chmod:
        os.chmod(filename, chmod)
    if owner:
        retcod = os.system("chown {} {}".format(owner, filename))
    return True
Exemple #7
0
 def run_forever(self, cycle, policy):
     while True:
         beg = time.time()
         logger.info("---------- rhubarbe accounts manager "
                     "policy = {}, cycle {}s"
                     .format(policy, cycle))
         self.manage_accounts(policy)
         now = time.time()
         duration = now - beg
         towait = cycle - duration
         if towait > 0:
             logger.info("---------- rhubarbe accounts manager - "
                         "sleeping for {:.2f}s"
                         .format(towait))
             time.sleep(towait)
         else:
             logger.info("duration {}s exceeded cycle {}s - "
                         "skipping sleep"
                         .format(duration, cycle))
Exemple #8
0
    def manage_accounts(self):
        self._running = True
        # get context
        passwd_entries = self.all_passwd_entries()
        home_basenames = self.authorized_home_basenames()
        legacy_basenames = self.authorized_legacy_basenames()

        # get plcapi specification of what should be
        slices = self.proxy().GetSlices(
            {}, ['slice_id', 'name', 'expires', 'person_ids'])
        persons = self.proxy().GetPersons(
            {}, ['person_id', 'email', 'slice_ids', 'key_ids'])
        keys = self.proxy().GetKeys()

        persons_by_id = {p['person_id']: p for p in persons}
        keys_by_id = {k['key_id']: k for k in keys}

        active_slices = []

        for slice in slices:
            slicename = slice['name']
            authorized_keys = self.authorized_key_lines(
                slice, persons_by_id, keys_by_id)

            # don't bother to create an account if the slice has no key
            if authorized_keys:
                try:
                    active_slices.append(slicename)
                    # create account if missing
                    if slicename not in passwd_entries:
                        self.create_account(slicename)
                    # dictate authorized_keys contents
                    ssh_auth_keys = "/home/{x}/.ssh/authorized_keys".format(
                        x=slicename)
                    replace_file_with_string(ssh_auth_keys, authorized_keys,
                                             chmod=0o400,
                                             owner="{x}:{x}".format(
                                                 x=slicename),
                                             remove_if_empty=True)
                    self.create_ssh_config(slicename)

                except Exception as e:
                    logger.exception("could not properly deal "
                                     "with active slice {x}"
                                     .format(x=slicename))

        # find out about slices that currently have suthorized keys but should
        # not
        for slicename in home_basenames:
            if slicename not in active_slices:
                try:
                    logger.info(
                        "Removing authorized_keys for {x}".format(x=slicename))
                    ssh_auth_keys = "/home/{x}/.ssh/authorized_keys".format(
                        x=slicename)
                    os.unlink(ssh_auth_keys)
                except Exception as e:
                    logger.exception("could not properly deal "
                                     "with inactive slice {x}"
                                     .format(x=slicename))

        # a one-shot piece of code : turn off legacy slices
        for slicename in legacy_basenames:
            try:
                logger.info(
                    "legacy slicename {x} "
                    "needs to be shutdown".format(x=slicename))
                ssh_auth_keys = "/home/{x}/.ssh/authorized_keys".format(
                    x=slicename)
                # xxx enable the following line to tear down legacy slices
                # os.unlink(ssh_auth_keys)
            except Exception as e:
                logger.exception("could not properly deal "
                                 "with legacy slice {x}"
                                 .format(x=slicename))

        self._running = False
Exemple #9
0
    def manage_accounts(self, policy):             # pylint: disable=r0914

        # get plcapi specification of what should be
        slices = self.proxy().GetSlices(
            {}, ['slice_id', 'name', 'expires', 'person_ids'])
        persons = self.proxy().GetPersons(
            {}, ['person_id', 'email', 'slice_ids', 'key_ids'])
        keys = self.proxy().GetKeys()

        current_leases = []
        if policy == 'leased':
            now = int(time.time())
            current_leases = self.proxy().GetLeases(
                {'alive': now}, ['name'])

        if (current_leases is None or slices is None
                or persons is None or keys is None):
            logger.info("PLCAPI unreachable - back to sleep")
            return

        # prepare data
        persons_by_id = {p['person_id']: p for p in persons}
        keys_by_id = {k['key_id']: k for k in keys}
        # current_slicenames will contain 0 or 1 item
        current_slicenames = [lease['name'] for lease in current_leases]

        # initialize with the slice names that are in /etc/passwd
        logins = self.slices_from_passwd()

        # initialize map login_name -> authorized_keys contents
        # this is where we handle the fact that obsolete slices
        # will effectively have their authorized_keys voided
        auths_by_login = {login: "" for login in logins}

        for sliceobj in slices:
            slicename = sliceobj['name']
            # policy-dependant
            if policy == 'closed':
                authorized_keys = ""

            elif policy == 'leased':
                authorized_keys = ""
                if slicename in current_slicenames:
                    authorized_keys = self.authorized_key_lines(
                        sliceobj, persons_by_id, keys_by_id)

            # policy == 'open'
            else:
                authorized_keys = self.authorized_key_lines(
                    sliceobj, persons_by_id, keys_by_id)

            auths_by_login[slicename] = authorized_keys

        # implement it
        for slicename, keys in auths_by_login.items():
            try:
                if slicename not in logins:
                    self.create_account(slicename)
                # do this always, allows to propagate later changes
                self.create_ssh_config(slicename)
                self.apply_keys(slicename, keys)
            except Exception:
                logger.exception("Could not deal with slice {}"
                                 .format(slicename))