ssl-managment/tests/test_letsencrypt.py
Mike Geppert 9c7acfa430 Switch to Let's Encrypt production environment by default
- 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
2025-07-20 23:00:40 -05:00

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