mirror of
https://github.com/ghndrx/prowler.git
synced 2026-02-10 06:45:08 +00:00
feat(regions): Filter Audited Regions (-f) (#1202)
* feat(filter-regions): Added -f and ebs encryption check. * feat(filter-regions): Added -f and ebs encryption check. * feat(regional_clients): add regional_clients. * fix(global variables): created global variables * chore(role option): Mixed -A/-R option including error handling * fix(arn): import errors from error.py file * fix(review_comments): Review PR comments. Co-authored-by: sergargar <sergio@verica.io> Co-authored-by: n4ch04 <nachor1992@gmail.com>
This commit is contained in:
@@ -5,3 +5,9 @@ prowler_version = "3.0-alfa"
|
||||
|
||||
# Groups
|
||||
groups_file = "groups.json"
|
||||
|
||||
# AWS services-regions matrix json
|
||||
aws_services_json_url = (
|
||||
"https://api.regional-table.region-services.aws.a2z.com/index.json"
|
||||
)
|
||||
aws_services_json_file = "providers/aws/aws_regions_services.json"
|
||||
|
||||
45
lib/arn/arn.py
Normal file
45
lib/arn/arn.py
Normal file
@@ -0,0 +1,45 @@
|
||||
from arnparse import arnparse
|
||||
|
||||
from lib.arn.error import (
|
||||
RoleArnParsingEmptyResource,
|
||||
RoleArnParsingFailedMissingFields,
|
||||
RoleArnParsingIAMRegionNotEmpty,
|
||||
RoleArnParsingInvalidAccountID,
|
||||
RoleArnParsingInvalidResourceType,
|
||||
RoleArnParsingPartitionEmpty,
|
||||
RoleArnParsingServiceNotIAM,
|
||||
)
|
||||
|
||||
|
||||
def arn_parsing(arn):
|
||||
# check for number of fields, must be six
|
||||
if len(arn.split(":")) != 6:
|
||||
raise RoleArnParsingFailedMissingFields
|
||||
else:
|
||||
arn_parsed = arnparse(arn)
|
||||
# First check if region is empty (in IAM arns region is always empty)
|
||||
if arn_parsed.region != None:
|
||||
raise RoleArnParsingIAMRegionNotEmpty
|
||||
else:
|
||||
# check if needed fields are filled:
|
||||
# - partition
|
||||
# - service
|
||||
# - account_id
|
||||
# - resource_type
|
||||
# - resource
|
||||
if arn_parsed.partition == None:
|
||||
raise RoleArnParsingPartitionEmpty
|
||||
elif arn_parsed.service != "iam":
|
||||
raise RoleArnParsingServiceNotIAM
|
||||
elif (
|
||||
arn_parsed.account_id == None
|
||||
or len(arn_parsed.account_id) != 12
|
||||
or not arn_parsed.account_id.isnumeric()
|
||||
):
|
||||
raise RoleArnParsingInvalidAccountID
|
||||
elif arn_parsed.resource_type != "role":
|
||||
raise RoleArnParsingInvalidResourceType
|
||||
elif arn_parsed.resource == "":
|
||||
raise RoleArnParsingEmptyResource
|
||||
else:
|
||||
return arn_parsed
|
||||
43
lib/arn/error.py
Normal file
43
lib/arn/error.py
Normal file
@@ -0,0 +1,43 @@
|
||||
class RoleArnParsingFailedMissingFields(Exception):
|
||||
# The arn contains a numberof fields different than six separated by :"
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn contains a number of fields different than six separated by :, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class RoleArnParsingIAMRegionNotEmpty(Exception):
|
||||
# The arn contains a non-empty value for region, since it is an IAM arn is not valid
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn contains a non-empty value for region, since it is an IAM arn is not valid, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class RoleArnParsingPartitionEmpty(Exception):
|
||||
# The arn contains an empty value for partition
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn does not contain a value for partition, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class RoleArnParsingServiceNotIAM(Exception):
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn contains a value for service distinct than iam, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class RoleArnParsingInvalidAccountID(Exception):
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn contains a value for account id empty or invalid, a valid account id must be composed of 12 numbers, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class RoleArnParsingInvalidResourceType(Exception):
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn contains a value for resource type different than role, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
|
||||
|
||||
class RoleArnParsingEmptyResource(Exception):
|
||||
def __init__(self):
|
||||
self.message = "The assumed role arn does not contain a value for resource, please input a valid arn"
|
||||
super().__init__(self.message)
|
||||
@@ -3,6 +3,7 @@ import pkgutil
|
||||
from abc import ABC, abstractmethod
|
||||
from dataclasses import dataclass
|
||||
from types import ModuleType
|
||||
from colorama import Fore, Style
|
||||
|
||||
from config.config import groups_file
|
||||
from lib.logger import logger
|
||||
@@ -126,7 +127,7 @@ def recover_modules_from_provider(provider: str, service: str = None) -> list:
|
||||
|
||||
|
||||
def run_check(check):
|
||||
print(f"\nCheck Name: {check.CheckName}")
|
||||
print(f"\nCheck Name: {check.CheckName} - {Fore.MAGENTA}{check.ServiceName}{Fore.YELLOW}[{check.Severity}]{Style.RESET_ALL}")
|
||||
logger.debug(f"Executing check: {check.CheckName}")
|
||||
findings = check.execute()
|
||||
report(findings)
|
||||
|
||||
@@ -2,6 +2,7 @@ from colorama import Fore, Style
|
||||
|
||||
|
||||
def report(check_findings):
|
||||
check_findings.sort(key=lambda x: x.region)
|
||||
for finding in check_findings:
|
||||
color = set_report_color(finding.status)
|
||||
print(
|
||||
|
||||
@@ -6,6 +6,7 @@ from boto3 import session
|
||||
from botocore.credentials import RefreshableCredentials
|
||||
from botocore.session import get_session
|
||||
|
||||
from lib.arn.arn import arn_parsing
|
||||
from lib.logger import logger
|
||||
|
||||
|
||||
@@ -20,20 +21,19 @@ class AWS_Credentials:
|
||||
@dataclass
|
||||
class Input_Data:
|
||||
profile: str
|
||||
role_name: str
|
||||
account_to_assume: str
|
||||
role_arn: str
|
||||
session_duration: int
|
||||
external_id: str
|
||||
regions: list
|
||||
|
||||
|
||||
@dataclass
|
||||
class AWS_Assume_Role:
|
||||
role_name: str
|
||||
account_to_assume: str
|
||||
role_arn: str
|
||||
session_duration: int
|
||||
external_id: str
|
||||
sts_session: session
|
||||
caller_identity: str
|
||||
partition: str
|
||||
|
||||
|
||||
@dataclass
|
||||
@@ -106,7 +106,6 @@ class AWS_Provider:
|
||||
|
||||
|
||||
def validate_credentials(validate_session):
|
||||
|
||||
try:
|
||||
validate_credentials_client = validate_session.client("sts")
|
||||
caller_identity = validate_credentials_client.get_caller_identity()
|
||||
@@ -118,36 +117,61 @@ def validate_credentials(validate_session):
|
||||
|
||||
|
||||
def provider_set_session(session_input):
|
||||
|
||||
# global variables that are going to be shared accross the project
|
||||
global aws_session
|
||||
global original_session
|
||||
global audited_regions
|
||||
global audited_partition
|
||||
global audited_account
|
||||
|
||||
assumed_session = None
|
||||
|
||||
# Initialize a session info dataclass only with info about the profile
|
||||
session_info = AWS_Session_Info(
|
||||
session_input.profile,
|
||||
None,
|
||||
None,
|
||||
)
|
||||
|
||||
# Create an global original session using only profile/basic credentials info
|
||||
original_session = AWS_Provider(session_info).get_session()
|
||||
logger.info("Validating credentials ...")
|
||||
# Verificate if we have valid credentials
|
||||
caller_identity = validate_credentials(original_session)
|
||||
|
||||
role_info = AWS_Assume_Role(
|
||||
session_input.role_name,
|
||||
session_input.account_to_assume,
|
||||
session_input.session_duration,
|
||||
session_input.external_id,
|
||||
original_session,
|
||||
caller_identity,
|
||||
)
|
||||
logger.info("Credentials validated")
|
||||
logger.info(f"Original caller identity UserId : {caller_identity['UserId']}")
|
||||
logger.info(f"Original caller identity ARN : {caller_identity['Arn']}")
|
||||
|
||||
if session_input.role_name and session_input.account_to_assume:
|
||||
logger.info(
|
||||
f"Assuming role {role_info.role_name} in account {role_info.account_to_assume}"
|
||||
# Set some global values for original session
|
||||
audited_regions = session_input.regions
|
||||
audited_account = caller_identity["Account"]
|
||||
audited_partition = arnparse(caller_identity["Arn"]).partition
|
||||
|
||||
if session_input.role_arn:
|
||||
# Check if role arn is valid
|
||||
try:
|
||||
# this returns the arn already parsed, calls arnparse, into a dict to be used when it is needed to access its fields
|
||||
role_arn_parsed = arn_parsing(session_input.role_arn)
|
||||
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
quit()
|
||||
|
||||
# Set info for role assumption if needed
|
||||
role_info = AWS_Assume_Role(
|
||||
session_input.role_arn,
|
||||
session_input.session_duration,
|
||||
session_input.external_id,
|
||||
original_session,
|
||||
audited_partition,
|
||||
)
|
||||
logger.info(f"Assuming role {role_info.role_arn}")
|
||||
# Assume the role
|
||||
assumed_role_response = assume_role(role_info)
|
||||
logger.info("Role assumed")
|
||||
# Set the info needed to create a session with an assumed role
|
||||
session_info = AWS_Session_Info(
|
||||
session_input.profile,
|
||||
AWS_Credentials(
|
||||
@@ -160,26 +184,33 @@ def provider_set_session(session_input):
|
||||
),
|
||||
role_info,
|
||||
)
|
||||
assumed_session = AWS_Provider(session_info).get_session()
|
||||
|
||||
aws_session = AWS_Provider(session_info).get_session()
|
||||
if assumed_session:
|
||||
aws_session = assumed_session
|
||||
audited_account = role_arn_parsed.account_id
|
||||
audited_partition = role_arn_parsed.partition
|
||||
else:
|
||||
aws_session = original_session
|
||||
|
||||
|
||||
def assume_role(role_info):
|
||||
|
||||
try:
|
||||
# set the info to assume the role from the partition, account and role name
|
||||
sts_client = role_info.sts_session.client("sts")
|
||||
arn_caller_identity = arnparse(role_info.caller_identity["Arn"])
|
||||
role_arn = f"arn:{arn_caller_identity.partition}:iam::{role_info.account_to_assume}:role/{role_info.role_name}"
|
||||
# If external id, set it to the assume role api call
|
||||
if role_info.external_id:
|
||||
assumed_credentials = sts_client.assume_role(
|
||||
RoleArn=role_arn,
|
||||
RoleArn=role_info.role_arn,
|
||||
RoleSessionName="ProwlerProSession",
|
||||
DurationSeconds=role_info.session_duration,
|
||||
ExternalId=role_info.external_id,
|
||||
)
|
||||
# else assume the role without the external id
|
||||
else:
|
||||
assumed_credentials = sts_client.assume_role(
|
||||
RoleArn=role_arn,
|
||||
RoleArn=role_info.role_arn,
|
||||
RoleSessionName="ProwlerProSession",
|
||||
DurationSeconds=role_info.session_duration,
|
||||
)
|
||||
|
||||
29866
providers/aws/aws_regions_services.json
Normal file
29866
providers/aws/aws_regions_services.json
Normal file
File diff suppressed because it is too large
Load Diff
0
providers/aws/services/ec2/__init__.py
Normal file
0
providers/aws/services/ec2/__init__.py
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"Categories": [
|
||||
"cat1",
|
||||
"cat2"
|
||||
],
|
||||
"CheckAlias": "extra740",
|
||||
"CheckID": "ec2_ebs_snapshots_encrypted",
|
||||
"CheckName": "ec2_ebs_snapshots_encrypted",
|
||||
"CheckTitle": "Check if EBS snapshots are encrypted",
|
||||
"CheckType": "Data Protection",
|
||||
"Compliance": [
|
||||
{
|
||||
"Control": [
|
||||
"4.4"
|
||||
],
|
||||
"Framework": "CIS-AWS",
|
||||
"Group": [
|
||||
"level1",
|
||||
"level2"
|
||||
],
|
||||
"Version": "1.4"
|
||||
}
|
||||
],
|
||||
"DependsOn": [
|
||||
"othercheck1",
|
||||
"othercheck2"
|
||||
],
|
||||
"Description": "If Security groups are not properly configured the attack surface is increased.",
|
||||
"Notes": "additional information",
|
||||
"Provider": "aws",
|
||||
"RelatedTo": [
|
||||
"othercheck3",
|
||||
"othercheck4"
|
||||
],
|
||||
"RelatedUrl": "https://serviceofficialsiteorpageforthissubject",
|
||||
"Remediation": {
|
||||
"Code": {
|
||||
"NativeIaC": "code or URL to the code location.",
|
||||
"Terraform": "code or URL to the code location.",
|
||||
"cli": "cli command or URL to the cli command location.",
|
||||
"other": "cli command or URL to the cli command location."
|
||||
},
|
||||
"Recommendation": {
|
||||
"Text": "Run sudo yum update and cross your fingers and toes.",
|
||||
"Url": "https://myfp.com/recommendations/dangerous_things_and_how_to_fix_them.html"
|
||||
}
|
||||
},
|
||||
"ResourceIdTemplate": "arn:partition:service:region:account-id:resource-id",
|
||||
"ResourceType": "AwsIamAccessAnalyzer",
|
||||
"Risk": "Risk associated.",
|
||||
"ServiceName": "ec2",
|
||||
"Severity": "low",
|
||||
"SubServiceName": "accessanalyzer",
|
||||
"Tags": {
|
||||
"Tag1Key": "value",
|
||||
"Tag2Key": "value"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
from lib.check.check import Check, Check_Report
|
||||
from providers.aws.services.ec2.ec2_service import ec2_client
|
||||
|
||||
|
||||
class ec2_ebs_snapshots_encrypted(Check):
|
||||
def execute(self):
|
||||
findings = []
|
||||
for regional_client in ec2_client.regional_clients:
|
||||
region = regional_client.region
|
||||
if hasattr(regional_client, "snapshots"):
|
||||
if regional_client.snapshots:
|
||||
for snapshot in regional_client.snapshots:
|
||||
if snapshot["Encrypted"]:
|
||||
report = Check_Report()
|
||||
report.status = "PASS"
|
||||
report.result_extended = (
|
||||
f"EBS Snapshot {snapshot['SnapshotId']} is encrypted"
|
||||
)
|
||||
report.region = region
|
||||
else:
|
||||
report = Check_Report()
|
||||
report.status = "FAIL"
|
||||
report.result_extended = (
|
||||
f"EBS Snapshot {snapshot['SnapshotId']} is unencrypted"
|
||||
)
|
||||
report.region = region
|
||||
else:
|
||||
report = Check_Report()
|
||||
report.status = "PASS"
|
||||
report.result_extended = "There are no EC2 EBS snapshots"
|
||||
report.region = region
|
||||
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
117
providers/aws/services/ec2/ec2_service.py
Normal file
117
providers/aws/services/ec2/ec2_service.py
Normal file
@@ -0,0 +1,117 @@
|
||||
import json
|
||||
import threading
|
||||
import urllib.request
|
||||
|
||||
from config.config import aws_services_json_file, aws_services_json_url
|
||||
from lib.logger import logger
|
||||
from lib.utils.utils import open_file, parse_json_file
|
||||
from providers.aws.aws_provider import (
|
||||
audited_account,
|
||||
audited_partition,
|
||||
audited_regions,
|
||||
aws_session,
|
||||
)
|
||||
|
||||
|
||||
################## EC2
|
||||
class EC2:
|
||||
def __init__(self, aws_session, audited_regions):
|
||||
self.service = "ec2"
|
||||
self.aws_session = aws_session
|
||||
self.regional_clients = self.__generate_regional_clients__(
|
||||
self.service, audited_regions
|
||||
)
|
||||
self.__threading_call__(self.__describe_snapshots__)
|
||||
|
||||
def __get_clients__(self):
|
||||
return self.clients
|
||||
|
||||
def __get_session__(self):
|
||||
return self.aws_session
|
||||
|
||||
def __generate_regional_clients__(self, service, audited_regions):
|
||||
regional_clients = []
|
||||
try: # Try to get the list online
|
||||
with urllib.request.urlopen(aws_services_json_url) as url:
|
||||
data = json.loads(url.read().decode())
|
||||
except:
|
||||
# Get the list locally
|
||||
f = open_file(aws_services_json_file)
|
||||
data = parse_json_file(f)
|
||||
|
||||
for att in data["prices"]:
|
||||
if audited_regions: # Check for input aws audited_regions
|
||||
if (
|
||||
service in att["id"].split(":")[0]
|
||||
and att["attributes"]["aws:region"] in audited_regions
|
||||
): # Check if service has this region
|
||||
region = att["attributes"]["aws:region"]
|
||||
regional_client = aws_session.client(service, region_name=region)
|
||||
regional_client.region = region
|
||||
regional_clients.append(regional_client)
|
||||
else:
|
||||
if audited_partition in "aws":
|
||||
if (
|
||||
service in att["id"].split(":")[0]
|
||||
and "gov" not in att["attributes"]["aws:region"]
|
||||
and "cn" not in att["attributes"]["aws:region"]
|
||||
):
|
||||
region = att["attributes"]["aws:region"]
|
||||
regional_client = aws_session.client(
|
||||
service, region_name=region
|
||||
)
|
||||
regional_client.region = region
|
||||
regional_clients.append(regional_client)
|
||||
elif audited_partition in "cn":
|
||||
if (
|
||||
service in att["id"].split(":")[0]
|
||||
and "cn" in att["attributes"]["aws:region"]
|
||||
):
|
||||
region = att["attributes"]["aws:region"]
|
||||
regional_client = aws_session.client(
|
||||
service, region_name=region
|
||||
)
|
||||
regional_client.region = region
|
||||
regional_clients.append(regional_client)
|
||||
elif audited_partition in "gov":
|
||||
if (
|
||||
service in att["id"].split(":")[0]
|
||||
and "gov" in att["attributes"]["aws:region"]
|
||||
):
|
||||
region = att["attributes"]["aws:region"]
|
||||
regional_client = aws_session.client(
|
||||
service, region_name=region
|
||||
)
|
||||
regional_client.region = region
|
||||
regional_clients.append(regional_client)
|
||||
|
||||
return regional_clients
|
||||
|
||||
def __threading_call__(self, call):
|
||||
threads = []
|
||||
for regional_client in self.regional_clients:
|
||||
threads.append(threading.Thread(target=call, args=(regional_client,)))
|
||||
for t in threads:
|
||||
t.start()
|
||||
for t in threads:
|
||||
t.join()
|
||||
|
||||
def __describe_snapshots__(self, regional_client):
|
||||
logger.info("EC2 - Describing Snapshots...")
|
||||
try:
|
||||
describe_snapshots_paginator = regional_client.get_paginator(
|
||||
"describe_snapshots"
|
||||
)
|
||||
snapshots = []
|
||||
for page in describe_snapshots_paginator.paginate(
|
||||
OwnerIds=[audited_account]
|
||||
):
|
||||
for snapshot in page["Snapshots"]:
|
||||
snapshots.append(snapshot)
|
||||
except Exception as error:
|
||||
logger.error(f"{error.__class__.__name__} -- {error}")
|
||||
else:
|
||||
regional_client.snapshots = snapshots
|
||||
|
||||
|
||||
ec2_client = EC2(aws_session, audited_regions)
|
||||
@@ -22,18 +22,18 @@ class iam_disable_30_days_credentials(Check):
|
||||
)
|
||||
if time_since_insertion.days > maximum_expiration_days:
|
||||
report.status = "FAIL"
|
||||
report.result_extended = f"User {user['UserName']} has not logged into the console in the past 90 days"
|
||||
report.result_extended = f"User {user['UserName']} has not logged into the console in the past 30 days"
|
||||
report.region = "us-east-1"
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.result_extended = f"User {user['UserName']} has logged into the console in the past 90 days"
|
||||
report.result_extended = f"User {user['UserName']} has logged into the console in the past 30 days"
|
||||
report.region = "us-east-1"
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.result_extended = (
|
||||
f"User {user['UserName']} has not console password"
|
||||
f"User {user['UserName']} has not a console password or is unused."
|
||||
)
|
||||
report.region = "us-east-1"
|
||||
|
||||
@@ -46,4 +46,4 @@ class iam_disable_30_days_credentials(Check):
|
||||
report.region = "us-east-1"
|
||||
findings.append(report)
|
||||
|
||||
return findings
|
||||
return findings
|
||||
@@ -7,14 +7,13 @@ maximum_expiration_days = 90
|
||||
|
||||
|
||||
class iam_disable_90_days_credentials(Check):
|
||||
def execute(self):
|
||||
def execute(self) -> Check_Report:
|
||||
findings = []
|
||||
report = Check_Report
|
||||
|
||||
response = iam_client.users
|
||||
|
||||
if response:
|
||||
for user in response:
|
||||
report = Check_Report
|
||||
report = Check_Report()
|
||||
if "PasswordLastUsed" in user and user["PasswordLastUsed"] != "":
|
||||
try:
|
||||
time_since_insertion = (
|
||||
@@ -34,13 +33,16 @@ class iam_disable_90_days_credentials(Check):
|
||||
else:
|
||||
report.status = "PASS"
|
||||
report.result_extended = (
|
||||
f"User {user['UserName']} has not console password"
|
||||
f"User {user['UserName']} has not a console password or is unused."
|
||||
)
|
||||
report.region = "us-east-1"
|
||||
|
||||
# Append report
|
||||
findings.append(report)
|
||||
else:
|
||||
report = Check_Report()
|
||||
report.status = "PASS"
|
||||
report.result_extended = "There is no IAM users"
|
||||
report.region = "us-east-1"
|
||||
|
||||
return findings
|
||||
return findings
|
||||
@@ -23,13 +23,8 @@ class IAM:
|
||||
def __get_roles__(self):
|
||||
try:
|
||||
get_roles_paginator = self.client.get_paginator("list_roles")
|
||||
except botocore.exceptions.ClientError as error:
|
||||
logger.error(
|
||||
f"{error.response['Error']['Code']} -- {error.response['Error']['Message']}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
quit()
|
||||
logger.error(f"{error.__class__.__name__} -- {error}")
|
||||
else:
|
||||
roles = []
|
||||
for page in get_roles_paginator.paginate():
|
||||
@@ -43,13 +38,8 @@ class IAM:
|
||||
while not report_is_completed:
|
||||
try:
|
||||
report_status = self.client.generate_credential_report()
|
||||
except botocore.exceptions.ClientError as error:
|
||||
logger.error(
|
||||
f"{error.response['Error']['Code']} -- {error.response['Error']['Message']}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
quit()
|
||||
logger.error(f"{error.__class__.__name__} -- {error}")
|
||||
else:
|
||||
if report_status["State"] == "COMPLETE":
|
||||
report_is_completed = True
|
||||
@@ -59,13 +49,8 @@ class IAM:
|
||||
def __get_groups__(self):
|
||||
try:
|
||||
get_groups_paginator = self.client.get_paginator("list_groups")
|
||||
except botocore.exceptions.ClientError as error:
|
||||
logger.error(
|
||||
f"{error.response['Error']['Code']} -- {error.response['Error']['Message']}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
quit()
|
||||
logger.error(f"{error.__class__.__name__} -- {error}")
|
||||
else:
|
||||
groups = []
|
||||
for page in get_groups_paginator.paginate():
|
||||
@@ -79,13 +64,8 @@ class IAM:
|
||||
get_customer_managed_policies_paginator = self.client.get_paginator(
|
||||
"list_policies"
|
||||
)
|
||||
except botocore.exceptions.ClientError as error:
|
||||
logger.error(
|
||||
f"{error.response['Error']['Code']} -- {error.response['Error']['Message']}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
quit()
|
||||
logger.error(f"{error.__class__.__name__} -- {error}")
|
||||
else:
|
||||
customer_managed_policies = []
|
||||
for page in get_customer_managed_policies_paginator.paginate(Scope="Local"):
|
||||
@@ -97,13 +77,8 @@ class IAM:
|
||||
def __get_users__(self):
|
||||
try:
|
||||
get_users_paginator = self.client.get_paginator("list_users")
|
||||
except botocore.exceptions.ClientError as error:
|
||||
logger.error(
|
||||
f"{error.response['Error']['Code']} -- {error.response['Error']['Message']}"
|
||||
)
|
||||
except Exception as error:
|
||||
logger.critical(f"{error.__class__.__name__} -- {error}")
|
||||
quit()
|
||||
logger.error(f"{error.__class__.__name__} -- {error}")
|
||||
else:
|
||||
users = []
|
||||
for page in get_users_paginator.paginate():
|
||||
|
||||
54
prowler.py
54
prowler.py
@@ -51,14 +51,7 @@ if __name__ == "__main__":
|
||||
"--role",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="Role name to be assumed in account passed with -A",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-A",
|
||||
"--account",
|
||||
nargs="?",
|
||||
default=None,
|
||||
help="AWS account id where the role passed by -R is assumed",
|
||||
help="ARN of the role to be assumed",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-T",
|
||||
@@ -75,6 +68,12 @@ if __name__ == "__main__":
|
||||
default=None,
|
||||
help="External ID to be passed when assuming role",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--filter-region",
|
||||
nargs="+",
|
||||
help="AWS region names to run Prowler against",
|
||||
)
|
||||
# Parse Arguments
|
||||
args = parser.parse_args()
|
||||
|
||||
@@ -84,38 +83,19 @@ if __name__ == "__main__":
|
||||
services = args.services
|
||||
groups = args.groups
|
||||
checks_file = args.checks_file
|
||||
|
||||
|
||||
# Set Logger
|
||||
logger.setLevel(logging_levels.get(args.log_level))
|
||||
|
||||
# Role assumption input options tests
|
||||
if args.role or args.account:
|
||||
if not args.account:
|
||||
logger.critical(
|
||||
"It is needed to input an Account Id to assume the role (-A option) when an IAM Role is provided with -R"
|
||||
)
|
||||
quit()
|
||||
elif not args.role:
|
||||
logger.critical(
|
||||
"It is needed to input an IAM Role name (-R option) when an Account Id is provided with -A"
|
||||
)
|
||||
quit()
|
||||
if args.session_duration not in range(900, 43200):
|
||||
logger.critical("Value for -T option must be between 900 and 43200")
|
||||
quit()
|
||||
if args.session_duration != 3600 or args.external_id:
|
||||
if not args.account or not args.role:
|
||||
logger.critical("To use -I/-T options both -A and -R options are needed")
|
||||
if not args.role:
|
||||
logger.critical("To use -I/-T options -R option is needed")
|
||||
quit()
|
||||
|
||||
session_input = Input_Data(
|
||||
profile=args.profile,
|
||||
role_name=args.role,
|
||||
account_to_assume=args.account,
|
||||
session_duration=args.session_duration,
|
||||
external_id=args.external_id,
|
||||
)
|
||||
|
||||
# Set Logger
|
||||
logger.setLevel(logging_levels.get(args.log_level))
|
||||
|
||||
if args.version:
|
||||
print_version()
|
||||
quit()
|
||||
@@ -124,6 +104,14 @@ if __name__ == "__main__":
|
||||
print_banner()
|
||||
|
||||
# Setting session
|
||||
session_input = Input_Data(
|
||||
profile=args.profile,
|
||||
role_arn=args.role,
|
||||
session_duration=args.session_duration,
|
||||
external_id=args.external_id,
|
||||
regions=args.filter_region,
|
||||
)
|
||||
|
||||
provider_set_session(session_input)
|
||||
|
||||
# Load checks to execute
|
||||
|
||||
Reference in New Issue
Block a user