def __recover_checkpoint(self, cp_dir):
        """
        Recover a specific checkpoint provided by cp_dir
        Note: this function does not reload augeas.

        returns: 0 success, 1 Unable to revert, -1 Unable to delete
        """

        if os.path.isfile(cp_dir + "/FILEPATHS"):
            try:
                with open(cp_dir + "/FILEPATHS") as f:
                    filepaths = f.read().splitlines()
                    for idx, fp in enumerate(filepaths):
                        shutil.copy2(cp_dir + '/' + os.path.basename(fp)
                                     + '_' + str(idx), fp)
            except:
                # This file is required in all checkpoints.
                logger.error("Unable to recover files from %s" % cp_dir)
                return 1

        # Remove any newly added files if they exist
        self.__remove_contained_files(cp_dir + "/NEW_FILES")

        try:
            shutil.rmtree(cp_dir)
        except:
            logger.error("Unable to remove directory: %s" % cp_dir)
            return -1

        return 0
Пример #2
0
    def is_expected_msg(self, msg_dict, expected, delay=3, rounds = 20):
        for i in range(rounds):
            if msg_dict["type"] == expected:
                return msg_dict

            elif msg_dict["type"] == "error":
                logger.error("%s: %s - More Info: %s" %
                             (msg_dict["error"],
                              msg_dict.get("message", ""),
                              msg_dict.get("moreInfo", "")))
                raise Exception(msg_dict["error"])

            elif msg_dict["type"] == "defer":
                logger.info("Waiting for %d seconds..." % delay)
                time.sleep(delay)
                msg_dict = self.send(self.status_request(msg_dict["token"]))
            else:
                logger.fatal("Received unexpected message")
                logger.fatal("Expected: %s" % expected)
                logger.fatal("Received: " + msg_dict)
                sys.exit(33)

        logger.error("Server has deferred past the max of %d seconds" %
                     (rounds * delay))
        return None
Пример #3
0
    def store_cert_key(self, encrypt = False):
        list_file = CERT_KEY_BACKUP + "LIST"
        le_util.make_or_verify_dir(CERT_KEY_BACKUP, 0700)
        idx = 0

        if encrypt:
            logger.error("Unfortunately securely storing the certificates/keys \
            is not yet available. Stay tuned for the next update!")
            return False

        if os.path.isfile(list_file):
            with open(list_file, 'r+b') as csvfile:
                csvreader = csv.reader(csvfile)
                for r in csvreader:
                    idx = int(r[0]) + 1
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow([str(idx), self.cert_file, self.key_file])

        else:
            with open(list_file, 'wb') as csvfile:
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow(["0", self.cert_file, self.key_file])

        shutil.copy2(self.key_file,
                     CERT_KEY_BACKUP + os.path.basename(self.key_file) +
                     "_" + str(idx))
        shutil.copy2(self.cert_file,
                     CERT_KEY_BACKUP + os.path.basename(self.cert_file) +
                     "_" + str(idx))
Пример #4
0
    def store_cert_key(self, encrypt=False):
        list_file = CERT_KEY_BACKUP + "LIST"
        le_util.make_or_verify_dir(CERT_KEY_BACKUP, 0700)
        idx = 0

        if encrypt:
            logger.error(
                "Unfortunately securely storing the certificates/keys \
            is not yet available. Stay tuned for the next update!")
            return False

        if os.path.isfile(list_file):
            with open(list_file, 'r+b') as csvfile:
                csvreader = csv.reader(csvfile)
                for r in csvreader:
                    idx = int(r[0]) + 1
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow([str(idx), self.cert_file, self.key_file])

        else:
            with open(list_file, 'wb') as csvfile:
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow(["0", self.cert_file, self.key_file])

        shutil.copy2(
            self.key_file,
            CERT_KEY_BACKUP + os.path.basename(self.key_file) + "_" + str(idx))
        shutil.copy2(
            self.cert_file, CERT_KEY_BACKUP +
            os.path.basename(self.cert_file) + "_" + str(idx))
Пример #5
0
    def _finalize_checkpoint(self, cp_dir, title):
        """Move IN_PROGRESS checkpoint to timestamped checkpoint.

        Adds title to cp_dir CHANGES_SINCE
        Move cp_dir to Backups directory and rename with timestamp

        :param cp_dir: "IN PROGRESS" directory
        :type cp_dir: str

        :returns: Success
        :rtype: bool

        """
        final_dir = os.path.join(CONFIG.BACKUP_DIR, str(time.time()))
        try:
            with open(cp_dir + "CHANGES_SINCE.tmp", 'w') as ft:
                ft.write("-- %s --\n" % title)
                with open(cp_dir + "CHANGES_SINCE", 'r') as f:
                    ft.write(f.read())
            shutil.move(cp_dir + "CHANGES_SINCE.tmp", cp_dir + "CHANGES_SINCE")
        except:
            logger.error("Unable to finalize checkpoint - adding title")
            return False
        try:
            os.rename(cp_dir, final_dir)
        except:
            logger.error("Unable to finalize checkpoint, %s -> %s" %
                         (cp_dir, final_dir))
            return False
        return True
Пример #6
0
    def register_file_creation(self, temporary, *files):
        """Register the creation of all files during letsencrypt execution.

        Call this method before writing to the file to make sure that the
        file will be cleaned up if the program exits unexpectedly.
        (Before a save occurs)

        :param temporary: If the file creation registry is for a temp or
        permanent save.
        :type temporary: bool

        :param *files: file paths to be registered
        :type *files: str

        """
        if temporary:
            cp_dir = CONFIG.TEMP_CHECKPOINT_DIR
        else:
            cp_dir = CONFIG.IN_PROGRESS_DIR

        le_util.make_or_verify_dir(cp_dir)
        try:
            with open(cp_dir + "NEW_FILES", 'a') as fd:
                for file_path in files:
                    fd.write("%s\n" % file_path)
        except:
            logger.error("ERROR: Unable to register file creation")
Пример #7
0
    def _recover_checkpoint(self, cp_dir):
        """Recover a specific checkpoint.

        Recover a specific checkpoint provided by cp_dir
        Note: this function does not reload augeas.

        :param cp_dir: checkpoint directory file path
        :type cp_dir: str

        :returns: 0 success, 1 Unable to revert, -1 Unable to delete
        :rtype: int

        """
        if os.path.isfile(cp_dir + "/FILEPATHS"):
            try:
                with open(cp_dir + "/FILEPATHS") as f:
                    filepaths = f.read().splitlines()
                    for idx, fp in enumerate(filepaths):
                        shutil.copy2(cp_dir + '/' + os.path.basename(fp)
                                     + '_' + str(idx), fp)
            except:
                # This file is required in all checkpoints.
                logger.error("Unable to recover files from %s" % cp_dir)
                return 1

        # Remove any newly added files if they exist
        self._remove_contained_files(cp_dir + "/NEW_FILES")

        try:
            shutil.rmtree(cp_dir)
        except:
            logger.error("Unable to remove directory: %s" % cp_dir)
            return -1

        return 0
Пример #8
0
    def generate_response(self):
        """
        Generates a response for a completed challenge
        """
        if self.s:
            return {"type":"dvsni", "s":self.s}

        logger.error("DVSNI Challenge was not completed before calling generate_response")
        return None
Пример #9
0
    def findApacheConfigFile(self):
        """
        Locates the file path to the user's main apache config

        result: returns file path if present
        """
        if path.isfile(SERVER_ROOT + "httpd.conf"):
            return SERVER_ROOT + "httpd.conf"
        logger.error("Unable to find httpd.conf, file does not exist in Apache ServerRoot")
        return None
    def check_parsing_errors(self, lens):
        """
        This function checks to see if Augeas was unable to parse any of the
        lens files
        """
        error_files = self.aug.match("/augeas//error")

        for e in error_files:
            # Check to see if it was an error resulting from the use of
            # the httpd lens
            lens_path = self.aug.get(e + '/lens')
            # As aug.get may return null
            if lens_path and lens in lens_path:
                # Strip off /augeas/files and /error
                logger.error('There has been an error in parsing the file: %s' % e[13:len(e) - 6])
                logger.error(self.aug.get(e + '/message'))
Пример #11
0
    def is_expected_msg(self, response, expected, delay=3, rounds=20):
        """Is reponse expected ACME message?

        :param response: ACME response message from server.
        :type response: dict

        :param expected: Name of the expected response ACME message type.
        :type expected: str

        :param delay: Number of seconds to delay before next round in case
                      of ACME "defer" response message.
        :type delay: int

        :param rounds: Number of resend attempts in case of ACME "defer"
                       reponse message.
        :type rounds: int

        :raises: Exception

        :returns: ACME response message from server.
        :rtype: dict

        """
        for _ in xrange(rounds):
            if response["type"] == expected:
                return response

            elif response["type"] == "error":
                logger.error("%s: %s - More Info: %s" %
                             (response["error"],
                              response.get("message", ""),
                              response.get("moreInfo", "")))
                raise errors.LetsEncryptClientError(response["error"])

            elif response["type"] == "defer":
                logger.info("Waiting for %d seconds..." % delay)
                time.sleep(delay)
                response = self.send(acme.status_request(response["token"]))
            else:
                logger.fatal("Received unexpected message")
                logger.fatal("Expected: %s" % expected)
                logger.fatal("Received: " + response)
                sys.exit(33)

        logger.error("Server has deferred past the max of %d seconds" %
                     (rounds * delay))
Пример #12
0
    def is_expected_msg(self, response, expected, delay=3, rounds=20):
        """Is reponse expected ACME message?

        :param response: ACME response message from server.
        :type response: dict

        :param expected: Name of the expected response ACME message type.
        :type expected: str

        :param delay: Number of seconds to delay before next round in case
                      of ACME "defer" response message.
        :type delay: int

        :param rounds: Number of resend attempts in case of ACME "defer"
                       reponse message.
        :type rounds: int

        :raises: Exception

        :returns: ACME response message from server.
        :rtype: dict

        """
        for _ in xrange(rounds):
            if response["type"] == expected:
                return response

            elif response["type"] == "error":
                logger.error("%s: %s - More Info: %s" %
                             (response["error"], response.get(
                                 "message", ""), response.get("moreInfo", "")))
                raise errors.LetsEncryptClientError(response["error"])

            elif response["type"] == "defer":
                logger.info("Waiting for %d seconds..." % delay)
                time.sleep(delay)
                response = self.send(acme.status_request(response["token"]))
            else:
                logger.fatal("Received unexpected message")
                logger.fatal("Expected: %s" % expected)
                logger.fatal("Received: " + response)
                sys.exit(33)

        logger.error("Server has deferred past the max of %d seconds" %
                     (rounds * delay))
Пример #13
0
    def store_cert_key(self, cert_file, encrypt=False):
        """Store certificate key.

        :param str cert_file: Path to a certificate file.

        :param bool encrypt: Should the certificate key be encrypted?

        :returns: True if key file was stored successfully, False otherwise.
        :rtype: bool

        """
        list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
        le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0o700)
        idx = 0

        if encrypt:
            logger.error("Unfortunately securely storing the certificates/"
                         "keys is not yet available. Stay tuned for the "
                         "next update!")
            return False

        if os.path.isfile(list_file):
            with open(list_file, 'r+b') as csvfile:
                csvreader = csv.reader(csvfile)
                for row in csvreader:
                    idx = int(row[0]) + 1
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow([str(idx), cert_file, self.privkey.file])

        else:
            with open(list_file, 'wb') as csvfile:
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow(["0", cert_file, self.privkey.file])

        shutil.copy2(self.privkey.file,
                     os.path.join(
                         CONFIG.CERT_KEY_BACKUP,
                         os.path.basename(self.privkey.file) + "_" + str(idx)))
        shutil.copy2(cert_file,
                     os.path.join(
                         CONFIG.CERT_KEY_BACKUP,
                         os.path.basename(cert_file) + "_" + str(idx)))

        return True
    def register_file_creation(self, temporary, *files):
        """
        This is used to register the creation of all files during Letsencrypt
        execution. Call this method before writing to the file to make sure
        that the file will be cleaned up if the program exits unexpectedly.
        (Before a save occurs)
        """
        if temporary:
            cp_dir = TEMP_CHECKPOINT_DIR
        else:
            cp_dir = IN_PROGRESS_DIR

        le_util.make_or_verify_dir(cp_dir)
        try:
            with open(cp_dir + "NEW_FILES", 'a') as fd:
                for f in files:
                    fd.write("%s\n" % f)
        except:
            logger.error("ERROR: Unable to register file creation")
Пример #15
0
    def check_parsing_errors(self, lens):
        """Verify Augeas can parse all of the lens files.

        :param lens: lens to check for errors
        :type lens: str

        """
        error_files = self.aug.match("/augeas//error")

        for e in error_files:
            # Check to see if it was an error resulting from the use of
            # the httpd lens
            lens_path = self.aug.get(e + '/lens')
            # As aug.get may return null
            if lens_path and lens in lens_path:
                # Strip off /augeas/files and /error
                logger.error('There has been an error in parsing the file: '
                             '%s' % e[13:len(e) - 6])
                logger.error(self.aug.get(e + '/message'))
Пример #16
0
    def store_cert_key(self, cert_file, encrypt=False):
        """Store certificate key.

        :param cert_file: Path to a certificate file.
        :type cert_file: str

        :param encrypt: Should the certificate key be encrypted?
        :type encrypt: bool

        """
        list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST")
        le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0o700)
        idx = 0

        if encrypt:
            logger.error("Unfortunately securely storing the certificates/"
                         "keys is not yet available. Stay tuned for the "
                         "next update!")
            return False

        if os.path.isfile(list_file):
            with open(list_file, 'r+b') as csvfile:
                csvreader = csv.reader(csvfile)
                for row in csvreader:
                    idx = int(row[0]) + 1
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow([str(idx), cert_file, self.key_file])

        else:
            with open(list_file, 'wb') as csvfile:
                csvwriter = csv.writer(csvfile)
                csvwriter.writerow(["0", cert_file, self.key_file])

        shutil.copy2(
            self.key_file,
            os.path.join(CONFIG.CERT_KEY_BACKUP,
                         os.path.basename(self.key_file) + "_" + str(idx)))
        shutil.copy2(
            cert_file,
            os.path.join(CONFIG.CERT_KEY_BACKUP,
                         os.path.basename(cert_file) + "_" + str(idx)))
 def __finalize_checkpoint(self, cp_dir, title):
     """
     Add title to cp_dir CHANGES_SINCE
     Move cp_dir to Backups directory and rename with timestamp
     """
     final_dir = BACKUP_DIR + str(time.time())
     try:
         with open(cp_dir + "CHANGES_SINCE.tmp", 'w') as ft:
             ft.write("-- %s --\n" % title)
             with open(cp_dir + "CHANGES_SINCE", 'r') as f:
               ft.write(f.read())
         shutil.move(cp_dir + "CHANGES_SINCE.tmp", cp_dir + "CHANGES_SINCE")
     except:
         logger.error("Unable to finalize checkpoint - adding title")
         return False
     try:
         os.rename(cp_dir, final_dir)
     except:
         logger.error("Unable to finalize checkpoint, %s -> %s" % cp_dir, final_dir)
         return False
     return True
    def rollback_checkpoints(self, rollback=1):
        """Revert 'rollback' number of configuration checkpoints."""
        try:
            rollback = int(rollback)
        except:
            logger.error("Rollback argument must be a positive integer")
        # Sanity check input
        if rollback < 1:
            logger.error("Rollback argument must be a positive integer")
            return

        backups = os.listdir(CONFIG.BACKUP_DIR)
        backups.sort()

        if len(backups) < rollback:
            logger.error(("Unable to rollback %d checkpoints, only "
                         "%d exist") % (rollback, len(backups)))

        while rollback > 0 and backups:
            cp_dir = CONFIG.BACKUP_DIR + backups.pop()
            result = self.__recover_checkpoint(cp_dir)
            if result != 0:
                logger.fatal("Failed to load checkpoint during rollback")
                sys.exit(39)
            rollback -= 1

        self.aug.load()
Пример #19
0
    def rollback_checkpoints(self, rollback=1):
        """Revert 'rollback' number of configuration checkpoints.

        :param rollback: Number of checkpoints to reverse
        :type rollback: int

        """
        try:
            rollback = int(rollback)
        except:
            logger.error("Rollback argument must be a positive integer")
        # Sanity check input
        if rollback < 1:
            logger.error("Rollback argument must be a positive integer")
            return

        backups = os.listdir(CONFIG.BACKUP_DIR)
        backups.sort()

        if len(backups) < rollback:
            logger.error(("Unable to rollback %d checkpoints, only "
                         "%d exist") % (rollback, len(backups)))

        while rollback > 0 and backups:
            cp_dir = CONFIG.BACKUP_DIR + backups.pop()
            result = self._recover_checkpoint(cp_dir)
            if result != 0:
                logger.fatal("Failed to load checkpoint during rollback")
                sys.exit(39)
            rollback -= 1

        self.aug.load()
Пример #20
0
    def is_expected_msg(self, msg_dict, expected, delay=3, rounds=20):
        for i in range(rounds):
            if msg_dict["type"] == expected:
                return msg_dict

            elif msg_dict["type"] == "error":
                logger.error("%s: %s - More Info: %s" %
                             (msg_dict["error"], msg_dict.get(
                                 "message", ""), msg_dict.get("moreInfo", "")))
                raise Exception(msg_dict["error"])

            elif msg_dict["type"] == "defer":
                logger.info("Waiting for %d seconds..." % delay)
                time.sleep(delay)
                msg_dict = self.send(self.status_request(msg_dict["token"]))
            else:
                logger.fatal("Received unexpected message")
                logger.fatal("Expected: %s" % expected)
                logger.fatal("Received: " + msg_dict)
                sys.exit(33)

        logger.error("Server has deferred past the max of %d seconds" %
                     (rounds * delay))
        return None
Пример #21
0
 def generate_response(self):
     logger.error("Error - base class challenge.generate_response()")
Пример #22
0
 def perform(self, quiet=True):
     logger.error("Error - base class challenge.perform()")
Пример #23
0
 def clean(self):
     logger.error("Error - base class challenge.clean()")
Пример #24
0
    def save(self, title=None, temporary=False):
        """Saves all changes to the configuration files.

        This function first checks for save errors, if none are found,
        all configuration changes made will be saved. According to the
        function parameters.

        :param title: The title of the save. If a title is given, the
                      configuration will be saved as a new checkpoint
                      and put in a timestamped directory.
        :type title: str

        :param temporary: Indicates whether the changes made will be quickly
                          reversed in the future (ie. challenges)
        :type temporary: bool

        """
        save_state = self.aug.get("/augeas/save")
        self.aug.set("/augeas/save", "noop")
        # Existing Errors
        ex_errs = self.aug.match("/augeas//error")
        try:
            # This is a noop save
            self.aug.save()
        except:
            # Check for the root of save problems
            new_errs = self.aug.match("/augeas//error")
            # logger.error("During Save - " + mod_conf)
            # Only print new errors caused by recent save
            for err in new_errs:
                if err not in ex_errs:
                    logger.error("Unable to save file - "
                                 "%s" % err[13:len(err)-6])
            logger.error("Attempted Save Notes")
            logger.error(self.save_notes)
            # Erase Save Notes
            self.save_notes = ""
            return False

        # Retrieve list of modified files
        # Note: Noop saves can cause the file to be listed twice, I used a
        # set to remove this possibility. This is a known augeas 0.10 error.
        save_paths = self.aug.match("/augeas/events/saved")

        # If the augeas tree didn't change, no files were saved and a backup
        # should not be created
        if save_paths:
            save_files = set()
            for p in save_paths:
                save_files.add(self.aug.get(p)[6:])

            valid, message = self.check_tempfile_saves(save_files)

            if not valid:
                logger.fatal(message)
                # What is the protocol in this situation?
                # This shouldn't happen if the challenge codebase is correct
                return False

            # Create Checkpoint
            if temporary:
                self.add_to_checkpoint(CONFIG.TEMP_CHECKPOINT_DIR, save_files)
            else:
                self.add_to_checkpoint(CONFIG.IN_PROGRESS_DIR, save_files)

        if title and not temporary and os.path.isdir(CONFIG.IN_PROGRESS_DIR):
            success = self._finalize_checkpoint(CONFIG.IN_PROGRESS_DIR, title)
            if not success:
                # This should never happen
                # This will be hopefully be cleaned up on the recovery
                # routine startup
                sys.exit(9)

        self.aug.set("/augeas/save", save_state)
        self.save_notes = ""
        self.aug.save()

        return True
    def save(self, title=None, temporary=False):
        """Saves all changes to the configuration files.

        This function is not transactional

        TODO: Instead rely on challenge to backup all files before
        modifications

        title:     string - The title of the save. If a title is given, the
                            configuration will be saved as a new checkpoint
                            and put in a timestamped directory.
                            `title` has no effect if temporary is true.
        temporary: boolean - Indicates whether the changes made will be
                             quickly reversed in the future (challenges)
        """
        save_state = self.aug.get("/augeas/save")
        self.aug.set("/augeas/save", "noop")
        # Existing Errors
        ex_errs = self.aug.match("/augeas//error")
        try:
            # This is a noop save
            self.aug.save()
        except:
            # Check for the root of save problems
            new_errs = self.aug.match("/augeas//error")
            # logger.error("During Save - " + mod_conf)
            # Only print new errors caused by recent save
            for err in new_errs:
                if err not in ex_errs:
                    logger.error("Unable to save file - "
                                 "%s" % err[13:len(err)-6])
            logger.error("Attempted Save Notes")
            logger.error(self.save_notes)
            # Erase Save Notes
            self.save_notes = ""
            return False

        # Retrieve list of modified files
        # Note: Noop saves can cause the file to be listed twice, I used a
        # set to remove this possibility. This is a known augeas 0.10 error.
        save_paths = self.aug.match("/augeas/events/saved")

        # If the augeas tree didn't change, no files were saved and a backup
        # should not be created
        if save_paths:
            save_files = set()
            for p in save_paths:
                save_files.add(self.aug.get(p)[6:])

            valid, message = self.check_tempfile_saves(save_files, temporary)

            if not valid:
                logger.fatal(message)
                # What is the protocol in this situation?
                # This shouldn't happen if the challenge codebase is correct
                return False

            # Create Checkpoint
            if temporary:
                self.add_to_checkpoint(CONFIG.TEMP_CHECKPOINT_DIR, save_files)
            else:
                self.add_to_checkpoint(CONFIG.IN_PROGRESS_DIR, save_files)

        if title and not temporary and os.path.isdir(CONFIG.IN_PROGRESS_DIR):
            success = self.__finalize_checkpoint(CONFIG.IN_PROGRESS_DIR, title)
            if not success:
                # This should never happen
                # This will be hopefully be cleaned up on the recovery
                # routine startup
                sys.exit(9)

        self.aug.set("/augeas/save", save_state)
        self.save_notes = ""
        self.aug.save()

        return True