/ 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."""