Add HTTP port configuration for Let's Encrypt validation

This commit adds support for configuring the HTTP port used by the standalone validation method when generating Let's Encrypt certificates. This allows using a non-privileged port (e.g., 8080) instead of the default port 80, which requires root privileges.

Changes include:
- Add http_port option to the letsencrypt section in config.json
- Update ssl_manager.py to use the configured HTTP port
- Add documentation for the http_port option in guidelines.md
This commit is contained in:
Mike Geppert 2025-07-20 23:20:34 -05:00
parent 9c7acfa430
commit e39e002351
3 changed files with 21 additions and 4 deletions

View File

@ -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

View File

@ -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)"
}

View File

@ -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
)