- Changed Let's Encrypt configuration to use production environment by default - Added DNS validation for Let's Encrypt certificates - Added certificate verification functionality - Added debug logging with file names and line numbers - Added test files for new features - Updated documentation to clarify Let's Encrypt usage
182 lines
6.9 KiB
Python
182 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Tests for the Let's Encrypt functionality of the SSL Manager.
|
|
|
|
This module contains tests for generating certificates using Let's Encrypt.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
import subprocess
|
|
from unittest.mock import patch, MagicMock
|
|
|
|
# Add the src directory to the Python path
|
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../src')))
|
|
|
|
from ssl_manager import SSLManager
|
|
|
|
|
|
class TestLetsEncrypt(unittest.TestCase):
|
|
"""Test cases for Let's Encrypt certificate generation."""
|
|
|
|
def setUp(self):
|
|
"""Set up test fixtures."""
|
|
# Create a temporary directory for test files
|
|
self.temp_dir = tempfile.TemporaryDirectory()
|
|
|
|
# Sample config for testing
|
|
self.test_config = {
|
|
"cert_dir": self.temp_dir.name,
|
|
"default_port": 8443,
|
|
"connection_timeout": 5.0,
|
|
"default_validity_days": 730,
|
|
"key_size": 4096,
|
|
"letsencrypt": {
|
|
"email": "test@example.com",
|
|
"validation_method": "standalone",
|
|
"use_staging": True,
|
|
"agree_tos": True
|
|
}
|
|
}
|
|
|
|
# Create a temporary config file
|
|
self.config_path = os.path.join(self.temp_dir.name, "test_config.json")
|
|
with open(self.config_path, 'w') as f:
|
|
import json
|
|
json.dump(self.test_config, f)
|
|
|
|
# Create an SSLManager with the test config
|
|
self.ssl_manager = SSLManager(config_path=self.config_path)
|
|
|
|
# Create directories for certbot
|
|
self.config_dir = os.path.join(self.temp_dir.name, '.config')
|
|
self.work_dir = os.path.join(self.temp_dir.name, '.work')
|
|
self.logs_dir = os.path.join(self.temp_dir.name, '.logs')
|
|
self.live_dir = os.path.join(self.config_dir, 'live', 'test.example.com')
|
|
|
|
# Create directories
|
|
os.makedirs(self.live_dir, exist_ok=True)
|
|
|
|
# Create dummy certificate and key files
|
|
self.fullchain_path = os.path.join(self.live_dir, 'fullchain.pem')
|
|
self.privkey_path = os.path.join(self.live_dir, 'privkey.pem')
|
|
|
|
with open(self.fullchain_path, 'w') as f:
|
|
f.write("-----BEGIN CERTIFICATE-----\nDummy Certificate\n-----END CERTIFICATE-----")
|
|
|
|
with open(self.privkey_path, 'w') as f:
|
|
f.write("-----BEGIN PRIVATE KEY-----\nDummy Private Key\n-----END PRIVATE KEY-----")
|
|
|
|
def tearDown(self):
|
|
"""Tear down test fixtures."""
|
|
# Clean up the temporary directory
|
|
self.temp_dir.cleanup()
|
|
|
|
@patch('subprocess.run')
|
|
def test_generate_letsencrypt_cert(self, mock_run):
|
|
"""Test generating a Let's Encrypt certificate."""
|
|
# Mock the subprocess.run call
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="Certificate issued successfully")
|
|
|
|
# Call the method
|
|
cert_path, key_path = self.ssl_manager.generate_letsencrypt_cert(
|
|
common_name="test.example.com",
|
|
email="test@example.com",
|
|
validation_method="standalone",
|
|
use_staging=True,
|
|
agree_tos=True
|
|
)
|
|
|
|
# Verify that subprocess.run was called with the correct arguments
|
|
mock_run.assert_called_once()
|
|
args, kwargs = mock_run.call_args
|
|
|
|
# Verify the command includes the expected arguments
|
|
cmd = args[0]
|
|
self.assertEqual(cmd[0], 'certbot')
|
|
self.assertEqual(cmd[1], 'certonly')
|
|
self.assertIn('--standalone', cmd)
|
|
self.assertIn('-d', cmd)
|
|
self.assertIn('test.example.com', cmd)
|
|
self.assertIn('-m', cmd)
|
|
self.assertIn('test@example.com', cmd)
|
|
self.assertIn('--test-cert', cmd)
|
|
self.assertIn('--agree-tos', cmd)
|
|
self.assertIn('-n', cmd)
|
|
|
|
# Verify the paths
|
|
expected_cert_path = os.path.join(self.temp_dir.name, 'test.example.com.crt')
|
|
expected_key_path = os.path.join(self.temp_dir.name, 'test.example.com.key')
|
|
self.assertEqual(cert_path, expected_cert_path)
|
|
self.assertEqual(key_path, expected_key_path)
|
|
|
|
# Verify the certificate and key files were created
|
|
self.assertTrue(os.path.isfile(cert_path))
|
|
self.assertTrue(os.path.isfile(key_path))
|
|
|
|
# Verify the content of the certificate and key files
|
|
with open(cert_path, 'r') as f:
|
|
cert_content = f.read()
|
|
with open(key_path, 'r') as f:
|
|
key_content = f.read()
|
|
|
|
self.assertIn("Dummy Certificate", cert_content)
|
|
self.assertIn("Dummy Private Key", key_content)
|
|
|
|
@patch('subprocess.run')
|
|
def test_generate_letsencrypt_cert_with_defaults(self, mock_run):
|
|
"""Test generating a Let's Encrypt certificate with default values from config."""
|
|
# Mock the subprocess.run call
|
|
mock_run.return_value = MagicMock(returncode=0, stdout="Certificate issued successfully")
|
|
|
|
# Call the method with minimal arguments
|
|
cert_path, key_path = self.ssl_manager.generate_letsencrypt_cert(
|
|
common_name="test.example.com"
|
|
)
|
|
|
|
# Verify that subprocess.run was called with the correct arguments
|
|
mock_run.assert_called_once()
|
|
args, kwargs = mock_run.call_args
|
|
|
|
# Verify the command includes the expected arguments
|
|
cmd = args[0]
|
|
self.assertEqual(cmd[0], 'certbot')
|
|
self.assertEqual(cmd[1], 'certonly')
|
|
self.assertIn('--standalone', cmd) # Default from config
|
|
self.assertIn('-d', cmd)
|
|
self.assertIn('test.example.com', cmd)
|
|
self.assertIn('-m', cmd)
|
|
self.assertIn('test@example.com', cmd) # Default from config
|
|
self.assertIn('--test-cert', cmd) # Default from config
|
|
self.assertIn('--agree-tos', cmd) # Default from config
|
|
self.assertIn('-n', cmd)
|
|
|
|
# Verify the paths
|
|
expected_cert_path = os.path.join(self.temp_dir.name, 'test.example.com.crt')
|
|
expected_key_path = os.path.join(self.temp_dir.name, 'test.example.com.key')
|
|
self.assertEqual(cert_path, expected_cert_path)
|
|
self.assertEqual(key_path, expected_key_path)
|
|
|
|
@patch('subprocess.run')
|
|
def test_generate_letsencrypt_cert_error(self, mock_run):
|
|
"""Test error handling when generating a Let's Encrypt certificate."""
|
|
# Mock the subprocess.run call to raise an exception
|
|
mock_run.side_effect = subprocess.CalledProcessError(
|
|
returncode=1,
|
|
cmd=['certbot', 'certonly'],
|
|
output="An error occurred",
|
|
stderr="Certificate issuance failed"
|
|
)
|
|
|
|
# Call the method and expect an exception
|
|
with self.assertRaises(subprocess.CalledProcessError):
|
|
self.ssl_manager.generate_letsencrypt_cert(
|
|
common_name="test.example.com",
|
|
email="test@example.com"
|
|
)
|
|
|
|
|
|
if __name__ == '__main__':
|
|
unittest.main() |