Coverage for frappe_manager / site_manager / modules / bench_ssl.py: 37%

43 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-07-02 18:13 +0530

1"""BenchSSL - SSL Certificate Management Module 

2 

3This module handles all SSL certificate operations for a bench including: 

4- Creating certificates 

5- Checking certificate existence 

6- Removing certificates 

7- Updating certificates 

8- Renewing certificates 

9""" 

10 

11from frappe_manager.site_manager.bench_config import SSLCertificate 

12from frappe_manager.site_manager.exceptions import ( 

13 BenchServiceNotRunning, 

14 BenchSSLCertificateAlreadyIssued, 

15 BenchSSLCertificateNotIssued, 

16) 

17from frappe_manager.ssl_manager.certificate import SUPPORTED_SSL_TYPES 

18from frappe_manager.ssl_manager.ssl_certificate_manager import SSLCertificateManager 

19 

20 

21class BenchSSL: 

22 """Manages SSL certificates for a bench.""" 

23 

24 def __init__( 

25 self, 

26 certificate_manager: SSLCertificateManager, 

27 bench_name: str, 

28 is_service_running_fn, 

29 ): 

30 """ 

31 Initialize BenchSSL module. 

32 

33 Args: 

34 certificate_manager: SSL certificate manager instance 

35 bench_name: Name of the bench 

36 is_service_running_fn: Function to check if a service is running 

37 """ 

38 self.certificate_manager = certificate_manager 

39 self.bench_name = bench_name 

40 self._is_service_running = is_service_running_fn 

41 

42 def create_individual_certificates(self) -> None: 

43 """ 

44 Create individual SSL certificates for all domains. 

45 

46 This generates separate certificates for the primary domain and each 

47 alias domain, rather than a single SAN certificate covering all domains. 

48 """ 

49 self.certificate_manager.generate_all_certificates() 

50 

51 def has_certificate(self) -> bool: 

52 """ 

53 Check if bench has an SSL certificate. 

54 

55 Returns: 

56 True if certificate exists, False otherwise 

57 """ 

58 return self.certificate_manager.has_certificate() 

59 

60 def remove_certificate(self, domain: str | None = None) -> None: 

61 """ 

62 Remove SSL certificate from the bench. 

63 

64 Args: 

65 domain: Domain to remove certificate for. If None, removes primary certificate. 

66 """ 

67 self.certificate_manager.remove_certificate(domain) 

68 

69 def remove_all_certificates(self) -> None: 

70 """ 

71 Remove ALL SSL certificates from the bench. 

72 

73 This removes all certificates for the primary domain and all alias domains, 

74 including their symlinks, vhost configs, and acme.sh configurations. 

75 This is useful for complete cleanup when deleting a bench. 

76 """ 

77 self.certificate_manager.remove_all_certificates() 

78 

79 def update_certificate(self, certificate: SSLCertificate, raise_error: bool = True) -> bool: 

80 """ 

81 Update SSL certificate configuration. 

82 

83 Args: 

84 certificate: New certificate configuration 

85 raise_error: Whether to raise error on failures 

86 

87 Returns: 

88 True if update was successful 

89 

90 Raises: 

91 BenchSSLCertificateAlreadyIssued: If certificate already exists (when raise_error=True) 

92 BenchSSLCertificateNotIssued: If trying to remove non-existent cert (when raise_error=True) 

93 """ 

94 if certificate.ssl_type == SUPPORTED_SSL_TYPES.le: 

95 if self.has_certificate(): 

96 if raise_error: 

97 raise BenchSSLCertificateAlreadyIssued(self.bench_name) 

98 return False 

99 self.create_individual_certificates() 

100 

101 elif certificate.ssl_type == SUPPORTED_SSL_TYPES.none: 

102 if self.has_certificate(): 

103 self.remove_certificate() 

104 else: 

105 if not raise_error: 

106 return False 

107 raise BenchSSLCertificateNotIssued(self.bench_name) 

108 

109 return True 

110 

111 def renew_certificate(self, domain: str | None = None, dry_run: bool = False, force: bool = False) -> None: 

112 """ 

113 Renew existing SSL certificate. 

114 

115 Args: 

116 domain: Domain to renew certificate for. If None, renews primary certificate. 

117 dry_run: If True, uses Let's Encrypt staging server and skips system modifications 

118 force: If True, forces renewal even if certificate is not due for renewal 

119 

120 Raises: 

121 BenchSSLCertificateNotIssued: If no certificate exists 

122 BenchServiceNotRunning: If nginx service is not running 

123 """ 

124 if not self.has_certificate(): 

125 raise BenchSSLCertificateNotIssued(self.bench_name) 

126 

127 if not self._is_service_running("nginx"): 

128 raise BenchServiceNotRunning(self.bench_name, "nginx") 

129 

130 self.certificate_manager.renew_certificate(domain, dry_run=dry_run, force=force) 

131 

132 def renew_all_certificates(self, dry_run: bool = False, force: bool = False) -> None: 

133 """ 

134 Renew all SSL certificates for the bench. 

135 

136 This renews all certificates that are due for renewal. 

137 Certificates not due for renewal are skipped. 

138 

139 Args: 

140 dry_run: If True, uses Let's Encrypt staging server and skips system modifications 

141 force: If True, forces renewal for all certificates regardless of expiry 

142 

143 Raises: 

144 BenchSSLCertificateNotIssued: If no certificates exist 

145 BenchServiceNotRunning: If nginx service is not running 

146 """ 

147 if not self.has_certificate(): 

148 raise BenchSSLCertificateNotIssued(self.bench_name) 

149 

150 if not self._is_service_running("nginx"): 

151 raise BenchServiceNotRunning(self.bench_name, "nginx") 

152 

153 self.certificate_manager.renew_all_certificates(dry_run=dry_run, force=force)