mirror of
https://github.com/prowler-cloud/prowler.git
synced 2026-04-14 16:50:04 +00:00
Compare commits
1 Commits
fix/ui-pro
...
dev-memory
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
000b5ec3d5 |
@@ -3,5 +3,7 @@ import sys
|
|||||||
|
|
||||||
from prowler.__main__ import prowler
|
from prowler.__main__ import prowler
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
||||||
sys.exit(prowler())
|
sys.exit(prowler())
|
||||||
|
|||||||
@@ -5,6 +5,15 @@ import sys
|
|||||||
from os import environ
|
from os import environ
|
||||||
|
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
|
from prowler.providers.aws.services.ec2.ec2_service import PaginatedDict, PaginatedList
|
||||||
|
import pdb
|
||||||
|
import psutil
|
||||||
|
import os
|
||||||
|
|
||||||
|
def check_memory_usage():
|
||||||
|
process = psutil.Process(os.getpid())
|
||||||
|
memory_info = process.memory_info()
|
||||||
|
return memory_info.rss # Resident Set Size: memory in bytes
|
||||||
|
|
||||||
from prowler.config.config import (
|
from prowler.config.config import (
|
||||||
csv_file_suffix,
|
csv_file_suffix,
|
||||||
@@ -70,9 +79,18 @@ from prowler.providers.aws.lib.s3.s3 import S3
|
|||||||
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub
|
from prowler.providers.aws.lib.security_hub.security_hub import SecurityHub
|
||||||
from prowler.providers.common.provider import Provider
|
from prowler.providers.common.provider import Provider
|
||||||
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
|
from prowler.providers.common.quick_inventory import run_provider_quick_inventory
|
||||||
|
from memory_profiler import profile
|
||||||
|
|
||||||
|
from pympler import asizeof
|
||||||
|
from pympler import tracker
|
||||||
|
from pympler import muppy
|
||||||
|
from pympler import summary
|
||||||
|
import objgraph
|
||||||
|
|
||||||
|
from memory_profiler import profile
|
||||||
|
|
||||||
def prowler():
|
def prowler():
|
||||||
|
#tr = tracker.SummaryTracker()
|
||||||
# Parse Arguments
|
# Parse Arguments
|
||||||
# Refactor(CLI)
|
# Refactor(CLI)
|
||||||
parser = ProwlerArgumentParser()
|
parser = ProwlerArgumentParser()
|
||||||
@@ -178,7 +196,9 @@ def prowler():
|
|||||||
categories,
|
categories,
|
||||||
provider,
|
provider,
|
||||||
)
|
)
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at checks_to_execute: {memory_usage / (1024 * 1024)} MB")
|
||||||
# if --list-checks-json, dump a json file and exit
|
# if --list-checks-json, dump a json file and exit
|
||||||
if args.list_checks_json:
|
if args.list_checks_json:
|
||||||
print(list_checks_json(provider, sorted(checks_to_execute)))
|
print(list_checks_json(provider, sorted(checks_to_execute)))
|
||||||
@@ -193,6 +213,10 @@ def prowler():
|
|||||||
Provider.set_global_provider(args)
|
Provider.set_global_provider(args)
|
||||||
global_provider = Provider.get_global_provider()
|
global_provider = Provider.get_global_provider()
|
||||||
|
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at global_provider = Provider. __main__.py:217 : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
|
||||||
# Print Provider Credentials
|
# Print Provider Credentials
|
||||||
if not args.only_logs:
|
if not args.only_logs:
|
||||||
global_provider.print_credentials()
|
global_provider.print_credentials()
|
||||||
@@ -242,7 +266,11 @@ def prowler():
|
|||||||
sys.exit()
|
sys.exit()
|
||||||
|
|
||||||
# Execute checks
|
# Execute checks
|
||||||
findings = []
|
paginated = 0
|
||||||
|
if paginated:
|
||||||
|
findings = PaginatedList()
|
||||||
|
else:
|
||||||
|
findings = []
|
||||||
|
|
||||||
if len(checks_to_execute):
|
if len(checks_to_execute):
|
||||||
findings = execute_checks(
|
findings = execute_checks(
|
||||||
@@ -255,7 +283,9 @@ def prowler():
|
|||||||
logger.error(
|
logger.error(
|
||||||
"There are no checks to execute. Please, check your input arguments"
|
"There are no checks to execute. Please, check your input arguments"
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at execute_checks __main__.py:284 {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
# Prowler Fixer
|
# Prowler Fixer
|
||||||
if global_provider.output_options.fixer:
|
if global_provider.output_options.fixer:
|
||||||
print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}")
|
print(f"{Style.BRIGHT}\nRunning Prowler Fixer, please wait...{Style.RESET_ALL}")
|
||||||
@@ -307,6 +337,11 @@ def prowler():
|
|||||||
]
|
]
|
||||||
|
|
||||||
generated_outputs = {"regular": [], "compliance": []}
|
generated_outputs = {"regular": [], "compliance": []}
|
||||||
|
logger.debug("Output generated")
|
||||||
|
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at findings_output: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
|
||||||
if args.output_formats:
|
if args.output_formats:
|
||||||
for mode in args.output_formats:
|
for mode in args.output_formats:
|
||||||
@@ -323,7 +358,9 @@ def prowler():
|
|||||||
generated_outputs["regular"].append(csv_output)
|
generated_outputs["regular"].append(csv_output)
|
||||||
# Write CSV Finding Object to file
|
# Write CSV Finding Object to file
|
||||||
csv_output.batch_write_data_to_file()
|
csv_output.batch_write_data_to_file()
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at csv_output_batch_write_data: {memory_usage / (1024 * 1024)} MB")
|
||||||
if mode == "json-asff":
|
if mode == "json-asff":
|
||||||
asff_output = ASFF(
|
asff_output = ASFF(
|
||||||
findings=finding_outputs,
|
findings=finding_outputs,
|
||||||
@@ -352,7 +389,9 @@ def prowler():
|
|||||||
html_output.batch_write_data_to_file(
|
html_output.batch_write_data_to_file(
|
||||||
provider=global_provider, stats=stats
|
provider=global_provider, stats=stats
|
||||||
)
|
)
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at html_output_batch_write: {memory_usage / (1024 * 1024)} MB")
|
||||||
# Compliance Frameworks
|
# Compliance Frameworks
|
||||||
input_compliance_frameworks = set(
|
input_compliance_frameworks = set(
|
||||||
global_provider.output_options.output_modes
|
global_provider.output_options.output_modes
|
||||||
@@ -647,12 +686,22 @@ def prowler():
|
|||||||
print(
|
print(
|
||||||
f"\nDetailed compliance results are in {Fore.YELLOW}{global_provider.output_options.output_directory}/compliance/{Style.RESET_ALL}\n"
|
f"\nDetailed compliance results are in {Fore.YELLOW}{global_provider.output_options.output_directory}/compliance/{Style.RESET_ALL}\n"
|
||||||
)
|
)
|
||||||
|
# Print the memory usage of the largest objects
|
||||||
|
#all_objects = muppy.get_objects()
|
||||||
|
#sum1 = summary.summarize(all_objects)
|
||||||
|
#summary.print_(sum1)
|
||||||
|
#objgraph.show_most_common_types(limit=20)
|
||||||
|
#objgraph.show_growth()
|
||||||
|
|
||||||
|
|
||||||
# If custom checks were passed, remove the modules
|
# If custom checks were passed, remove the modules
|
||||||
if checks_folder:
|
if checks_folder:
|
||||||
remove_custom_checks_module(checks_folder, provider)
|
remove_custom_checks_module(checks_folder, provider)
|
||||||
|
|
||||||
# If there are failed findings exit code 3, except if -z is input
|
# If there are failed findings exit code 3, except if -z is input
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at ending: {memory_usage / (1024 * 1024)} MB")
|
||||||
if (
|
if (
|
||||||
not args.ignore_exit_code_3
|
not args.ignore_exit_code_3
|
||||||
and stats["total_fail"] > 0
|
and stats["total_fail"] > 0
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import traceback
|
|||||||
from pkgutil import walk_packages
|
from pkgutil import walk_packages
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
from memory_profiler import profile
|
||||||
|
|
||||||
from alive_progress import alive_bar
|
from alive_progress import alive_bar
|
||||||
from colorama import Fore, Style
|
from colorama import Fore, Style
|
||||||
@@ -23,6 +24,14 @@ from prowler.lib.outputs.outputs import report
|
|||||||
from prowler.lib.utils.utils import open_file, parse_json_file, print_boxes
|
from prowler.lib.utils.utils import open_file, parse_json_file, print_boxes
|
||||||
from prowler.providers.common.models import Audit_Metadata
|
from prowler.providers.common.models import Audit_Metadata
|
||||||
|
|
||||||
|
import pdb
|
||||||
|
import psutil
|
||||||
|
import os
|
||||||
|
|
||||||
|
def check_memory_usage():
|
||||||
|
process = psutil.Process(os.getpid())
|
||||||
|
memory_info = process.memory_info()
|
||||||
|
return memory_info.rss # Resident Set Size: memory in bytes
|
||||||
|
|
||||||
# Load all checks metadata
|
# Load all checks metadata
|
||||||
def bulk_load_checks_metadata(provider: str) -> dict:
|
def bulk_load_checks_metadata(provider: str) -> dict:
|
||||||
@@ -433,10 +442,13 @@ def list_modules(provider: str, service: str):
|
|||||||
|
|
||||||
|
|
||||||
# Import an input check using its path
|
# Import an input check using its path
|
||||||
def import_check(check_path: str) -> ModuleType:
|
|
||||||
lib = importlib.import_module(f"{check_path}")
|
|
||||||
return lib
|
|
||||||
|
|
||||||
|
def import_check(check_path: str) -> ModuleType:
|
||||||
|
|
||||||
|
print(f"{check_memory_usage() / (1024 * 1024)} MB : Memory usage before import {check_path}")
|
||||||
|
lib = importlib.import_module(f"{check_path}")
|
||||||
|
print(f"{check_memory_usage() / (1024 * 1024)} MB : Memory usage after import {check_path}")
|
||||||
|
return lib
|
||||||
|
|
||||||
def run_check(check: Check, verbose: bool = False, only_logs: bool = False) -> list:
|
def run_check(check: Check, verbose: bool = False, only_logs: bool = False) -> list:
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ class Check(ABC, Check_Metadata_Model):
|
|||||||
)
|
)
|
||||||
# Store it to validate them with Pydantic
|
# Store it to validate them with Pydantic
|
||||||
data = Check_Metadata_Model.parse_file(metadata_file).dict()
|
data = Check_Metadata_Model.parse_file(metadata_file).dict()
|
||||||
|
# data = {}
|
||||||
# Calls parents init function
|
# Calls parents init function
|
||||||
super().__init__(**data)
|
super().__init__(**data)
|
||||||
# TODO: verify that the CheckID is the same as the filename and classname
|
# TODO: verify that the CheckID is the same as the filename and classname
|
||||||
|
|||||||
@@ -86,6 +86,7 @@ class Finding(BaseModel):
|
|||||||
notes: str
|
notes: str
|
||||||
prowler_version: str = prowler_version
|
prowler_version: str = prowler_version
|
||||||
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def generate_output(
|
def generate_output(
|
||||||
cls, provider: Provider, check_output: Check_Report
|
cls, provider: Provider, check_output: Check_Report
|
||||||
@@ -186,7 +187,7 @@ class Finding(BaseModel):
|
|||||||
f"prowler-{provider.type}-{check_output.check_metadata.CheckID}-{output_data['account_uid']}-"
|
f"prowler-{provider.type}-{check_output.check_metadata.CheckID}-{output_data['account_uid']}-"
|
||||||
f"{output_data['region']}-{output_data['resource_name']}"
|
f"{output_data['region']}-{output_data['resource_name']}"
|
||||||
)
|
)
|
||||||
|
logger.debug("Generating finding: = %s", output_data["finding_uid"])
|
||||||
return cls(**output_data)
|
return cls(**output_data)
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import sys
|
|||||||
from argparse import Namespace
|
from argparse import Namespace
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
|
||||||
from boto3 import client, session
|
from boto3 import client, session
|
||||||
from boto3.session import Session
|
from boto3.session import Session
|
||||||
from botocore.config import Config
|
from botocore.config import Config
|
||||||
@@ -49,7 +50,6 @@ from prowler.providers.aws.models import (
|
|||||||
from prowler.providers.common.models import Audit_Metadata
|
from prowler.providers.common.models import Audit_Metadata
|
||||||
from prowler.providers.common.provider import Provider
|
from prowler.providers.common.provider import Provider
|
||||||
|
|
||||||
|
|
||||||
class AwsProvider(Provider):
|
class AwsProvider(Provider):
|
||||||
_type: str = "aws"
|
_type: str = "aws"
|
||||||
_identity: AWSIdentityInfo
|
_identity: AWSIdentityInfo
|
||||||
@@ -62,7 +62,7 @@ class AwsProvider(Provider):
|
|||||||
_output_options: AWSOutputOptions
|
_output_options: AWSOutputOptions
|
||||||
# TODO: this is not optional, enforce for all providers
|
# TODO: this is not optional, enforce for all providers
|
||||||
audit_metadata: Audit_Metadata
|
audit_metadata: Audit_Metadata
|
||||||
|
|
||||||
def __init__(self, arguments: Namespace):
|
def __init__(self, arguments: Namespace):
|
||||||
logger.info("Initializing AWS provider ...")
|
logger.info("Initializing AWS provider ...")
|
||||||
######## Parse Arguments
|
######## Parse Arguments
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||||
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
|
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
|
||||||
|
from prowler.providers.aws.services.ec2.ec2_service import PaginatedDict, PaginatedList
|
||||||
|
|
||||||
class ec2_instance_internet_facing_with_instance_profile(Check):
|
class ec2_instance_internet_facing_with_instance_profile(Check):
|
||||||
def execute(self):
|
def execute(self):
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
from pympler import asizeof
|
||||||
|
|
||||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||||
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
|
from prowler.providers.aws.services.ec2.ec2_client import ec2_client
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
|
||||||
class ec2_instance_older_than_specific_days(Check):
|
class ec2_instance_older_than_specific_days(Check):
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@@ -12,6 +14,10 @@ class ec2_instance_older_than_specific_days(Check):
|
|||||||
max_ec2_instance_age_in_days = ec2_client.audit_config.get(
|
max_ec2_instance_age_in_days = ec2_client.audit_config.get(
|
||||||
"max_ec2_instance_age_in_days", 180
|
"max_ec2_instance_age_in_days", 180
|
||||||
)
|
)
|
||||||
|
size_bytes = asizeof.asizeof(ec2_client.instances)
|
||||||
|
size_mb = size_bytes / (1024 * 1024)
|
||||||
|
print("Size of dictionary:", size_mb, "MB")
|
||||||
|
|
||||||
for instance in ec2_client.instances:
|
for instance in ec2_client.instances:
|
||||||
report = Check_Report_AWS(self.metadata())
|
report = Check_Report_AWS(self.metadata())
|
||||||
report.region = instance.region
|
report.region = instance.region
|
||||||
@@ -31,4 +37,6 @@ class ec2_instance_older_than_specific_days(Check):
|
|||||||
|
|
||||||
findings.append(report)
|
findings.append(report)
|
||||||
|
|
||||||
|
|
||||||
|
ec2_client.cleanup()
|
||||||
return findings
|
return findings
|
||||||
|
|||||||
@@ -3,67 +3,430 @@ from typing import Optional
|
|||||||
|
|
||||||
from botocore.client import ClientError
|
from botocore.client import ClientError
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from pympler import asizeof
|
||||||
|
|
||||||
from prowler.lib.logger import logger
|
from prowler.lib.logger import logger
|
||||||
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||||
from prowler.providers.aws.lib.service.service import AWSService
|
from prowler.providers.aws.lib.service.service import AWSService
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
|
||||||
|
import dill as pickle
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
from collections import deque
|
||||||
|
from sys import getsizeof
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
import boto3
|
||||||
|
from moto import mock_aws
|
||||||
|
from memory_profiler import profile
|
||||||
|
import pdb
|
||||||
|
import psutil
|
||||||
|
import os
|
||||||
|
|
||||||
|
def check_memory_usage():
|
||||||
|
process = psutil.Process(os.getpid())
|
||||||
|
memory_info = process.memory_info()
|
||||||
|
return memory_info.rss # Resident Set Size: memory in bytes
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedList:
|
||||||
|
instance_counter = 0
|
||||||
|
|
||||||
|
def __init__(self, page_size=1):
|
||||||
|
self.page_size = page_size
|
||||||
|
self.file_paths = []
|
||||||
|
self.cache = {}
|
||||||
|
self.length = 0 # Track the length dynamically
|
||||||
|
self.instance_id = PaginatedList.instance_counter
|
||||||
|
PaginatedList.instance_counter += 1
|
||||||
|
self.temp_dir = tempfile.mkdtemp(prefix=f'paginated_list_{self.instance_id}_', dir='/Users/snaow/repos/prowler')
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
def _save_page(self, page_data, page_num):
|
||||||
|
file_path = os.path.join(self.temp_dir, f'page_{page_num}.pkl')
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
pickle.dump(page_data, f)
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self.file_paths.append(file_path)
|
||||||
|
else:
|
||||||
|
self.file_paths[page_num] = file_path
|
||||||
|
|
||||||
|
def _load_page(self, page_num):
|
||||||
|
if page_num in self.cache:
|
||||||
|
return self.cache[page_num]
|
||||||
|
with open(self.file_paths[page_num], 'rb') as f:
|
||||||
|
page_data = pickle.load(f)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
return page_data
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
if index < 0 or index >= self.length:
|
||||||
|
raise IndexError('Index out of range')
|
||||||
|
page_num = index // self.page_size
|
||||||
|
page_index = index % self.page_size
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
return page_data[page_index]
|
||||||
|
|
||||||
|
def __setitem__(self, index, value):
|
||||||
|
if index < 0 or index >= self.length:
|
||||||
|
raise IndexError('Index out of range')
|
||||||
|
page_num = index // self.page_size
|
||||||
|
page_index = index % self.page_size
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[page_index] = value
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
def __delitem__(self, index):
|
||||||
|
if index < 0 or index >= self.length:
|
||||||
|
raise IndexError('Index out of range')
|
||||||
|
page_num = index // self.page_size
|
||||||
|
page_index = index % self.page_size
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
del page_data[page_index]
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length -= 1
|
||||||
|
|
||||||
|
# Shift subsequent elements
|
||||||
|
for i in range(index, self.length):
|
||||||
|
next_page_num = (i + 1) // self.page_size
|
||||||
|
next_page_index = (i + 1) % self.page_size
|
||||||
|
if next_page_index == 0:
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
page_num = next_page_num
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[page_index] = page_data.pop(next_page_index)
|
||||||
|
page_index = next_page_index
|
||||||
|
|
||||||
|
# Save the last page
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
# Remove the last page if it's empty
|
||||||
|
if self.length % self.page_size == 0:
|
||||||
|
os.remove(self.file_paths.pop())
|
||||||
|
self.cache.pop(page_num, None)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for page_num in range(len(self.file_paths)):
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
for item in page_data:
|
||||||
|
yield item
|
||||||
|
|
||||||
|
def append(self, value):
|
||||||
|
page_num = self.length // self.page_size
|
||||||
|
page_index = self.length % self.page_size
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self._save_page([], page_num)
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data.append(value)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length += 1
|
||||||
|
|
||||||
|
def extend(self, values):
|
||||||
|
for value in values:
|
||||||
|
self.append(value)
|
||||||
|
|
||||||
|
def remove(self, value):
|
||||||
|
for index, item in enumerate(self):
|
||||||
|
if item == value:
|
||||||
|
del self[index]
|
||||||
|
return
|
||||||
|
raise ValueError(f"{value} not in list")
|
||||||
|
|
||||||
|
def pop(self, index=-1):
|
||||||
|
if self.length == 0:
|
||||||
|
raise IndexError("pop from empty list")
|
||||||
|
if index < 0:
|
||||||
|
index += self.length
|
||||||
|
value = self[index]
|
||||||
|
del self[index]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.cache.clear()
|
||||||
|
self.file_paths = []
|
||||||
|
self.length = 0
|
||||||
|
|
||||||
|
def index(self, value, start=0, stop=None):
|
||||||
|
if stop is None:
|
||||||
|
stop = self.length
|
||||||
|
for i in range(start, stop):
|
||||||
|
if self[i] == value:
|
||||||
|
return i
|
||||||
|
raise ValueError(f"{value} is not in list")
|
||||||
|
|
||||||
|
def get(self, index, default=None):
|
||||||
|
try:
|
||||||
|
return self[index]
|
||||||
|
except IndexError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
for file_path in self.file_paths:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
if os.path.exists(self.temp_dir):
|
||||||
|
os.rmdir(self.temp_dir)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedDict:
|
||||||
|
instance_counter = 0
|
||||||
|
|
||||||
|
def __init__(self, page_size=1):
|
||||||
|
self.page_size = page_size
|
||||||
|
self.file_paths = []
|
||||||
|
self.cache = {}
|
||||||
|
self.key_to_page = {}
|
||||||
|
self.length = 0 # Track the number of items
|
||||||
|
self.instance_id = PaginatedDict.instance_counter
|
||||||
|
PaginatedDict.instance_counter += 1
|
||||||
|
self.temp_dir = tempfile.mkdtemp(prefix=f'paginated_dict_{self.instance_id}_', dir='/Users/snaow/repos/prowler')
|
||||||
|
print(f"Temporary directory for instance {self.instance_id}: {self.temp_dir}")
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
def _save_page(self, page_data, page_num):
|
||||||
|
file_path = os.path.join(self.temp_dir, f'page_{page_num}.pkl')
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
pickle.dump(page_data, f)
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self.file_paths.append(file_path)
|
||||||
|
else:
|
||||||
|
self.file_paths[page_num] = file_path
|
||||||
|
|
||||||
|
def _load_page(self, page_num):
|
||||||
|
if page_num in self.cache:
|
||||||
|
return self.cache[page_num]
|
||||||
|
with open(self.file_paths[page_num], 'rb') as f:
|
||||||
|
page_data = pickle.load(f)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
return page_data
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key in self.key_to_page:
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[key] = value
|
||||||
|
else:
|
||||||
|
page_num = self.length // self.page_size
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self._save_page({}, page_num)
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[key] = value
|
||||||
|
self.key_to_page[key] = page_num
|
||||||
|
self.length += 1
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key not in self.key_to_page:
|
||||||
|
raise KeyError(f"Key {key} not found")
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
return page_data[key]
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if key not in self.key_to_page:
|
||||||
|
raise KeyError(f"Key {key} not found")
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
del page_data[key]
|
||||||
|
del self.key_to_page[key]
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length -= 1
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for page_num in range(len(self.file_paths)):
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
for key in page_data:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
for key in self:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
for key in self:
|
||||||
|
yield self[key]
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
for key in self:
|
||||||
|
yield (key, self[key])
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.cache.clear()
|
||||||
|
self.key_to_page.clear()
|
||||||
|
self.file_paths = []
|
||||||
|
self.length = 0
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
for file_path in self.file_paths:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
if os.path.exists(self.temp_dir):
|
||||||
|
os.rmdir(self.temp_dir)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
################## EC2
|
################## EC2
|
||||||
|
|
||||||
class EC2(AWSService):
|
class EC2(AWSService):
|
||||||
|
|
||||||
def __init__(self, provider):
|
def __init__(self, provider):
|
||||||
# Call AWSService's __init__
|
# Call AWSService's __init__
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at __init__ ec2_service.py : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
super().__init__(__class__.__name__, provider)
|
super().__init__(__class__.__name__, provider)
|
||||||
self.account_arn_template = f"arn:{self.audited_partition}:ec2:{self.region}:{self.audited_account}:account"
|
self.account_arn_template = f"arn:{self.audited_partition}:ec2:{self.region}:{self.audited_account}:account"
|
||||||
self.instances = []
|
paginated = 1
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at super() ec2_service.py : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
if paginated:
|
||||||
|
self.instances = PaginatedList()
|
||||||
|
self.security_groups = PaginatedList()
|
||||||
|
self.regions_with_sgs = PaginatedList()
|
||||||
|
self.volumes_with_snapshots = PaginatedDict()
|
||||||
|
self.regions_with_snapshots = PaginatedDict()
|
||||||
|
self.network_acls = PaginatedList()
|
||||||
|
self.snapshots = PaginatedList()
|
||||||
|
self.network_interfaces = PaginatedList()
|
||||||
|
self.images = PaginatedList()
|
||||||
|
self.volumes = PaginatedList()
|
||||||
|
self.attributes_for_regions = PaginatedDict()
|
||||||
|
self.ebs_encryption_by_default = PaginatedList()
|
||||||
|
self.elastic_ips = PaginatedList()
|
||||||
|
self.ebs_block_public_access_snapshots_states = PaginatedList()
|
||||||
|
self.instance_metadata_defaults = PaginatedList()
|
||||||
|
self.launch_templates = PaginatedList()
|
||||||
|
else:
|
||||||
|
self.instances = []
|
||||||
|
self.security_groups = []
|
||||||
|
self.regions_with_sgs = []
|
||||||
|
self.volumes_with_snapshots = {}
|
||||||
|
self.regions_with_snapshots = {}
|
||||||
|
self.network_acls = []
|
||||||
|
self.snapshots = []
|
||||||
|
self.network_interfaces = []
|
||||||
|
self.images = []
|
||||||
|
self.volumes = []
|
||||||
|
self.attributes_for_regions = {}
|
||||||
|
self.ebs_encryption_by_default = []
|
||||||
|
self.elastic_ips = []
|
||||||
|
self.ebs_block_public_access_snapshots_states = []
|
||||||
|
self.instance_metadata_defaults = []
|
||||||
|
self.launch_templates = []
|
||||||
|
|
||||||
|
|
||||||
self.__threading_call__(self.__describe_instances__)
|
self.__threading_call__(self.__describe_instances__)
|
||||||
|
#self.__describe_instances__(next(iter(self.regional_clients.values())))
|
||||||
self.__threading_call__(self.__get_instance_user_data__, self.instances)
|
self.__threading_call__(self.__get_instance_user_data__, self.instances)
|
||||||
self.security_groups = []
|
|
||||||
self.regions_with_sgs = []
|
|
||||||
self.__threading_call__(self.__describe_security_groups__)
|
self.__threading_call__(self.__describe_security_groups__)
|
||||||
self.network_acls = []
|
|
||||||
self.__threading_call__(self.__describe_network_acls__)
|
self.__threading_call__(self.__describe_network_acls__)
|
||||||
self.snapshots = []
|
|
||||||
self.volumes_with_snapshots = {}
|
|
||||||
self.regions_with_snapshots = {}
|
|
||||||
self.__threading_call__(self.__describe_snapshots__)
|
self.__threading_call__(self.__describe_snapshots__)
|
||||||
self.__threading_call__(self.__determine_public_snapshots__, self.snapshots)
|
self.__threading_call__(self.__determine_public_snapshots__, self.snapshots)
|
||||||
self.network_interfaces = []
|
|
||||||
self.__threading_call__(self.__describe_network_interfaces__)
|
self.__threading_call__(self.__describe_network_interfaces__)
|
||||||
self.images = []
|
|
||||||
self.__threading_call__(self.__describe_images__)
|
self.__threading_call__(self.__describe_images__)
|
||||||
self.volumes = []
|
|
||||||
self.__threading_call__(self.__describe_volumes__)
|
self.__threading_call__(self.__describe_volumes__)
|
||||||
self.attributes_for_regions = {}
|
|
||||||
self.__threading_call__(self.__get_resources_for_regions__)
|
self.__threading_call__(self.__get_resources_for_regions__)
|
||||||
self.ebs_encryption_by_default = []
|
|
||||||
self.__threading_call__(self.__get_ebs_encryption_settings__)
|
self.__threading_call__(self.__get_ebs_encryption_settings__)
|
||||||
self.elastic_ips = []
|
|
||||||
self.__threading_call__(self.__describe_ec2_addresses__)
|
self.__threading_call__(self.__describe_ec2_addresses__)
|
||||||
self.ebs_block_public_access_snapshots_states = []
|
|
||||||
self.__threading_call__(self.__get_snapshot_block_public_access_state__)
|
self.__threading_call__(self.__get_snapshot_block_public_access_state__)
|
||||||
self.instance_metadata_defaults = []
|
|
||||||
self.__threading_call__(self.__get_instance_metadata_defaults__)
|
self.__threading_call__(self.__get_instance_metadata_defaults__)
|
||||||
self.launch_templates = []
|
|
||||||
self.__threading_call__(self.__describe_launch_templates)
|
self.__threading_call__(self.__describe_launch_templates)
|
||||||
self.__threading_call__(
|
self.__threading_call__(
|
||||||
self.__get_launch_template_versions__, self.launch_templates
|
self.__get_launch_template_versions__, self.launch_templates
|
||||||
)
|
)
|
||||||
|
|
||||||
|
print("MY DICT---<>")
|
||||||
|
print(list(self.instances))
|
||||||
|
def cleanup(self):
|
||||||
|
del self.instances
|
||||||
|
del self.security_groups
|
||||||
|
del self.regions_with_sgs
|
||||||
|
del self.volumes_with_snapshots
|
||||||
|
del self.regions_with_snapshots
|
||||||
|
del self.network_acls
|
||||||
|
del self.snapshots
|
||||||
|
del self.network_interfaces
|
||||||
|
del self.images
|
||||||
|
del self.volumes
|
||||||
|
del self.attributes_for_regions
|
||||||
|
del self.ebs_encryption_by_default
|
||||||
|
del self.elastic_ips
|
||||||
|
del self.ebs_block_public_access_snapshots_states
|
||||||
|
del self.instance_metadata_defaults
|
||||||
|
del self.launch_templates
|
||||||
|
gc.collect()
|
||||||
|
|
||||||
def __get_volume_arn_template__(self, region):
|
def __get_volume_arn_template__(self, region):
|
||||||
return (
|
return (
|
||||||
f"arn:{self.audited_partition}:ec2:{region}:{self.audited_account}:volume"
|
f"arn:{self.audited_partition}:ec2:{region}:{self.audited_account}:volume"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
#@mock_aws
|
||||||
def __describe_instances__(self, regional_client):
|
def __describe_instances__(self, regional_client):
|
||||||
try:
|
try:
|
||||||
|
mock_enabled = 0
|
||||||
|
if mock_enabled:
|
||||||
|
ec2 = boto3.resource('ec2', region_name='eu-west-1')
|
||||||
|
instances = []
|
||||||
|
counter = 0
|
||||||
|
|
||||||
|
instance = ec2.create_instances(
|
||||||
|
ImageId='ami-12345678', # Example AMI ID, replace with a valid one if testing with real AWS
|
||||||
|
MinCount=3000,
|
||||||
|
MaxCount=3000,
|
||||||
|
InstanceType='t2.micro'
|
||||||
|
)[0]
|
||||||
|
instance.wait_until_running()
|
||||||
|
instance.reload()
|
||||||
|
|
||||||
describe_instances_paginator = regional_client.get_paginator(
|
describe_instances_paginator = regional_client.get_paginator(
|
||||||
"describe_instances"
|
"describe_instances"
|
||||||
)
|
)
|
||||||
for page in describe_instances_paginator.paginate():
|
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at regional_client.get_paginator ({regional_client.region}) : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
describe_instances_paginator_iterator = describe_instances_paginator.paginate(PaginationConfig={'MaxItems': 1})
|
||||||
|
#describe_instances_paginator_iterator = describe_instances_paginator.paginate()
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at describe_instances_paginator.paginate() ({regional_client.region}) : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
|
||||||
|
for page in describe_instances_paginator_iterator:
|
||||||
|
size_bytes = asizeof.asizeof(page)
|
||||||
|
size_mb = size_bytes / (1024 * 1024)
|
||||||
|
print("\tMemory usage of page", size_mb, "MB")
|
||||||
|
#for page in describe_instances_paginator.paginate():
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"\tMemory usage at describe_instances_paginator.paginate() start : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
for reservation in page["Reservations"]:
|
for reservation in page["Reservations"]:
|
||||||
for instance in reservation["Instances"]:
|
for instance in reservation["Instances"]:
|
||||||
arn = f"arn:{self.audited_partition}:ec2:{regional_client.region}:{self.audited_account}:instance/{instance['InstanceId']}"
|
arn = f"arn:{self.audited_partition}:ec2:{regional_client.region}:{self.audited_account}:instance/{instance['InstanceId']}"
|
||||||
|
#print(arn)
|
||||||
if not self.audit_resources or (
|
if not self.audit_resources or (
|
||||||
is_resource_filtered(arn, self.audit_resources)
|
is_resource_filtered(arn, self.audit_resources)
|
||||||
):
|
):
|
||||||
@@ -98,6 +461,17 @@ class EC2(AWSService):
|
|||||||
tags=instance.get("Tags"),
|
tags=instance.get("Tags"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"\t\tMemory usage at self.instances.append : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"\tMemory usage at describe_instances_paginator.paginate() end : {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage at the end of describe_instances_paginator ({regional_client.region}): {memory_usage / (1024 * 1024)} MB")
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -108,7 +482,8 @@ class EC2(AWSService):
|
|||||||
describe_security_groups_paginator = regional_client.get_paginator(
|
describe_security_groups_paginator = regional_client.get_paginator(
|
||||||
"describe_security_groups"
|
"describe_security_groups"
|
||||||
)
|
)
|
||||||
for page in describe_security_groups_paginator.paginate():
|
describe_security_groups_iterator = describe_security_groups_paginator.paginate(PaginationConfig={'MaxItems': 1})
|
||||||
|
for page in describe_security_groups_iterator:
|
||||||
for sg in page["SecurityGroups"]:
|
for sg in page["SecurityGroups"]:
|
||||||
arn = f"arn:{self.audited_partition}:ec2:{regional_client.region}:{self.audited_account}:security-group/{sg['GroupId']}"
|
arn = f"arn:{self.audited_partition}:ec2:{regional_client.region}:{self.audited_account}:security-group/{sg['GroupId']}"
|
||||||
if not self.audit_resources or (
|
if not self.audit_resources or (
|
||||||
@@ -135,6 +510,9 @@ class EC2(AWSService):
|
|||||||
)
|
)
|
||||||
if sg["GroupName"] != "default":
|
if sg["GroupName"] != "default":
|
||||||
self.regions_with_sgs.append(regional_client.region)
|
self.regions_with_sgs.append(regional_client.region)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __describe_security_groups__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -145,7 +523,7 @@ class EC2(AWSService):
|
|||||||
describe_network_acls_paginator = regional_client.get_paginator(
|
describe_network_acls_paginator = regional_client.get_paginator(
|
||||||
"describe_network_acls"
|
"describe_network_acls"
|
||||||
)
|
)
|
||||||
for page in describe_network_acls_paginator.paginate():
|
for page in describe_network_acls_paginator.paginate(PaginationConfig={'MaxItems': 1}):
|
||||||
for nacl in page["NetworkAcls"]:
|
for nacl in page["NetworkAcls"]:
|
||||||
arn = f"arn:{self.audited_partition}:ec2:{regional_client.region}:{self.audited_account}:network-acl/{nacl['NetworkAclId']}"
|
arn = f"arn:{self.audited_partition}:ec2:{regional_client.region}:{self.audited_account}:network-acl/{nacl['NetworkAclId']}"
|
||||||
if not self.audit_resources or (
|
if not self.audit_resources or (
|
||||||
@@ -165,6 +543,9 @@ class EC2(AWSService):
|
|||||||
tags=nacl.get("Tags"),
|
tags=nacl.get("Tags"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __describe_network_acls__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -198,6 +579,9 @@ class EC2(AWSService):
|
|||||||
self.volumes_with_snapshots[snapshot["VolumeId"]] = True
|
self.volumes_with_snapshots[snapshot["VolumeId"]] = True
|
||||||
# Store that the region has at least one snapshot
|
# Store that the region has at least one snapshot
|
||||||
self.regions_with_snapshots[regional_client.region] = snapshots_in_region
|
self.regions_with_snapshots[regional_client.region] = snapshots_in_region
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after describe_snapshots: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -256,6 +640,9 @@ class EC2(AWSService):
|
|||||||
self.__add_network_interfaces_to_security_groups__(
|
self.__add_network_interfaces_to_security_groups__(
|
||||||
eni, interface.get("Groups", [])
|
eni, interface.get("Groups", [])
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after network_interfaces: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
|
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -283,6 +670,9 @@ class EC2(AWSService):
|
|||||||
)["UserData"]
|
)["UserData"]
|
||||||
if "Value" in user_data:
|
if "Value" in user_data:
|
||||||
instance.user_data = user_data["Value"]
|
instance.user_data = user_data["Value"]
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __get_instance_user_data__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except ClientError as error:
|
except ClientError as error:
|
||||||
if error.response["Error"]["Code"] == "InvalidInstanceID.NotFound":
|
if error.response["Error"]["Code"] == "InvalidInstanceID.NotFound":
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -310,6 +700,9 @@ class EC2(AWSService):
|
|||||||
tags=image.get("Tags"),
|
tags=image.get("Tags"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __describe_images__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -335,6 +728,9 @@ class EC2(AWSService):
|
|||||||
tags=volume.get("Tags"),
|
tags=volume.get("Tags"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __describe_volumes__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -366,6 +762,9 @@ class EC2(AWSService):
|
|||||||
tags=address.get("Tags"),
|
tags=address.get("Tags"),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __describe_ec2_addresses__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -426,6 +825,9 @@ class EC2(AWSService):
|
|||||||
region=regional_client.region,
|
region=regional_client.region,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __get_instance_metadata_defaults__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
@@ -453,6 +855,9 @@ class EC2(AWSService):
|
|||||||
"has_snapshots": has_snapshots,
|
"has_snapshots": has_snapshots,
|
||||||
"has_volumes": has_volumes,
|
"has_volumes": has_volumes,
|
||||||
}
|
}
|
||||||
|
memory_usage = check_memory_usage()
|
||||||
|
print(f"Memory usage after __get_resources_for_regions__: {memory_usage / (1024 * 1024)} MB")
|
||||||
|
#pdb.set_trace() # Break
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
f"{regional_client.region} -- {error.__class__.__name__}[{error.__traceback__.tb_lineno}]: {error}"
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
from prowler.lib.check.models import Check, Check_Report_AWS
|
from prowler.lib.check.models import Check, Check_Report_AWS
|
||||||
from prowler.providers.aws.services.s3.s3_client import s3_client
|
from prowler.providers.aws.services.s3.s3_client import s3_client
|
||||||
|
import importlib
|
||||||
|
import sys
|
||||||
|
import gc
|
||||||
|
|
||||||
class s3_bucket_object_lock(Check):
|
class s3_bucket_object_lock(Check):
|
||||||
def execute(self):
|
def execute(self):
|
||||||
@@ -23,4 +25,7 @@ class s3_bucket_object_lock(Check):
|
|||||||
)
|
)
|
||||||
findings.append(report)
|
findings.append(report)
|
||||||
|
|
||||||
|
|
||||||
|
del sys.modules['prowler.providers.aws.services.s3.s3_client']
|
||||||
|
gc.collect()
|
||||||
return findings
|
return findings
|
||||||
|
|||||||
@@ -7,8 +7,273 @@ from pydantic import BaseModel
|
|||||||
from prowler.lib.logger import logger
|
from prowler.lib.logger import logger
|
||||||
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||||
from prowler.providers.aws.lib.service.service import AWSService
|
from prowler.providers.aws.lib.service.service import AWSService
|
||||||
|
import os
|
||||||
|
import dill as pickle
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
from collections import deque
|
||||||
|
from sys import getsizeof
|
||||||
|
import tempfile
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedList:
|
||||||
|
instance_counter = 0
|
||||||
|
|
||||||
|
def __init__(self, page_size=10):
|
||||||
|
self.page_size = page_size
|
||||||
|
self.file_paths = []
|
||||||
|
self.cache = {}
|
||||||
|
self.length = 0 # Track the length dynamically
|
||||||
|
self.instance_id = PaginatedList.instance_counter
|
||||||
|
PaginatedList.instance_counter += 1
|
||||||
|
self.temp_dir = tempfile.mkdtemp(prefix=f'paginated_list_{self.instance_id}_', dir='/Users/snaow/repos/prowler')
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
def _save_page(self, page_data, page_num):
|
||||||
|
file_path = os.path.join(self.temp_dir, f'page_{page_num}.pkl')
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
pickle.dump(page_data, f)
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self.file_paths.append(file_path)
|
||||||
|
else:
|
||||||
|
self.file_paths[page_num] = file_path
|
||||||
|
|
||||||
|
def _load_page(self, page_num):
|
||||||
|
if page_num in self.cache:
|
||||||
|
return self.cache[page_num]
|
||||||
|
with open(self.file_paths[page_num], 'rb') as f:
|
||||||
|
page_data = pickle.load(f)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
return page_data
|
||||||
|
|
||||||
|
def __getitem__(self, index):
|
||||||
|
if index < 0 or index >= self.length:
|
||||||
|
raise IndexError('Index out of range')
|
||||||
|
page_num = index // self.page_size
|
||||||
|
page_index = index % self.page_size
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
return page_data[page_index]
|
||||||
|
|
||||||
|
def __setitem__(self, index, value):
|
||||||
|
if index < 0 or index >= self.length:
|
||||||
|
raise IndexError('Index out of range')
|
||||||
|
page_num = index // self.page_size
|
||||||
|
page_index = index % self.page_size
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[page_index] = value
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
def __delitem__(self, index):
|
||||||
|
if index < 0 or index >= self.length:
|
||||||
|
raise IndexError('Index out of range')
|
||||||
|
page_num = index // self.page_size
|
||||||
|
page_index = index % self.page_size
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
del page_data[page_index]
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length -= 1
|
||||||
|
|
||||||
|
# Shift subsequent elements
|
||||||
|
for i in range(index, self.length):
|
||||||
|
next_page_num = (i + 1) // self.page_size
|
||||||
|
next_page_index = (i + 1) % self.page_size
|
||||||
|
if next_page_index == 0:
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
page_num = next_page_num
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[page_index] = page_data.pop(next_page_index)
|
||||||
|
page_index = next_page_index
|
||||||
|
|
||||||
|
# Save the last page
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
# Remove the last page if it's empty
|
||||||
|
if self.length % self.page_size == 0:
|
||||||
|
os.remove(self.file_paths.pop())
|
||||||
|
self.cache.pop(page_num, None)
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for page_num in range(len(self.file_paths)):
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
for item in page_data:
|
||||||
|
yield item
|
||||||
|
|
||||||
|
def append(self, value):
|
||||||
|
page_num = self.length // self.page_size
|
||||||
|
page_index = self.length % self.page_size
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self._save_page([], page_num)
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data.append(value)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length += 1
|
||||||
|
|
||||||
|
def extend(self, values):
|
||||||
|
for value in values:
|
||||||
|
self.append(value)
|
||||||
|
|
||||||
|
def remove(self, value):
|
||||||
|
for index, item in enumerate(self):
|
||||||
|
if item == value:
|
||||||
|
del self[index]
|
||||||
|
return
|
||||||
|
raise ValueError(f"{value} not in list")
|
||||||
|
|
||||||
|
def pop(self, index=-1):
|
||||||
|
if self.length == 0:
|
||||||
|
raise IndexError("pop from empty list")
|
||||||
|
if index < 0:
|
||||||
|
index += self.length
|
||||||
|
value = self[index]
|
||||||
|
del self[index]
|
||||||
|
return value
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.cache.clear()
|
||||||
|
self.file_paths = []
|
||||||
|
self.length = 0
|
||||||
|
|
||||||
|
def index(self, value, start=0, stop=None):
|
||||||
|
if stop is None:
|
||||||
|
stop = self.length
|
||||||
|
for i in range(start, stop):
|
||||||
|
if self[i] == value:
|
||||||
|
return i
|
||||||
|
raise ValueError(f"{value} is not in list")
|
||||||
|
|
||||||
|
def get(self, index, default=None):
|
||||||
|
try:
|
||||||
|
return self[index]
|
||||||
|
except IndexError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
if hasattr(self, 'file_paths'):
|
||||||
|
for file_path in self.file_paths:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
if os.path.exists(self.temp_dir):
|
||||||
|
os.rmdir(self.temp_dir)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
|
|
||||||
|
class PaginatedDict:
|
||||||
|
instance_counter = 0
|
||||||
|
|
||||||
|
def __init__(self, page_size=1):
|
||||||
|
self.page_size = page_size
|
||||||
|
self.file_paths = []
|
||||||
|
self.cache = {}
|
||||||
|
self.key_to_page = {}
|
||||||
|
self.length = 0 # Track the number of items
|
||||||
|
self.instance_id = PaginatedDict.instance_counter
|
||||||
|
PaginatedDict.instance_counter += 1
|
||||||
|
self.temp_dir = tempfile.mkdtemp(prefix=f'paginated_dict_{self.instance_id}_', dir='/Users/snaow/repos/prowler')
|
||||||
|
print(f"Temporary directory for instance {self.instance_id}: {self.temp_dir}")
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
def _save_page(self, page_data, page_num):
|
||||||
|
file_path = os.path.join(self.temp_dir, f'page_{page_num}.pkl')
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
pickle.dump(page_data, f)
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self.file_paths.append(file_path)
|
||||||
|
else:
|
||||||
|
self.file_paths[page_num] = file_path
|
||||||
|
|
||||||
|
def _load_page(self, page_num):
|
||||||
|
if page_num in self.cache:
|
||||||
|
return self.cache[page_num]
|
||||||
|
with open(self.file_paths[page_num], 'rb') as f:
|
||||||
|
page_data = pickle.load(f)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
return page_data
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key in self.key_to_page:
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[key] = value
|
||||||
|
else:
|
||||||
|
page_num = self.length // self.page_size
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self._save_page({}, page_num)
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[key] = value
|
||||||
|
self.key_to_page[key] = page_num
|
||||||
|
self.length += 1
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key not in self.key_to_page:
|
||||||
|
raise KeyError(f"Key {key} not found")
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
return page_data[key]
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if key not in self.key_to_page:
|
||||||
|
raise KeyError(f"Key {key} not found")
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
del page_data[key]
|
||||||
|
del self.key_to_page[key]
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length -= 1
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for page_num in range(len(self.file_paths)):
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
for key in page_data:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def get(self, key, default=None):
|
||||||
|
try:
|
||||||
|
return self[key]
|
||||||
|
except KeyError:
|
||||||
|
return default
|
||||||
|
|
||||||
|
def keys(self):
|
||||||
|
for key in self:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def values(self):
|
||||||
|
for key in self:
|
||||||
|
yield self[key]
|
||||||
|
|
||||||
|
def items(self):
|
||||||
|
for key in self:
|
||||||
|
yield (key, self[key])
|
||||||
|
|
||||||
|
def clear(self):
|
||||||
|
self.cache.clear()
|
||||||
|
self.key_to_page.clear()
|
||||||
|
self.file_paths = []
|
||||||
|
self.length = 0
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
for file_path in self.file_paths:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
if os.path.exists(self.temp_dir):
|
||||||
|
os.rmdir(self.temp_dir)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
################## S3
|
################## S3
|
||||||
class S3(AWSService):
|
class S3(AWSService):
|
||||||
def __init__(self, provider):
|
def __init__(self, provider):
|
||||||
@@ -27,9 +292,13 @@ class S3(AWSService):
|
|||||||
self.__threading_call__(self.__get_object_lock_configuration__, self.buckets)
|
self.__threading_call__(self.__get_object_lock_configuration__, self.buckets)
|
||||||
self.__threading_call__(self.__get_bucket_tagging__, self.buckets)
|
self.__threading_call__(self.__get_bucket_tagging__, self.buckets)
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
del self.regions_with_buckets
|
||||||
|
del self.buckets
|
||||||
|
|
||||||
def __list_buckets__(self, provider):
|
def __list_buckets__(self, provider):
|
||||||
logger.info("S3 - Listing buckets...")
|
logger.info("S3 - Listing buckets...")
|
||||||
buckets = []
|
buckets = PaginatedList()
|
||||||
try:
|
try:
|
||||||
list_buckets = self.client.list_buckets()
|
list_buckets = self.client.list_buckets()
|
||||||
for bucket in list_buckets["Buckets"]:
|
for bucket in list_buckets["Buckets"]:
|
||||||
|
|||||||
@@ -10,15 +10,114 @@ from prowler.lib.logger import logger
|
|||||||
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
from prowler.lib.scan_filters.scan_filters import is_resource_filtered
|
||||||
from prowler.providers.aws.lib.service.service import AWSService
|
from prowler.providers.aws.lib.service.service import AWSService
|
||||||
|
|
||||||
|
import pickle
|
||||||
|
import os
|
||||||
|
import atexit
|
||||||
|
from collections import deque
|
||||||
|
from sys import getsizeof
|
||||||
|
import tempfile
|
||||||
|
from memory_profiler import profile
|
||||||
|
|
||||||
|
class PaginatedDict:
|
||||||
|
instance_counter = 0
|
||||||
|
|
||||||
|
def __init__(self, page_size=100):
|
||||||
|
self.page_size = page_size
|
||||||
|
self.file_paths = []
|
||||||
|
self.cache = {}
|
||||||
|
self.key_to_page = {}
|
||||||
|
self.length = 0 # Track the number of items
|
||||||
|
self.instance_id = PaginatedDict.instance_counter
|
||||||
|
PaginatedDict.instance_counter += 1
|
||||||
|
self.temp_dir = tempfile.mkdtemp(prefix=f'paginated_dict_{self.instance_id}_', dir='/Users/snaow/repos/prowler')
|
||||||
|
print(f"Temporary directory for instance {self.instance_id}: {self.temp_dir}")
|
||||||
|
atexit.register(self.cleanup)
|
||||||
|
|
||||||
|
def _save_page(self, page_data, page_num):
|
||||||
|
file_path = os.path.join(self.temp_dir, f'page_{page_num}.pkl')
|
||||||
|
with open(file_path, 'wb') as f:
|
||||||
|
pickle.dump(page_data, f)
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self.file_paths.append(file_path)
|
||||||
|
else:
|
||||||
|
self.file_paths[page_num] = file_path
|
||||||
|
|
||||||
|
def _load_page(self, page_num):
|
||||||
|
if page_num in self.cache:
|
||||||
|
return self.cache[page_num]
|
||||||
|
with open(self.file_paths[page_num], 'rb') as f:
|
||||||
|
page_data = pickle.load(f)
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
return page_data
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
if key in self.key_to_page:
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[key] = value
|
||||||
|
else:
|
||||||
|
page_num = self.length // self.page_size
|
||||||
|
if page_num >= len(self.file_paths):
|
||||||
|
self._save_page({}, page_num)
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
page_data[key] = value
|
||||||
|
self.key_to_page[key] = page_num
|
||||||
|
self.length += 1
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
if key not in self.key_to_page:
|
||||||
|
raise KeyError(f"Key {key} not found")
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
return page_data[key]
|
||||||
|
|
||||||
|
def __delitem__(self, key):
|
||||||
|
if key not in self.key_to_page:
|
||||||
|
raise KeyError(f"Key {key} not found")
|
||||||
|
page_num = self.key_to_page[key]
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
del page_data[key]
|
||||||
|
del self.key_to_page[key]
|
||||||
|
self.cache[page_num] = page_data
|
||||||
|
self._save_page(page_data, page_num)
|
||||||
|
self.length -= 1
|
||||||
|
|
||||||
|
def __len__(self):
|
||||||
|
return self.length
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
for page_num in range(len(self.file_paths)):
|
||||||
|
page_data = self._load_page(page_num)
|
||||||
|
for key in page_data:
|
||||||
|
yield key
|
||||||
|
|
||||||
|
def cleanup(self):
|
||||||
|
for file_path in self.file_paths:
|
||||||
|
if os.path.exists(file_path):
|
||||||
|
os.remove(file_path)
|
||||||
|
if os.path.exists(self.temp_dir):
|
||||||
|
os.rmdir(self.temp_dir)
|
||||||
|
|
||||||
|
def __del__(self):
|
||||||
|
self.cleanup()
|
||||||
|
|
||||||
################## SSM
|
################## SSM
|
||||||
class SSM(AWSService):
|
class SSM(AWSService):
|
||||||
def __init__(self, provider):
|
def __init__(self, provider):
|
||||||
# Call AWSService's __init__
|
# Call AWSService's __init__
|
||||||
super().__init__(__class__.__name__, provider)
|
super().__init__(__class__.__name__, provider)
|
||||||
self.documents = {}
|
paginated = 0
|
||||||
self.compliance_resources = {}
|
if paginated == 1:
|
||||||
self.managed_instances = {}
|
self.documents = PaginatedDict()
|
||||||
|
self.compliance_resources = PaginatedDict()
|
||||||
|
self.managed_instances = PaginatedDict()
|
||||||
|
else:
|
||||||
|
self.documents = {}
|
||||||
|
self.compliance_resources = {}
|
||||||
|
self.managed_instances = {}
|
||||||
|
|
||||||
self.__threading_call__(self.__list_documents__)
|
self.__threading_call__(self.__list_documents__)
|
||||||
self.__threading_call__(self.__get_document__)
|
self.__threading_call__(self.__get_document__)
|
||||||
self.__threading_call__(self.__describe_document_permission__)
|
self.__threading_call__(self.__describe_document_permission__)
|
||||||
|
|||||||
Reference in New Issue
Block a user