diff --git a/.junie/guidelines.md b/.junie/guidelines.md index 20cefc6..0f1f221 100644 --- a/.junie/guidelines.md +++ b/.junie/guidelines.md @@ -118,8 +118,10 @@ The SSL Manager provides three main commands. All commands support the following When generating certificates with Let's Encrypt, you need to prove that you control the domain. The SSL Manager supports three validation methods: 1. **Standalone** (`--validation-method standalone`): - - Starts a temporary web server on port 80 to respond to Let's Encrypt's validation requests - - Requires port 80 to be available and accessible from the internet + - Starts a temporary web server to respond to Let's Encrypt's validation requests + - By default, uses port 80, which requires root privileges + - Can use a non-privileged port (e.g., 8080) with the `--http-port` option or `http_port` in config.json + - Requires the specified port to be available and accessible from the internet - Best for servers where you don't have a web server running - **Requires the hostname to be in public DNS** with an A/AAAA record pointing to your server diff --git a/config.json b/config.json index 22ac87f..6f757b0 100644 --- a/config.json +++ b/config.json @@ -6,7 +6,7 @@ "key_size": 2048, "debug": false, "unifi": { - "host": "udm-se.mgeppert.com", + "host": "mgeppert.com", "username": "SSLCertificate", "password": "cYu2E1OWt0XseVf9j5ML", "site": "default", @@ -18,6 +18,7 @@ "letsencrypt": { "email": "mgeppert1@gmail.com", "validation_method": "standalone", + "http_port": 8080, "use_staging": false, "agree_tos": true }, @@ -40,6 +41,7 @@ "letsencrypt": "Let's Encrypt certificate settings", "letsencrypt.email": "Email address for Let's Encrypt registration and important notifications", "letsencrypt.validation_method": "Method to use for domain validation (standalone, webroot, dns)", + "letsencrypt.http_port": "Port to use for HTTP validation when using standalone method (default: 80, requires root privileges)", "letsencrypt.use_staging": "Whether to use Let's Encrypt's staging environment for testing (true/false)", "letsencrypt.agree_tos": "Whether to automatically agree to the Terms of Service (true/false)" } diff --git a/src/ssl_manager.py b/src/ssl_manager.py index 078f015..e0cb12e 100644 --- a/src/ssl_manager.py +++ b/src/ssl_manager.py @@ -143,6 +143,7 @@ def load_config(config_path: str = "config.json") -> Dict[str, Any]: "letsencrypt": { "email": "", "validation_method": "standalone", + "http_port": 80, "use_staging": True, "agree_tos": True } @@ -244,10 +245,12 @@ class SSLManager: # Store Let's Encrypt settings self.letsencrypt_email = self.config["letsencrypt"]["email"] self.letsencrypt_validation_method = self.config["letsencrypt"]["validation_method"] + self.letsencrypt_http_port = self.config["letsencrypt"]["http_port"] self.letsencrypt_use_staging = self.config["letsencrypt"]["use_staging"] self.letsencrypt_agree_tos = self.config["letsencrypt"]["agree_tos"] logging.debug(f"Loaded Let's Encrypt settings: email={self.letsencrypt_email}, " f"validation_method={self.letsencrypt_validation_method}, " + f"http_port={self.letsencrypt_http_port}, " f"use_staging={self.letsencrypt_use_staging}, " f"agree_tos={self.letsencrypt_agree_tos}") @@ -467,7 +470,8 @@ class SSLManager: email: str = None, validation_method: str = None, use_staging: bool = None, - agree_tos: bool = None + agree_tos: bool = None, + http_port: int = None ) -> Tuple[str, str]: """ Generate a certificate using Let's Encrypt. @@ -478,6 +482,7 @@ class SSLManager: validation_method: Method to use for domain validation (default: from config) use_staging: Whether to use Let's Encrypt's staging environment (default: from config) agree_tos: Whether to automatically agree to the Terms of Service (default: from config) + http_port: Port to use for HTTP validation (default: 80, requires root privileges) Returns: Tuple of (cert_path, key_path) @@ -487,10 +492,12 @@ class SSLManager: validation_method = validation_method or self.letsencrypt_validation_method use_staging = use_staging if use_staging is not None else self.letsencrypt_use_staging agree_tos = agree_tos if agree_tos is not None else self.letsencrypt_agree_tos + http_port = http_port or self.letsencrypt_http_port logging.debug(f"Generating Let's Encrypt certificate for {common_name}") logging.debug(f"Using email: {email}") logging.debug(f"Using validation method: {validation_method}") + logging.debug(f"Using HTTP port: {http_port}") logging.debug(f"Using staging environment: {use_staging}") logging.debug(f"Automatically agree to ToS: {agree_tos}") @@ -520,6 +527,10 @@ class SSLManager: # Add validation method if validation_method == 'standalone': cmd.append('--standalone') + # Add HTTP port option if not using the default port 80 + if http_port != 80: + cmd.extend(['--http-01-port', str(http_port)]) + logging.debug(f"Using non-standard HTTP port: {http_port}") elif validation_method == 'webroot': cmd.extend(['--webroot', '--webroot-path', '/var/www/html']) elif validation_method == 'dns': @@ -694,6 +705,7 @@ def main(): gen_parser.add_argument('--email', help='Email address for Let\'s Encrypt registration') gen_parser.add_argument('--validation-method', choices=['standalone', 'webroot', 'dns'], help='Method to use for domain validation') + gen_parser.add_argument('--http-port', type=int, help='Port to use for HTTP validation (default: 80, requires root privileges)') gen_parser.add_argument('--staging', action='store_true', help='Use Let\'s Encrypt staging environment') gen_parser.add_argument('--production', action='store_true', help='Use Let\'s Encrypt production environment') @@ -790,6 +802,7 @@ def main(): common_name=common_name, email=args.email, validation_method=args.validation_method, + http_port=args.http_port, use_staging=use_staging, agree_tos=True # Always agree to ToS from command line )