@@ -596,6 +596,73 @@ def generate_a007_altered_settings_report(self, cluster: str = "local", node_nam
596596
597597 return self .format_report_data ("A007" , altered_settings , node_name , postgres_version = self ._get_postgres_version_info (cluster , node_name ))
598598
599+ def generate_s002_ssl_tls_report (self , cluster : str = "local" , node_name : str = "node-01" ) -> Dict [str , Any ]:
600+ """
601+ Generate S002 SSL/TLS Settings report.
602+
603+ This report collects SSL/TLS related PostgreSQL settings to assess
604+ the security posture of encrypted connections.
605+
606+ Args:
607+ cluster: Cluster name
608+ node_name: Node name
609+
610+ Returns:
611+ Dictionary containing SSL/TLS settings information
612+ """
613+ logger .info ("Generating S002 SSL/TLS Settings report..." )
614+
615+ # SSL-related settings to collect
616+ ssl_settings = [
617+ 'ssl' ,
618+ 'ssl_ca_file' ,
619+ 'ssl_cert_file' ,
620+ 'ssl_ciphers' ,
621+ 'ssl_crl_dir' ,
622+ 'ssl_crl_file' ,
623+ 'ssl_dh_params_file' ,
624+ 'ssl_ecdh_curve' ,
625+ 'ssl_key_file' ,
626+ 'ssl_max_protocol_version' ,
627+ 'ssl_min_protocol_version' ,
628+ 'ssl_passphrase_command' ,
629+ 'ssl_passphrase_command_supports_reload' ,
630+ 'ssl_prefer_server_ciphers' ,
631+ ]
632+
633+ # Query all PostgreSQL settings using the pgwatch_settings_configured metric
634+ settings_query = f'last_over_time(pgwatch_settings_configured{{cluster="{ cluster } ", node_name="{ node_name } "}}[3h])'
635+ result = self .query_instant (settings_query )
636+
637+ settings_data = {}
638+ if result .get ('status' ) == 'success' and result .get ('data' , {}).get ('result' ):
639+ for item in result ['data' ]['result' ]:
640+ setting_name = item ['metric' ].get ('setting_name' , '' )
641+
642+ # Only include SSL-related settings
643+ if setting_name not in ssl_settings :
644+ continue
645+
646+ setting_value = item ['metric' ].get ('setting_value' , '' )
647+ category = item ['metric' ].get ('category' , 'Other' )
648+ unit = item ['metric' ].get ('unit' , '' )
649+ context = item ['metric' ].get ('context' , '' )
650+ vartype = item ['metric' ].get ('vartype' , '' )
651+
652+ settings_data [setting_name ] = {
653+ "setting" : setting_value ,
654+ "unit" : unit ,
655+ "category" : category ,
656+ "context" : context ,
657+ "vartype" : vartype ,
658+ "pretty_value" : self .format_setting_value (setting_name , setting_value , unit )
659+ }
660+ else :
661+ logger .warning (f"S002 - No settings data returned for cluster={ cluster } , node_name={ node_name } " )
662+ logger .info (f"Query result status: { result .get ('status' )} " )
663+
664+ return self .format_report_data ("S002" , settings_data , node_name , postgres_version = self ._get_postgres_version_info (cluster , node_name ))
665+
599666 def generate_h001_invalid_indexes_report (self , cluster : str = "local" , node_name : str = "node-01" ) -> Dict [
600667 str , Any ]:
601668 """
@@ -3761,6 +3828,7 @@ def get_check_title(self, check_id: str) -> str:
37613828 "L002" : "Data types being used" ,
37623829 "L003" : "Integer out-of-range risks in PKs" ,
37633830 "L004" : "Tables without PK/UK" ,
3831+ "S002" : "SSL/TLS settings" ,
37643832 }
37653833 return check_titles .get (check_id , f"Check { check_id } " )
37663834
@@ -3970,6 +4038,7 @@ def generate_all_reports(self, cluster: str = "local", node_name: str = None, co
39704038 ('A003' , self .generate_a003_settings_report ),
39714039 ('A004' , self .generate_a004_cluster_report ),
39724040 ('A007' , self .generate_a007_altered_settings_report ),
4041+ ('S002' , self .generate_s002_ssl_tls_report ),
39734042 ('F004' , self .generate_f004_heap_bloat_report ),
39744043 ('F005' , self .generate_f005_btree_bloat_report ),
39754044 ('H001' , self .generate_h001_invalid_indexes_report ),
@@ -4834,7 +4903,8 @@ def main():
48344903 help = 'Disable combining primary and replica reports into single report' )
48354904 parser .add_argument ('--check-id' ,
48364905 choices = ['A002' , 'A003' , 'A004' , 'A007' , 'D004' , 'F001' , 'F004' , 'F005' , 'G001' , 'H001' , 'H002' ,
4837- 'H004' , 'K001' , 'K003' , 'K004' , 'K005' , 'K006' , 'K007' , 'K008' , 'M001' , 'M002' , 'M003' , 'N001' , 'ALL' ],
4906+ 'H004' , 'K001' , 'K003' , 'K004' , 'K005' , 'K006' , 'K007' , 'K008' , 'M001' , 'M002' , 'M003' , 'N001' ,
4907+ 'S002' , 'ALL' ],
48384908 help = 'Specific check ID to generate (default: ALL)' )
48394909 parser .add_argument ('--output' , default = '-' ,
48404910 help = 'Output file (default: stdout)' )
@@ -4960,6 +5030,8 @@ def main():
49605030 report = generator .generate_a004_cluster_report (cluster , args .node_name )
49615031 elif args .check_id == 'A007' :
49625032 report = generator .generate_a007_altered_settings_report (cluster , args .node_name )
5033+ elif args .check_id == 'S002' :
5034+ report = generator .generate_s002_ssl_tls_report (cluster , args .node_name )
49635035 elif args .check_id == 'D004' :
49645036 if a003_report :
49655037 report = generator .generate_d004_from_a003 (a003_report , cluster , args .node_name )
0 commit comments