We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[Bug]: cloudwatch_log_group_retention_policy_specific_days_enabled not measuring "never expire"
### Steps to Reproduce

When having log groups in cloudwatch that are set to "never expire", the mentioned check sees it as "0 days"

### Expected behavior

Check should be able to detect the "never expire" log group retention setting

### Actual Result with Screenshots or Logs

![image](https://user-images.githubusercontent.com/49124793/234209263-caf09398-6eee-40f3-af76-2249899021b8.png)
![image](https://user-images.githubusercontent.com/49124793/234209482-7fe1abab-20dd-4982-a05c-f3b254d8e393.png)


### How did you install Prowler?

From pip package (pip install prowler)

### Environment Resource

Workstation

### OS used

WSL2 on Windows 11

### Prowler version

Prowler 3.4.0 (it is the latest version, yay!)

### Pip version

pip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)

### Context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py`
Content:
```
1 from prowler.config.config import get_config_var
2 from prowler.lib.check.models import Check, Check_Report_AWS
3 from prowler.providers.aws.services.cloudwatch.logs_client import logs_client
4 
5 
6 class cloudwatch_log_group_retention_policy_specific_days_enabled(Check):
7     def execute(self):
8         findings = []
9         specific_retention_days = get_config_var("log_group_retention_days")
10         for log_group in logs_client.log_groups:
11             report = Check_Report_AWS(self.metadata())
12             report.region = log_group.region
13             report.resource_id = log_group.name
14             report.resource_arn = log_group.arn
15             report.resource_tags = log_group.tags
16             if log_group.retention_days < specific_retention_days:
17                 report.status = "FAIL"
18                 report.status_extended = f"Log Group {log_group.name} has less than {specific_retention_days} days retention period ({log_group.retention_days} days)."
19             else:
20                 report.status = "PASS"
21                 report.status_extended = f"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days."
22             findings.append(report)
23         return findings
24
```

Path: `prowler/providers/aws/services/cloudwatch/cloudwatch_service.py`
Content:
```
1 import threading
2 from datetime import datetime, timezone
3 from typing import Optional
4 
5 from pydantic import BaseModel
6 
7 from prowler.lib.logger import logger
8 from prowler.lib.scan_filters.scan_filters import is_resource_filtered
9 from prowler.providers.aws.aws_provider import generate_regional_clients
10 
11 
12 ################## CloudWatch
13 class CloudWatch:
14     def __init__(self, audit_info):
15         self.service = "cloudwatch"
16         self.session = audit_info.audit_session
17         self.audited_account = audit_info.audited_account
18         self.audit_resources = audit_info.audit_resources
19         self.region = list(
20             generate_regional_clients(
21                 self.service, audit_info, global_service=True
22             ).keys()
23         )[0]
24         self.regional_clients = generate_regional_clients(self.service, audit_info)
25         self.metric_alarms = []
26         self.__threading_call__(self.__describe_alarms__)
27         self.__list_tags_for_resource__()
28 
29     def __get_session__(self):
30         return self.session
31 
32     def __threading_call__(self, call):
33         threads = []
34         for regional_client in self.regional_clients.values():
35             threads.append(threading.Thread(target=call, args=(regional_client,)))
36         for t in threads:
37             t.start()
38         for t in threads:
39             t.join()
40 
41     def __describe_alarms__(self, regional_client):
42         logger.info("CloudWatch - Describing alarms...")
43         try:
44             describe_alarms_paginator = regional_client.get_paginator("describe_alarms")
45             for page in describe_alarms_paginator.paginate():
46                 for alarm in page["MetricAlarms"]:
47                     if not self.audit_resources or (
48                         is_resource_filtered(alarm["AlarmArn"], self.audit_resources)
49                     ):
50                         metric_name = None
51                         if "MetricName" in alarm:
52                             metric_name = alarm["MetricName"]
53                         namespace = None
54                         if "Namespace" in alarm:
55                             namespace = alarm["Namespace"]
56                         self.metric_alarms.append(
57                             MetricAlarm(
58                                 arn=alarm["AlarmArn"],
59                                 name=alarm["AlarmName"],
60                                 metric=metric_name,
61                                 name_space=namespace,
62                                 region=regional_client.region,
63                             )
64                         )
65         except Exception as error:
66             logger.error(
67                 f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
68             )
69 
70     def __list_tags_for_resource__(self):
71         logger.info("CloudWatch - List Tags...")
72         try:
73             for metric_alarm in self.metric_alarms:
74                 regional_client = self.regional_clients[metric_alarm.region]
75                 response = regional_client.list_tags_for_resource(
76                     ResourceARN=metric_alarm.arn
77                 )["Tags"]
78                 metric_alarm.tags = response
79         except Exception as error:
80             logger.error(
81                 f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
82             )
83 
84 
85 ################## CloudWatch Logs
86 class Logs:
87     def __init__(self, audit_info):
88         self.service = "logs"
89         self.session = audit_info.audit_session
90         self.audited_account = audit_info.audited_account
91         self.audit_resources = audit_info.audit_resources
92         self.regional_clients = generate_regional_clients(self.service, audit_info)
93         self.metric_filters = []
94         self.log_groups = []
95         self.__threading_call__(self.__describe_metric_filters__)
96         self.__threading_call__(self.__describe_log_groups__)
97         if (
98             "cloudwatch_log_group_no_secrets_in_logs"
99             in audit_info.audit_metadata.expected_checks
100         ):
101             self.events_per_log_group_threshold = (
102                 1000  # The threshold for number of events to return per log group.
103             )
104             self.__threading_call__(self.__get_log_events__)
105         self.__list_tags_for_resource__()
106 
107     def __get_session__(self):
108         return self.session
109 
110     def __threading_call__(self, call):
111         threads = []
112         for regional_client in self.regional_clients.values():
113             threads.append(threading.Thread(target=call, args=(regional_client,)))
114         for t in threads:
115             t.start()
116         for t in threads:
117             t.join()
118 
119     def __describe_metric_filters__(self, regional_client):
120         logger.info("CloudWatch Logs - Describing metric filters...")
121         try:
122             describe_metric_filters_paginator = regional_client.get_paginator(
123                 "describe_metric_filters"
124             )
125             for page in describe_metric_filters_paginator.paginate():
126                 for filter in page["metricFilters"]:
127                     if not self.audit_resources or (
128                         is_resource_filtered(filter["filterName"], self.audit_resources)
129                     ):
130                         self.metric_filters.append(
131                             MetricFilter(
132                                 name=filter["filterName"],
133                                 metric=filter["metricTransformations"][0]["metricName"],
134                                 pattern=filter.get("filterPattern", ""),
135                                 log_group=filter["logGroupName"],
136                                 region=regional_client.region,
137                             )
138                         )
139         except Exception as error:
140             logger.error(
141                 f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
142             )
143 
144     def __describe_log_groups__(self, regional_client):
145         logger.info("CloudWatch Logs - Describing log groups...")
146         try:
147             describe_log_groups_paginator = regional_client.get_paginator(
148                 "describe_log_groups"
149             )
150             for page in describe_log_groups_paginator.paginate():
151                 for log_group in page["logGroups"]:
152                     if not self.audit_resources or (
153                         is_resource_filtered(log_group["arn"], self.audit_resources)
154                     ):
155                         kms = None
156                         retention_days = 0
157                         if "kmsKeyId" in log_group:
158                             kms = log_group["kmsKeyId"]
159                         if "retentionInDays" in log_group:
160                             retention_days = log_group["retentionInDays"]
161                         self.log_groups.append(
162                             LogGroup(
163                                 arn=log_group["arn"],
164                                 name=log_group["logGroupName"],
165                                 retention_days=retention_days,
166                                 kms_id=kms,
167                                 region=regional_client.region,
168                             )
169                         )
170         except Exception as error:
171             logger.error(
172                 f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
173             )
174 
175     def __get_log_events__(self, regional_client):
176         regional_log_groups = [
177             log_group
178             for log_group in self.log_groups
179             if log_group.region == regional_client.region
180         ]
181         total_log_groups = len(regional_log_groups)
182         logger.info(
183             f"CloudWatch Logs - Retrieving log events for {total_log_groups} log groups in {regional_client.region}..."
184         )
185         try:
186             for count, log_group in enumerate(regional_log_groups, start=1):
187                 events = regional_client.filter_log_events(
188                     logGroupName=log_group.name,
189                     limit=self.events_per_log_group_threshold,
190                 )["events"]
191                 for event in events:
192                     if event["logStreamName"] not in log_group.log_streams:
193                         log_group.log_streams[event["logStreamName"]] = []
194                     log_group.log_streams[event["logStreamName"]].append(event)
195                 if count % 10 == 0:
196                     logger.info(
197                         f"CloudWatch Logs - Retrieved log events for {count}/{total_log_groups} log groups in {regional_client.region}..."
198                     )
199         except Exception as error:
200             logger.error(
201                 f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
202             )
203         logger.info(
204             f"CloudWatch Logs - Finished retrieving log events in {regional_client.region}..."
205         )
206 
207     def __list_tags_for_resource__(self):
208         logger.info("CloudWatch Logs - List Tags...")
209         try:
210             for log_group in self.log_groups:
211                 regional_client = self.regional_clients[log_group.region]
212                 response = regional_client.list_tags_log_group(
213                     logGroupName=log_group.name
214                 )["tags"]
215                 log_group.tags = [response]
216         except Exception as error:
217             logger.error(
218                 f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
219             )
220 
221 
222 class MetricAlarm(BaseModel):
223     arn: str
224     name: str
225     metric: Optional[str]
226     name_space: Optional[str]
227     region: str
228     tags: Optional[list] = []
229 
230 
231 class MetricFilter(BaseModel):
232     name: str
233     metric: str
234     pattern: str
235     log_group: str
236     region: str
237 
238 
239 class LogGroup(BaseModel):
240     arn: str
241     name: str
242     retention_days: int
243     kms_id: Optional[str]
244     region: str
245     log_streams: dict[
246         str, list[str]
247     ] = {}  # Log stream name as the key, array of events as the value
248     tags: Optional[list] = []
249 
250 
251 def convert_to_cloudwatch_timestamp_format(epoch_time):
252     date_time = datetime.fromtimestamp(
253         epoch_time / 1000, datetime.now(timezone.utc).astimezone().tzinfo
254     )
255     datetime_str = date_time.strftime(
256         "%Y-%m-%dT%H:%M:%S.!%f!%z"
257     )  # use exclamation marks as placeholders to convert datetime str to cloudwatch timestamp str
258     datetime_parts = datetime_str.split("!")
259     return (
260         datetime_parts[0]
261         + datetime_parts[1][:-3]
262         + datetime_parts[2][:-2]
263         + ":"
264         + datetime_parts[2][-2:]
265     )  # Removes the microseconds, and places a ':' character in the timezone offset
266
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py
--- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py
+++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py
@@ -13,11 +13,17 @@
             report.resource_id = log_group.name
             report.resource_arn = log_group.arn
             report.resource_tags = log_group.tags
-            if log_group.retention_days < specific_retention_days:
+            if (
+                log_group.never_expire is False
+                and log_group.retention_days < specific_retention_days
+            ):
                 report.status = "FAIL"
                 report.status_extended = f"Log Group {log_group.name} has less than {specific_retention_days} days retention period ({log_group.retention_days} days)."
             else:
                 report.status = "PASS"
-                report.status_extended = f"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days."
+                if log_group.never_expire is True:
+                    report.status_extended = f"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it never expires."
+                else:
+                    report.status_extended = f"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days."
             findings.append(report)
         return findings
diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py
--- a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py
+++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py
@@ -152,17 +152,18 @@
                     if not self.audit_resources or (
                         is_resource_filtered(log_group["arn"], self.audit_resources)
                     ):
-                        kms = None
-                        retention_days = 0
-                        if "kmsKeyId" in log_group:
-                            kms = log_group["kmsKeyId"]
-                        if "retentionInDays" in log_group:
-                            retention_days = log_group["retentionInDays"]
+                        never_expire = False
+                        kms = log_group.get("kmsKeyId")
+                        retention_days = log_group.get("retentionInDays")
+                        if not retention_days:
+                            never_expire = True
+                            retention_days = 9999
                         self.log_groups.append(
                             LogGroup(
                                 arn=log_group["arn"],
                                 name=log_group["logGroupName"],
                                 retention_days=retention_days,
+                                never_expire=never_expire,
                                 kms_id=kms,
                                 region=regional_client.region,
                             )
@@ -240,6 +241,7 @@
     arn: str
     name: str
     retention_days: int
+    never_expire: bool
     kms_id: Optional[str]
     region: str
     log_streams: dict[

{"golden_diff": "diff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py\n--- a/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py\n+++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py\n@@ -13,11 +13,17 @@\n             report.resource_id = log_group.name\n             report.resource_arn = log_group.arn\n             report.resource_tags = log_group.tags\n-            if log_group.retention_days < specific_retention_days:\n+            if (\n+                log_group.never_expire is False\n+                and log_group.retention_days < specific_retention_days\n+            ):\n                 report.status = \"FAIL\"\n                 report.status_extended = f\"Log Group {log_group.name} has less than {specific_retention_days} days retention period ({log_group.retention_days} days).\"\n             else:\n                 report.status = \"PASS\"\n-                report.status_extended = f\"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days.\"\n+                if log_group.never_expire is True:\n+                    report.status_extended = f\"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it never expires.\"\n+                else:\n+                    report.status_extended = f\"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days.\"\n             findings.append(report)\n         return findings\ndiff --git a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py\n--- a/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py\n+++ b/prowler/providers/aws/services/cloudwatch/cloudwatch_service.py\n@@ -152,17 +152,18 @@\n                     if not self.audit_resources or (\n                         is_resource_filtered(log_group[\"arn\"], self.audit_resources)\n                     ):\n-                        kms = None\n-                        retention_days = 0\n-                        if \"kmsKeyId\" in log_group:\n-                            kms = log_group[\"kmsKeyId\"]\n-                        if \"retentionInDays\" in log_group:\n-                            retention_days = log_group[\"retentionInDays\"]\n+                        never_expire = False\n+                        kms = log_group.get(\"kmsKeyId\")\n+                        retention_days = log_group.get(\"retentionInDays\")\n+                        if not retention_days:\n+                            never_expire = True\n+                            retention_days = 9999\n                         self.log_groups.append(\n                             LogGroup(\n                                 arn=log_group[\"arn\"],\n                                 name=log_group[\"logGroupName\"],\n                                 retention_days=retention_days,\n+                                never_expire=never_expire,\n                                 kms_id=kms,\n                                 region=regional_client.region,\n                             )\n@@ -240,6 +241,7 @@\n     arn: str\n     name: str\n     retention_days: int\n+    never_expire: bool\n     kms_id: Optional[str]\n     region: str\n     log_streams: dict[\n", "issue": "[Bug]: cloudwatch_log_group_retention_policy_specific_days_enabled not measuring \"never expire\"\n### Steps to Reproduce\n\nWhen having log groups in cloudwatch that are set to \"never expire\", the mentioned check sees it as \"0 days\"\n\n### Expected behavior\n\nCheck should be able to detect the \"never expire\" log group retention setting\n\n### Actual Result with Screenshots or Logs\n\n![image](https://user-images.githubusercontent.com/49124793/234209263-caf09398-6eee-40f3-af76-2249899021b8.png)\r\n![image](https://user-images.githubusercontent.com/49124793/234209482-7fe1abab-20dd-4982-a05c-f3b254d8e393.png)\r\n\n\n### How did you install Prowler?\n\nFrom pip package (pip install prowler)\n\n### Environment Resource\n\nWorkstation\n\n### OS used\n\nWSL2 on Windows 11\n\n### Prowler version\n\nProwler 3.4.0 (it is the latest version, yay!)\n\n### Pip version\n\npip 22.0.2 from /usr/lib/python3/dist-packages/pip (python 3.10)\n\n### Context\n\n_No response_\n", "before_files": [{"content": "from prowler.config.config import get_config_var\nfrom prowler.lib.check.models import Check, Check_Report_AWS\nfrom prowler.providers.aws.services.cloudwatch.logs_client import logs_client\n\n\nclass cloudwatch_log_group_retention_policy_specific_days_enabled(Check):\n    def execute(self):\n        findings = []\n        specific_retention_days = get_config_var(\"log_group_retention_days\")\n        for log_group in logs_client.log_groups:\n            report = Check_Report_AWS(self.metadata())\n            report.region = log_group.region\n            report.resource_id = log_group.name\n            report.resource_arn = log_group.arn\n            report.resource_tags = log_group.tags\n            if log_group.retention_days < specific_retention_days:\n                report.status = \"FAIL\"\n                report.status_extended = f\"Log Group {log_group.name} has less than {specific_retention_days} days retention period ({log_group.retention_days} days).\"\n            else:\n                report.status = \"PASS\"\n                report.status_extended = f\"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days.\"\n            findings.append(report)\n        return findings\n", "path": "prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py"}, {"content": "import threading\nfrom datetime import datetime, timezone\nfrom typing import Optional\n\nfrom pydantic import BaseModel\n\nfrom prowler.lib.logger import logger\nfrom prowler.lib.scan_filters.scan_filters import is_resource_filtered\nfrom prowler.providers.aws.aws_provider import generate_regional_clients\n\n\n################## CloudWatch\nclass CloudWatch:\n    def __init__(self, audit_info):\n        self.service = \"cloudwatch\"\n        self.session = audit_info.audit_session\n        self.audited_account = audit_info.audited_account\n        self.audit_resources = audit_info.audit_resources\n        self.region = list(\n            generate_regional_clients(\n                self.service, audit_info, global_service=True\n            ).keys()\n        )[0]\n        self.regional_clients = generate_regional_clients(self.service, audit_info)\n        self.metric_alarms = []\n        self.__threading_call__(self.__describe_alarms__)\n        self.__list_tags_for_resource__()\n\n    def __get_session__(self):\n        return self.session\n\n    def __threading_call__(self, call):\n        threads = []\n        for regional_client in self.regional_clients.values():\n            threads.append(threading.Thread(target=call, args=(regional_client,)))\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n    def __describe_alarms__(self, regional_client):\n        logger.info(\"CloudWatch - Describing alarms...\")\n        try:\n            describe_alarms_paginator = regional_client.get_paginator(\"describe_alarms\")\n            for page in describe_alarms_paginator.paginate():\n                for alarm in page[\"MetricAlarms\"]:\n                    if not self.audit_resources or (\n                        is_resource_filtered(alarm[\"AlarmArn\"], self.audit_resources)\n                    ):\n                        metric_name = None\n                        if \"MetricName\" in alarm:\n                            metric_name = alarm[\"MetricName\"]\n                        namespace = None\n                        if \"Namespace\" in alarm:\n                            namespace = alarm[\"Namespace\"]\n                        self.metric_alarms.append(\n                            MetricAlarm(\n                                arn=alarm[\"AlarmArn\"],\n                                name=alarm[\"AlarmName\"],\n                                metric=metric_name,\n                                name_space=namespace,\n                                region=regional_client.region,\n                            )\n                        )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n    def __list_tags_for_resource__(self):\n        logger.info(\"CloudWatch - List Tags...\")\n        try:\n            for metric_alarm in self.metric_alarms:\n                regional_client = self.regional_clients[metric_alarm.region]\n                response = regional_client.list_tags_for_resource(\n                    ResourceARN=metric_alarm.arn\n                )[\"Tags\"]\n                metric_alarm.tags = response\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n\n################## CloudWatch Logs\nclass Logs:\n    def __init__(self, audit_info):\n        self.service = \"logs\"\n        self.session = audit_info.audit_session\n        self.audited_account = audit_info.audited_account\n        self.audit_resources = audit_info.audit_resources\n        self.regional_clients = generate_regional_clients(self.service, audit_info)\n        self.metric_filters = []\n        self.log_groups = []\n        self.__threading_call__(self.__describe_metric_filters__)\n        self.__threading_call__(self.__describe_log_groups__)\n        if (\n            \"cloudwatch_log_group_no_secrets_in_logs\"\n            in audit_info.audit_metadata.expected_checks\n        ):\n            self.events_per_log_group_threshold = (\n                1000  # The threshold for number of events to return per log group.\n            )\n            self.__threading_call__(self.__get_log_events__)\n        self.__list_tags_for_resource__()\n\n    def __get_session__(self):\n        return self.session\n\n    def __threading_call__(self, call):\n        threads = []\n        for regional_client in self.regional_clients.values():\n            threads.append(threading.Thread(target=call, args=(regional_client,)))\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n    def __describe_metric_filters__(self, regional_client):\n        logger.info(\"CloudWatch Logs - Describing metric filters...\")\n        try:\n            describe_metric_filters_paginator = regional_client.get_paginator(\n                \"describe_metric_filters\"\n            )\n            for page in describe_metric_filters_paginator.paginate():\n                for filter in page[\"metricFilters\"]:\n                    if not self.audit_resources or (\n                        is_resource_filtered(filter[\"filterName\"], self.audit_resources)\n                    ):\n                        self.metric_filters.append(\n                            MetricFilter(\n                                name=filter[\"filterName\"],\n                                metric=filter[\"metricTransformations\"][0][\"metricName\"],\n                                pattern=filter.get(\"filterPattern\", \"\"),\n                                log_group=filter[\"logGroupName\"],\n                                region=regional_client.region,\n                            )\n                        )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n    def __describe_log_groups__(self, regional_client):\n        logger.info(\"CloudWatch Logs - Describing log groups...\")\n        try:\n            describe_log_groups_paginator = regional_client.get_paginator(\n                \"describe_log_groups\"\n            )\n            for page in describe_log_groups_paginator.paginate():\n                for log_group in page[\"logGroups\"]:\n                    if not self.audit_resources or (\n                        is_resource_filtered(log_group[\"arn\"], self.audit_resources)\n                    ):\n                        kms = None\n                        retention_days = 0\n                        if \"kmsKeyId\" in log_group:\n                            kms = log_group[\"kmsKeyId\"]\n                        if \"retentionInDays\" in log_group:\n                            retention_days = log_group[\"retentionInDays\"]\n                        self.log_groups.append(\n                            LogGroup(\n                                arn=log_group[\"arn\"],\n                                name=log_group[\"logGroupName\"],\n                                retention_days=retention_days,\n                                kms_id=kms,\n                                region=regional_client.region,\n                            )\n                        )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n    def __get_log_events__(self, regional_client):\n        regional_log_groups = [\n            log_group\n            for log_group in self.log_groups\n            if log_group.region == regional_client.region\n        ]\n        total_log_groups = len(regional_log_groups)\n        logger.info(\n            f\"CloudWatch Logs - Retrieving log events for {total_log_groups} log groups in {regional_client.region}...\"\n        )\n        try:\n            for count, log_group in enumerate(regional_log_groups, start=1):\n                events = regional_client.filter_log_events(\n                    logGroupName=log_group.name,\n                    limit=self.events_per_log_group_threshold,\n                )[\"events\"]\n                for event in events:\n                    if event[\"logStreamName\"] not in log_group.log_streams:\n                        log_group.log_streams[event[\"logStreamName\"]] = []\n                    log_group.log_streams[event[\"logStreamName\"]].append(event)\n                if count % 10 == 0:\n                    logger.info(\n                        f\"CloudWatch Logs - Retrieved log events for {count}/{total_log_groups} log groups in {regional_client.region}...\"\n                    )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n        logger.info(\n            f\"CloudWatch Logs - Finished retrieving log events in {regional_client.region}...\"\n        )\n\n    def __list_tags_for_resource__(self):\n        logger.info(\"CloudWatch Logs - List Tags...\")\n        try:\n            for log_group in self.log_groups:\n                regional_client = self.regional_clients[log_group.region]\n                response = regional_client.list_tags_log_group(\n                    logGroupName=log_group.name\n                )[\"tags\"]\n                log_group.tags = [response]\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n\nclass MetricAlarm(BaseModel):\n    arn: str\n    name: str\n    metric: Optional[str]\n    name_space: Optional[str]\n    region: str\n    tags: Optional[list] = []\n\n\nclass MetricFilter(BaseModel):\n    name: str\n    metric: str\n    pattern: str\n    log_group: str\n    region: str\n\n\nclass LogGroup(BaseModel):\n    arn: str\n    name: str\n    retention_days: int\n    kms_id: Optional[str]\n    region: str\n    log_streams: dict[\n        str, list[str]\n    ] = {}  # Log stream name as the key, array of events as the value\n    tags: Optional[list] = []\n\n\ndef convert_to_cloudwatch_timestamp_format(epoch_time):\n    date_time = datetime.fromtimestamp(\n        epoch_time / 1000, datetime.now(timezone.utc).astimezone().tzinfo\n    )\n    datetime_str = date_time.strftime(\n        \"%Y-%m-%dT%H:%M:%S.!%f!%z\"\n    )  # use exclamation marks as placeholders to convert datetime str to cloudwatch timestamp str\n    datetime_parts = datetime_str.split(\"!\")\n    return (\n        datetime_parts[0]\n        + datetime_parts[1][:-3]\n        + datetime_parts[2][:-2]\n        + \":\"\n        + datetime_parts[2][-2:]\n    )  # Removes the microseconds, and places a ':' character in the timezone offset\n", "path": "prowler/providers/aws/services/cloudwatch/cloudwatch_service.py"}], "after_files": [{"content": "from prowler.config.config import get_config_var\nfrom prowler.lib.check.models import Check, Check_Report_AWS\nfrom prowler.providers.aws.services.cloudwatch.logs_client import logs_client\n\n\nclass cloudwatch_log_group_retention_policy_specific_days_enabled(Check):\n    def execute(self):\n        findings = []\n        specific_retention_days = get_config_var(\"log_group_retention_days\")\n        for log_group in logs_client.log_groups:\n            report = Check_Report_AWS(self.metadata())\n            report.region = log_group.region\n            report.resource_id = log_group.name\n            report.resource_arn = log_group.arn\n            report.resource_tags = log_group.tags\n            if (\n                log_group.never_expire is False\n                and log_group.retention_days < specific_retention_days\n            ):\n                report.status = \"FAIL\"\n                report.status_extended = f\"Log Group {log_group.name} has less than {specific_retention_days} days retention period ({log_group.retention_days} days).\"\n            else:\n                report.status = \"PASS\"\n                if log_group.never_expire is True:\n                    report.status_extended = f\"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it never expires.\"\n                else:\n                    report.status_extended = f\"Log Group {log_group.name} comply with {specific_retention_days} days retention period since it has {log_group.retention_days} days.\"\n            findings.append(report)\n        return findings\n", "path": "prowler/providers/aws/services/cloudwatch/cloudwatch_log_group_retention_policy_specific_days_enabled/cloudwatch_log_group_retention_policy_specific_days_enabled.py"}, {"content": "import threading\nfrom datetime import datetime, timezone\nfrom typing import Optional\n\nfrom pydantic import BaseModel\n\nfrom prowler.lib.logger import logger\nfrom prowler.lib.scan_filters.scan_filters import is_resource_filtered\nfrom prowler.providers.aws.aws_provider import generate_regional_clients\n\n\n################## CloudWatch\nclass CloudWatch:\n    def __init__(self, audit_info):\n        self.service = \"cloudwatch\"\n        self.session = audit_info.audit_session\n        self.audited_account = audit_info.audited_account\n        self.audit_resources = audit_info.audit_resources\n        self.region = list(\n            generate_regional_clients(\n                self.service, audit_info, global_service=True\n            ).keys()\n        )[0]\n        self.regional_clients = generate_regional_clients(self.service, audit_info)\n        self.metric_alarms = []\n        self.__threading_call__(self.__describe_alarms__)\n        self.__list_tags_for_resource__()\n\n    def __get_session__(self):\n        return self.session\n\n    def __threading_call__(self, call):\n        threads = []\n        for regional_client in self.regional_clients.values():\n            threads.append(threading.Thread(target=call, args=(regional_client,)))\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n    def __describe_alarms__(self, regional_client):\n        logger.info(\"CloudWatch - Describing alarms...\")\n        try:\n            describe_alarms_paginator = regional_client.get_paginator(\"describe_alarms\")\n            for page in describe_alarms_paginator.paginate():\n                for alarm in page[\"MetricAlarms\"]:\n                    if not self.audit_resources or (\n                        is_resource_filtered(alarm[\"AlarmArn\"], self.audit_resources)\n                    ):\n                        metric_name = None\n                        if \"MetricName\" in alarm:\n                            metric_name = alarm[\"MetricName\"]\n                        namespace = None\n                        if \"Namespace\" in alarm:\n                            namespace = alarm[\"Namespace\"]\n                        self.metric_alarms.append(\n                            MetricAlarm(\n                                arn=alarm[\"AlarmArn\"],\n                                name=alarm[\"AlarmName\"],\n                                metric=metric_name,\n                                name_space=namespace,\n                                region=regional_client.region,\n                            )\n                        )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n    def __list_tags_for_resource__(self):\n        logger.info(\"CloudWatch - List Tags...\")\n        try:\n            for metric_alarm in self.metric_alarms:\n                regional_client = self.regional_clients[metric_alarm.region]\n                response = regional_client.list_tags_for_resource(\n                    ResourceARN=metric_alarm.arn\n                )[\"Tags\"]\n                metric_alarm.tags = response\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n\n################## CloudWatch Logs\nclass Logs:\n    def __init__(self, audit_info):\n        self.service = \"logs\"\n        self.session = audit_info.audit_session\n        self.audited_account = audit_info.audited_account\n        self.audit_resources = audit_info.audit_resources\n        self.regional_clients = generate_regional_clients(self.service, audit_info)\n        self.metric_filters = []\n        self.log_groups = []\n        self.__threading_call__(self.__describe_metric_filters__)\n        self.__threading_call__(self.__describe_log_groups__)\n        if (\n            \"cloudwatch_log_group_no_secrets_in_logs\"\n            in audit_info.audit_metadata.expected_checks\n        ):\n            self.events_per_log_group_threshold = (\n                1000  # The threshold for number of events to return per log group.\n            )\n            self.__threading_call__(self.__get_log_events__)\n        self.__list_tags_for_resource__()\n\n    def __get_session__(self):\n        return self.session\n\n    def __threading_call__(self, call):\n        threads = []\n        for regional_client in self.regional_clients.values():\n            threads.append(threading.Thread(target=call, args=(regional_client,)))\n        for t in threads:\n            t.start()\n        for t in threads:\n            t.join()\n\n    def __describe_metric_filters__(self, regional_client):\n        logger.info(\"CloudWatch Logs - Describing metric filters...\")\n        try:\n            describe_metric_filters_paginator = regional_client.get_paginator(\n                \"describe_metric_filters\"\n            )\n            for page in describe_metric_filters_paginator.paginate():\n                for filter in page[\"metricFilters\"]:\n                    if not self.audit_resources or (\n                        is_resource_filtered(filter[\"filterName\"], self.audit_resources)\n                    ):\n                        self.metric_filters.append(\n                            MetricFilter(\n                                name=filter[\"filterName\"],\n                                metric=filter[\"metricTransformations\"][0][\"metricName\"],\n                                pattern=filter.get(\"filterPattern\", \"\"),\n                                log_group=filter[\"logGroupName\"],\n                                region=regional_client.region,\n                            )\n                        )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n    def __describe_log_groups__(self, regional_client):\n        logger.info(\"CloudWatch Logs - Describing log groups...\")\n        try:\n            describe_log_groups_paginator = regional_client.get_paginator(\n                \"describe_log_groups\"\n            )\n            for page in describe_log_groups_paginator.paginate():\n                for log_group in page[\"logGroups\"]:\n                    if not self.audit_resources or (\n                        is_resource_filtered(log_group[\"arn\"], self.audit_resources)\n                    ):\n                        never_expire = False\n                        kms = log_group.get(\"kmsKeyId\")\n                        retention_days = log_group.get(\"retentionInDays\")\n                        if not retention_days:\n                            never_expire = True\n                            retention_days = 9999\n                        self.log_groups.append(\n                            LogGroup(\n                                arn=log_group[\"arn\"],\n                                name=log_group[\"logGroupName\"],\n                                retention_days=retention_days,\n                                never_expire=never_expire,\n                                kms_id=kms,\n                                region=regional_client.region,\n                            )\n                        )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n    def __get_log_events__(self, regional_client):\n        regional_log_groups = [\n            log_group\n            for log_group in self.log_groups\n            if log_group.region == regional_client.region\n        ]\n        total_log_groups = len(regional_log_groups)\n        logger.info(\n            f\"CloudWatch Logs - Retrieving log events for {total_log_groups} log groups in {regional_client.region}...\"\n        )\n        try:\n            for count, log_group in enumerate(regional_log_groups, start=1):\n                events = regional_client.filter_log_events(\n                    logGroupName=log_group.name,\n                    limit=self.events_per_log_group_threshold,\n                )[\"events\"]\n                for event in events:\n                    if event[\"logStreamName\"] not in log_group.log_streams:\n                        log_group.log_streams[event[\"logStreamName\"]] = []\n                    log_group.log_streams[event[\"logStreamName\"]].append(event)\n                if count % 10 == 0:\n                    logger.info(\n                        f\"CloudWatch Logs - Retrieved log events for {count}/{total_log_groups} log groups in {regional_client.region}...\"\n                    )\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n        logger.info(\n            f\"CloudWatch Logs - Finished retrieving log events in {regional_client.region}...\"\n        )\n\n    def __list_tags_for_resource__(self):\n        logger.info(\"CloudWatch Logs - List Tags...\")\n        try:\n            for log_group in self.log_groups:\n                regional_client = self.regional_clients[log_group.region]\n                response = regional_client.list_tags_log_group(\n                    logGroupName=log_group.name\n                )[\"tags\"]\n                log_group.tags = [response]\n        except Exception as error:\n            logger.error(\n                f\"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}\"\n            )\n\n\nclass MetricAlarm(BaseModel):\n    arn: str\n    name: str\n    metric: Optional[str]\n    name_space: Optional[str]\n    region: str\n    tags: Optional[list] = []\n\n\nclass MetricFilter(BaseModel):\n    name: str\n    metric: str\n    pattern: str\n    log_group: str\n    region: str\n\n\nclass LogGroup(BaseModel):\n    arn: str\n    name: str\n    retention_days: int\n    never_expire: bool\n    kms_id: Optional[str]\n    region: str\n    log_streams: dict[\n        str, list[str]\n    ] = {}  # Log stream name as the key, array of events as the value\n    tags: Optional[list] = []\n\n\ndef convert_to_cloudwatch_timestamp_format(epoch_time):\n    date_time = datetime.fromtimestamp(\n        epoch_time / 1000, datetime.now(timezone.utc).astimezone().tzinfo\n    )\n    datetime_str = date_time.strftime(\n        \"%Y-%m-%dT%H:%M:%S.!%f!%z\"\n    )  # use exclamation marks as placeholders to convert datetime str to cloudwatch timestamp str\n    datetime_parts = datetime_str.split(\"!\")\n    return (\n        datetime_parts[0]\n        + datetime_parts[1][:-3]\n        + datetime_parts[2][:-2]\n        + \":\"\n        + datetime_parts[2][-2:]\n    )  # Removes the microseconds, and places a ':' character in the timezone offset\n", "path": "prowler/providers/aws/services/cloudwatch/cloudwatch_service.py"}]}