1"""Certbot user-supplied configuration.""" 2import argparse 3import copy 4from typing import Any 5from typing import List 6from typing import Optional 7from urllib import parse 8 9from certbot import errors 10from certbot import util 11from certbot._internal import constants 12from certbot.compat import misc 13from certbot.compat import os 14 15 16class NamespaceConfig: 17 """Configuration wrapper around :class:`argparse.Namespace`. 18 19 Please note that the following attributes are dynamically resolved using 20 :attr:`~certbot.configuration.NamespaceConfig.work_dir` and relative 21 paths defined in :py:mod:`certbot._internal.constants`: 22 23 - `accounts_dir` 24 - `csr_dir` 25 - `in_progress_dir` 26 - `key_dir` 27 - `temp_checkpoint_dir` 28 29 And the following paths are dynamically resolved using 30 :attr:`~certbot.configuration.NamespaceConfig.config_dir` and relative 31 paths defined in :py:mod:`certbot._internal.constants`: 32 33 - `default_archive_dir` 34 - `live_dir` 35 - `renewal_configs_dir` 36 37 :ivar namespace: Namespace typically produced by 38 :meth:`argparse.ArgumentParser.parse_args`. 39 :type namespace: :class:`argparse.Namespace` 40 41 """ 42 43 def __init__(self, namespace: argparse.Namespace) -> None: 44 self.namespace: argparse.Namespace 45 # Avoid recursion loop because of the delegation defined in __setattr__ 46 object.__setattr__(self, 'namespace', namespace) 47 48 self.namespace.config_dir = os.path.abspath(self.namespace.config_dir) 49 self.namespace.work_dir = os.path.abspath(self.namespace.work_dir) 50 self.namespace.logs_dir = os.path.abspath(self.namespace.logs_dir) 51 52 # Check command line parameters sanity, and error out in case of problem. 53 _check_config_sanity(self) 54 55 # Delegate any attribute not explicitly defined to the underlying namespace object. 56 57 def __getattr__(self, name: str) -> Any: 58 return getattr(self.namespace, name) 59 60 def __setattr__(self, name: str, value: Any) -> None: 61 setattr(self.namespace, name, value) 62 63 @property 64 def server(self) -> str: 65 """ACME Directory Resource URI.""" 66 return self.namespace.server 67 68 @server.setter 69 def server(self, server_: str) -> None: 70 self.namespace.server = server_ 71 72 @property 73 def email(self) -> Optional[str]: 74 """Email used for registration and recovery contact. 75 76 Use comma to register multiple emails, 77 ex: u1@example.com,u2@example.com. (default: Ask). 78 """ 79 return self.namespace.email 80 81 @email.setter 82 def email(self, mail: str) -> None: 83 self.namespace.email = mail 84 85 @property 86 def rsa_key_size(self) -> int: 87 """Size of the RSA key.""" 88 return self.namespace.rsa_key_size 89 90 @rsa_key_size.setter 91 def rsa_key_size(self, ksize: int) -> None: 92 """Set the rsa_key_size property""" 93 self.namespace.rsa_key_size = ksize 94 95 @property 96 def elliptic_curve(self) -> str: 97 """The SECG elliptic curve name to use. 98 99 Please see RFC 8446 for supported values. 100 """ 101 return self.namespace.elliptic_curve 102 103 @elliptic_curve.setter 104 def elliptic_curve(self, ecurve: str) -> None: 105 """Set the elliptic_curve property""" 106 self.namespace.elliptic_curve = ecurve 107 108 @property 109 def key_type(self) -> str: 110 """Type of generated private key. 111 112 Only *ONE* per invocation can be provided at this time. 113 """ 114 return self.namespace.key_type 115 116 @key_type.setter 117 def key_type(self, ktype: str) -> None: 118 """Set the key_type property""" 119 self.namespace.key_type = ktype 120 121 @property 122 def must_staple(self) -> bool: 123 """Adds the OCSP Must Staple extension to the certificate. 124 125 Autoconfigures OCSP Stapling for supported setups 126 (Apache version >= 2.3.3 ). 127 """ 128 return self.namespace.must_staple 129 130 @property 131 def config_dir(self) -> str: 132 """Configuration directory.""" 133 return self.namespace.config_dir 134 135 @property 136 def work_dir(self) -> str: 137 """Working directory.""" 138 return self.namespace.work_dir 139 140 @property 141 def accounts_dir(self) -> str: 142 """Directory where all account information is stored.""" 143 return self.accounts_dir_for_server_path(self.server_path) 144 145 @property 146 def backup_dir(self) -> str: 147 """Configuration backups directory.""" 148 return os.path.join(self.namespace.work_dir, constants.BACKUP_DIR) 149 150 @property 151 def csr_dir(self) -> str: 152 """Directory where new Certificate Signing Requests (CSRs) are saved.""" 153 return os.path.join(self.namespace.config_dir, constants.CSR_DIR) 154 155 @property 156 def in_progress_dir(self) -> str: 157 """Directory used before a permanent checkpoint is finalized.""" 158 return os.path.join(self.namespace.work_dir, constants.IN_PROGRESS_DIR) 159 160 @property 161 def key_dir(self) -> str: 162 """Keys storage.""" 163 return os.path.join(self.namespace.config_dir, constants.KEY_DIR) 164 165 @property 166 def temp_checkpoint_dir(self) -> str: 167 """Temporary checkpoint directory.""" 168 return os.path.join( 169 self.namespace.work_dir, constants.TEMP_CHECKPOINT_DIR) 170 171 @property 172 def no_verify_ssl(self) -> bool: 173 """Disable verification of the ACME server's certificate.""" 174 return self.namespace.no_verify_ssl 175 176 @property 177 def http01_port(self) -> int: 178 """Port used in the http-01 challenge. 179 180 This only affects the port Certbot listens on. 181 A conforming ACME server will still attempt to connect on port 80. 182 """ 183 return self.namespace.http01_port 184 185 @property 186 def http01_address(self) -> str: 187 """The address the server listens to during http-01 challenge.""" 188 return self.namespace.http01_address 189 190 @property 191 def https_port(self) -> int: 192 """Port used to serve HTTPS. 193 194 This affects which port Nginx will listen on after a LE certificate 195 is installed. 196 """ 197 return self.namespace.https_port 198 199 @property 200 def pref_challs(self) -> List[str]: 201 """List of user specified preferred challenges. 202 203 Sorted with the most preferred challenge listed first. 204 """ 205 return self.namespace.pref_challs 206 207 @property 208 def allow_subset_of_names(self) -> bool: 209 """Allow only a subset of names to be authorized to perform validations. 210 211 When performing domain validation, do not consider it a failure 212 if authorizations can not be obtained for a strict subset of 213 the requested domains. This may be useful for allowing renewals for 214 multiple domains to succeed even if some domains no longer point 215 at this system. 216 """ 217 return self.namespace.allow_subset_of_names 218 219 @property 220 def strict_permissions(self) -> bool: 221 """Enable strict permissions checks. 222 223 Require that all configuration files are owned by the current 224 user; only needed if your config is somewhere unsafe like /tmp/. 225 """ 226 return self.namespace.strict_permissions 227 228 @property 229 def disable_renew_updates(self) -> bool: 230 """Disable renewal updates. 231 232 If updates provided by installer enhancements when Certbot is being run 233 with \"renew\" verb should be disabled. 234 """ 235 return self.namespace.disable_renew_updates 236 237 @property 238 def preferred_chain(self) -> Optional[str]: 239 """Set the preferred certificate chain. 240 241 If the CA offers multiple certificate chains, prefer the chain whose 242 topmost certificate was issued from this Subject Common Name. 243 If no match, the default offered chain will be used. 244 """ 245 return self.namespace.preferred_chain 246 247 @property 248 def server_path(self) -> str: 249 """File path based on ``server``.""" 250 parsed = parse.urlparse(self.namespace.server) 251 return (parsed.netloc + parsed.path).replace('/', os.path.sep) 252 253 def accounts_dir_for_server_path(self, server_path: str) -> str: 254 """Path to accounts directory based on server_path""" 255 server_path = misc.underscores_for_unsupported_characters_in_path(server_path) 256 return os.path.join( 257 self.namespace.config_dir, constants.ACCOUNTS_DIR, server_path) 258 259 @property 260 def default_archive_dir(self) -> str: # pylint: disable=missing-function-docstring 261 return os.path.join(self.namespace.config_dir, constants.ARCHIVE_DIR) 262 263 @property 264 def live_dir(self) -> str: # pylint: disable=missing-function-docstring 265 return os.path.join(self.namespace.config_dir, constants.LIVE_DIR) 266 267 @property 268 def renewal_configs_dir(self) -> str: # pylint: disable=missing-function-docstring 269 return os.path.join( 270 self.namespace.config_dir, constants.RENEWAL_CONFIGS_DIR) 271 272 @property 273 def renewal_hooks_dir(self) -> str: 274 """Path to directory with hooks to run with the renew subcommand.""" 275 return os.path.join(self.namespace.config_dir, 276 constants.RENEWAL_HOOKS_DIR) 277 278 @property 279 def renewal_pre_hooks_dir(self) -> str: 280 """Path to the pre-hook directory for the renew subcommand.""" 281 return os.path.join(self.renewal_hooks_dir, 282 constants.RENEWAL_PRE_HOOKS_DIR) 283 284 @property 285 def renewal_deploy_hooks_dir(self) -> str: 286 """Path to the deploy-hook directory for the renew subcommand.""" 287 return os.path.join(self.renewal_hooks_dir, 288 constants.RENEWAL_DEPLOY_HOOKS_DIR) 289 290 @property 291 def renewal_post_hooks_dir(self) -> str: 292 """Path to the post-hook directory for the renew subcommand.""" 293 return os.path.join(self.renewal_hooks_dir, 294 constants.RENEWAL_POST_HOOKS_DIR) 295 296 @property 297 def issuance_timeout(self) -> int: 298 """This option specifies how long (in seconds) Certbot will wait 299 for the server to issue a certificate. 300 """ 301 return self.namespace.issuance_timeout 302 303 # Magic methods 304 305 def __deepcopy__(self, _memo: Any) -> 'NamespaceConfig': 306 # Work around https://bugs.python.org/issue1515 for py26 tests :( :( 307 # https://travis-ci.org/letsencrypt/letsencrypt/jobs/106900743#L3276 308 new_ns = copy.deepcopy(self.namespace) 309 return type(self)(new_ns) 310 311 312def _check_config_sanity(config: NamespaceConfig) -> None: 313 """Validate command line options and display error message if 314 requirements are not met. 315 316 :param config: NamespaceConfig instance holding user configuration 317 :type args: :class:`certbot.configuration.NamespaceConfig` 318 319 """ 320 # Port check 321 if config.http01_port == config.https_port: 322 raise errors.ConfigurationError( 323 "Trying to run http-01 and https-port " 324 "on the same port ({0})".format(config.https_port)) 325 326 # Domain checks 327 if config.namespace.domains is not None: 328 for domain in config.namespace.domains: 329 # This may be redundant, but let's be paranoid 330 util.enforce_domain_sanity(domain) 331