/ letsencrypt / interfaces.py
interfaces.py
  1  """Let's Encrypt client interfaces."""
  2  import abc
  3  import zope.interface
  4  
  5  # pylint: disable=no-self-argument,no-method-argument,no-init,inherit-non-class
  6  # pylint: disable=too-few-public-methods
  7  
  8  
  9  class AccountStorage(object):
 10      """Accounts storage interface."""
 11  
 12      __metaclass__ = abc.ABCMeta
 13  
 14      @abc.abstractmethod
 15      def find_all(self):  # pragma: no cover
 16          """Find all accounts.
 17  
 18          :returns: All found accounts.
 19          :rtype: list
 20  
 21          """
 22          raise NotImplementedError()
 23  
 24      @abc.abstractmethod
 25      def load(self, account_id):  # pragma: no cover
 26          """Load an account by its id.
 27  
 28          :raises .AccountNotFound: if account could not be found
 29          :raises .AccountStorageError: if account could not be loaded
 30  
 31          """
 32          raise NotImplementedError()
 33  
 34      @abc.abstractmethod
 35      def save(self, account):  # pragma: no cover
 36          """Save account.
 37  
 38          :raises .AccountStorageError: if account could not be saved
 39  
 40          """
 41          raise NotImplementedError()
 42  
 43  
 44  class IPluginFactory(zope.interface.Interface):
 45      """IPlugin factory.
 46  
 47      Objects providing this interface will be called without satisfying
 48      any entry point "extras" (extra dependencies) you might have defined
 49      for your plugin, e.g (excerpt from ``setup.py`` script)::
 50  
 51        setup(
 52            ...
 53            entry_points={
 54                'letsencrypt.plugins': [
 55                    'name=example_project.plugin[plugin_deps]',
 56                ],
 57            },
 58            extras_require={
 59                'plugin_deps': ['dep1', 'dep2'],
 60            }
 61        )
 62  
 63      Therefore, make sure such objects are importable and usable without
 64      extras. This is necessary, because CLI does the following operations
 65      (in order):
 66  
 67        - loads an entry point,
 68        - calls `inject_parser_options`,
 69        - requires an entry point,
 70        - creates plugin instance (`__call__`).
 71  
 72      """
 73  
 74      description = zope.interface.Attribute("Short plugin description")
 75  
 76      def __call__(config, name):
 77          """Create new `IPlugin`.
 78  
 79          :param IConfig config: Configuration.
 80          :param str name: Unique plugin name.
 81  
 82          """
 83  
 84      def inject_parser_options(parser, name):
 85          """Inject argument parser options (flags).
 86  
 87          1. Be nice and prepend all options and destinations with
 88          `~.common.option_namespace` and `~common.dest_namespace`.
 89  
 90          2. Inject options (flags) only. Positional arguments are not
 91          allowed, as this would break the CLI.
 92  
 93          :param ArgumentParser parser: (Almost) top-level CLI parser.
 94          :param str name: Unique plugin name.
 95  
 96          """
 97  
 98  
 99  class IPlugin(zope.interface.Interface):
100      """Let's Encrypt plugin."""
101  
102      def prepare():
103          """Prepare the plugin.
104  
105          Finish up any additional initialization.
106  
107          :raises .PluginError:
108              when full initialization cannot be completed.
109          :raises .MisconfigurationError:
110              when full initialization cannot be completed. Plugin will
111              be displayed on a list of available plugins.
112          :raises .NoInstallationError:
113              when the necessary programs/files cannot be located. Plugin
114              will NOT be displayed on a list of available plugins.
115          :raises .NotSupportedError:
116              when the installation is recognized, but the version is not
117              currently supported.
118  
119          """
120  
121      def more_info():
122          """Human-readable string to help the user.
123  
124          Should describe the steps taken and any relevant info to help the user
125          decide which plugin to use.
126  
127          :rtype str:
128  
129          """
130  
131  
132  class IAuthenticator(IPlugin):
133      """Generic Let's Encrypt Authenticator.
134  
135      Class represents all possible tools processes that have the
136      ability to perform challenges and attain a certificate.
137  
138      """
139  
140      def get_chall_pref(domain):
141          """Return list of challenge preferences.
142  
143          :param str domain: Domain for which challenge preferences are sought.
144  
145          :returns: List of challenge types (subclasses of
146              :class:`acme.challenges.Challenge`) with the most
147              preferred challenges first. If a type is not specified, it means the
148              Authenticator cannot perform the challenge.
149          :rtype: list
150  
151          """
152  
153      def perform(achalls):
154          """Perform the given challenge.
155  
156          :param list achalls: Non-empty (guaranteed) list of
157              :class:`~letsencrypt.achallenges.AnnotatedChallenge`
158              instances, such that it contains types found within
159              :func:`get_chall_pref` only.
160  
161          :returns: List of ACME
162              :class:`~acme.challenges.ChallengeResponse` instances
163              or if the :class:`~acme.challenges.Challenge` cannot
164              be fulfilled then:
165  
166              ``None``
167                Authenticator can perform challenge, but not at this time.
168              ``False``
169                Authenticator will never be able to perform (error).
170  
171          :rtype: :class:`list` of
172              :class:`acme.challenges.ChallengeResponse`
173  
174          :raises .PluginError: If challenges cannot be performed
175  
176          """
177  
178      def cleanup(achalls):
179          """Revert changes and shutdown after challenges complete.
180  
181          :param list achalls: Non-empty (guaranteed) list of
182              :class:`~letsencrypt.achallenges.AnnotatedChallenge`
183              instances, a subset of those previously passed to :func:`perform`.
184  
185          :raises PluginError: if original configuration cannot be restored
186  
187          """
188  
189  
190  class IConfig(zope.interface.Interface):
191      """Let's Encrypt user-supplied configuration.
192  
193      .. warning:: The values stored in the configuration have not been
194          filtered, stripped or sanitized.
195  
196      """
197      server = zope.interface.Attribute("ACME Directory Resource URI.")
198      email = zope.interface.Attribute(
199          "Email used for registration and recovery contact.")
200      rsa_key_size = zope.interface.Attribute("Size of the RSA key.")
201  
202      config_dir = zope.interface.Attribute("Configuration directory.")
203      work_dir = zope.interface.Attribute("Working directory.")
204  
205      accounts_dir = zope.interface.Attribute(
206          "Directory where all account information is stored.")
207      backup_dir = zope.interface.Attribute("Configuration backups directory.")
208      csr_dir = zope.interface.Attribute(
209          "Directory where newly generated Certificate Signing Requests "
210          "(CSRs) are saved.")
211      in_progress_dir = zope.interface.Attribute(
212          "Directory used before a permanent checkpoint is finalized.")
213      key_dir = zope.interface.Attribute("Keys storage.")
214      temp_checkpoint_dir = zope.interface.Attribute(
215          "Temporary checkpoint directory.")
216  
217      renewer_config_file = zope.interface.Attribute(
218          "Location of renewal configuration file.")
219  
220      no_verify_ssl = zope.interface.Attribute(
221          "Disable SSL certificate verification.")
222      dvsni_port = zope.interface.Attribute(
223          "Port number to perform DVSNI challenge. "
224          "Boulder in testing mode defaults to 5001.")
225  
226      simple_http_port = zope.interface.Attribute(
227          "Port used in the SimpleHttp challenge.")
228  
229  
230  class IInstaller(IPlugin):
231      """Generic Let's Encrypt Installer Interface.
232  
233      Represents any server that an X509 certificate can be placed.
234  
235      """
236  
237      def get_all_names():
238          """Returns all names that may be authenticated.
239  
240          :rtype: `list` of `str`
241  
242          """
243  
244      def deploy_cert(domain, cert_path, key_path, chain_path, fullchain_path):
245          """Deploy certificate.
246  
247          :param str domain: domain to deploy certificate file
248          :param str cert_path: absolute path to the certificate file
249          :param str key_path: absolute path to the private key file
250          :param str chain_path: absolute path to the certificate chain file
251          :param str fullchain_path: absolute path to the certificate fullchain
252              file (cert plus chain)
253  
254          :raises .PluginError: when cert cannot be deployed
255  
256          """
257  
258      def enhance(domain, enhancement, options=None):
259          """Perform a configuration enhancement.
260  
261          :param str domain: domain for which to provide enhancement
262          :param str enhancement: An enhancement as defined in
263              :const:`~letsencrypt.constants.ENHANCEMENTS`
264          :param options: Flexible options parameter for enhancement.
265              Check documentation of
266              :const:`~letsencrypt.constants.ENHANCEMENTS`
267              for expected options for each enhancement.
268  
269          :raises .PluginError: If Enhancement is not supported, or if
270              an error occurs during the enhancement.
271  
272          """
273  
274      def supported_enhancements():
275          """Returns a list of supported enhancements.
276  
277          :returns: supported enhancements which should be a subset of
278              :const:`~letsencrypt.constants.ENHANCEMENTS`
279          :rtype: :class:`list` of :class:`str`
280  
281          """
282  
283      def get_all_certs_keys():
284          """Retrieve all certs and keys set in configuration.
285  
286          :returns: tuples with form `[(cert, key, path)]`, where:
287  
288              - `cert` - str path to certificate file
289              - `key` - str path to associated key file
290              - `path` - file path to configuration file
291  
292          :rtype: list
293  
294          """
295  
296      def save(title=None, temporary=False):
297          """Saves all changes to the configuration files.
298  
299          Both title and temporary are needed because a save may be
300          intended to be permanent, but the save is not ready to be a full
301          checkpoint
302  
303          :param str title: The title of the save. If a title is given, the
304              configuration will be saved as a new checkpoint and put in a
305              timestamped directory. `title` has no effect if temporary is true.
306  
307          :param bool temporary: Indicates whether the changes made will
308              be quickly reversed in the future (challenges)
309  
310          :raises .PluginError: when save is unsuccessful
311  
312          """
313  
314      def rollback_checkpoints(rollback=1):
315          """Revert `rollback` number of configuration checkpoints.
316  
317          :raises .PluginError: when configuration cannot be fully reverted
318  
319          """
320  
321      def recovery_routine():
322          """Revert configuration to most recent finalized checkpoint.
323  
324          Remove all changes (temporary and permanent) that have not been
325          finalized. This is useful to protect against crashes and other
326          execution interruptions.
327  
328          :raises .errors.PluginError: If unable to recover the configuration
329  
330          """
331  
332      def view_config_changes():
333          """Display all of the LE config changes.
334  
335          :raises .PluginError: when config changes cannot be parsed
336  
337          """
338  
339      def config_test():
340          """Make sure the configuration is valid.
341  
342          :raises .MisconfigurationError: when the config is not in a usable state
343  
344          """
345  
346      def restart():
347          """Restart or refresh the server content.
348  
349          :raises .PluginError: when server cannot be restarted
350  
351          """
352  
353  
354  class IDisplay(zope.interface.Interface):
355      """Generic display."""
356  
357      def notification(message, height, pause):
358          """Displays a string message
359  
360          :param str message: Message to display
361          :param int height: Height of dialog box if applicable
362          :param bool pause: Whether or not the application should pause for
363              confirmation (if available)
364  
365          """
366  
367      def menu(message, choices,
368               ok_label="OK", cancel_label="Cancel", help_label=""):
369          """Displays a generic menu.
370  
371          :param str message: message to display
372  
373          :param choices: choices
374          :type choices: :class:`list` of :func:`tuple` or :class:`str`
375  
376          :param str ok_label: label for OK button
377          :param str cancel_label: label for Cancel button
378          :param str help_label: label for Help button
379  
380          :returns: tuple of (`code`, `index`) where
381              `code` - str display exit code
382              `index` - int index of the user's selection
383  
384          """
385  
386      def input(message):
387          """Accept input from the user.
388  
389          :param str message: message to display to the user
390  
391          :returns: tuple of (`code`, `input`) where
392              `code` - str display exit code
393              `input` - str of the user's input
394          :rtype: tuple
395  
396          """
397  
398      def yesno(message, yes_label="Yes", no_label="No"):
399          """Query the user with a yes/no question.
400  
401          Yes and No label must begin with different letters.
402  
403          :param str message: question for the user
404  
405          :returns: True for "Yes", False for "No"
406          :rtype: bool
407  
408          """
409  
410      def checklist(message, tags, default_state):
411          """Allow for multiple selections from a menu.
412  
413          :param str message: message to display to the user
414          :param list tags: where each is of type :class:`str` len(tags) > 0
415          :param bool default_status: If True, items are in a selected state by
416              default.
417  
418          """
419  
420  
421  class IValidator(zope.interface.Interface):
422      """Configuration validator."""
423  
424      def certificate(cert, name, alt_host=None, port=443):
425          """Verifies the certificate presented at name is cert
426  
427          :param OpenSSL.crypto.X509 cert: Expected certificate
428          :param str name: Server's domain name
429          :param bytes alt_host: Host to connect to instead of the IP
430              address of host
431          :param int port: Port to connect to
432  
433          :returns: True if the certificate was verified successfully
434          :rtype: bool
435  
436          """
437  
438      def redirect(name, port=80, headers=None):
439          """Verify redirect to HTTPS
440  
441          :param str name: Server's domain name
442          :param int port: Port to connect to
443          :param dict headers: HTTP headers to include in request
444  
445          :returns: True if redirect is successfully enabled
446          :rtype: bool
447  
448          """
449  
450      def hsts(name):
451          """Verify HSTS header is enabled
452  
453          :param str name: Server's domain name
454  
455          :returns: True if HSTS header is successfully enabled
456          :rtype: bool
457  
458          """
459  
460      def ocsp_stapling(name):
461          """Verify ocsp stapling for domain
462  
463          :param str name: Server's domain name
464  
465          :returns: True if ocsp stapling is successfully enabled
466          :rtype: bool
467  
468          """
469  
470  
471  class IReporter(zope.interface.Interface):
472      """Interface to collect and display information to the user."""
473  
474      HIGH_PRIORITY = zope.interface.Attribute(
475          "Used to denote high priority messages")
476      MEDIUM_PRIORITY = zope.interface.Attribute(
477          "Used to denote medium priority messages")
478      LOW_PRIORITY = zope.interface.Attribute(
479          "Used to denote low priority messages")
480  
481      def add_message(self, msg, priority, on_crash=True):
482          """Adds msg to the list of messages to be printed.
483  
484          :param str msg: Message to be displayed to the user.
485  
486          :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or
487              LOW_PRIORITY.
488  
489          :param bool on_crash: Whether or not the message should be printed if
490              the program exits abnormally.
491  
492          """
493  
494      def print_messages(self):
495          """Prints messages to the user and clears the message queue."""