def _check_symlinks(self): """Raises an exception if a symlink doesn't exist""" for kind in ALL_FOUR: link = getattr(self, kind) if not os.path.islink(link): raise errors.CertStorageError( "expected {0} to be a symlink".format(link)) target = get_link_target(link) if not os.path.exists(target): raise errors.CertStorageError("target {0} of symlink {1} does " "not exist".format(target, link))
def __init__(self, configfile, config_opts=None, cli_config=None): """Instantiate a RenewableCert object from an existing lineage. :param configobj.ConfigObj configfile: an already-parsed ConfigObj object made from reading the renewal config file that defines this lineage. :param configobj.ConfigObj config_opts: systemwide defaults for renewal properties not otherwise specified in the individual renewal config file. :param .RenewerConfiguration cli_config: :raises .CertStorageError: if the configuration file's name didn't end in ".conf", or the file is missing or broken. :raises TypeError: if the provided renewal configuration isn't a ConfigObj object. """ self.cli_config = cli_config if isinstance(configfile, configobj.ConfigObj): if not os.path.basename(configfile.filename).endswith(".conf"): raise errors.CertStorageError( "renewal config file name must end in .conf") self.lineagename = os.path.basename( configfile.filename)[:-len(".conf")] else: raise TypeError("RenewableCert config must be ConfigObj object") # self.configuration should be used to read parameters that # may have been chosen based on default values from the # systemwide renewal configuration; self.configfile should be # used to make and save changes. self.configfile = configfile # TODO: Do we actually use anything from defaults and do we want to # read further defaults from the systemwide renewal configuration # file at this stage? self.configuration = config_with_defaults(config_opts) self.configuration.merge(self.configfile) if not all(x in self.configuration for x in ALL_FOUR): raise errors.CertStorageError( "renewal config file {0} is missing a required " "file reference".format(configfile)) self.cert = self.configuration["cert"] self.privkey = self.configuration["privkey"] self.chain = self.configuration["chain"] self.fullchain = self.configuration["fullchain"]
def __init__(self, config_filename, cli_config): """Instantiate a RenewableCert object from an existing lineage. :param str config_filename: the path to the renewal config file that defines this lineage. :param .RenewerConfiguration: parsed command line arguments :raises .CertStorageError: if the configuration file's name didn't end in ".conf", or the file is missing or broken. """ self.cli_config = cli_config if not config_filename.endswith(".conf"): raise errors.CertStorageError( "renewal config file name must end in .conf") self.lineagename = os.path.basename( config_filename[:-len(".conf")]) # self.configuration should be used to read parameters that # may have been chosen based on default values from the # systemwide renewal configuration; self.configfile should be # used to make and save changes. try: self.configfile = configobj.ConfigObj(config_filename) except configobj.ConfigObjError: raise errors.CertStorageError( "error parsing {0}".format(config_filename)) # TODO: Do we actually use anything from defaults and do we want to # read further defaults from the systemwide renewal configuration # file at this stage? self.configuration = config_with_defaults(self.configfile) if not all(x in self.configuration for x in ALL_FOUR): raise errors.CertStorageError( "renewal config file {0} is missing a required " "file reference".format(self.configfile)) self.cert = self.configuration["cert"] self.privkey = self.configuration["privkey"] self.chain = self.configuration["chain"] self.fullchain = self.configuration["fullchain"] self._fix_symlinks() self._check_symlinks()
def _check_symlinks(self): """Raises an exception if a symlink doesn't exist""" def check(link): """Checks if symlink points to a file that exists""" return os.path.exists(os.path.realpath(link)) for kind in ALL_FOUR: if not check(getattr(self, kind)): raise errors.CertStorageError( "link: {0} does not exist".format(getattr(self, kind)))
def current_target(self, kind): """Returns full path to which the specified item currently points. :param str kind: the lineage member item ("cert", "privkey", "chain", or "fullchain") :returns: The path to the current version of the specified member. :rtype: str """ if kind not in ALL_FOUR: raise errors.CertStorageError("unknown kind of item") link = getattr(self, kind) if not os.path.exists(link): return None target = os.readlink(link) if not os.path.isabs(target): target = os.path.join(os.path.dirname(link), target) return os.path.abspath(target)
def new_lineage(cls, lineagename, cert, privkey, chain, renewalparams=None, config=None, cli_config=None): # pylint: disable=too-many-locals,too-many-arguments """Create a new certificate lineage. Attempts to create a certificate lineage -- enrolled for potential future renewal -- with the (suggested) lineage name lineagename, and the associated cert, privkey, and chain (the associated fullchain will be created automatically). Optional configurator and renewalparams record the configuration that was originally used to obtain this cert, so that it can be reused later during automated renewal. Returns a new RenewableCert object referring to the created lineage. (The actual lineage name, as well as all the relevant file paths, will be available within this object.) :param str lineagename: the suggested name for this lineage (normally the current cert's first subject DNS name) :param str cert: the initial certificate version in PEM format :param str privkey: the private key in PEM format :param str chain: the certificate chain in PEM format :param configobj.ConfigObj renewalparams: parameters that should be used when instantiating authenticator and installer objects in the future to attempt to renew this cert or deploy new versions of it :param configobj.ConfigObj config: renewal configuration defaults, affecting, for example, the locations of the directories where the associated files will be saved :param .RenewerConfiguration cli_config: parsed command line arguments :returns: the newly-created RenewalCert object :rtype: :class:`storage.renewableCert`""" config = config_with_defaults(config) # This attempts to read the renewer config file and augment or replace # the renewer defaults with any options contained in that file. If # renewer_config_file is undefined or if the file is nonexistent or # empty, this .merge() will have no effect. config.merge(configobj.ConfigObj(cli_config.renewer_config_file)) # Examine the configuration and find the new lineage's name for i in (cli_config.renewal_configs_dir, cli_config.archive_dir, cli_config.live_dir): if not os.path.exists(i): os.makedirs(i, 0700) config_file, config_filename = le_util.unique_lineage_name( cli_config.renewal_configs_dir, lineagename) if not config_filename.endswith(".conf"): raise errors.CertStorageError( "renewal config file name must end in .conf") # Determine where on disk everything will go # lineagename will now potentially be modified based on which # renewal configuration file could actually be created lineagename = os.path.basename(config_filename)[:-len(".conf")] archive = os.path.join(cli_config.archive_dir, lineagename) live_dir = os.path.join(cli_config.live_dir, lineagename) if os.path.exists(archive): raise errors.CertStorageError("archive directory exists for " + lineagename) if os.path.exists(live_dir): raise errors.CertStorageError("live directory exists for " + lineagename) os.mkdir(archive) os.mkdir(live_dir) relative_archive = os.path.join("..", "..", "archive", lineagename) # Put the data into the appropriate files on disk target = dict([(kind, os.path.join(live_dir, kind + ".pem")) for kind in ALL_FOUR]) for kind in ALL_FOUR: os.symlink(os.path.join(relative_archive, kind + "1.pem"), target[kind]) with open(target["cert"], "w") as f: f.write(cert) with open(target["privkey"], "w") as f: f.write(privkey) # XXX: Let's make sure to get the file permissions right here with open(target["chain"], "w") as f: f.write(chain) with open(target["fullchain"], "w") as f: # assumes that OpenSSL.crypto.dump_certificate includes # ending newline character f.write(cert + chain) # Document what we've done in a new renewal config file config_file.close() new_config = configobj.ConfigObj(config_filename, create_empty=True) for kind in ALL_FOUR: new_config[kind] = target[kind] if renewalparams: new_config["renewalparams"] = renewalparams new_config.comments["renewalparams"] = [ "", "Options and defaults used" " in the renewal process" ] # TODO: add human-readable comments explaining other available # parameters new_config.write() return cls(new_config.filename, cli_config)